@fitlab-ai/agent-infra 0.9.15 → 0.10.0
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/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +0 -1
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/issue-comments.js +16 -10
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +6 -2
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/workflow-executor.js +4 -14
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/task/artifact-command.js +6 -28
- package/dist/lib/task/artifact-operations.js +16 -230
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +73 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +38 -7
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +164 -22
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +66 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +148 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +1 -0
- package/dist/lib/task/review-finalization.js +104 -77
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +3 -14
- package/dist/lib/task/workflow-command.js +0 -1
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +0 -1
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/issue-comments.ts +18 -10
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +6 -2
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/protocol.ts +5 -0
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/workflow-executor.ts +4 -14
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/task/artifact-command.ts +8 -27
- package/lib/task/artifact-operations.ts +21 -251
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +99 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +47 -14
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +187 -23
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +50 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +176 -132
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +1 -0
- package/lib/task/review-finalization.ts +106 -87
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +0 -1
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/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/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
|
@@ -98,7 +98,23 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
98
98
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
99
99
|
}
|
|
100
100
|
if (TASK_ID_RE.test(requestedRef)) {
|
|
101
|
-
|
|
101
|
+
let task;
|
|
102
|
+
try {
|
|
103
|
+
task = resolveTaskWorkspace(requestedRef, repoRoot);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!(error instanceof Error && error.message === `Task not found: ${requestedRef}`))
|
|
107
|
+
throw error;
|
|
108
|
+
const branch = options.resolveMissingTask?.(requestedRef);
|
|
109
|
+
if (!branch)
|
|
110
|
+
throw error;
|
|
111
|
+
return {
|
|
112
|
+
requestedRef,
|
|
113
|
+
branch,
|
|
114
|
+
workspace: { mode: 'task-bound', taskId: requestedRef },
|
|
115
|
+
taskState: 'unknown'
|
|
116
|
+
};
|
|
117
|
+
}
|
|
102
118
|
if (!options.allowProtected && (task.state === 'blocked' || task.state === 'archive')) {
|
|
103
119
|
throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${requestedRef} is ${task.state}`);
|
|
104
120
|
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { readStableFileSync } from '../host-control/secure-fs.js';
|
|
4
2
|
import { resolveArtifactContext, hasOpenArtifactRound } from './artifact-lifecycle.js';
|
|
5
3
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
4
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
5
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
8
6
|
import { finalizeLocalArtifact } from './local-artifact-finalization.js';
|
|
9
|
-
import {
|
|
7
|
+
import { initializeArtifactSkeleton } from './artifact-operations.js';
|
|
10
8
|
/** One option contract for the CLI and trusted projection executor. */
|
|
11
9
|
export function parseArtifactCommand(args) {
|
|
12
10
|
const [taskRef, operation] = args;
|
|
13
|
-
if (!taskRef || !['inspect', 'init', '
|
|
11
|
+
if (!taskRef || !['inspect', 'init', 'finalize-local'].includes(operation ?? '')) {
|
|
14
12
|
throw new Error('task ref and a supported artifact operation are required');
|
|
15
13
|
}
|
|
16
14
|
const fields = {};
|
|
17
15
|
const allowed = {
|
|
18
16
|
inspect: ['--family'],
|
|
19
17
|
init: ['--family', '--artifact', '--locale'],
|
|
20
|
-
|
|
21
|
-
'finalize-local': ['--family', '--artifact']
|
|
18
|
+
'finalize-local': ['--family', '--artifact', '--recovery-id']
|
|
22
19
|
}[operation];
|
|
23
20
|
for (let index = 2; index < args.length; index += 1) {
|
|
24
21
|
const flag = args[index];
|
|
@@ -38,18 +35,14 @@ export function parseArtifactCommand(args) {
|
|
|
38
35
|
const locale = fields['--locale'];
|
|
39
36
|
if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en')
|
|
40
37
|
throw new Error("option '--locale' must be 'zh-CN' or 'en'");
|
|
41
|
-
if (operation === 'repair' && !['--expected-sha256', '--expected-semantic-digest'].every((flag) => /^[a-f0-9]{64}$/u.test(fields[flag] ?? ''))) {
|
|
42
|
-
throw new Error('repair requires lowercase 64-character digests');
|
|
43
|
-
}
|
|
44
38
|
return {
|
|
45
39
|
taskRef, operation: operation, family: fields['--family'],
|
|
46
|
-
artifact: fields['--artifact'] ?? '', locale,
|
|
47
|
-
expectedSha256: fields['--expected-sha256'] ?? '', expectedSemanticDigest: fields['--expected-semantic-digest'] ?? ''
|
|
40
|
+
artifact: fields['--artifact'] ?? '', locale, recoveryId: fields['--recovery-id']
|
|
48
41
|
};
|
|
49
42
|
}
|
|
50
43
|
/** Resolve authoritative task metadata separately from a trusted candidate directory. */
|
|
51
44
|
export function executeArtifactCommand(command, options = {}) {
|
|
52
|
-
const { taskRef, operation, family, artifact, locale,
|
|
45
|
+
const { taskRef, operation, family, artifact, locale, recoveryId } = command;
|
|
53
46
|
const fail = (code, message) => ({ status: 'failed', changed: false, error: { code, message } });
|
|
54
47
|
if (operation === 'inspect') {
|
|
55
48
|
const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
|
|
@@ -78,24 +71,9 @@ export function executeArtifactCommand(command, options = {}) {
|
|
|
78
71
|
}
|
|
79
72
|
return { ...initializeArtifactSkeleton({ ...input, ...(locale ? { locale } : {}) }), ...identity };
|
|
80
73
|
}
|
|
81
|
-
if (operation === 'repair') {
|
|
82
|
-
let content;
|
|
83
|
-
try {
|
|
84
|
-
content = readStableFileSync(path.join(taskDir, artifact), { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
return fail('ARTIFACT_REPAIR_TARGET_INVALID', String(error));
|
|
88
|
-
}
|
|
89
|
-
const inspection = inspectArtifactStructure(content, schema);
|
|
90
|
-
if (!inspection.repair) {
|
|
91
|
-
return { ...fail('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available'),
|
|
92
|
-
artifact, family, diagnostics: inspection.diagnostics };
|
|
93
|
-
}
|
|
94
|
-
return { ...applyArtifactRepair({ ...input, expectedSha256, expectedSemanticDigest, operation: inspection.repair }), ...identity };
|
|
95
|
-
}
|
|
96
74
|
if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
|
|
97
75
|
return fail('ARTIFACT_PAYLOAD_INVALID', "finalize-local only supports 'analysis', 'plan', and 'code'");
|
|
98
76
|
}
|
|
99
|
-
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
|
|
77
|
+
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
|
|
100
78
|
}
|
|
101
79
|
//# sourceMappingURL=artifact-command.js.map
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { parseArtifactName } from './artifact-name.js';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { createHash } from 'node:crypto';
|
|
5
4
|
import { readStableFileSync } from '../host-control/secure-fs.js';
|
|
5
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
|
-
import { hasOpenArtifactRound } from './artifact-lifecycle.js';
|
|
8
|
-
import { readArtifactRepairIntent } from './artifact-repair-intent.js';
|
|
9
|
-
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
7
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
11
8
|
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
12
9
|
function sha256Content(content) {
|
|
@@ -60,70 +57,26 @@ function stripHtmlComments(content) {
|
|
|
60
57
|
}
|
|
61
58
|
return result;
|
|
62
59
|
}
|
|
63
|
-
function diagnostic(code, message, sectionId, line
|
|
64
|
-
return {
|
|
65
|
-
code, message, sectionId, line, repairable,
|
|
66
|
-
...(operation ? { operation } : {})
|
|
67
|
-
};
|
|
60
|
+
function diagnostic(code, message, sectionId, line) {
|
|
61
|
+
return { code, message, sectionId, line };
|
|
68
62
|
}
|
|
69
63
|
function sectionHeadings(schema, section) {
|
|
70
64
|
return [section.headings.zh, section.headings.en];
|
|
71
65
|
}
|
|
72
|
-
function
|
|
73
|
-
const to = heading.text.slice(0, -1);
|
|
74
|
-
return {
|
|
75
|
-
kind: 'replace-line',
|
|
76
|
-
sectionId: section.id,
|
|
77
|
-
line: lineNumber(content, heading.start),
|
|
78
|
-
from: heading.text,
|
|
79
|
-
to,
|
|
80
|
-
start: heading.start,
|
|
81
|
-
end: heading.end
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
function createSectionInsertionRepair(content, section, marker, locale) {
|
|
85
|
-
return {
|
|
86
|
-
kind: 'insert-section',
|
|
87
|
-
sectionId: section.id,
|
|
88
|
-
line: marker.line,
|
|
89
|
-
from: '',
|
|
90
|
-
to: `## ${section.headings[locale === 'en' ? 'en' : 'zh']}\n`,
|
|
91
|
-
start: marker.start,
|
|
92
|
-
end: marker.start
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
function applyRepairOperation(content, operation) {
|
|
96
|
-
if (operation.start < 0 || operation.end < operation.start || operation.end > content.length)
|
|
97
|
-
return null;
|
|
98
|
-
if (operation.kind === 'insert-section') {
|
|
99
|
-
if (operation.from !== '' || operation.start !== operation.end || !operation.to)
|
|
100
|
-
return null;
|
|
101
|
-
return `${content.slice(0, operation.start)}${operation.to}${content.slice(operation.end)}`;
|
|
102
|
-
}
|
|
103
|
-
const raw = content.slice(operation.start, operation.end);
|
|
104
|
-
const offset = raw.indexOf(operation.from);
|
|
105
|
-
if (!operation.from || offset < 0 || raw.indexOf(operation.from, offset + operation.from.length) >= 0)
|
|
106
|
-
return null;
|
|
107
|
-
return `${content.slice(0, operation.start)}${raw.slice(0, offset)}${operation.to}${raw.slice(offset + operation.from.length)}${content.slice(operation.end)}`;
|
|
108
|
-
}
|
|
109
|
-
function canonicalContent(content, operation = null) {
|
|
66
|
+
function canonicalContent(content) {
|
|
110
67
|
let normalized = content;
|
|
111
|
-
if (operation) {
|
|
112
|
-
normalized = applyRepairOperation(normalized, operation) ?? normalized;
|
|
113
|
-
}
|
|
114
68
|
normalized = normalized.replace(/\r\n/g, '\n');
|
|
115
69
|
normalized = normalized.replace(/^<!--\s*artifact-context:[^\n]+-->\s*\n?/gm, '');
|
|
116
70
|
normalized = normalized.replace(/^\s*<!--\s*artifact-section:[^\n]+-->\s*\n?/gm, '');
|
|
117
71
|
normalized = normalized.replace(/^\s*<!--\s*artifact-slot:empty\s*-->\s*\n?/gm, '');
|
|
118
72
|
return normalized;
|
|
119
73
|
}
|
|
120
|
-
function canonicalSemanticDigest(content
|
|
121
|
-
return sha256Content(canonicalContent(content
|
|
74
|
+
function canonicalSemanticDigest(content) {
|
|
75
|
+
return sha256Content(canonicalContent(content));
|
|
122
76
|
}
|
|
123
77
|
function inspectArtifactStructure(content, schema) {
|
|
124
78
|
const diagnostics = [];
|
|
125
79
|
const scanned = scanVisibleMarkdown(content);
|
|
126
|
-
const repairCandidates = [];
|
|
127
80
|
if (!content.trim())
|
|
128
81
|
diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
|
|
129
82
|
if (scanned.hasUnclosedFence)
|
|
@@ -133,22 +86,19 @@ function inspectArtifactStructure(content, schema) {
|
|
|
133
86
|
const aliases = sectionHeadings(schema, section);
|
|
134
87
|
const exact = scanned.headings.filter((heading) => heading.level === 2 && aliases.includes(heading.text));
|
|
135
88
|
const punctuated = scanned.headings.filter((heading) => heading.level === 2 && aliases.some((alias) => heading.text === `${alias}:` || heading.text === `${alias}:`));
|
|
136
|
-
headingsBySection.set(section.id, exact.length
|
|
89
|
+
headingsBySection.set(section.id, exact.length > 0 ? exact : punctuated);
|
|
137
90
|
if (exact.length > 1 || punctuated.length > 0 && exact.length > 0 || punctuated.length > 1) {
|
|
138
91
|
const first = exact[1] ?? punctuated[0] ?? exact[0];
|
|
139
92
|
diagnostics.push(diagnostic('ARTIFACT_DUPLICATE_SECTION', `required section '${aliases[0]}' is duplicated or has an ambiguous punctuation variant`, section.id, first ? lineNumber(content, first.start) : null));
|
|
140
93
|
}
|
|
141
94
|
else if (exact.length === 0 && punctuated.length === 1) {
|
|
142
|
-
|
|
95
|
+
diagnostics.push(diagnostic('ARTIFACT_HEADING_TRAILING_PUNCTUATION', `visible required H2 '${punctuated[0].text}' has trailing punctuation`, section.id, lineNumber(content, punctuated[0].start)));
|
|
143
96
|
}
|
|
144
97
|
else if (exact.length === 0) {
|
|
145
98
|
diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
|
|
146
99
|
}
|
|
147
100
|
}
|
|
148
101
|
const markers = markerEntries(content, scanned);
|
|
149
|
-
const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
|
|
150
|
-
const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
|
|
151
|
-
const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
|
|
152
102
|
for (const section of schema.sections) {
|
|
153
103
|
const matches = markers.filter((entry) => entry.marker === section.marker);
|
|
154
104
|
if (matches.length === 0) {
|
|
@@ -174,10 +124,7 @@ function inspectArtifactStructure(content, schema) {
|
|
|
174
124
|
if (marker && !heading && marker.standalone) {
|
|
175
125
|
const nextHeading = scanned.headings.find((candidate) => candidate.start > marker.start && candidate.level <= 2);
|
|
176
126
|
const body = stripHtmlComments(content.slice(marker.end, nextHeading?.start ?? content.length)).trim();
|
|
177
|
-
if (body
|
|
178
|
-
repairCandidates.push(createSectionInsertionRepair(content, section, marker, locale));
|
|
179
|
-
}
|
|
180
|
-
else if (!body) {
|
|
127
|
+
if (!body) {
|
|
181
128
|
diagnostics.push(diagnostic('ARTIFACT_EMPTY_SECTION', `section '${section.headings.zh}' has no semantic body`, section.id, marker.line));
|
|
182
129
|
}
|
|
183
130
|
}
|
|
@@ -187,17 +134,6 @@ function inspectArtifactStructure(content, schema) {
|
|
|
187
134
|
diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `unknown section marker '${marker.marker}'`, null, marker.line));
|
|
188
135
|
}
|
|
189
136
|
}
|
|
190
|
-
for (const candidate of repairCandidates) {
|
|
191
|
-
if (candidate.kind === 'replace-line') {
|
|
192
|
-
diagnostics.push(diagnostic('ARTIFACT_HEADING_TRAILING_PUNCTUATION', `visible required H2 '${candidate.from}' may be normalized to '${candidate.to}'`, candidate.sectionId, candidate.line, true, candidate));
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const missing = diagnostics.findIndex((item) => item.code === 'ARTIFACT_MISSING_SECTION' && item.sectionId === candidate.sectionId);
|
|
196
|
-
if (missing >= 0) {
|
|
197
|
-
const section = schema.sections.find((item) => item.id === candidate.sectionId);
|
|
198
|
-
diagnostics.splice(missing, 1, diagnostic('ARTIFACT_MISSING_SECTION', `required section '${section.headings.zh}' is missing and can be inserted at its unique marker`, candidate.sectionId, candidate.line, true, candidate));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
137
|
const orderedHeadings = schema.sections.flatMap((section) => {
|
|
202
138
|
const heading = headingsBySection.get(section.id)?.[0];
|
|
203
139
|
return heading ? [{ section, start: heading.start }] : [];
|
|
@@ -212,14 +148,11 @@ function inspectArtifactStructure(content, schema) {
|
|
|
212
148
|
if (orderedMarkers.some((item, index) => index > 0 && item.start <= orderedMarkers[index - 1].start)) {
|
|
213
149
|
diagnostics.push(diagnostic('ARTIFACT_SECTION_ORDER_INVALID', 'section markers are not in schema order', null, null));
|
|
214
150
|
}
|
|
215
|
-
const repair = repairCandidates.length === 1 && (diagnostics.length === 0 ||
|
|
216
|
-
(diagnostics.length === 1 && diagnostics[0]?.operation?.kind === repairCandidates[0]?.kind)) ? repairCandidates[0] : null;
|
|
217
151
|
return {
|
|
218
152
|
ok: diagnostics.length === 0,
|
|
219
153
|
family: schema.family,
|
|
220
|
-
semanticDigest: canonicalSemanticDigest(content
|
|
221
|
-
diagnostics
|
|
222
|
-
repair
|
|
154
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
155
|
+
diagnostics
|
|
223
156
|
};
|
|
224
157
|
}
|
|
225
158
|
function inspectArtifactPatterns(content, schema) {
|
|
@@ -230,25 +163,22 @@ function inspectArtifactPatterns(content, schema) {
|
|
|
230
163
|
ok: diagnostics.length === 0,
|
|
231
164
|
family: schema.family,
|
|
232
165
|
semanticDigest: canonicalSemanticDigest(content),
|
|
233
|
-
diagnostics
|
|
234
|
-
repair: null
|
|
166
|
+
diagnostics
|
|
235
167
|
};
|
|
236
168
|
}
|
|
237
169
|
function inspectArtifactContract(content, schema) {
|
|
238
170
|
const structure = inspectArtifactStructure(content, schema);
|
|
239
171
|
const patterns = inspectArtifactPatterns(content, schema);
|
|
240
172
|
const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
|
|
241
|
-
const repair = patterns.diagnostics.length === 0 ? structure.repair : null;
|
|
242
173
|
return {
|
|
243
174
|
ok: diagnostics.length === 0,
|
|
244
175
|
family: schema.family,
|
|
245
|
-
semanticDigest: canonicalSemanticDigest(content
|
|
246
|
-
diagnostics
|
|
247
|
-
repair
|
|
176
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
177
|
+
diagnostics
|
|
248
178
|
};
|
|
249
179
|
}
|
|
250
180
|
function resultFailure(code, message) {
|
|
251
|
-
return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null,
|
|
181
|
+
return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null, error: { code, message } };
|
|
252
182
|
}
|
|
253
183
|
function resultNoOp(content) {
|
|
254
184
|
return {
|
|
@@ -256,156 +186,13 @@ function resultNoOp(content) {
|
|
|
256
186
|
changed: false,
|
|
257
187
|
artifactSha256: sha256Content(content),
|
|
258
188
|
semanticDigest: canonicalSemanticDigest(content),
|
|
259
|
-
operation: null,
|
|
260
189
|
error: null
|
|
261
190
|
};
|
|
262
191
|
}
|
|
263
|
-
function validateRepairContext(request, content) {
|
|
264
|
-
const round = artifactRound(request.family, request.artifact);
|
|
265
|
-
if (round === null)
|
|
266
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact round is not canonical' };
|
|
267
|
-
const contextPattern = /^<!--\s*artifact-context:([^:\s]+):([^:\s]+):(\d+)\s*-->\s*$/;
|
|
268
|
-
const contexts = scanVisibleMarkdown(content).lines
|
|
269
|
-
.map((line) => line.text.trim().match(contextPattern))
|
|
270
|
-
.filter((match) => match !== null);
|
|
271
|
-
if (contexts.length !== 1
|
|
272
|
-
|| contexts[0][1] !== request.taskId
|
|
273
|
-
|| contexts[0][2] !== request.family
|
|
274
|
-
|| Number(contexts[0][3]) !== round) {
|
|
275
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
|
|
276
|
-
}
|
|
277
|
-
let taskContent;
|
|
278
|
-
const resolved = resolveTaskRef(request.taskId, { repoRoot: request.repoRoot });
|
|
279
|
-
if (!resolved.ok)
|
|
280
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: resolved.message };
|
|
281
|
-
try {
|
|
282
|
-
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` };
|
|
286
|
-
}
|
|
287
|
-
if (!hasOpenArtifactRound(taskContent, request.family, round)) {
|
|
288
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
|
|
289
|
-
}
|
|
290
|
-
let intent;
|
|
291
|
-
try {
|
|
292
|
-
intent = readArtifactRepairIntent(request.repoRoot, request.taskId, request.family, request.artifact);
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: String(error) };
|
|
296
|
-
}
|
|
297
|
-
if (!intent || intent.state !== 'awaiting-repair') {
|
|
298
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'artifact has no awaiting-repair finalization provenance' };
|
|
299
|
-
}
|
|
300
|
-
if (intent.artifactSha256 !== request.expectedSha256 || intent.semanticDigest !== request.expectedSemanticDigest) {
|
|
301
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'repair request does not match the current finalization provenance' };
|
|
302
|
-
}
|
|
303
|
-
return { ok: true };
|
|
304
|
-
}
|
|
305
192
|
function artifactRound(family, artifact) {
|
|
306
193
|
const identity = parseArtifactName(artifact);
|
|
307
194
|
return identity?.family === family ? identity.round : null;
|
|
308
195
|
}
|
|
309
|
-
function validateTarget(taskDir, family, artifact) {
|
|
310
|
-
if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md'))
|
|
311
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be a canonical top-level Markdown file');
|
|
312
|
-
if (!/^TASK-\d{8}-\d{6}$/.test(path.basename(path.resolve(taskDir))))
|
|
313
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'target directory is not a task directory');
|
|
314
|
-
const target = path.join(taskDir, artifact);
|
|
315
|
-
if (path.dirname(path.resolve(target)) !== path.resolve(taskDir))
|
|
316
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
317
|
-
let stat;
|
|
318
|
-
try {
|
|
319
|
-
stat = fs.lstatSync(target);
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact cannot be read: ${String(error)}`);
|
|
323
|
-
}
|
|
324
|
-
if (stat.isSymbolicLink() || !stat.isFile())
|
|
325
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact is not a regular file');
|
|
326
|
-
try {
|
|
327
|
-
fs.accessSync(target, fs.constants.R_OK | fs.constants.W_OK);
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact is not readable and writable: ${String(error)}`);
|
|
331
|
-
}
|
|
332
|
-
return { path: target };
|
|
333
|
-
}
|
|
334
|
-
function applyRepairUnlocked(request) {
|
|
335
|
-
const schema = getArtifactSchema(request.family);
|
|
336
|
-
if (!schema)
|
|
337
|
-
return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
|
|
338
|
-
const target = validateTarget(request.taskDir, request.family, request.artifact);
|
|
339
|
-
if ('status' in target)
|
|
340
|
-
return target;
|
|
341
|
-
let content;
|
|
342
|
-
try {
|
|
343
|
-
content = readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error));
|
|
347
|
-
}
|
|
348
|
-
const actualSha256 = sha256Content(content);
|
|
349
|
-
if (actualSha256 !== request.expectedSha256)
|
|
350
|
-
return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact SHA-256 does not match the expected repair baseline');
|
|
351
|
-
const context = validateRepairContext(request, content);
|
|
352
|
-
if (!context.ok)
|
|
353
|
-
return resultFailure(context.code, context.message);
|
|
354
|
-
const inspection = inspectArtifactStructure(content, schema);
|
|
355
|
-
if (!inspection.repair || inspection.diagnostics.length !== 1) {
|
|
356
|
-
return resultFailure('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available');
|
|
357
|
-
}
|
|
358
|
-
if (inspection.repair.kind !== request.operation.kind
|
|
359
|
-
|| inspection.repair.sectionId !== request.operation.sectionId
|
|
360
|
-
|| inspection.repair.from !== request.operation.from
|
|
361
|
-
|| inspection.repair.to !== request.operation.to
|
|
362
|
-
|| inspection.repair.start !== request.operation.start
|
|
363
|
-
|| inspection.repair.end !== request.operation.end) {
|
|
364
|
-
return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact structure');
|
|
365
|
-
}
|
|
366
|
-
if (inspection.semanticDigest !== request.expectedSemanticDigest)
|
|
367
|
-
return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact semantic digest does not match the expected repair baseline');
|
|
368
|
-
const transformed = applyRepairOperation(content, inspection.repair);
|
|
369
|
-
if (transformed === null)
|
|
370
|
-
return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact bytes');
|
|
371
|
-
if (transformed === content)
|
|
372
|
-
return resultFailure('ARTIFACT_REPAIR_NO_PROGRESS', 'repair operation produced no byte change');
|
|
373
|
-
const tempPath = path.join(request.taskDir, `.${request.artifact}.repair-${process.pid}-${Date.now()}.tmp`);
|
|
374
|
-
let mode = 0o600;
|
|
375
|
-
try {
|
|
376
|
-
mode = fs.statSync(target.path).mode & 0o777;
|
|
377
|
-
fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
|
|
378
|
-
const currentStat = fs.lstatSync(target.path);
|
|
379
|
-
if (currentStat.isSymbolicLink() || !currentStat.isFile() || readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).sha256 !== actualSha256) {
|
|
380
|
-
fs.unlinkSync(tempPath);
|
|
381
|
-
return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
|
|
382
|
-
}
|
|
383
|
-
fs.renameSync(tempPath, target.path);
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
try {
|
|
387
|
-
fs.unlinkSync(tempPath);
|
|
388
|
-
}
|
|
389
|
-
catch { /* preserve primary error */ }
|
|
390
|
-
return resultFailure('ARTIFACT_REPAIR_WRITE_FAILED', String(error));
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
status: 'applied',
|
|
394
|
-
changed: true,
|
|
395
|
-
artifactSha256: sha256Content(transformed),
|
|
396
|
-
semanticDigest: canonicalSemanticDigest(transformed),
|
|
397
|
-
operation: inspection.repair,
|
|
398
|
-
error: null
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
function applyArtifactRepair(request) {
|
|
402
|
-
try {
|
|
403
|
-
return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.repair', () => applyRepairUnlocked(request));
|
|
404
|
-
}
|
|
405
|
-
catch (error) {
|
|
406
|
-
return resultFailure('ARTIFACT_REPAIR_LOCK_FAILED', String(error));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
196
|
function initializeArtifactSkeleton(request) {
|
|
410
197
|
const schema = getArtifactSchema(request.family);
|
|
411
198
|
if (!schema)
|
|
@@ -461,7 +248,6 @@ function initializeArtifactSkeleton(request) {
|
|
|
461
248
|
changed: true,
|
|
462
249
|
artifactSha256: sha256Content(content),
|
|
463
250
|
semanticDigest: canonicalSemanticDigest(content),
|
|
464
|
-
operation: null,
|
|
465
251
|
error: null
|
|
466
252
|
};
|
|
467
253
|
});
|
|
@@ -470,5 +256,5 @@ function initializeArtifactSkeleton(request) {
|
|
|
470
256
|
return resultFailure('ARTIFACT_INIT_LOCK_FAILED', String(error));
|
|
471
257
|
}
|
|
472
258
|
}
|
|
473
|
-
export {
|
|
259
|
+
export { canonicalSemanticDigest, initializeArtifactSkeleton, inspectArtifactContract, inspectArtifactPatterns, inspectArtifactStructure, sha256Content };
|
|
474
260
|
//# sourceMappingURL=artifact-operations.js.map
|