@fitlab-ai/agent-infra 0.11.3 → 0.11.5
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 -6
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -4
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +48 -1
- package/dist/lib/internal/task-ledger.js +14 -44
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +2 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/activity-intent.js +1 -2
- 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 +37 -28
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +8 -7
- 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 +12 -168
- package/dist/lib/task/decision-intents.js +14 -4
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +68 -73
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +423 -154
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +159 -20
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +60 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -47
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +4 -18
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +25 -30
- package/dist/lib/task/verification.js +2 -13
- package/dist/lib/task/workflow-warning-intents.js +23 -10
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lib/task/write.js +1 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +1 -3
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +47 -1
- package/lib/internal/task-ledger.ts +15 -41
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +2 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +59 -28
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +10 -7
- 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 +17 -189
- package/lib/task/decision-intents.ts +11 -4
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +73 -74
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +388 -122
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +157 -20
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +58 -13
- package/lib/task/local-artifact-finalization.ts +14 -56
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +4 -20
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +29 -30
- package/lib/task/verification.ts +3 -16
- package/lib/task/workflow-warning-intents.ts +20 -10
- package/lib/task/workspace-snapshot.ts +3 -2
- package/lib/task/write.ts +1 -4
- 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 +21 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +21 -5
- 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 +7 -7
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- 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/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/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-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- 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-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- 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/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- 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/internal/task-override.js +0 -138
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/sandbox/control/finalization-response.ts +0 -35
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -3,8 +3,12 @@ import {
|
|
|
3
3
|
dispatchTaskControlOperation,
|
|
4
4
|
parseTaskControlOperation
|
|
5
5
|
} from '../task/control-authority.ts';
|
|
6
|
-
import { applyTaskFinalization } from '../task/finalization.ts';
|
|
6
|
+
import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.ts';
|
|
7
|
+
import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.ts';
|
|
7
8
|
import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.ts';
|
|
9
|
+
import { verifyTaskEvent } from '../task/verification.ts';
|
|
10
|
+
import { resolveSandboxControlTransport } from './task-operation-registry.ts';
|
|
11
|
+
import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.ts';
|
|
8
12
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
9
13
|
|
|
10
14
|
const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
|
|
@@ -73,10 +77,52 @@ async function taskFinalization(args: string[] = []): Promise<void> {
|
|
|
73
77
|
...operation,
|
|
74
78
|
request: { ...operation.request, taskRef: resolved.taskId }
|
|
75
79
|
};
|
|
80
|
+
const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
|
|
81
|
+
if (transport.kind === 'sandbox-local') {
|
|
82
|
+
const prepared = await prepareTaskFinalization(boundOperation.request, {
|
|
83
|
+
repoRoot,
|
|
84
|
+
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
85
|
+
});
|
|
86
|
+
if (prepared.status !== 'prepared') {
|
|
87
|
+
process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
|
|
88
|
+
process.exitCode = exitCode(prepared.status);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const response = requestSandboxTaskFinalization({
|
|
93
|
+
agent: boundOperation.request.agent,
|
|
94
|
+
prepareHandoff: (binding) => {
|
|
95
|
+
const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
|
|
96
|
+
return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
process.stdout.write(response.stdout);
|
|
100
|
+
process.stderr.write(response.stderr);
|
|
101
|
+
process.exitCode = response.exitCode ?? 1;
|
|
102
|
+
return;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const detail = error instanceof SandboxControlClientError
|
|
105
|
+
? error.detail
|
|
106
|
+
: { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
|
|
107
|
+
process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
|
|
108
|
+
process.exitCode = detail.retryable ? 2 : 1;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
76
112
|
const result = await dispatchTaskControlOperation(
|
|
77
113
|
createDirectHostExecutionContext({ repoRoot }),
|
|
78
114
|
boundOperation
|
|
79
115
|
);
|
|
116
|
+
if (result.status === 'prepared') {
|
|
117
|
+
const detail = {
|
|
118
|
+
code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
|
|
119
|
+
message: 'direct finalization prepared external work without committing the lifecycle transition',
|
|
120
|
+
retryable: false
|
|
121
|
+
};
|
|
122
|
+
process.stdout.write(envelope('failed', result.changed, true, result, detail));
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
80
126
|
process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
|
|
81
127
|
process.exitCode = exitCode(result.status);
|
|
82
128
|
}
|
|
@@ -2,23 +2,22 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { applyLedgerIntent } from '../task/ledger-intents.ts';
|
|
4
4
|
import type { LedgerIntent } from '../task/ledger-intents.ts';
|
|
5
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
6
5
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewStage, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from '../task/ledger.ts';
|
|
7
6
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
9
8
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
10
9
|
|
|
11
|
-
const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
|
|
10
|
+
const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, rework-intent-rebuild, stage-status\n`;
|
|
12
11
|
const FLAGS: Record<string, string> = {
|
|
13
12
|
'--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
|
|
14
13
|
'--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
|
|
15
14
|
'--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
|
|
16
15
|
'--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
|
|
17
|
-
'--source-sha256': 'sourceSha256', '--
|
|
18
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
16
|
+
'--source-sha256': 'sourceSha256', '--classification': 'classification'
|
|
19
17
|
};
|
|
20
18
|
const NUMERIC = new Set(['ordinal', 'round']);
|
|
21
19
|
const BOOLEAN = new Set(['needsImplementation']);
|
|
20
|
+
const REWORK_CLASSIFICATIONS = new Set(['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence']);
|
|
22
21
|
|
|
23
22
|
function usageFailure(message: string): void {
|
|
24
23
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -38,6 +37,7 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
38
37
|
internalHandlerRoute('task-ledger', 'decision-next-id', kind),
|
|
39
38
|
internalHandlerRoute('task-ledger', 'decision-upsert', kind),
|
|
40
39
|
internalHandlerRoute('task-ledger', 'rework-intent-upsert', kind),
|
|
40
|
+
internalHandlerRoute('task-ledger', 'rework-intent-rebuild', kind),
|
|
41
41
|
internalHandlerRoute('task-ledger', 'stage-status', kind)
|
|
42
42
|
].some(Boolean)) {
|
|
43
43
|
usageFailure(`unknown ledger intent '${kind}'`); return;
|
|
@@ -67,23 +67,23 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
67
67
|
'finding-review': ['id', 'status', 'evidence'],
|
|
68
68
|
'decision-next-id': [],
|
|
69
69
|
'decision-upsert': ['id', 'stage', 'artifact'],
|
|
70
|
-
'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', '
|
|
70
|
+
'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'classification'],
|
|
71
|
+
'rework-intent-rebuild': [],
|
|
71
72
|
'stage-status': ['stage']
|
|
72
73
|
};
|
|
73
74
|
const optional: Record<string, string[]> = {
|
|
74
75
|
'finding-review': ['needsImplementation'],
|
|
75
|
-
'decision-upsert': ['needsImplementation']
|
|
76
|
+
'decision-upsert': ['needsImplementation'],
|
|
77
|
+
'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
|
|
76
78
|
};
|
|
77
|
-
const
|
|
78
|
-
? []
|
|
79
|
-
: ['overrideTicket', 'overrideTarget', 'overrideScope'];
|
|
80
|
-
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? []), ...overrideFields]);
|
|
79
|
+
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...(optional[kind] ?? [])]);
|
|
81
80
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
82
81
|
const missing = required[kind]!.find((key) => values[key] === undefined);
|
|
83
82
|
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
84
83
|
if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
|
|
85
|
+
usageFailure(`invalid rework classification '${values.classification}'`); return;
|
|
86
|
+
}
|
|
87
87
|
if (kind === 'stage-status') {
|
|
88
88
|
if (!isReviewStage(String(values.stage))) { usageFailure(`stage-status requires a valid review stage`); return; }
|
|
89
89
|
const resolved = resolveTaskRef(taskRef);
|
|
@@ -117,42 +117,16 @@ async function taskLedger(args: string[] = []): Promise<void> {
|
|
|
117
117
|
const lockResolved = resolveTaskRef(taskRef);
|
|
118
118
|
if (!lockResolved.ok) { usageFailure(lockResolved.message); return; }
|
|
119
119
|
let result;
|
|
120
|
-
let humanOverride: unknown = null;
|
|
121
120
|
try {
|
|
122
|
-
result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () =>
|
|
123
|
-
let current = applyLedgerIntent(values as LedgerIntent);
|
|
124
|
-
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
125
|
-
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
126
|
-
const consumed = await consumeHumanOverride({
|
|
127
|
-
taskRef,
|
|
128
|
-
ticketId: String(values.overrideTicket),
|
|
129
|
-
failureId: failureId('ledger-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
130
|
-
target: String(values.overrideTarget),
|
|
131
|
-
scope: String(values.overrideScope)
|
|
132
|
-
}, {
|
|
133
|
-
effectExecutor: (capability) => {
|
|
134
|
-
const retried = applyLedgerIntent(values as LedgerIntent, { manualOverride: capability });
|
|
135
|
-
current = retried;
|
|
136
|
-
return retried.status === 'failed'
|
|
137
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'LEDGER_FAILED'}: ${retried.error?.message ?? 'manual ledger effect failed'}` }
|
|
138
|
-
: null;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
humanOverride = consumed;
|
|
142
|
-
return current;
|
|
143
|
-
});
|
|
121
|
+
result = await withTaskExecutionLock(lockResolved.repoRoot, lockResolved.taskId, `task-ledger.${kind}`, async () => applyLedgerIntent(values as LedgerIntent));
|
|
144
122
|
} catch (error) {
|
|
145
123
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
146
124
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
147
125
|
process.exitCode = 1;
|
|
148
126
|
return;
|
|
149
127
|
}
|
|
150
|
-
process.stdout.write(`${JSON.stringify(
|
|
151
|
-
|
|
152
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
153
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
154
|
-
);
|
|
155
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
128
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
129
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
156
130
|
}
|
|
157
131
|
|
|
158
132
|
export { taskLedger };
|
|
@@ -10,7 +10,7 @@ import type { TaskLifecycleResult } from '../task/lifecycle.ts';
|
|
|
10
10
|
import type { AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult } from '../agent-clients/adapter.ts';
|
|
11
11
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
12
12
|
|
|
13
|
-
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\
|
|
13
|
+
const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\nrecover-started: --auto\n`;
|
|
14
14
|
|
|
15
15
|
function usageFailure(message: string): void {
|
|
16
16
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LIFECYCLE_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -62,7 +62,7 @@ async function taskLifecycle(args: string[] = []): Promise<void> {
|
|
|
62
62
|
const result = await dispatchTaskControlOperation(
|
|
63
63
|
createDirectHostExecutionContext({ repoRoot }),
|
|
64
64
|
operation
|
|
65
|
-
) as
|
|
65
|
+
) as TaskLifecycleResult | LifecycleRecoveryResult;
|
|
66
66
|
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
67
67
|
if (result.status === 'failed' || result.status === 'owner-unknown' || result.status === 'conflict') process.exitCode = 1;
|
|
68
68
|
}
|
|
@@ -125,6 +125,7 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
125
125
|
descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
|
|
126
126
|
descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
|
|
127
127
|
descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
|
|
128
|
+
descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
|
|
128
129
|
descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
|
|
129
130
|
descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
|
|
130
131
|
descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
|
|
@@ -142,9 +143,6 @@ function internalTaskRoutes(): TaskOperationDescriptor[] {
|
|
|
142
143
|
descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
|
|
143
144
|
descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
|
|
144
145
|
descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
|
|
145
|
-
descriptor('internal', 'task-override', 'diagnose', 'task-bound', 'diagnostic'),
|
|
146
|
-
descriptor('internal', 'task-override', 'issue', 'task-bound', 'progress'),
|
|
147
|
-
descriptor('internal', 'task-override', 'consume', 'task-bound', 'progress'),
|
|
148
146
|
descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
|
|
149
147
|
descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
|
|
150
148
|
descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
|
|
@@ -223,7 +221,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
|
|
|
223
221
|
'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
|
|
224
222
|
'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
|
|
225
223
|
'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
|
|
226
|
-
'task-finalization', 'task-
|
|
224
|
+
'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
|
|
227
225
|
]);
|
|
228
226
|
export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
|
|
229
227
|
'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { parseReviewCommand } from '../task/review-command.ts';
|
|
2
2
|
import { finalizeReviewSummary, preflightReviewSummary } from '../task/review-finalization.ts';
|
|
3
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
4
3
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
4
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
6
5
|
import { ensureInternalHandlerRoute } from './cli-route-inventory.ts';
|
|
7
6
|
|
|
8
|
-
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]
|
|
7
|
+
const USAGE = 'Usage: agent-infra-internal task-review <task-ref> <preflight|finalize-summary> --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run]\n';
|
|
9
8
|
|
|
10
9
|
function failUsage(message: string): void {
|
|
11
10
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -27,37 +26,16 @@ async function taskReview(args: string[] = []): Promise<void> {
|
|
|
27
26
|
let request;
|
|
28
27
|
try { request = parseReviewCommand(args); }
|
|
29
28
|
catch (error) { failUsage(error instanceof Error ? error.message : String(error)); return; }
|
|
30
|
-
const { stage, artifact, dryRun, orchestrated
|
|
29
|
+
const { stage, artifact, dryRun, orchestrated } = request;
|
|
31
30
|
const operation = args[1]!;
|
|
32
|
-
const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
|
|
33
|
-
if (dryRunConflict) { failUsage(dryRunConflict.message); return; }
|
|
34
31
|
const resolved = resolveTaskRef(args[0]!);
|
|
35
32
|
if (!resolved.ok) { failUsage(resolved.message); return; }
|
|
36
33
|
let result;
|
|
37
|
-
let humanOverride: unknown = null;
|
|
38
34
|
try {
|
|
39
35
|
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-review.${operation}`, async () => {
|
|
40
|
-
|
|
36
|
+
return operation === 'preflight'
|
|
41
37
|
? preflightReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true })
|
|
42
38
|
: finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true });
|
|
43
|
-
if (operation === 'preflight') return current;
|
|
44
|
-
if (current.status !== 'failed' || !overrideTicket) return current;
|
|
45
|
-
if (!overrideTarget || !overrideScope) { failUsage('override ticket requires target and scope'); return current; }
|
|
46
|
-
const consumed = await consumeHumanOverride({
|
|
47
|
-
taskRef: args[0]!, ticketId: overrideTicket,
|
|
48
|
-
failureId: failureId('review-finalization', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
49
|
-
target: overrideTarget, scope: overrideScope
|
|
50
|
-
}, {
|
|
51
|
-
effectExecutor: (capability) => {
|
|
52
|
-
const retried = finalizeReviewSummary({ taskRef: args[0]!, stage, artifact, orchestrated, dryRun }, { lockAlreadyHeld: true, manualOverride: capability });
|
|
53
|
-
current = retried;
|
|
54
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
55
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no review effect was committed' : `${retried.error?.code ?? 'REVIEW_FAILED'}: ${retried.error?.message ?? 'manual review effect failed'}` }
|
|
56
|
-
: null;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
humanOverride = consumed;
|
|
60
|
-
return current;
|
|
61
39
|
});
|
|
62
40
|
} catch (error) {
|
|
63
41
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
@@ -65,12 +43,8 @@ async function taskReview(args: string[] = []): Promise<void> {
|
|
|
65
43
|
process.exitCode = 1;
|
|
66
44
|
return;
|
|
67
45
|
}
|
|
68
|
-
process.stdout.write(`${JSON.stringify(
|
|
69
|
-
|
|
70
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
71
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
72
|
-
);
|
|
73
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
46
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
47
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
74
48
|
}
|
|
75
49
|
|
|
76
50
|
export { taskReview };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { renderTaskVerification, verifyTaskEvent } from '../task/verification.ts';
|
|
2
|
-
import { consumeHumanOverride, failureId } from '../task/human-override.ts';
|
|
3
2
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
4
3
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
5
4
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
6
5
|
|
|
7
|
-
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]
|
|
6
|
+
const USAGE = 'Usage: agent-infra-internal task-verify <N | TASK-id> <verification-event> [--artifact <canonical.md>] [--format json|text]\n';
|
|
8
7
|
|
|
9
8
|
function fail(message: string): void {
|
|
10
9
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'VERIFY_PAYLOAD_INVALID', message } })}\n`);
|
|
@@ -21,63 +20,28 @@ async function taskVerify(args: string[] = []): Promise<void> {
|
|
|
21
20
|
const event = args[1];
|
|
22
21
|
let artifact: string | undefined;
|
|
23
22
|
let format: 'json' | 'text' = 'json';
|
|
24
|
-
let overrideTicket: string | undefined;
|
|
25
|
-
let overrideTarget: string | undefined;
|
|
26
|
-
let overrideScope: string | undefined;
|
|
27
23
|
const seen = new Set<string>();
|
|
28
24
|
for (let index = 2; index < args.length; index += 1) {
|
|
29
25
|
const flag = args[index]!;
|
|
30
|
-
if (!['--artifact', '--format'
|
|
26
|
+
if (!['--artifact', '--format'].includes(flag)) { fail(`unknown option '${flag}'`); return; }
|
|
31
27
|
if (seen.has(flag)) { fail(`duplicate option '${flag}'`); return; }
|
|
32
28
|
const value = args[++index];
|
|
33
29
|
if (!value || value.startsWith('--')) { fail(`option '${flag}' requires a value`); return; }
|
|
34
30
|
seen.add(flag);
|
|
35
31
|
if (flag === '--artifact') artifact = value;
|
|
36
|
-
else if (flag === '--override-ticket') overrideTicket = value;
|
|
37
|
-
else if (flag === '--override-target') overrideTarget = value;
|
|
38
|
-
else if (flag === '--override-scope') overrideScope = value;
|
|
39
32
|
else if (value === 'json' || value === 'text') format = value;
|
|
40
33
|
else { fail("option '--format' requires 'json' or 'text'"); return; }
|
|
41
34
|
}
|
|
42
35
|
const resolved = resolveTaskRef(taskRef);
|
|
43
36
|
if (!resolved.ok) { fail(resolved.message); return; }
|
|
44
37
|
let result;
|
|
45
|
-
let humanOverride: unknown = null;
|
|
46
38
|
try {
|
|
47
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => {
|
|
48
|
-
let current = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) });
|
|
49
|
-
if (current.status === 'pass' || !overrideTicket) return current;
|
|
50
|
-
if (!overrideTarget || !overrideScope) { fail('override ticket requires target and scope'); return current; }
|
|
51
|
-
const failureProducer = current.status === 'blocked' || current.status === 'fail'
|
|
52
|
-
? 'verification-engine'
|
|
53
|
-
: 'task-verify';
|
|
54
|
-
const failureCode = current.status === 'blocked'
|
|
55
|
-
? 'CHECK_BLOCKED'
|
|
56
|
-
: current.status === 'fail'
|
|
57
|
-
? 'CHECK_FAILED'
|
|
58
|
-
: current.error?.code ?? 'VERIFY_TASK_STATE_MISMATCH';
|
|
59
|
-
const consumed = await consumeHumanOverride({
|
|
60
|
-
taskRef, ticketId: overrideTicket,
|
|
61
|
-
failureId: failureId(failureProducer, failureCode),
|
|
62
|
-
target: overrideTarget, scope: overrideScope
|
|
63
|
-
}, {
|
|
64
|
-
effectExecutor: async (capability) => {
|
|
65
|
-
const retried = await verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }, { manualOverride: capability });
|
|
66
|
-
current = retried;
|
|
67
|
-
return retried.status !== 'pass'
|
|
68
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: `${retried.error?.code ?? 'VERIFY_FAILED'}: ${retried.error?.message ?? 'manual verification effect failed'}` }
|
|
69
|
-
: null;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
humanOverride = consumed;
|
|
73
|
-
return current;
|
|
74
|
-
});
|
|
39
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-verify.${event}`, async () => verifyTaskEvent({ taskRef, event, ...(artifact ? { artifact } : {}) }));
|
|
75
40
|
} catch (error) {
|
|
76
41
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
77
42
|
fail(`${error.code}: ${error.message}`);
|
|
78
43
|
return;
|
|
79
44
|
}
|
|
80
|
-
if (humanOverride) result = { ...result, humanOverride } as typeof result;
|
|
81
45
|
process.stdout.write(format === 'text' ? renderTaskVerification(result) : `${JSON.stringify(result)}\n`);
|
|
82
46
|
process.exitCode = result.status === 'pass' ? 0 : result.status === 'blocked' ? 2 : 1;
|
|
83
47
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
|
|
2
2
|
import type { WorkflowWarningIntent } from '../task/workflow-warning-intents.ts';
|
|
3
|
-
import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.ts';
|
|
4
3
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
4
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
6
5
|
import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
|
|
@@ -9,7 +8,6 @@ const USAGE = `Usage: agent-infra-internal task-warning <task-ref> <add|set-stat
|
|
|
9
8
|
const FLAGS: Record<string, string> = {
|
|
10
9
|
'--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
|
|
11
10
|
'--message': 'message', '--action': 'action', '--id': 'id', '--status': 'status', '--resolution': 'resolution'
|
|
12
|
-
, '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
13
11
|
};
|
|
14
12
|
|
|
15
13
|
function usageFailure(message: string): void {
|
|
@@ -48,50 +46,23 @@ async function taskWarning(args: string[] = []): Promise<void> {
|
|
|
48
46
|
};
|
|
49
47
|
const optional = kind === 'list' ? ['status'] : [];
|
|
50
48
|
const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]!, ...optional]);
|
|
51
|
-
allowed.add('overrideTicket'); allowed.add('overrideTarget'); allowed.add('overrideScope');
|
|
52
49
|
const unexpected = Object.keys(values).find((key) => !allowed.has(key));
|
|
53
50
|
const missing = required[kind]!.find((key) => values[key] === undefined);
|
|
54
51
|
if (unexpected) { usageFailure(`${kind} does not accept '${unexpected}'`); return; }
|
|
55
52
|
if (missing) { usageFailure(`${kind} requires '${missing}'`); return; }
|
|
56
|
-
const dryRunConflict = overrideDryRunConflict(values);
|
|
57
|
-
if (dryRunConflict) { usageFailure(dryRunConflict.message); return; }
|
|
58
53
|
const resolved = resolveTaskRef(taskRef);
|
|
59
54
|
if (!resolved.ok) { usageFailure(resolved.message); return; }
|
|
60
55
|
let result;
|
|
61
|
-
let humanOverride: unknown = null;
|
|
62
56
|
try {
|
|
63
|
-
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () =>
|
|
64
|
-
let current = applyWorkflowWarningIntent(values as WorkflowWarningIntent);
|
|
65
|
-
if (current.status !== 'failed' || !values.overrideTicket) return current;
|
|
66
|
-
if (!values.overrideTarget || !values.overrideScope) { usageFailure('override ticket requires target and scope'); return current; }
|
|
67
|
-
const consumed = await consumeHumanOverride({
|
|
68
|
-
taskRef, ticketId: String(values.overrideTicket),
|
|
69
|
-
failureId: failureId('workflow-warning', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
70
|
-
target: String(values.overrideTarget), scope: String(values.overrideScope)
|
|
71
|
-
}, {
|
|
72
|
-
effectExecutor: (capability) => {
|
|
73
|
-
const retried = applyWorkflowWarningIntent(values as WorkflowWarningIntent, { manualOverride: capability });
|
|
74
|
-
current = retried;
|
|
75
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
76
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no workflow warning effect was committed' : `${retried.error?.code ?? 'WARNING_FAILED'}: ${retried.error?.message ?? 'manual warning effect failed'}` }
|
|
77
|
-
: null;
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
humanOverride = consumed;
|
|
81
|
-
return current;
|
|
82
|
-
});
|
|
57
|
+
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-warning.${kind}`, async () => applyWorkflowWarningIntent(values as WorkflowWarningIntent));
|
|
83
58
|
} catch (error) {
|
|
84
59
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
85
60
|
process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
|
|
86
61
|
process.exitCode = 1;
|
|
87
62
|
return;
|
|
88
63
|
}
|
|
89
|
-
process.stdout.write(`${JSON.stringify(
|
|
90
|
-
|
|
91
|
-
humanOverride && typeof humanOverride === 'object' &&
|
|
92
|
-
(humanOverride as { status?: unknown }).status === 'failed'
|
|
93
|
-
);
|
|
94
|
-
if (result.status === 'failed' || overrideFailed) process.exitCode = 1;
|
|
64
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
65
|
+
if (result.status === 'failed') process.exitCode = 1;
|
|
95
66
|
}
|
|
96
67
|
|
|
97
68
|
export { taskWarning };
|
|
@@ -19,6 +19,24 @@ type CompletionBackfillResult = PlatformResult & {
|
|
|
19
19
|
artifacts: Array<{ artifact: string; status: PlatformResult['status'] }>;
|
|
20
20
|
warnings: Array<{ id: string; status: string }>;
|
|
21
21
|
};
|
|
22
|
+
type CompletionBackfillEligibility = Readonly<{
|
|
23
|
+
status: 'ready' | 'failed';
|
|
24
|
+
eligible: boolean;
|
|
25
|
+
error: { code: string; message: string; retryable: boolean } | null;
|
|
26
|
+
}>;
|
|
27
|
+
type BackfillArtifactReference = Readonly<
|
|
28
|
+
| { status: 'ignored'; artifact: null; error: null }
|
|
29
|
+
| { status: 'ready'; artifact: string; error: null }
|
|
30
|
+
| { status: 'failed'; artifact: null; error: { code: string; message: string; retryable: false } }
|
|
31
|
+
>;
|
|
32
|
+
type BackfillWarningMatch = Readonly<{
|
|
33
|
+
warning: ReturnType<typeof getOpenWorkflowWarnings>[number];
|
|
34
|
+
artifact: string;
|
|
35
|
+
}>;
|
|
36
|
+
type BackfillWarningMatches = Readonly<
|
|
37
|
+
| { status: 'ready'; matches: BackfillWarningMatch[]; error: null }
|
|
38
|
+
| { status: 'failed'; matches: []; error: { code: string; message: string; retryable: false } }
|
|
39
|
+
>;
|
|
22
40
|
|
|
23
41
|
function result(
|
|
24
42
|
base: PlatformResult,
|
|
@@ -28,18 +46,65 @@ function result(
|
|
|
28
46
|
return { ...base, artifacts, warnings };
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
function
|
|
32
|
-
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(
|
|
49
|
+
function referencedBackfillArtifact(taskDir: string, message: string): BackfillArtifactReference {
|
|
50
|
+
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
|
|
33
51
|
.map((match) => match[1]!);
|
|
34
|
-
if (names.length !== 1) return null;
|
|
52
|
+
if (names.length !== 1) return { status: 'ignored', artifact: null, error: null };
|
|
35
53
|
const parsed = parseArtifactName(names[0]!);
|
|
36
|
-
if (!parsed || parsed.family !== 'pr-review')
|
|
54
|
+
if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family as typeof COMPLETION_BACKFILL_FAMILIES[number]))) {
|
|
55
|
+
return { status: 'ignored', artifact: null, error: null };
|
|
56
|
+
}
|
|
37
57
|
const artifactPath = path.join(taskDir, parsed.name);
|
|
38
58
|
try {
|
|
39
59
|
const stat = fs.lstatSync(artifactPath);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
if (stat.isSymbolicLink()) return {
|
|
61
|
+
status: 'failed', artifact: null,
|
|
62
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
|
|
63
|
+
};
|
|
64
|
+
if (!stat.isFile()) return {
|
|
65
|
+
status: 'failed', artifact: null,
|
|
66
|
+
error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
|
|
67
|
+
};
|
|
68
|
+
fs.accessSync(artifactPath, fs.constants.R_OK);
|
|
69
|
+
return { status: 'ready', artifact: parsed.name, error: null };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return {
|
|
72
|
+
status: 'failed', artifact: null,
|
|
73
|
+
error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function matchingBackfillWarnings(taskDir: string, content: string): BackfillWarningMatches {
|
|
79
|
+
const matches: BackfillWarningMatch[] = [];
|
|
80
|
+
for (const warning of getOpenWorkflowWarnings(content)) {
|
|
81
|
+
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') continue;
|
|
82
|
+
const reference = referencedBackfillArtifact(taskDir, warning.message);
|
|
83
|
+
if (reference.status === 'failed') return { status: 'failed', matches: [], error: reference.error };
|
|
84
|
+
if (reference.status === 'ready') matches.push({ warning, artifact: reference.artifact });
|
|
85
|
+
}
|
|
86
|
+
return { status: 'ready', matches, error: null };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function inspectCompletionBackfillEligibility(
|
|
90
|
+
taskRef: string,
|
|
91
|
+
options: Pick<CompletionBackfillOptions, 'cwd'> = {}
|
|
92
|
+
): CompletionBackfillEligibility {
|
|
93
|
+
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
94
|
+
if (!resolved.ok) return {
|
|
95
|
+
status: 'failed', eligible: false,
|
|
96
|
+
error: { code: resolved.code, message: resolved.message, retryable: false }
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
100
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, content);
|
|
101
|
+
if (matching.status === 'failed') return { status: 'failed', eligible: false, error: matching.error };
|
|
102
|
+
return { status: 'ready', eligible: matching.matches.length > 0, error: null };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
status: 'failed', eligible: false,
|
|
106
|
+
error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
107
|
+
};
|
|
43
108
|
}
|
|
44
109
|
}
|
|
45
110
|
|
|
@@ -60,6 +125,8 @@ async function backfillCompletionComments(
|
|
|
60
125
|
}
|
|
61
126
|
}), [], []);
|
|
62
127
|
const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
128
|
+
const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
|
|
129
|
+
if (matching.status === 'failed') return result(platformResult('failed', { error: matching.error }), [], []);
|
|
63
130
|
const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
|
|
64
131
|
if (!issueIdentity) return result(platformResult('no-op', {
|
|
65
132
|
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
@@ -92,17 +159,12 @@ async function backfillCompletionComments(
|
|
|
92
159
|
}), artifacts, []);
|
|
93
160
|
}
|
|
94
161
|
|
|
95
|
-
const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
|
|
96
|
-
if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact') return [];
|
|
97
|
-
const excluded = excludedPrReview(resolved.taskDir, warning.message);
|
|
98
|
-
return excluded ? [{ warning, excluded }] : [];
|
|
99
|
-
});
|
|
100
162
|
const warnings: CompletionBackfillResult['warnings'] = [];
|
|
101
163
|
let metadata: ReturnType<typeof captureTaskWriteMetadata> | null = null;
|
|
102
|
-
for (const { warning,
|
|
164
|
+
for (const { warning, artifact } of matching.matches) {
|
|
103
165
|
metadata ??= captureTaskWriteMetadata();
|
|
104
166
|
const resolution = [
|
|
105
|
-
`
|
|
167
|
+
`artifact=${artifact}`,
|
|
106
168
|
`completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
|
|
107
169
|
`synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
|
|
108
170
|
`completedAt=${metadata.timestamp}`
|
|
@@ -131,5 +193,5 @@ async function backfillCompletionComments(
|
|
|
131
193
|
}), artifacts, warnings);
|
|
132
194
|
}
|
|
133
195
|
|
|
134
|
-
export { backfillCompletionComments };
|
|
135
|
-
export type { CompletionBackfillOptions, CompletionBackfillResult };
|
|
196
|
+
export { backfillCompletionComments, inspectCompletionBackfillEligibility };
|
|
197
|
+
export type { CompletionBackfillEligibility, CompletionBackfillOptions, CompletionBackfillResult };
|
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { enumerateArtifacts } from '../task/artifacts.ts';
|
|
5
5
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
|
|
6
|
-
import { renderHumanOverrideAudit } from '../task/human-override.ts';
|
|
7
6
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
8
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
|
|
9
8
|
import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
|
|
@@ -11,7 +10,7 @@ import type { PrDeliveryFact } from '../task/pr-delivery-fact.ts';
|
|
|
11
10
|
import { resolvePlatformProviderContext } from './context.ts';
|
|
12
11
|
import type { LoadedContext, PlatformClient } from './context.ts';
|
|
13
12
|
import { normalizeCommentContent } from './issue-comments.ts';
|
|
14
|
-
import { CONTROL_MARKER_PATTERN,
|
|
13
|
+
import { CONTROL_MARKER_PATTERN, sanitizeMarkdownDocument, splitDocumentPlaceholder } from './comment-safety.ts';
|
|
15
14
|
import { platformResult } from './types.ts';
|
|
16
15
|
import type { PlatformResult } from './types.ts';
|
|
17
16
|
import type { OperationWarning } from '../task/operation-outcome.ts';
|
|
@@ -74,25 +73,6 @@ function summaryMarker(taskId: string): string {
|
|
|
74
73
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
function escapeSummaryAuditText(value: string): string {
|
|
78
|
-
return escapeHtmlText(value).replace(/\r\n?/g, '\n');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function renderSummaryAuditLine(value: string): string {
|
|
82
|
-
const safe = escapeSummaryAuditText(value);
|
|
83
|
-
const longestRun = Math.max(0, ...(safe.match(/`+/g) || []).map((run) => run.length));
|
|
84
|
-
const delimiter = '`'.repeat(longestRun + 1);
|
|
85
|
-
return `${delimiter} ${safe} ${delimiter}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function renderSafeHumanOverrideAudit(value: string): string {
|
|
89
|
-
const normalized = value.trim().replace(/\r\n?/g, '\n');
|
|
90
|
-
if (!normalized) return '';
|
|
91
|
-
const [heading, ...rows] = normalized.split('\n');
|
|
92
|
-
const safeHeading = heading === '## Human Override Audit' ? heading : renderSummaryAuditLine(heading || '');
|
|
93
|
-
return [safeHeading, ...rows.map((row) => row ? renderSummaryAuditLine(row) : '')].join('\n');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
76
|
function isSafeSummaryEnvelope(value: string, taskId: string): boolean {
|
|
97
77
|
return value.includes(summaryMarker(taskId))
|
|
98
78
|
&& (value.match(/<!--\s*sync-pr:/gi) || []).length === 1
|
|
@@ -136,9 +116,8 @@ function platformError(error: { code: string; message: string; retryable?: boole
|
|
|
136
116
|
return { code: error.code, message: error.message, retryable: error.retryable ?? false };
|
|
137
117
|
}
|
|
138
118
|
|
|
139
|
-
function buildPullRequestSummary(taskId: string, body: string, headSha: string
|
|
140
|
-
|
|
141
|
-
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${sections}\n`);
|
|
119
|
+
function buildPullRequestSummary(taskId: string, body: string, headSha: string): string {
|
|
120
|
+
return normalizeCommentContent(`${summaryMarker(taskId)}\n<!-- last-commit: ${headSha} -->\n\n${body.replace(/\s+$/, '')}\n`);
|
|
142
121
|
}
|
|
143
122
|
|
|
144
123
|
function reconcileSummaryComment(comments: SummaryComment[], taskId: string, desired: string):
|
|
@@ -643,7 +622,7 @@ async function syncPullRequestSummary(
|
|
|
643
622
|
`<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`
|
|
644
623
|
)
|
|
645
624
|
: replaced.value;
|
|
646
|
-
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha
|
|
625
|
+
const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha);
|
|
647
626
|
if (!isSafeSummaryEnvelope(desired, resolved.taskId)) return fail('failed', context, {
|
|
648
627
|
code: 'PR_SUMMARY_RENDER_INVALID',
|
|
649
628
|
message: 'Summary contains an invalid or duplicated reserved control marker',
|