@fitlab-ai/agent-infra 0.9.12 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
5
|
import { buildProcessTreeStopCommand } from '../../server/process-control.js';
|
|
6
6
|
import { getProcessIdentityState } from '../../server/process-state.js';
|
|
7
|
+
import { parseSandboxControlResultEvidence, parseSandboxControlPayload, parseSandboxControlReservation, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_LOGICAL_RECORDS, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES, SANDBOX_CONTROL_RESERVATION_BYTES } from './protocol.js';
|
|
7
8
|
export const SANDBOX_CONTROL_AUDIT_MAX_BYTES = 1024 * 1024;
|
|
8
9
|
export const SANDBOX_CONTROL_EXECUTION_STOP_MS = 2_000;
|
|
9
10
|
function assertRegularFile(filePath) {
|
|
@@ -23,10 +24,212 @@ export function atomicWriteJson(filePath, value, mode = 0o600, createParent = tr
|
|
|
23
24
|
fs.rmSync(temporary, { force: true });
|
|
24
25
|
}
|
|
25
26
|
}
|
|
27
|
+
export function sandboxControlEncodedJsonBytes(value) {
|
|
28
|
+
return Buffer.byteLength(`${JSON.stringify(value)}\n`, 'utf8');
|
|
29
|
+
}
|
|
30
|
+
export function atomicWriteJsonNoReplace(filePath, value, mode = 0o600, createParent = true) {
|
|
31
|
+
atomicCreateJson(filePath, value, 'SANDBOX_CONTROL_TERMINAL_ALREADY_EXISTS', mode, createParent);
|
|
32
|
+
}
|
|
33
|
+
function atomicCreateJson(filePath, value, duplicateCode, mode = 0o600, createParent = true) {
|
|
34
|
+
if (createParent)
|
|
35
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
36
|
+
const temporary = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`);
|
|
37
|
+
try {
|
|
38
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode, flag: 'wx' });
|
|
39
|
+
try {
|
|
40
|
+
fs.linkSync(temporary, filePath);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code === 'EEXIST') {
|
|
44
|
+
throw new Error(duplicateCode);
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
fs.rmSync(temporary, { force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
26
53
|
export function readJsonFile(filePath) {
|
|
27
54
|
assertRegularFile(filePath);
|
|
28
55
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
29
56
|
}
|
|
57
|
+
export function resultEvidencePath(manifest, requestId) {
|
|
58
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
|
|
59
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
60
|
+
return path.join(manifest.processingDir, requestId, 'result.json');
|
|
61
|
+
}
|
|
62
|
+
export function payloadPath(manifest, requestId) {
|
|
63
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
|
|
64
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
65
|
+
return path.join(manifest.channelDir, 'responses', `${requestId}.payload.json`);
|
|
66
|
+
}
|
|
67
|
+
export function reservationPath(manifest, requestId) {
|
|
68
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId))
|
|
69
|
+
throw new Error('SANDBOX_CONTROL_RESERVATION_INVALID');
|
|
70
|
+
return path.join(manifest.processingDir, requestId, 'reservation.json');
|
|
71
|
+
}
|
|
72
|
+
export function readSandboxControlResultEvidence(filePath) {
|
|
73
|
+
assertRegularFile(filePath);
|
|
74
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
75
|
+
if (Buffer.byteLength(raw, 'utf8') > SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
|
|
76
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_TOO_LARGE');
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return parseSandboxControlResultEvidence(JSON.parse(raw));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof Error && error.message === 'SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID')
|
|
83
|
+
throw error;
|
|
84
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function sanitizeSandboxControlOutput(manifest, output) {
|
|
88
|
+
return manifest.token.length === 0 ? output : output.split(manifest.token).join('[REDACTED]');
|
|
89
|
+
}
|
|
90
|
+
export function sanitizeSandboxControlResult(manifest, result) {
|
|
91
|
+
return {
|
|
92
|
+
exitCode: result.exitCode,
|
|
93
|
+
stdout: sanitizeSandboxControlOutput(manifest, result.stdout),
|
|
94
|
+
stderr: sanitizeSandboxControlOutput(manifest, result.stderr)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function writeSandboxControlResultEvidence(manifest, requestId, result) {
|
|
98
|
+
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
99
|
+
const stdout = normalized.stdout;
|
|
100
|
+
const stderr = normalized.stderr;
|
|
101
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(requestId)
|
|
102
|
+
|| !Number.isSafeInteger(result.exitCode)
|
|
103
|
+
|| Buffer.byteLength(stdout, 'utf8') + Buffer.byteLength(stderr, 'utf8') > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
104
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
105
|
+
}
|
|
106
|
+
const evidence = {
|
|
107
|
+
version: 1,
|
|
108
|
+
id: requestId,
|
|
109
|
+
generation: manifest.generation,
|
|
110
|
+
exitCode: result.exitCode,
|
|
111
|
+
stdoutBytes: Buffer.byteLength(stdout, 'utf8'),
|
|
112
|
+
stderrBytes: Buffer.byteLength(stderr, 'utf8'),
|
|
113
|
+
stdoutSha256: createHash('sha256').update(stdout, 'utf8').digest('hex'),
|
|
114
|
+
stderrSha256: createHash('sha256').update(stderr, 'utf8').digest('hex'),
|
|
115
|
+
captureState: 'metadata-only'
|
|
116
|
+
};
|
|
117
|
+
if (sandboxControlEncodedJsonBytes(evidence) > SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
|
|
118
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_TOO_LARGE');
|
|
119
|
+
}
|
|
120
|
+
atomicCreateJson(resultEvidencePath(manifest, requestId), evidence, 'SANDBOX_CONTROL_RESULT_EVIDENCE_ALREADY_EXISTS');
|
|
121
|
+
return readSandboxControlResultEvidence(resultEvidencePath(manifest, requestId));
|
|
122
|
+
}
|
|
123
|
+
export function writeSandboxControlPayload(manifest, requestId, result) {
|
|
124
|
+
const payload = createSandboxControlPayload(manifest, requestId, result);
|
|
125
|
+
atomicCreateJson(payloadPath(manifest, requestId), payload, 'SANDBOX_CONTROL_PAYLOAD_ALREADY_EXISTS');
|
|
126
|
+
return readSandboxControlPayload(payloadPath(manifest, requestId));
|
|
127
|
+
}
|
|
128
|
+
export function createSandboxControlPayload(manifest, requestId, result) {
|
|
129
|
+
const stdout = sanitizeSandboxControlOutput(manifest, result.stdout);
|
|
130
|
+
const stderr = sanitizeSandboxControlOutput(manifest, result.stderr);
|
|
131
|
+
const payload = {
|
|
132
|
+
version: 1,
|
|
133
|
+
id: requestId,
|
|
134
|
+
generation: manifest.generation,
|
|
135
|
+
stdout,
|
|
136
|
+
stderr,
|
|
137
|
+
stdoutBytes: Buffer.byteLength(stdout, 'utf8'),
|
|
138
|
+
stderrBytes: Buffer.byteLength(stderr, 'utf8'),
|
|
139
|
+
stdoutSha256: createHash('sha256').update(stdout, 'utf8').digest('hex'),
|
|
140
|
+
stderrSha256: createHash('sha256').update(stderr, 'utf8').digest('hex')
|
|
141
|
+
};
|
|
142
|
+
if (payload.stdoutBytes + payload.stderrBytes > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
143
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
|
|
144
|
+
}
|
|
145
|
+
if (sandboxControlEncodedJsonBytes(payload) > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
146
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
|
|
147
|
+
}
|
|
148
|
+
return payload;
|
|
149
|
+
}
|
|
150
|
+
export function readSandboxControlPayload(filePath) {
|
|
151
|
+
assertRegularFile(filePath);
|
|
152
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
153
|
+
if (Buffer.byteLength(raw, 'utf8') > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
154
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_TOO_LARGE');
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
return parseSandboxControlPayload(JSON.parse(raw));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
throw new Error('SANDBOX_CONTROL_PAYLOAD_INVALID');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function writeSandboxControlReservation(manifest, requestId, usage, now = Date.now()) {
|
|
164
|
+
if (usage.logicalRecords >= SANDBOX_CONTROL_MAX_LOGICAL_RECORDS
|
|
165
|
+
|| usage.bytes + SANDBOX_CONTROL_RESERVATION_BYTES > SANDBOX_CONTROL_MAX_RESPONSE_BYTES) {
|
|
166
|
+
throw new Error('SANDBOX_CONTROL_CAPACITY_EXHAUSTED');
|
|
167
|
+
}
|
|
168
|
+
const reservation = {
|
|
169
|
+
version: 1,
|
|
170
|
+
id: requestId,
|
|
171
|
+
generation: manifest.generation,
|
|
172
|
+
logicalRecords: 1,
|
|
173
|
+
bytes: SANDBOX_CONTROL_RESERVATION_BYTES,
|
|
174
|
+
createdAt: now
|
|
175
|
+
};
|
|
176
|
+
atomicCreateJson(reservationPath(manifest, requestId), reservation, 'SANDBOX_CONTROL_RESERVATION_ALREADY_EXISTS');
|
|
177
|
+
return parseSandboxControlReservation(readJsonFile(reservationPath(manifest, requestId)));
|
|
178
|
+
}
|
|
179
|
+
export function readSandboxControlReservation(filePath) {
|
|
180
|
+
return parseSandboxControlReservation(readJsonFile(filePath));
|
|
181
|
+
}
|
|
182
|
+
export function sandboxControlGenerationUsage(manifest) {
|
|
183
|
+
const records = new Map();
|
|
184
|
+
const responsesDir = path.join(manifest.channelDir, 'responses');
|
|
185
|
+
if (fs.existsSync(responsesDir)) {
|
|
186
|
+
for (const entry of fs.readdirSync(responsesDir, { withFileTypes: true })) {
|
|
187
|
+
if (!entry.isFile())
|
|
188
|
+
continue;
|
|
189
|
+
const terminal = entry.name.match(/^([a-f0-9-]{16,64})\.json$/u);
|
|
190
|
+
const accepted = entry.name.match(/^([a-f0-9-]{16,64})\.accepted\.json$/u);
|
|
191
|
+
const payload = entry.name.match(/^([a-f0-9-]{16,64})\.payload\.json$/u);
|
|
192
|
+
if (!terminal && !accepted && !payload)
|
|
193
|
+
continue;
|
|
194
|
+
const id = (terminal ?? accepted ?? payload)[1];
|
|
195
|
+
const previous = records.get(id) ?? { baseBytes: 0, payloadBytes: 0, logical: false };
|
|
196
|
+
const fileBytes = fs.statSync(path.join(responsesDir, entry.name)).size;
|
|
197
|
+
if (payload)
|
|
198
|
+
previous.payloadBytes += fileBytes;
|
|
199
|
+
else
|
|
200
|
+
previous.baseBytes += fileBytes;
|
|
201
|
+
previous.logical ||= Boolean(terminal || accepted);
|
|
202
|
+
records.set(id, previous);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (fs.existsSync(manifest.processingDir)) {
|
|
206
|
+
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
207
|
+
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/u.test(entry.name))
|
|
208
|
+
continue;
|
|
209
|
+
const resultPath = resultEvidencePath(manifest, entry.name);
|
|
210
|
+
const filePath = reservationPath(manifest, entry.name);
|
|
211
|
+
const previous = records.get(entry.name) ?? { baseBytes: 0, payloadBytes: 0, logical: false };
|
|
212
|
+
if (fs.existsSync(resultPath)) {
|
|
213
|
+
const result = readSandboxControlResultEvidence(resultPath);
|
|
214
|
+
if (result.id !== entry.name || result.generation !== manifest.generation) {
|
|
215
|
+
throw new Error('SANDBOX_CONTROL_RESULT_EVIDENCE_INVALID');
|
|
216
|
+
}
|
|
217
|
+
previous.baseBytes += fs.statSync(resultPath).size;
|
|
218
|
+
previous.logical = true;
|
|
219
|
+
}
|
|
220
|
+
if (fs.existsSync(filePath)) {
|
|
221
|
+
const reservation = readSandboxControlReservation(filePath);
|
|
222
|
+
previous.baseBytes = Math.max(previous.baseBytes, reservation.bytes);
|
|
223
|
+
previous.logical = true;
|
|
224
|
+
}
|
|
225
|
+
records.set(entry.name, previous);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
logicalRecords: [...records.values()].filter((record) => record.logical).length,
|
|
230
|
+
bytes: [...records.values()].reduce((total, record) => total + record.baseBytes + record.payloadBytes, 0)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
30
233
|
export function statusPath(manifest) {
|
|
31
234
|
return path.join(manifest.publicStatusDir, 'status.json');
|
|
32
235
|
}
|
|
@@ -9,6 +9,28 @@ function splitWords(text) {
|
|
|
9
9
|
function taskSubcommand(subcommand) {
|
|
10
10
|
return subcommand === 'list' ? 'ls' : subcommand;
|
|
11
11
|
}
|
|
12
|
+
function splitRunArguments(args) {
|
|
13
|
+
const hostArgs = [];
|
|
14
|
+
const skillArgs = [];
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
const arg = args[i];
|
|
17
|
+
if (arg === '--tui') {
|
|
18
|
+
hostArgs.push(arg);
|
|
19
|
+
const value = args[i + 1];
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
hostArgs.push(value);
|
|
22
|
+
i += 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else if (arg === '--recreate') {
|
|
26
|
+
hostArgs.push(arg);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
skillArgs.push(arg);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { hostArgs, skillArgs };
|
|
33
|
+
}
|
|
12
34
|
export function commandHelp() {
|
|
13
35
|
return [
|
|
14
36
|
`agent-infra ${VERSION}`,
|
|
@@ -78,7 +100,22 @@ export function parseCommand(text) {
|
|
|
78
100
|
const spec = getSkillRunSpec(subcommand);
|
|
79
101
|
if (!spec)
|
|
80
102
|
return { kind: 'error', message: `Unknown skill: ${subcommand}` };
|
|
81
|
-
|
|
103
|
+
if (spec.kind === 'create') {
|
|
104
|
+
return { kind: 'ai', role: spec.role, argv: ['run', '--skill', subcommand, ...rest] };
|
|
105
|
+
}
|
|
106
|
+
const [taskRef, ...skillArgs] = rest;
|
|
107
|
+
if (!taskRef)
|
|
108
|
+
return { kind: 'error', message: '/run task skills require a task-ref' };
|
|
109
|
+
const { hostArgs, skillArgs: forwardedSkillArgs } = splitRunArguments(skillArgs);
|
|
110
|
+
return {
|
|
111
|
+
kind: 'ai',
|
|
112
|
+
role: spec.role,
|
|
113
|
+
argv: [
|
|
114
|
+
'run', '--skill', subcommand, '--task', taskRef,
|
|
115
|
+
...hostArgs,
|
|
116
|
+
...(forwardedSkillArgs.length > 0 ? ['--', ...forwardedSkillArgs] : [])
|
|
117
|
+
]
|
|
118
|
+
};
|
|
82
119
|
}
|
|
83
120
|
return { kind: 'error', message: `Unknown command: ${command}` };
|
|
84
121
|
}
|
|
@@ -5,6 +5,7 @@ const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
|
|
|
5
5
|
const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
|
|
6
6
|
const ATTEMPT_RE = /(?:^|;\s*)attempt=([A-Za-z0-9-]{8,64})(?:;|$)/;
|
|
7
7
|
const COMMIT_STARTED_RE = /^started; attempt=([A-Za-z0-9-]{8,64}); baseline=([a-f0-9]{40,64}); agent=([a-z-]+)$/;
|
|
8
|
+
const LIFECYCLE_STEP_RE = /^(?:Complete Manual Validation|(?:Analyze Task|Review Analysis|Plan Task|Review Plan|Code Task|Review Code|Run Manual Validation) \(Round [1-9]\d*(?:(?:, fix for review-code(?:-r[2-9]|-r[1-9]\d+)?\.md)|(?:, decision II-[1-9]\d*))?\))$/;
|
|
8
9
|
function parseActivityLog(content) {
|
|
9
10
|
const section = locateActivityLog(content);
|
|
10
11
|
return section ? { sectionFound: true, entries: section.entries } : { sectionFound: false, entries: [] };
|
|
@@ -87,5 +88,11 @@ function pairEntries(entries) {
|
|
|
87
88
|
function startedBackedRows(rows) {
|
|
88
89
|
return rows.filter((row) => row.started !== '');
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
function hasOpenLifecycleExecution(content) {
|
|
92
|
+
const section = locateActivityLog(content);
|
|
93
|
+
if (!section)
|
|
94
|
+
return false;
|
|
95
|
+
return pairEntries(section.entries).some((row) => row.done === '' && LIFECYCLE_STEP_RE.test(row.step));
|
|
96
|
+
}
|
|
97
|
+
export { activityAttempt, appendActivityEntry, hasOpenLifecycleExecution, commitAttemptStartedNote, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted, startedBackedRows };
|
|
91
98
|
//# sourceMappingURL=activity-log.js.map
|
|
@@ -6,6 +6,7 @@ import { parseImplementationInputs, selectPendingImplementationInput } from './i
|
|
|
6
6
|
import { parseVerdict } from './review-artifacts.js';
|
|
7
7
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
8
8
|
import { receiptForOutput, sha256File } from './artifact-receipts.js';
|
|
9
|
+
import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
9
10
|
const artifactFamilyCatalog = [
|
|
10
11
|
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
11
12
|
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
@@ -85,7 +86,24 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
|
|
|
85
86
|
taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
|
|
86
87
|
});
|
|
87
88
|
}
|
|
89
|
+
let invalidation = { operations: [], targets: [] };
|
|
90
|
+
try {
|
|
91
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
92
|
+
const parsed = parseInvalidationDocument(taskContent);
|
|
93
|
+
if (!parsed.ok) {
|
|
94
|
+
return failure(requestRef, family, { code: 'ARTIFACT_INVALIDATION_INVALID', message: parsed.message }, {
|
|
95
|
+
taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
invalidation = parsed.document;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return failure(requestRef, family, { code: 'ARTIFACT_INVALIDATION_INVALID', message: String(error) }, {
|
|
102
|
+
taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null
|
|
103
|
+
});
|
|
104
|
+
}
|
|
88
105
|
const artifacts = [];
|
|
106
|
+
const knownArtifacts = [];
|
|
89
107
|
const diagnostics = [];
|
|
90
108
|
for (const entry of entries) {
|
|
91
109
|
const parsed = parseArtifactName(entry.name);
|
|
@@ -117,19 +135,23 @@ function inspectArtifactDirectory(taskDir, family, identity = {}) {
|
|
|
117
135
|
diagnostics.push(diagnostic('FILESYSTEM_RACE', family, entry.name, `artifact is not readable: ${entry.name}`));
|
|
118
136
|
continue;
|
|
119
137
|
}
|
|
120
|
-
|
|
138
|
+
const identity = { ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
139
|
+
knownArtifacts.push(identity);
|
|
140
|
+
if (!isArtifactInvalidated(invalidation, family, entry.name))
|
|
141
|
+
artifacts.push(identity);
|
|
121
142
|
}
|
|
122
143
|
catch (error) {
|
|
123
144
|
diagnostics.push(diagnostic('FILESYSTEM_RACE', family, entry.name, String(error)));
|
|
124
145
|
}
|
|
125
146
|
}
|
|
126
147
|
artifacts.sort((left, right) => left.round - right.round);
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
knownArtifacts.sort((left, right) => left.round - right.round);
|
|
149
|
+
const knownRounds = new Set(knownArtifacts.map((item) => item.round));
|
|
150
|
+
if (knownArtifacts.length > 0 && !knownRounds.has(1))
|
|
129
151
|
diagnostics.push(diagnostic('MISSING_BASE', family, null, `${family}.md is missing`));
|
|
130
|
-
const max =
|
|
152
|
+
const max = knownArtifacts.at(-1)?.round ?? 0;
|
|
131
153
|
for (let round = 1; round <= max; round += 1) {
|
|
132
|
-
if (!
|
|
154
|
+
if (!knownRounds.has(round))
|
|
133
155
|
diagnostics.push(diagnostic('ROUND_GAP', family, null, `${artifactName(family, round)} is missing`));
|
|
134
156
|
}
|
|
135
157
|
const latest = artifacts.at(-1) ?? null;
|
|
@@ -163,6 +185,12 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
163
185
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
164
186
|
throw new Error('referenced input is not a regular file');
|
|
165
187
|
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
188
|
+
const invalidation = parseInvalidationDocument(taskContent);
|
|
189
|
+
if (!invalidation.ok)
|
|
190
|
+
throw new Error(invalidation.message);
|
|
191
|
+
if (isArtifactInvalidated(invalidation.document, expectedFamily, parsed.name)) {
|
|
192
|
+
throw new Error(`${parsed.name} is invalidated`);
|
|
193
|
+
}
|
|
166
194
|
const receipt = receiptForOutput(taskContent, review.name);
|
|
167
195
|
if (!receipt)
|
|
168
196
|
throw new Error(`receipt for ${review.name} is missing`);
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
4
|
+
import { receiptForOutput } from './artifact-receipts.js';
|
|
5
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
|
+
import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
7
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.js';
|
|
8
|
+
import { parseReworkIntentDocument } from './rework-intent.js';
|
|
9
|
+
import { sha256File } from './artifact-receipts.js';
|
|
10
|
+
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
11
|
+
const REASONS = new Set([
|
|
12
|
+
'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
|
|
13
|
+
]);
|
|
14
|
+
function deny(reasonCode, ...evidence) {
|
|
15
|
+
return { allowed: false, reasonCode, evidence };
|
|
16
|
+
}
|
|
17
|
+
function allow(...evidence) {
|
|
18
|
+
return { allowed: true, reasonCode: 'CAPABILITY_ALLOWED', evidence };
|
|
19
|
+
}
|
|
20
|
+
function hasArtifact(facts, action) {
|
|
21
|
+
return (facts.artifacts[action]?.length ?? 0) > 0;
|
|
22
|
+
}
|
|
23
|
+
function canStart(action, facts, trigger) {
|
|
24
|
+
if (!trigger || trigger.requestedAction !== action)
|
|
25
|
+
return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
|
|
26
|
+
if (!trigger.requestId || !trigger.requestId.trim())
|
|
27
|
+
return deny('TRIGGER_REQUEST_ID_REQUIRED');
|
|
28
|
+
if (!REASONS.has(trigger.reasonCode))
|
|
29
|
+
return deny('TRIGGER_REASON_INVALID');
|
|
30
|
+
if (trigger.sourceArtifact && !trigger.sourceSha256)
|
|
31
|
+
return deny('SOURCE_ARTIFACT_HASH_REQUIRED');
|
|
32
|
+
if (trigger.sourceSha256 && !trigger.sourceArtifact)
|
|
33
|
+
return deny('SOURCE_ARTIFACT_REQUIRED');
|
|
34
|
+
if (trigger.sourceArtifact && trigger.sourceSha256) {
|
|
35
|
+
const actual = facts.artifactHashes[trigger.sourceArtifact];
|
|
36
|
+
if (!actual)
|
|
37
|
+
return deny('SOURCE_ARTIFACT_MISSING', trigger.sourceArtifact);
|
|
38
|
+
if (actual !== trigger.sourceSha256)
|
|
39
|
+
return deny('SOURCE_ARTIFACT_HASH_MISMATCH', trigger.sourceArtifact);
|
|
40
|
+
}
|
|
41
|
+
if (facts.taskState !== 'active')
|
|
42
|
+
return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
|
|
43
|
+
if (invalidationBlocks(facts.invalidation))
|
|
44
|
+
return deny('INVALIDATION_INCOMPLETE');
|
|
45
|
+
if (facts.executionBusy)
|
|
46
|
+
return deny('EXECUTION_BUSY');
|
|
47
|
+
const implicit = trigger.explicitRequest === false;
|
|
48
|
+
if (action === 'analysis') {
|
|
49
|
+
if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis')
|
|
50
|
+
return allow('analysis-capability');
|
|
51
|
+
return deny('ANALYSIS_REQUEST_REQUIRES_EXPLICIT_TRIGGER');
|
|
52
|
+
}
|
|
53
|
+
if (action === 'review-analysis') {
|
|
54
|
+
return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
|
|
55
|
+
}
|
|
56
|
+
if (action === 'plan') {
|
|
57
|
+
if (!hasArtifact(facts, 'review-analysis'))
|
|
58
|
+
return deny('ANALYSIS_REVIEW_REQUIRED');
|
|
59
|
+
if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis'))
|
|
60
|
+
return deny('ANALYSIS_REVIEW_NOT_LATEST');
|
|
61
|
+
if (facts.reviews['review-analysis'] !== 'approved')
|
|
62
|
+
return deny('ANALYSIS_REVIEW_NOT_APPROVED');
|
|
63
|
+
if (facts.unresolvedLedger.analysis > 0)
|
|
64
|
+
return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
|
|
65
|
+
return allow('analysis-review-approved');
|
|
66
|
+
}
|
|
67
|
+
if (action === 'review-plan') {
|
|
68
|
+
return hasArtifact(facts, 'plan') ? allow('plan-artifact') : deny('PLAN_ARTIFACT_REQUIRED');
|
|
69
|
+
}
|
|
70
|
+
if (action === 'code') {
|
|
71
|
+
if (trigger.implementationInput) {
|
|
72
|
+
if (!hasArtifact(facts, 'review-code'))
|
|
73
|
+
return deny('CODE_REVIEW_REQUIRED');
|
|
74
|
+
if (!reviewMatchesLatest(facts, 'code', 'review-code'))
|
|
75
|
+
return deny('CODE_REVIEW_NOT_LATEST');
|
|
76
|
+
if (facts.reviews['review-code'] !== 'approved')
|
|
77
|
+
return deny('CODE_REVIEW_NOT_APPROVED');
|
|
78
|
+
return allow('implementation-input');
|
|
79
|
+
}
|
|
80
|
+
if (!hasArtifact(facts, 'review-plan'))
|
|
81
|
+
return deny('PLAN_REVIEW_REQUIRED');
|
|
82
|
+
if (!reviewMatchesLatest(facts, 'plan', 'review-plan'))
|
|
83
|
+
return deny('PLAN_REVIEW_NOT_LATEST');
|
|
84
|
+
if (facts.reviews['review-plan'] !== 'approved')
|
|
85
|
+
return deny('PLAN_REVIEW_NOT_APPROVED');
|
|
86
|
+
if (facts.unresolvedLedger.plan > 0)
|
|
87
|
+
return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
|
|
88
|
+
return allow('plan-review-approved');
|
|
89
|
+
}
|
|
90
|
+
if (action === 'review-code') {
|
|
91
|
+
return hasArtifact(facts, 'code') ? allow('code-artifact') : deny('CODE_ARTIFACT_REQUIRED');
|
|
92
|
+
}
|
|
93
|
+
if (action === 'manual-validation' || action === 'validation-run') {
|
|
94
|
+
if (!hasArtifact(facts, 'review-code'))
|
|
95
|
+
return deny('CODE_REVIEW_REQUIRED');
|
|
96
|
+
if (!reviewMatchesLatest(facts, 'code', 'review-code'))
|
|
97
|
+
return deny('CODE_REVIEW_NOT_LATEST');
|
|
98
|
+
if (facts.reviews['review-code'] !== 'approved')
|
|
99
|
+
return deny('CODE_REVIEW_NOT_APPROVED');
|
|
100
|
+
if (facts.unresolvedLedger.code > 0)
|
|
101
|
+
return deny('CODE_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.code}`);
|
|
102
|
+
return allow('code-review-approved');
|
|
103
|
+
}
|
|
104
|
+
return deny('CAPABILITY_ACTION_UNKNOWN');
|
|
105
|
+
}
|
|
106
|
+
function artifactRound(name) {
|
|
107
|
+
const match = /-r(\d+)\.md$/.exec(name);
|
|
108
|
+
return match ? Number(match[1]) : 1;
|
|
109
|
+
}
|
|
110
|
+
function latestArtifact(names) {
|
|
111
|
+
return [...names].sort((left, right) => artifactRound(right) - artifactRound(left) || left.localeCompare(right))[0] ?? null;
|
|
112
|
+
}
|
|
113
|
+
function reviewedInputName(content, expectedFamily) {
|
|
114
|
+
const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
115
|
+
if (header < 0)
|
|
116
|
+
return null;
|
|
117
|
+
const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
|
|
118
|
+
const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
|
|
119
|
+
return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
|
|
120
|
+
}
|
|
121
|
+
function reviewMatchesLatest(facts, source, review) {
|
|
122
|
+
const latestSource = latestArtifact(facts.artifacts[source] ?? []);
|
|
123
|
+
return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
|
|
124
|
+
}
|
|
125
|
+
function recommendNext(facts) {
|
|
126
|
+
if (invalidationBlocks(facts.invalidation))
|
|
127
|
+
return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
|
|
128
|
+
const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
|
|
129
|
+
if (pendingIntent)
|
|
130
|
+
return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
|
|
131
|
+
if (!hasArtifact(facts, 'analysis'))
|
|
132
|
+
return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
|
|
133
|
+
if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
|
|
134
|
+
return { action: 'review-analysis', reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
|
|
135
|
+
}
|
|
136
|
+
if (facts.reviews['review-analysis'] !== 'approved' || facts.unresolvedLedger.analysis > 0) {
|
|
137
|
+
return { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review or ledger is not clear'] };
|
|
138
|
+
}
|
|
139
|
+
if (!hasArtifact(facts, 'plan'))
|
|
140
|
+
return { action: 'plan', reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
|
|
141
|
+
if (!hasArtifact(facts, 'review-plan') || !reviewMatchesLatest(facts, 'plan', 'review-plan')) {
|
|
142
|
+
return { action: 'review-plan', reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
|
|
143
|
+
}
|
|
144
|
+
if (facts.reviews['review-plan'] !== 'approved' || facts.unresolvedLedger.plan > 0) {
|
|
145
|
+
return { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review or ledger is not clear'] };
|
|
146
|
+
}
|
|
147
|
+
if (!hasArtifact(facts, 'code'))
|
|
148
|
+
return { action: 'code', reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
|
|
149
|
+
if (!hasArtifact(facts, 'review-code') || !reviewMatchesLatest(facts, 'code', 'review-code')) {
|
|
150
|
+
return { action: 'review-code', reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
|
|
151
|
+
}
|
|
152
|
+
if (facts.reviews['review-code'] !== 'approved') {
|
|
153
|
+
return { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review or ledger is not clear'] };
|
|
154
|
+
}
|
|
155
|
+
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
156
|
+
}
|
|
157
|
+
function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false) {
|
|
158
|
+
try {
|
|
159
|
+
const metadata = parseTypedTaskFrontmatter(content);
|
|
160
|
+
const invalidation = parseInvalidationDocument(content);
|
|
161
|
+
if (!invalidation.ok)
|
|
162
|
+
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalidation.message };
|
|
163
|
+
const rework = parseReworkIntentDocument(content);
|
|
164
|
+
if (!rework.ok)
|
|
165
|
+
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: rework.message };
|
|
166
|
+
const ledger = parseLedgerDocument(content);
|
|
167
|
+
if (ledger.present) {
|
|
168
|
+
const invalid = validateLedgerRows(ledger.rows);
|
|
169
|
+
if (invalid)
|
|
170
|
+
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalid.message };
|
|
171
|
+
}
|
|
172
|
+
const files = fs.readdirSync(taskDir).filter((name) => {
|
|
173
|
+
if (!name.endsWith('.md'))
|
|
174
|
+
return false;
|
|
175
|
+
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
176
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
177
|
+
});
|
|
178
|
+
const artifactFamilies = {
|
|
179
|
+
analysis: /^analysis(?:-r\d+)?\.md$/,
|
|
180
|
+
'review-analysis': /^review-analysis(?:-r\d+)?\.md$/,
|
|
181
|
+
plan: /^plan(?:-r\d+)?\.md$/,
|
|
182
|
+
'review-plan': /^review-plan(?:-r\d+)?\.md$/,
|
|
183
|
+
code: /^code(?:-r\d+)?\.md$/,
|
|
184
|
+
'review-code': /^review-code(?:-r\d+)?\.md$/
|
|
185
|
+
};
|
|
186
|
+
const familyFor = (name) => Object.keys(artifactFamilies)
|
|
187
|
+
.find((family) => artifactFamilies[family].test(name)) ?? null;
|
|
188
|
+
const activeFiles = files.filter((name) => {
|
|
189
|
+
const family = familyFor(name);
|
|
190
|
+
return !family || !isArtifactInvalidated(invalidation.document, family, name);
|
|
191
|
+
});
|
|
192
|
+
const artifactHashes = {};
|
|
193
|
+
for (const name of activeFiles)
|
|
194
|
+
artifactHashes[name] = sha256File(path.join(taskDir, name));
|
|
195
|
+
const artifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
|
|
196
|
+
family, activeFiles.filter((name) => artifactFamilies[family].test(name))
|
|
197
|
+
]));
|
|
198
|
+
const staleArtifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
|
|
199
|
+
family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
|
|
200
|
+
]));
|
|
201
|
+
const reviews = {};
|
|
202
|
+
const reviewedInputs = {};
|
|
203
|
+
for (const family of ['review-analysis', 'review-plan', 'review-code']) {
|
|
204
|
+
const latest = latestArtifact(artifacts[family] ?? []);
|
|
205
|
+
if (!latest)
|
|
206
|
+
continue;
|
|
207
|
+
const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
|
|
208
|
+
const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
209
|
+
const input = reviewedInputName(reviewContent, expectedFamily);
|
|
210
|
+
const receipt = input ? receiptForOutput(content, latest) : null;
|
|
211
|
+
if (input && receipt?.input === input && artifactHashes[input] === receipt.inputSha256)
|
|
212
|
+
reviewedInputs[family] = input;
|
|
213
|
+
const parsed = parseReviewSummary(reviewContent);
|
|
214
|
+
if (!parsed.ok)
|
|
215
|
+
continue;
|
|
216
|
+
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
217
|
+
if (verdict.ok)
|
|
218
|
+
reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
|
|
219
|
+
}
|
|
220
|
+
const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
|
|
221
|
+
if (ledger.present) {
|
|
222
|
+
for (const stage of ['analysis', 'plan', 'code'])
|
|
223
|
+
unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
|
|
224
|
+
}
|
|
225
|
+
const facts = {
|
|
226
|
+
taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
|
|
227
|
+
staleArtifacts, reviewedInputs, artifactHashes,
|
|
228
|
+
invalidation: invalidation.document, reworkIntents: rework.intents,
|
|
229
|
+
unresolvedLedger, executionBusy: executionBusy || hasOpenLifecycleExecution(content), recommendedAction: null
|
|
230
|
+
};
|
|
231
|
+
facts.recommendedAction = recommendNext(facts).action;
|
|
232
|
+
return { ok: true, facts };
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export { buildLifecycleFacts, canStart, recommendNext };
|
|
239
|
+
//# sourceMappingURL=capabilities.js.map
|