@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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'] as const;
|
|
2
|
+
const REWORK_INTENT_COLUMNS = [
|
|
3
|
+
'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
|
|
4
|
+
'status', 'declared_at', 'consumed_at'
|
|
5
|
+
] as const;
|
|
6
|
+
type ReworkTarget = 'analysis' | 'plan' | 'code';
|
|
7
|
+
type ReworkIntentStatus = 'pending' | 'consumed' | 'superseded';
|
|
8
|
+
type ReworkIntent = {
|
|
9
|
+
intentId: string;
|
|
10
|
+
findingId: string;
|
|
11
|
+
sourceArtifact: string;
|
|
12
|
+
sourceSha256: string;
|
|
13
|
+
target: ReworkTarget;
|
|
14
|
+
status: ReworkIntentStatus;
|
|
15
|
+
declaredAt: string;
|
|
16
|
+
consumedAt: string;
|
|
17
|
+
};
|
|
18
|
+
type ReworkIntentParseResult =
|
|
19
|
+
| { ok: true; present: boolean; intents: readonly ReworkIntent[] }
|
|
20
|
+
| { ok: false; code: 'TASK_REWORK_INTENT_INVALID'; message: string };
|
|
21
|
+
|
|
22
|
+
function cells(line: string): string[] | null {
|
|
23
|
+
const first = line.indexOf('|');
|
|
24
|
+
const last = line.lastIndexOf('|');
|
|
25
|
+
if (first < 0 || first === last || line.slice(0, first).trim() || line.slice(last + 1).trim()) return null;
|
|
26
|
+
const inner = line.slice(first + 1, last);
|
|
27
|
+
const result: string[] = [];
|
|
28
|
+
let start = 0;
|
|
29
|
+
let escaped = false;
|
|
30
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
31
|
+
const char = inner[index]!;
|
|
32
|
+
if (char === '|' && !escaped) {
|
|
33
|
+
result.push(inner.slice(start, index));
|
|
34
|
+
start = index + 1;
|
|
35
|
+
}
|
|
36
|
+
escaped = char === '\\' && !escaped;
|
|
37
|
+
if (char !== '\\') escaped = false;
|
|
38
|
+
}
|
|
39
|
+
result.push(inner.slice(start));
|
|
40
|
+
return result.map((cell) => cell.replace(/\\([\\|])/g, '$1').trim());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function sectionBody(content: string): string | null {
|
|
44
|
+
const heading = REWORK_INTENT_HEADINGS.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
45
|
+
const match = new RegExp(`^##\\s+(${heading})\\s*$`, 'm').exec(content);
|
|
46
|
+
if (!match) return null;
|
|
47
|
+
const start = (match.index ?? 0) + match[0].length;
|
|
48
|
+
const rest = content.slice(start);
|
|
49
|
+
const end = rest.search(/^##\s+/m);
|
|
50
|
+
return rest.slice(0, end < 0 ? rest.length : end);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseReworkIntentDocument(content: string): ReworkIntentParseResult {
|
|
54
|
+
const body = sectionBody(content);
|
|
55
|
+
if (body === null) return { ok: true, present: false, intents: [] };
|
|
56
|
+
const lines = body.split(/\r?\n/).filter((line) => line.trim());
|
|
57
|
+
const header = lines[0] ? cells(lines[0]) : null;
|
|
58
|
+
const separator = lines[1] ? cells(lines[1]) : null;
|
|
59
|
+
if (!header || !separator || header.length !== REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === REWORK_INTENT_COLUMNS[index]) || !separator.every((value) => /^:?-{3,}:?$/.test(value))) {
|
|
60
|
+
return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section must contain its canonical table' };
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const intents = lines.slice(2).map((line) => {
|
|
64
|
+
const values = cells(line);
|
|
65
|
+
if (!values || values.length !== REWORK_INTENT_COLUMNS.length) throw new Error('rework intent row has the wrong column count');
|
|
66
|
+
const row = Object.fromEntries(REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]!])) as Record<string, string>;
|
|
67
|
+
if (!/^RI-[1-9]\d*$/.test(row.intent_id!)) throw new Error(`invalid intent id '${row.intent_id}'`);
|
|
68
|
+
if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id!)) throw new Error(`invalid finding id '${row.finding_id}'`);
|
|
69
|
+
if (!['analysis', 'plan', 'code'].includes(row.target!)) throw new Error(`invalid rework target '${row.target}'`);
|
|
70
|
+
if (!['pending', 'consumed', 'superseded'].includes(row.status!)) throw new Error(`invalid rework status '${row.status}'`);
|
|
71
|
+
if (!/^[a-f0-9]{64}$/.test(row.source_sha256!)) throw new Error(`invalid source hash for '${row.intent_id}'`);
|
|
72
|
+
if (!row.source_artifact || !row.declared_at) throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
|
|
73
|
+
const terminal = row.status === 'consumed' || row.status === 'superseded';
|
|
74
|
+
if (terminal !== Boolean(row.consumed_at)) throw new Error(`rework intent '${row.intent_id}' has invalid consumed_at`);
|
|
75
|
+
return {
|
|
76
|
+
intentId: row.intent_id!, findingId: row.finding_id!, sourceArtifact: row.source_artifact!,
|
|
77
|
+
sourceSha256: row.source_sha256!, target: row.target as ReworkTarget,
|
|
78
|
+
status: row.status as ReworkIntentStatus, declaredAt: row.declared_at!, consumedAt: row.consumed_at!
|
|
79
|
+
} satisfies ReworkIntent;
|
|
80
|
+
});
|
|
81
|
+
const ids = new Set<string>();
|
|
82
|
+
for (const intent of intents) {
|
|
83
|
+
if (ids.has(intent.intentId)) throw new Error(`duplicate intent '${intent.intentId}'`);
|
|
84
|
+
ids.add(intent.intentId);
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, present: true, intents };
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderReworkIntents(intents: readonly ReworkIntent[]): string {
|
|
93
|
+
const escape = (value: string) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ');
|
|
94
|
+
return [
|
|
95
|
+
`| ${REWORK_INTENT_COLUMNS.join(' | ')} |`,
|
|
96
|
+
`| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
|
|
97
|
+
...intents.map((intent) => `| ${[
|
|
98
|
+
intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
|
|
99
|
+
intent.target, intent.status, intent.declaredAt, intent.consumedAt
|
|
100
|
+
].map(escape).join(' | ')} |`)
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function upsertReworkIntent(intents: readonly ReworkIntent[], intent: ReworkIntent): { changed: boolean; intents: readonly ReworkIntent[] } {
|
|
105
|
+
const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
|
|
106
|
+
if (!existing) return { changed: true, intents: [...intents, intent] };
|
|
107
|
+
const identity = (value: ReworkIntent) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
|
|
108
|
+
if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent))) throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
|
|
109
|
+
return { changed: false, intents };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function consumeReworkIntents(
|
|
113
|
+
intents: readonly ReworkIntent[],
|
|
114
|
+
target: ReworkTarget,
|
|
115
|
+
sourceHashes: Readonly<Record<string, string>>,
|
|
116
|
+
consumedAt: string
|
|
117
|
+
): { changed: boolean; intents: readonly ReworkIntent[] } {
|
|
118
|
+
let changed = false;
|
|
119
|
+
const next = intents.map((intent) => {
|
|
120
|
+
if (
|
|
121
|
+
intent.status !== 'pending'
|
|
122
|
+
|| intent.target !== target
|
|
123
|
+
|| sourceHashes[intent.sourceArtifact] !== intent.sourceSha256
|
|
124
|
+
) return intent;
|
|
125
|
+
changed = true;
|
|
126
|
+
return { ...intent, status: 'consumed' as const, consumedAt };
|
|
127
|
+
});
|
|
128
|
+
return { changed, intents: next };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function supersedeReworkIntents(
|
|
132
|
+
intents: readonly ReworkIntent[],
|
|
133
|
+
sourceArtifact: string,
|
|
134
|
+
sourceSha256: string,
|
|
135
|
+
supersededAt: string
|
|
136
|
+
): { changed: boolean; intents: readonly ReworkIntent[] } {
|
|
137
|
+
let changed = false;
|
|
138
|
+
const next = intents.map((intent) => {
|
|
139
|
+
if (intent.status !== 'pending' || intent.sourceArtifact !== sourceArtifact || intent.sourceSha256 === sourceSha256) return intent;
|
|
140
|
+
changed = true;
|
|
141
|
+
return { ...intent, status: 'superseded' as const, consumedAt: supersededAt };
|
|
142
|
+
});
|
|
143
|
+
return { changed, intents: next };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function reworkIntentMutation(content: string, intents: readonly ReworkIntent[]) {
|
|
147
|
+
return {
|
|
148
|
+
kind: 'section' as const,
|
|
149
|
+
aliases: REWORK_INTENT_HEADINGS,
|
|
150
|
+
heading: /^##\s+Rework Intent\s*$/m.test(content) ? REWORK_INTENT_HEADINGS[1] : REWORK_INTENT_HEADINGS[0],
|
|
151
|
+
body: renderReworkIntents(intents)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
REWORK_INTENT_COLUMNS,
|
|
157
|
+
REWORK_INTENT_HEADINGS,
|
|
158
|
+
parseReworkIntentDocument,
|
|
159
|
+
renderReworkIntents,
|
|
160
|
+
reworkIntentMutation,
|
|
161
|
+
consumeReworkIntents,
|
|
162
|
+
supersedeReworkIntents,
|
|
163
|
+
upsertReworkIntent
|
|
164
|
+
};
|
|
165
|
+
export type { ReworkIntent, ReworkIntentParseResult, ReworkIntentStatus, ReworkTarget };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const TASK_SCOPE_SKILLS: ReadonlySet<string> = new Set([
|
|
2
|
+
'analyze-task',
|
|
3
|
+
'block-task',
|
|
4
|
+
'cancel-task',
|
|
5
|
+
'check-task',
|
|
6
|
+
'code-task',
|
|
7
|
+
'commit',
|
|
8
|
+
'complete-manual-validation',
|
|
9
|
+
'complete-task',
|
|
10
|
+
'create-pr',
|
|
11
|
+
'plan-task',
|
|
12
|
+
'review-analysis',
|
|
13
|
+
'review-code',
|
|
14
|
+
'review-plan',
|
|
15
|
+
'run-manual-validation',
|
|
16
|
+
'run-task',
|
|
17
|
+
'watch-pr'
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function isTaskScopeSkill(skillName: string): boolean {
|
|
21
|
+
return TASK_SCOPE_SKILLS.has(skillName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { TASK_SCOPE_SKILLS, isTaskScopeSkill };
|
|
@@ -224,6 +224,22 @@ function withTaskExecutionLock<T>(
|
|
|
224
224
|
};
|
|
225
225
|
const serialized = `${JSON.stringify(owner)}\n`;
|
|
226
226
|
let ownsFixed = false;
|
|
227
|
+
let deferRelease = false;
|
|
228
|
+
const release = (): void => {
|
|
229
|
+
try {
|
|
230
|
+
unlinkIfPresent(candidate);
|
|
231
|
+
} catch {
|
|
232
|
+
// Candidate cleanup is best-effort and must not replace the primary result.
|
|
233
|
+
}
|
|
234
|
+
if (ownsFixed) {
|
|
235
|
+
try {
|
|
236
|
+
const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
|
|
237
|
+
if (current.token === token) unlinkIfPresent(fixed);
|
|
238
|
+
} catch {
|
|
239
|
+
// A missing or replaced fixed lock is not owned by this invocation.
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
227
243
|
try {
|
|
228
244
|
fs.mkdirSync(lockRoot, { recursive: true });
|
|
229
245
|
cleanStaleCandidates(lockRoot, key, options.identityMatches ?? processIdentityMatches);
|
|
@@ -269,7 +285,12 @@ function withTaskExecutionLock<T>(
|
|
|
269
285
|
);
|
|
270
286
|
}
|
|
271
287
|
unlinkIfPresent(candidate);
|
|
272
|
-
|
|
288
|
+
const value = callback();
|
|
289
|
+
if (value && typeof (value as { then?: unknown }).then === 'function') {
|
|
290
|
+
deferRelease = true;
|
|
291
|
+
return (value as unknown as Promise<unknown>).finally(release) as T;
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
273
294
|
} catch (error) {
|
|
274
295
|
if (error instanceof TaskExecutionLockError) throw error;
|
|
275
296
|
if (error instanceof Error && error.name === 'OrchestrationStateError') throw error;
|
|
@@ -279,19 +300,7 @@ function withTaskExecutionLock<T>(
|
|
|
279
300
|
{ operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key }
|
|
280
301
|
);
|
|
281
302
|
} finally {
|
|
282
|
-
|
|
283
|
-
unlinkIfPresent(candidate);
|
|
284
|
-
} catch {
|
|
285
|
-
// Candidate cleanup is best-effort and must not replace the primary result.
|
|
286
|
-
}
|
|
287
|
-
if (ownsFixed) {
|
|
288
|
-
try {
|
|
289
|
-
const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
|
|
290
|
-
if (current.token === token) unlinkIfPresent(fixed);
|
|
291
|
-
} catch {
|
|
292
|
-
// A missing or replaced fixed lock is not owned by this invocation.
|
|
293
|
-
}
|
|
294
|
-
}
|
|
303
|
+
if (!deferRelease) release();
|
|
295
304
|
}
|
|
296
305
|
}
|
|
297
306
|
|
|
@@ -34,6 +34,7 @@ import { snapshotReview } from "../git/review-snapshot.ts";
|
|
|
34
34
|
import { OrchestrationStateError, readRun } from "./orchestration.ts";
|
|
35
35
|
import { resolveDeliveryTarget } from "./delivery-target.ts";
|
|
36
36
|
import { readPrDeliveryFact } from "./pr-delivery-fact.ts";
|
|
37
|
+
import { validateLocalArtifact } from "./local-artifact-finalization.ts";
|
|
37
38
|
|
|
38
39
|
const TASK_ENUMS = {
|
|
39
40
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
@@ -88,7 +89,7 @@ let repoRoot = path.resolve(path.dirname(scriptPath), "..", "..");
|
|
|
88
89
|
|
|
89
90
|
const PLATFORM_ADAPTERS: Record<string, (context: any, shared: any) => any> = {
|
|
90
91
|
"platform-sync": checkPlatformSync,
|
|
91
|
-
"platform-sync-preflight": (context: any, shared: any) => ({ ...checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
|
|
92
|
+
"platform-sync-preflight": async (context: any, shared: any) => ({ ...await checkPlatformSync(context, shared), type: "platform-sync-preflight" }),
|
|
92
93
|
"required-checks": checkRequiredChecks
|
|
93
94
|
};
|
|
94
95
|
const OPTIONAL_PLATFORM_CHECKS = new Set(["platform-sync"]);
|
|
@@ -107,7 +108,7 @@ const sharedUtils = {
|
|
|
107
108
|
parsePrNumber
|
|
108
109
|
};
|
|
109
110
|
|
|
110
|
-
function runCheck(type: any, context: any, shared: any): any {
|
|
111
|
+
async function runCheck(type: any, context: any, shared: any): Promise<any> {
|
|
111
112
|
switch (type) {
|
|
112
113
|
case "task-meta":
|
|
113
114
|
return checkTaskMeta(context);
|
|
@@ -147,7 +148,7 @@ function runCheck(type: any, context: any, shared: any): any {
|
|
|
147
148
|
return failResult(type, `Unsupported check type '${type}'.`);
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
return adapter(context, shared);
|
|
151
|
+
return await adapter(context, shared);
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
}
|
|
@@ -488,7 +489,7 @@ function checkTaskMeta({ taskDir, config }: any): any {
|
|
|
488
489
|
);
|
|
489
490
|
}
|
|
490
491
|
|
|
491
|
-
function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
492
|
+
async function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): Promise<any> {
|
|
492
493
|
const task = loadTask(taskDir);
|
|
493
494
|
if (!task.ok) return failResult('required-pr-delivery', task.message);
|
|
494
495
|
let projectConfig: any = {};
|
|
@@ -503,9 +504,10 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
|
503
504
|
if (factRead.status === 'invalid') return failResult('required-pr-delivery', factRead.error.message);
|
|
504
505
|
const fact = factRead.status === 'valid' ? factRead.fact : null;
|
|
505
506
|
const status = fact?.state || 'unbound';
|
|
506
|
-
const
|
|
507
|
+
const prIdentity = fact?.state === 'bound' ? fact.identity.resource : null;
|
|
508
|
+
const prNumber = prIdentity?.kind === 'number' ? prIdentity.value : null;
|
|
507
509
|
if (flow === 'disabled') return passResult('required-pr-delivery', 'Pull request delivery is disabled by project policy');
|
|
508
|
-
if (flow === 'required' && (status !== 'bound' ||
|
|
510
|
+
if (flow === 'required' && (status !== 'bound' || !prIdentity)) {
|
|
509
511
|
return failResult('required-pr-delivery', 'Project policy requires a bound pull request before completion');
|
|
510
512
|
}
|
|
511
513
|
if (flow !== 'required' && status === 'unbound') {
|
|
@@ -515,7 +517,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
|
515
517
|
if (mode === 'gate') {
|
|
516
518
|
return passResult('required-pr-delivery', 'Bound pull request shape is valid; merged-state inspection is reserved for hard preflight');
|
|
517
519
|
}
|
|
518
|
-
const inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
|
|
520
|
+
const inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: repositoryRoot });
|
|
519
521
|
if (inspected.status === 'blocked') {
|
|
520
522
|
return blockedResult(
|
|
521
523
|
'required-pr-delivery',
|
|
@@ -527,7 +529,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
|
527
529
|
const repository = inspected.platform.repository?.toLowerCase() || '';
|
|
528
530
|
const taskBranch = String(task.metadata.branch || '').trim();
|
|
529
531
|
const deliveryBaseRef = String(task.metadata.delivery_base_ref || projectConfig.delivery?.baseRef || '').trim();
|
|
530
|
-
if (inspected.status !== 'no-op' || !pullRequest
|
|
532
|
+
if (inspected.status !== 'no-op' || !pullRequest) {
|
|
531
533
|
return failResult('required-pr-delivery', 'Bound pull request identity could not be verified');
|
|
532
534
|
}
|
|
533
535
|
if (
|
|
@@ -542,7 +544,7 @@ function checkRequiredPrDelivery({ taskDir, repositoryRoot, mode }: any): any {
|
|
|
542
544
|
if (pullRequest.state !== 'closed' || !pullRequest.mergedAt || !pullRequest.mergeCommitSha) {
|
|
543
545
|
return failResult('required-pr-delivery', 'Project policy requires the bound pull request to be merged');
|
|
544
546
|
}
|
|
545
|
-
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR
|
|
547
|
+
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (merged PR ${prNumber === null ? 'identity' : `#${prNumber}`})`);
|
|
546
548
|
}
|
|
547
549
|
return passResult('required-pr-delivery', `Pull request delivery policy satisfied (${status})`);
|
|
548
550
|
}
|
|
@@ -581,7 +583,7 @@ function loadProjectName(): any {
|
|
|
581
583
|
}
|
|
582
584
|
}
|
|
583
585
|
|
|
584
|
-
function checkArtifact({ taskDir, config, artifactFile }: any): any {
|
|
586
|
+
function checkArtifact({ taskDir, config, artifactFile, skillName }: any): any {
|
|
585
587
|
const resolvedArtifact = resolveArtifactPath(taskDir, config.file_pattern, artifactFile);
|
|
586
588
|
if (!resolvedArtifact.ok) {
|
|
587
589
|
return failResult("artifact", resolvedArtifact.message);
|
|
@@ -599,6 +601,26 @@ function checkArtifact({ taskDir, config, artifactFile }: any): any {
|
|
|
599
601
|
|
|
600
602
|
const content = fs.readFileSync(artifactPath, "utf8");
|
|
601
603
|
const requiredSections = config.required_sections || [];
|
|
604
|
+
const localFamily = skillName === "analyze-task"
|
|
605
|
+
? "analysis"
|
|
606
|
+
: skillName === "plan-task" ? "plan" : skillName === "code-task" ? "code" : null;
|
|
607
|
+
if (localFamily) {
|
|
608
|
+
const local = validateLocalArtifact(content, {
|
|
609
|
+
family: localFamily,
|
|
610
|
+
requiredSections: requiredSections.filter((section: unknown): section is string => typeof section === "string"),
|
|
611
|
+
requiredPatterns: (config.required_patterns || []).filter((pattern: unknown): pattern is string => typeof pattern === "string")
|
|
612
|
+
});
|
|
613
|
+
if (!local.ok) {
|
|
614
|
+
return failResult(
|
|
615
|
+
"artifact",
|
|
616
|
+
`${path.basename(artifactPath)} has local structural errors: ${local.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return passResult(
|
|
620
|
+
"artifact",
|
|
621
|
+
`${path.basename(artifactPath)} passed (${requiredSections.length} sections)`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
602
624
|
const missingSections = requiredSections.filter(
|
|
603
625
|
(section: any) => !new RegExp(`^##\\s+${escapeRegExp(section)}\\s*$`, "m").test(content)
|
|
604
626
|
);
|
|
@@ -1031,6 +1053,7 @@ function checkManualValidation({ taskDir }: any): any {
|
|
|
1031
1053
|
// 1. Latest review-code artifact; none -> pass (no review, check not applicable).
|
|
1032
1054
|
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1033
1055
|
if (!review.ok) {
|
|
1056
|
+
if (review.error) return failResult("manual-validation", `Review-code artifact is unavailable: ${review.error}`);
|
|
1034
1057
|
return passResult("manual-validation", "No review-code artifact; manual validation not applicable");
|
|
1035
1058
|
}
|
|
1036
1059
|
// 2. Parse the numeric manual-validation count; unparseable/null -> fail closed
|
|
@@ -1096,9 +1119,10 @@ function checkManualValidation({ taskDir }: any): any {
|
|
|
1096
1119
|
return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
|
|
1097
1120
|
}
|
|
1098
1121
|
|
|
1099
|
-
function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
1122
|
+
async function checkPostReviewCommit({ taskDir, config }: any): Promise<any> {
|
|
1100
1123
|
const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1101
1124
|
if (!reviewArtifact.ok) {
|
|
1125
|
+
if (reviewArtifact.error) return failResult("post-review-commit", `Review-code artifact is unavailable: ${reviewArtifact.error}`);
|
|
1102
1126
|
return passResult("post-review-commit", "No review-code artifact; check inactive");
|
|
1103
1127
|
}
|
|
1104
1128
|
|
|
@@ -1117,9 +1141,9 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
1117
1141
|
const lastReviewedCommit = task.ok ? (task.metadata.last_reviewed_commit || "").trim() : "";
|
|
1118
1142
|
const globs = resolvePostReviewGlobs(config, loadPostReviewConfig(repoRoot));
|
|
1119
1143
|
const hasPullRequest = fact?.state === 'bound';
|
|
1120
|
-
let inspected: ReturnType<typeof inspectPlatformPullRequest
|
|
1144
|
+
let inspected: Awaited<ReturnType<typeof inspectPlatformPullRequest>> | null = null;
|
|
1121
1145
|
if (hasPullRequest) {
|
|
1122
|
-
inspected = inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
|
|
1146
|
+
inspected = await inspectPlatformPullRequest(task.metadata.id, { cwd: gitRoot });
|
|
1123
1147
|
if (!inspected.pullRequest) {
|
|
1124
1148
|
const message = inspected.error?.message || "PR merge snapshot is unavailable";
|
|
1125
1149
|
const code = inspected.error?.code || "PR_INSPECTION_INVALID";
|
|
@@ -1132,7 +1156,7 @@ function checkPostReviewCommit({ taskDir, config }: any): any {
|
|
|
1132
1156
|
inspected.pullRequest.mergedAt &&
|
|
1133
1157
|
inspected.pullRequest.mergeCommitSha
|
|
1134
1158
|
) {
|
|
1135
|
-
const relation = resolveMaterializedReviewedHeadRelation({
|
|
1159
|
+
const relation = await resolveMaterializedReviewedHeadRelation({
|
|
1136
1160
|
cwd: gitRoot,
|
|
1137
1161
|
platformType: inspected.platform.type,
|
|
1138
1162
|
lastReviewedCommit,
|
|
@@ -1299,6 +1323,17 @@ function checkReviewFact({ taskDir, artifactFile }: any): any {
|
|
|
1299
1323
|
return failResult("review-fact", resolvedArtifact.message);
|
|
1300
1324
|
}
|
|
1301
1325
|
|
|
1326
|
+
const authoritative = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
1327
|
+
if (!authoritative.ok) {
|
|
1328
|
+
return failResult("review-fact", authoritative.error ?? "No active review-code artifact is available");
|
|
1329
|
+
}
|
|
1330
|
+
if (path.basename(resolvedArtifact.path) !== authoritative.fileName) {
|
|
1331
|
+
return failResult(
|
|
1332
|
+
"review-fact",
|
|
1333
|
+
`review-code artifact ${path.basename(resolvedArtifact.path)} is not the current active artifact ${authoritative.fileName}; re-run review-code`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1302
1337
|
const task = loadTask(taskDir);
|
|
1303
1338
|
if (!task.ok) {
|
|
1304
1339
|
return failResult("review-fact", task.message);
|
|
@@ -1655,7 +1690,7 @@ function isBlank(value: any): any {
|
|
|
1655
1690
|
return value === undefined || value === null || String(value).trim() === "";
|
|
1656
1691
|
}
|
|
1657
1692
|
|
|
1658
|
-
function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): any {
|
|
1693
|
+
async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: requestedChecks, repositoryRoot }: any): Promise<any> {
|
|
1659
1694
|
if (repositoryRoot) repoRoot = path.resolve(repositoryRoot);
|
|
1660
1695
|
else {
|
|
1661
1696
|
let cursor = path.resolve(taskDir);
|
|
@@ -1668,7 +1703,7 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
|
|
|
1668
1703
|
const checks = [];
|
|
1669
1704
|
for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
|
|
1670
1705
|
if (checkConfig === null) continue;
|
|
1671
|
-
const result = runCheck(type, {
|
|
1706
|
+
const result = await runCheck(type, {
|
|
1672
1707
|
mode,
|
|
1673
1708
|
skillName,
|
|
1674
1709
|
taskDir: path.resolve(taskDir),
|
|
@@ -1688,7 +1723,7 @@ function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks: reque
|
|
|
1688
1723
|
if (config === null) return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
|
|
1689
1724
|
return {
|
|
1690
1725
|
skill: skillName,
|
|
1691
|
-
...runCheck(type, {
|
|
1726
|
+
...await runCheck(type, {
|
|
1692
1727
|
skillName,
|
|
1693
1728
|
taskDir: path.resolve(taskDir),
|
|
1694
1729
|
artifactFile,
|
package/lib/task/verification.ts
CHANGED
|
@@ -49,7 +49,7 @@ type TaskVerificationResult = {
|
|
|
49
49
|
};
|
|
50
50
|
type VerificationOptions = {
|
|
51
51
|
repoRoot?: string;
|
|
52
|
-
engine?: typeof verifyInProcess
|
|
52
|
+
engine?: (input: Parameters<typeof verifyInProcess>[0]) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
53
53
|
manualOverride?: ManualOverrideCapability;
|
|
54
54
|
};
|
|
55
55
|
|
|
@@ -91,7 +91,7 @@ function failure(request: { taskRef: string; event: string; artifact?: string },
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: string }, options: VerificationOptions = {}): TaskVerificationResult {
|
|
94
|
+
async function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: string }, options: VerificationOptions = {}): Promise<TaskVerificationResult> {
|
|
95
95
|
const spec = VERIFICATION_CATALOG[request.event as VerificationEvent];
|
|
96
96
|
if (!spec) return failure(request, 'VERIFY_EVENT_UNKNOWN', `unknown verification event '${request.event}'`);
|
|
97
97
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
@@ -113,7 +113,7 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
|
|
|
113
113
|
const invocations: VerificationInvocation[] = [];
|
|
114
114
|
const engine = options.engine ?? verifyInProcess;
|
|
115
115
|
if (spec.mode === 'gate') {
|
|
116
|
-
const payload = engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot })
|
|
116
|
+
const payload = await engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
|
|
117
117
|
const status = payload.gate as 'pass' | 'fail' | 'blocked';
|
|
118
118
|
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
119
119
|
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
|
|
@@ -125,7 +125,7 @@ function verifyTaskEvent(request: { taskRef: string; event: string; artifact?: s
|
|
|
125
125
|
invocations.push({ status, exitCode: ({ pass: 0, fail: 1, blocked: 2 } as const)[status], payload });
|
|
126
126
|
} else {
|
|
127
127
|
for (const check of spec.checks ?? []) {
|
|
128
|
-
const payload = engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot })
|
|
128
|
+
const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
|
|
129
129
|
const status = payload.status as 'pass' | 'fail' | 'blocked';
|
|
130
130
|
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
131
131
|
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
|
|
@@ -68,7 +68,7 @@ function sectionMutation(content: string): TaskMutation[] {
|
|
|
68
68
|
const english = /^##\s+Activity Log\s*$/m.test(content);
|
|
69
69
|
return [{
|
|
70
70
|
kind: 'section', aliases: WORKFLOW_WARNING_HEADINGS, heading: english ? 'Workflow Warnings' : '工作流告警',
|
|
71
|
-
body: `| ${WORKFLOW_WARNING_COLUMNS.join(' | ')} |\n
|
|
71
|
+
body: `| ${WORKFLOW_WARNING_COLUMNS.join(' | ')} |\n|----|------|------|----------|------|--------|---------|--------|-------------|------------|------------|`
|
|
72
72
|
}];
|
|
73
73
|
}
|
|
74
74
|
|
package/lib/task/write.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { allowsManualOverride } from './guard-override.ts';
|
|
|
16
16
|
import type { ManualOverrideCapability } from './guard-override.ts';
|
|
17
17
|
import { mutateTableRow, upsertSection } from './sections.ts';
|
|
18
18
|
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
19
|
+
import { invalidationBlocks, parseInvalidationDocument } from './invalidation.ts';
|
|
19
20
|
import type {
|
|
20
21
|
TableRowDeleteMutation,
|
|
21
22
|
TableRowUpsertMutation
|
|
@@ -86,6 +87,8 @@ type TaskWriteErrorCode =
|
|
|
86
87
|
| ResolveTaskRefErrorCode
|
|
87
88
|
| 'TASK_STATE_MISMATCH'
|
|
88
89
|
| 'TASK_CURRENT_CONTRACT_INVALID'
|
|
90
|
+
| 'TASK_INVALIDATION_INVALID'
|
|
91
|
+
| 'TASK_INVALIDATION_BLOCKED'
|
|
89
92
|
| 'TASK_READ_FAILED'
|
|
90
93
|
| 'TASK_DOCUMENT_INVALID'
|
|
91
94
|
| 'MUTATION_INVALID'
|
|
@@ -161,6 +164,7 @@ type TaskWriteOptions = {
|
|
|
161
164
|
randomSuffix?: () => string;
|
|
162
165
|
fileSystem?: Partial<TaskFileSystem>;
|
|
163
166
|
manualOverride?: ManualOverrideCapability;
|
|
167
|
+
invalidationContext?: 'standard' | 'source-completion' | 'reconcile';
|
|
164
168
|
};
|
|
165
169
|
|
|
166
170
|
const DEFAULT_FILE_SYSTEM: TaskFileSystem = {
|
|
@@ -275,6 +279,21 @@ function writeTask(request: TaskWriteRequest, options: TaskWriteOptions = {}): T
|
|
|
275
279
|
if (!contract.ok) {
|
|
276
280
|
return failure(request, identity, contract.code, contract.message);
|
|
277
281
|
}
|
|
282
|
+
if (
|
|
283
|
+
invalidationBlocks(contract.invalidation)
|
|
284
|
+
&& options.invalidationContext !== 'source-completion'
|
|
285
|
+
&& options.invalidationContext !== 'reconcile'
|
|
286
|
+
) {
|
|
287
|
+
return failure(
|
|
288
|
+
request,
|
|
289
|
+
identity,
|
|
290
|
+
'TASK_INVALIDATION_BLOCKED',
|
|
291
|
+
'task has an incomplete artifact invalidation operation; reconcile it before writing downstream state'
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
const invalidation = parseInvalidationDocument(original);
|
|
296
|
+
if (!invalidation.ok) return failure(request, identity, 'TASK_INVALIDATION_INVALID', invalidation.message);
|
|
278
297
|
}
|
|
279
298
|
|
|
280
299
|
let candidate = original;
|
package/lib/update.ts
CHANGED
|
@@ -19,7 +19,7 @@ type UpdateConfig = {
|
|
|
19
19
|
project: string;
|
|
20
20
|
org: string;
|
|
21
21
|
language: string;
|
|
22
|
-
platform?: { type?: string };
|
|
22
|
+
platform?: { type?: string; providers?: Record<string, unknown> };
|
|
23
23
|
prFlow?: 'required' | 'disabled';
|
|
24
24
|
sandbox?: Record<string, unknown>;
|
|
25
25
|
task?: { shortIdLength: number };
|
package/lib/version.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import semver from 'semver';
|
|
2
3
|
|
|
3
4
|
const packageJsonUrl = [
|
|
4
5
|
new URL('../package.json', import.meta.url),
|
|
@@ -12,9 +13,22 @@ if (!packageJsonUrl) {
|
|
|
12
13
|
const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
|
|
13
14
|
const VERSION = `v${version}`;
|
|
14
15
|
const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
16
|
+
const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
|
|
15
17
|
|
|
16
18
|
function isValidAgentInfraVersion(value: unknown): value is string {
|
|
17
19
|
return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
function isLegacyCompatibilityEnabled(runtimeVersion = VERSION): boolean {
|
|
23
|
+
const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
|
|
24
|
+
const parsed = semver.valid(normalized);
|
|
25
|
+
return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function legacyCompatibilityError(resource: string, runtimeVersion = VERSION): Error & { code: string } {
|
|
29
|
+
return Object.assign(new Error(
|
|
30
|
+
`PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`
|
|
31
|
+
), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
"access": "public",
|
|
18
18
|
"registry": "https://registry.npmjs.org/"
|
|
19
19
|
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"./platform-provider": {
|
|
22
|
+
"types": "./dist/lib/platform/provider-contract.d.ts",
|
|
23
|
+
"import": "./dist/lib/platform/provider-contract.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
20
26
|
"bin": {
|
|
21
27
|
"agent-infra": "./dist/bin/cli.js",
|
|
22
28
|
"ai": "./dist/bin/cli.js",
|
|
@@ -30,7 +30,7 @@ so you can quickly find "which ones to read" without opening each file.
|
|
|
30
30
|
- [`lifecycle-orchestration.md`](lifecycle-orchestration.md) — Fresh executor/reviewer, one-use receipt, pause/recovery, and safe endpoint rules for `run-task`.
|
|
31
31
|
- [`review-handshake.md`](review-handshake.md) — Three-stage bidirectional review handshake: four-state disposition, symmetric evidence, disagreement ledger, convergence and post-review commit gate.
|
|
32
32
|
- [`review-method.md`](review-method.md) — Shared three-stage review method: multi-pass review, risk lenses, traceability, and finding evidence.
|
|
33
|
-
- [`local-artifact-repair.md`](local-artifact-repair.md) —
|
|
33
|
+
- [`local-artifact-repair.md`](local-artifact-repair.md) — Pre-completion local artifact validation for analysis/plan/code and model-per-case repair, safety gates, and convergence for failed review artifacts.
|
|
34
34
|
- [`human-decision-context.md`](human-decision-context.md) — Self-contained context and canonical structure for new human-decision details.
|
|
35
35
|
- [`task-short-id.md`](task-short-id.md) — Resolution, allocation and lifecycle of bare-number short ids.
|
|
36
36
|
- [`milestone-inference.md`](milestone-inference.md) — Milestone inference for create-task / code-task / create-pr.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- [`lifecycle-orchestration.md`](lifecycle-orchestration.md) — `run-task` 的 fresh executor/reviewer、一次性 receipt、暂停恢复与安全终点规则。
|
|
30
30
|
- [`review-handshake.md`](review-handshake.md) — 三阶段双向审查握手协议:四态处置、对称证据、分歧账本、收敛与 post-review commit 门禁。
|
|
31
31
|
- [`review-method.md`](review-method.md) — 三阶段共享检视方法:多遍检视、风险镜头、追踪与 finding 证据契约。
|
|
32
|
-
- [`local-artifact-repair.md`](local-artifact-repair.md) — review artifact
|
|
32
|
+
- [`local-artifact-repair.md`](local-artifact-repair.md) — analysis/plan/code completed 前的本地产物校验、review artifact 失败后的模型逐例修复、机械安全门与收敛契约。
|
|
33
33
|
- [`human-decision-context.md`](human-decision-context.md) — 新建人工裁决详情的自足上下文与规范结构。
|
|
34
34
|
- [`task-short-id.md`](task-short-id.md) — 裸数字任务短号的解析、分配与生命周期。
|
|
35
35
|
- [`milestone-inference.md`](milestone-inference.md) — create-task / code-task / create-pr 的 milestone 推断。
|
|
@@ -29,6 +29,6 @@ agent-infra-internal platform-pr sync {task-id} \
|
|
|
29
29
|
--agent {standard-agent-token} --metadata --closing-issue
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Conflicting bindings do not change `task.md`.
|
|
32
|
+
Conflicting bindings do not change `task.md`. The shared core computes one `in:` target from task-bound diff/PR files and the repository mapping. A unique closing Issue is converged before the PR; other metadata comes from the linked Issue. Zero or multiple closing Issues update only the PR and return `degraded`; `in:` labels are never copied back from the Issue or removed from unrelated resources. Permission-bound items degrade independently, partial side effects return `blocked` with `IN_LABEL_SYNC_PARTIAL`, and an already-created PR is never rolled back.
|
|
33
33
|
|
|
34
34
|
Statuses `planned|applied|no-op|degraded` exit 0, `failed` exits 1, and `blocked` exits 2.
|
|
@@ -39,7 +39,7 @@ agent-infra-internal platform-pr sync {task-id} \
|
|
|
39
39
|
--agent {standard-agent-token} --metadata --closing-issue
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
core
|
|
42
|
+
core 根据 task-bound diff/PR files 与仓库映射计算唯一 `in:` target;唯一 closing Issue 按 Issue→PR 顺序收敛,其他 metadata 从关联 Issue 复制。零或多个 closing Issue 只更新 PR 并返回 `degraded`;不得从 Issue 反向复制 `in:`,也不清除非 `in:` labels。权限不足逐项返回 `skipped`,部分副作用返回 `blocked` 与 `IN_LABEL_SYNC_PARTIAL`。
|
|
43
43
|
|
|
44
44
|
## 状态语义
|
|
45
45
|
|