@fitlab-ai/agent-infra 0.9.11 → 0.9.12
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 +6 -6
- package/README.zh-CN.md +6 -6
- package/dist/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +1 -0
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-pr.js +8 -2
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/platform/github-client.js +7 -1
- package/dist/lib/platform/pr-checks.js +8 -5
- package/dist/lib/platform/pr-summary.js +12 -8
- package/dist/lib/platform/pull-requests.js +393 -91
- package/dist/lib/platform/verification-required.js +13 -2
- package/dist/lib/platform/verification-sync.js +9 -4
- package/dist/lib/pr-review/evidence-grading.js +12 -4
- package/dist/lib/sandbox/config.js +5 -0
- package/dist/lib/sandbox/control/lifecycle.js +29 -7
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/task/artifact-lifecycle.js +2 -2
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +195 -101
- package/dist/lib/task/create.js +33 -7
- package/dist/lib/task/decision-details.js +3 -48
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/human-override.js +5 -2
- package/dist/lib/task/lifecycle.js +106 -9
- package/dist/lib/task/orchestration.js +55 -62
- package/dist/lib/task/pr-delivery-fact.js +182 -0
- package/dist/lib/task/review-finalization.js +4 -8
- package/dist/lib/task/review-fingerprint.js +8 -0
- package/dist/lib/task/verification-engine.js +48 -24
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/update.js +9 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
- package/lib/defaults.json +4 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +3 -0
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-pr.ts +8 -1
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/platform/github-client.ts +8 -1
- package/lib/platform/pr-checks.ts +7 -5
- package/lib/platform/pr-summary.ts +10 -7
- package/lib/platform/pull-requests.ts +425 -87
- package/lib/platform/verification-required.ts +12 -2
- package/lib/platform/verification-sync.ts +9 -4
- package/lib/pr-review/evidence-grading.ts +7 -4
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/control/lifecycle.ts +30 -8
- package/lib/server/process-state.ts +2 -2
- package/lib/task/artifact-lifecycle.ts +2 -2
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +174 -87
- package/lib/task/create.ts +34 -9
- package/lib/task/decision-details.ts +2 -62
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/human-override.ts +5 -2
- package/lib/task/lifecycle.ts +93 -4
- package/lib/task/orchestration.ts +62 -65
- package/lib/task/pr-delivery-fact.ts +254 -0
- package/lib/task/review-finalization.ts +4 -14
- package/lib/task/review-fingerprint.ts +10 -0
- package/lib/task/verification-engine.ts +45 -28
- package/lib/task/verification-review.ts +2 -0
- package/lib/update.ts +10 -0
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/create-issue.en.md +11 -2
- package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
- package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
- package/templates/.agents/rules/issue-sync.en.md +26 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
- package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
- package/templates/.agents/rules/milestone-inference.en.md +12 -2
- package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
- package/templates/.agents/rules/pr-sync.en.md +22 -13
- package/templates/.agents/rules/pr-sync.zh-CN.md +22 -1
- package/templates/.agents/rules/release-commands.en.md +42 -3
- package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
- package/templates/.agents/rules/security-alerts.en.md +17 -2
- package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
- package/templates/.agents/rules/sync-content-generation.en.md +8 -0
- package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/security-alerts.github.sh +151 -0
- package/templates/.agents/scripts/security-alerts.sh +31 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +5 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/commit/SKILL.en.md +3 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/config/verify.json +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +10 -10
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +7 -6
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -0
- 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/skills/update-agent-infra/scripts/sync-templates.js +4 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/templates/task.en.md +5 -1
- package/templates/.agents/templates/task.zh-CN.md +5 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/feature-development.en.yaml +9 -8
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/refactoring.en.yaml +9 -8
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
- package/templates/.agents/rules/create-issue.github.en.md +0 -14
- package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
- package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
- package/templates/.agents/rules/issue-sync.github.en.md +0 -40
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
- package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
- package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
- package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
- package/templates/.agents/rules/pr-sync.github.en.md +0 -43
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
- package/templates/.agents/rules/release-commands.github.en.md +0 -44
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
- /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
- /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
|
@@ -8,7 +8,7 @@ import { mergeOperationWarnings } from './operation-outcome.js';
|
|
|
8
8
|
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
9
|
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
10
10
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
11
|
-
import {
|
|
11
|
+
import { checkpointIntentDigest, readCheckpointIntent, removeCheckpointIntent, sameCheckpointIntent, updateCheckpointIntent, writeCheckpointIntent } from './commit-intent.js';
|
|
12
12
|
const HEAD_REF = /^refs\/heads\/(.+)$/;
|
|
13
13
|
function isCommitExecutionMode(value) {
|
|
14
14
|
return value === 'direct' || value === 'orchestrated';
|
|
@@ -20,6 +20,13 @@ function gitText(repoRoot, args) {
|
|
|
20
20
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
21
21
|
}).trim();
|
|
22
22
|
}
|
|
23
|
+
function effectiveDelivery(input) {
|
|
24
|
+
if (input.delivery)
|
|
25
|
+
return input.delivery;
|
|
26
|
+
if (input.push)
|
|
27
|
+
return { mode: 'push', remote: input.push.remote, refs: input.push.refs };
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
23
30
|
function canonicalRepositoryRoot(cwd) {
|
|
24
31
|
return gitText(cwd, ['rev-parse', '--show-toplevel']);
|
|
25
32
|
}
|
|
@@ -86,8 +93,9 @@ function validateCommon(input, branch) {
|
|
|
86
93
|
return { code: 'GIT_HEAD_EXPECTATION_REQUIRED', message: 'A valid expected HEAD is required' };
|
|
87
94
|
if (!/^[a-f0-9]{40,64}$/.test(input.expectedTree))
|
|
88
95
|
return { code: 'GIT_TREE_EXPECTATION_REQUIRED', message: 'A valid expected tree is required' };
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
const delivery = effectiveDelivery(input);
|
|
97
|
+
if (!delivery)
|
|
98
|
+
return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Commit delivery requires a delivery policy' };
|
|
91
99
|
if (input.paths.some((candidate) => !candidate || candidate.startsWith('/') || candidate.split(/[\\/]/).includes('..'))) {
|
|
92
100
|
return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Commit paths must stay inside the repository' };
|
|
93
101
|
}
|
|
@@ -96,38 +104,16 @@ function validateCommon(input, branch) {
|
|
|
96
104
|
}
|
|
97
105
|
if (branch !== null && branch === '')
|
|
98
106
|
return { code: 'GIT_BRANCH_INVALID', message: 'A named branch is required' };
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
if (delivery.mode === 'push') {
|
|
108
|
+
if (!delivery.remote || delivery.refs.length !== 1)
|
|
109
|
+
return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push requires one remote and one ref' };
|
|
110
|
+
const ref = delivery.refs[0] ?? '';
|
|
111
|
+
if (!HEAD_REF.test(ref) || HEAD_REF.exec(ref)?.[1] !== branch) {
|
|
112
|
+
return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push ref must be the current full heads ref' };
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
return null;
|
|
106
116
|
}
|
|
107
|
-
function validateOrchestrated(input, task) {
|
|
108
|
-
if (!input.agent)
|
|
109
|
-
return { error: { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'orchestrated commit requires an agent' } };
|
|
110
|
-
const planned = planOrchestrationStageCompletion(task.taskId, {
|
|
111
|
-
stage: 'commit',
|
|
112
|
-
round: 1,
|
|
113
|
-
artifact: 'commit',
|
|
114
|
-
role: 'executor',
|
|
115
|
-
agent: input.agent
|
|
116
|
-
}, { repoRoot: task.repoRoot });
|
|
117
|
-
const run = planned.result.run;
|
|
118
|
-
if (!planned.plan
|
|
119
|
-
|| !run
|
|
120
|
-
|| run.status !== 'running'
|
|
121
|
-
|| !run.commitAuthorization.issuedAt
|
|
122
|
-
|| run.commitAuthorization.consumedAt)
|
|
123
|
-
return {
|
|
124
|
-
error: {
|
|
125
|
-
code: planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH',
|
|
126
|
-
message: planned.result.error?.message ?? 'orchestrated commit requires one matching activated commit delegation'
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
return { plan: planned.plan };
|
|
130
|
-
}
|
|
131
117
|
function syncTaskCommit(input, task, committedHead) {
|
|
132
118
|
let content;
|
|
133
119
|
try {
|
|
@@ -146,7 +132,10 @@ function syncTaskCommit(input, task, committedHead) {
|
|
|
146
132
|
heading: activity.heading,
|
|
147
133
|
body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Commit', agent: input.agent, note: commitNote })
|
|
148
134
|
});
|
|
149
|
-
const frontmatter = {
|
|
135
|
+
const frontmatter = {
|
|
136
|
+
assigned_to: input.agent,
|
|
137
|
+
checkpoint_commit: committedHead
|
|
138
|
+
};
|
|
150
139
|
mutations.push({ kind: 'frontmatter', set: frontmatter });
|
|
151
140
|
if (mutations.length === 0)
|
|
152
141
|
return null;
|
|
@@ -163,10 +152,44 @@ function syncTaskCommit(input, task, committedHead) {
|
|
|
163
152
|
return warning('TASK_STATUS_SYNC_FAILED', error instanceof Error ? error.message : String(error), true, 'task', task.taskId, 'ACTION_REQUIRED');
|
|
164
153
|
}
|
|
165
154
|
}
|
|
155
|
+
function checkpointChildMatches(task, intent, head) {
|
|
156
|
+
try {
|
|
157
|
+
const parent = gitText(task.repoRoot, ['rev-parse', `${head}^`]);
|
|
158
|
+
const tree = gitText(task.repoRoot, ['rev-parse', `${head}^{tree}`]);
|
|
159
|
+
const message = gitText(task.repoRoot, ['show', '-s', '--format=%s', head]);
|
|
160
|
+
const changed = gitText(task.repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head]).split('\n').filter(Boolean).sort();
|
|
161
|
+
return parent === intent.expectedHead
|
|
162
|
+
&& tree === intent.expectedTree
|
|
163
|
+
&& message === intent.message
|
|
164
|
+
&& JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function checkpointIdentity(input, task, delivery) {
|
|
171
|
+
if (delivery.mode !== 'local')
|
|
172
|
+
throw new Error('checkpoint identity requires local delivery');
|
|
173
|
+
return {
|
|
174
|
+
taskId: task.taskId,
|
|
175
|
+
branch: task.branch,
|
|
176
|
+
mode: 'local',
|
|
177
|
+
expectedHead: input.expectedHead,
|
|
178
|
+
expectedTree: input.expectedTree,
|
|
179
|
+
paths: [...input.paths],
|
|
180
|
+
message: input.message,
|
|
181
|
+
round: input.round ?? 1
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function checkpointTimestamp() {
|
|
185
|
+
return new Date().toISOString();
|
|
186
|
+
}
|
|
166
187
|
function executeUnlocked(input, task, mode) {
|
|
167
188
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
168
189
|
if (!inspected.snapshot)
|
|
169
190
|
return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
|
|
191
|
+
if (mode === 'orchestrated')
|
|
192
|
+
return failure(input, mode, task?.taskId ?? null, 'ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', 'blocked', inspected.snapshot);
|
|
170
193
|
const branch = inspected.snapshot.branch;
|
|
171
194
|
const commonError = validateCommon(input, branch);
|
|
172
195
|
if (commonError)
|
|
@@ -177,60 +200,131 @@ function executeUnlocked(input, task, mode) {
|
|
|
177
200
|
return failure(input, mode, task.taskId, 'GIT_BRANCH_MISMATCH', `Task branch '${task.branch}' does not match current branch '${branch}'`, 'blocked', inspected.snapshot);
|
|
178
201
|
if (task && !input.agent)
|
|
179
202
|
return failure(input, mode, task.taskId, 'COMMIT_AGENT_REQUIRED', 'task-bound commit requires an agent', 'blocked', inspected.snapshot);
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const error = orchestrationValidation.error;
|
|
187
|
-
return failure(input, mode, task?.taskId ?? null, error.code, error.message, 'blocked', inspected.snapshot);
|
|
188
|
-
}
|
|
189
|
-
const orchestrationCompletion = orchestrationValidation && 'plan' in orchestrationValidation
|
|
190
|
-
? orchestrationValidation.plan
|
|
191
|
-
: null;
|
|
192
|
-
const committed = input.paths.length === 0
|
|
203
|
+
const delivery = effectiveDelivery(input);
|
|
204
|
+
if (delivery.mode === 'local' && !task)
|
|
205
|
+
return failure(input, mode, null, 'CHECKPOINT_TASK_REQUIRED', 'local checkpoint delivery requires a task context', 'blocked', inspected.snapshot);
|
|
206
|
+
let pendingIntent = null;
|
|
207
|
+
let checkpointCommittedHead = null;
|
|
208
|
+
const committed = delivery.mode === 'local'
|
|
193
209
|
? (() => {
|
|
194
|
-
|
|
210
|
+
const identity = checkpointIdentity(input, task, delivery);
|
|
211
|
+
try {
|
|
212
|
+
pendingIntent = readCheckpointIntent(task.repoRoot, task.taskId);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
195
215
|
return {
|
|
196
|
-
status: 'failed',
|
|
197
|
-
|
|
198
|
-
snapshot: inspected.snapshot,
|
|
199
|
-
operations: [],
|
|
200
|
-
error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Push-only retry requires a clean working tree' }
|
|
216
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
217
|
+
error: { code: 'COMMIT_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) }
|
|
201
218
|
};
|
|
202
|
-
|
|
219
|
+
}
|
|
220
|
+
if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity))
|
|
203
221
|
return {
|
|
204
|
-
status: 'failed',
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
222
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
223
|
+
error: { code: 'COMMIT_INTENT_CONFLICT', message: 'A different checkpoint intent is still pending for this task' }
|
|
224
|
+
};
|
|
225
|
+
if (!pendingIntent) {
|
|
226
|
+
const timestamp = checkpointTimestamp();
|
|
227
|
+
pendingIntent = {
|
|
228
|
+
version: 1, ...identity, digest: checkpointIntentDigest(identity), state: 'prepared',
|
|
229
|
+
committedHead: null, createdAt: timestamp, updatedAt: timestamp
|
|
230
|
+
};
|
|
231
|
+
try {
|
|
232
|
+
writeCheckpointIntent(task.repoRoot, pendingIntent);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
237
|
+
error: { code: 'COMMIT_INTENT_WRITE_FAILED', message: error instanceof Error ? error.message : String(error) }
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (pendingIntent.state !== 'prepared') {
|
|
242
|
+
checkpointCommittedHead = pendingIntent.committedHead;
|
|
243
|
+
return {
|
|
244
|
+
status: 'no-op', changed: false, snapshot: inspected.snapshot,
|
|
245
|
+
operations: [{ name: 'commit', status: 'no-op' }], error: null
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (inspected.snapshot.head !== identity.expectedHead && !checkpointChildMatches(task, pendingIntent, inspected.snapshot.head))
|
|
249
|
+
return {
|
|
250
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
251
|
+
error: { code: 'COMMIT_INTENT_HEAD_CONFLICT', message: 'Current HEAD does not match the pending checkpoint intent' }
|
|
209
252
|
};
|
|
210
|
-
|
|
211
|
-
|
|
253
|
+
if (inspected.snapshot.head !== identity.expectedHead) {
|
|
254
|
+
checkpointCommittedHead = inspected.snapshot.head;
|
|
255
|
+
pendingIntent = updateCheckpointIntent(pendingIntent, {
|
|
256
|
+
state: 'committed', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
|
|
257
|
+
});
|
|
258
|
+
writeCheckpointIntent(task.repoRoot, pendingIntent);
|
|
259
|
+
return {
|
|
260
|
+
status: 'no-op', changed: false, snapshot: inspected.snapshot,
|
|
261
|
+
operations: [{ name: 'commit', status: 'no-op' }], error: null
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (input.paths.length === 0)
|
|
265
|
+
return {
|
|
266
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
267
|
+
error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Local checkpoint requires explicit paths' }
|
|
268
|
+
};
|
|
269
|
+
const created = commitExplicitPaths({
|
|
270
|
+
cwd: input.cwd, paths: input.paths, message: input.message,
|
|
271
|
+
expectedHead: identity.expectedHead, expectedTree: identity.expectedTree
|
|
272
|
+
});
|
|
273
|
+
if (created.status === 'applied' && created.snapshot?.head) {
|
|
274
|
+
checkpointCommittedHead = created.snapshot.head;
|
|
275
|
+
pendingIntent = updateCheckpointIntent(pendingIntent, {
|
|
276
|
+
state: 'committed', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
|
|
277
|
+
});
|
|
278
|
+
writeCheckpointIntent(task.repoRoot, pendingIntent);
|
|
279
|
+
}
|
|
280
|
+
else if (created.status === 'no-op') {
|
|
281
|
+
removeCheckpointIntent(task.repoRoot, task.taskId);
|
|
282
|
+
pendingIntent = null;
|
|
283
|
+
}
|
|
284
|
+
return created;
|
|
285
|
+
})()
|
|
286
|
+
: input.paths.length === 0
|
|
287
|
+
? (() => {
|
|
288
|
+
if (inspected.snapshot.worktree.length > 0 || inspected.snapshot.staged.length > 0)
|
|
289
|
+
return {
|
|
290
|
+
status: 'failed',
|
|
291
|
+
changed: false,
|
|
292
|
+
snapshot: inspected.snapshot,
|
|
293
|
+
operations: [],
|
|
294
|
+
error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Push-only retry requires a clean working tree' }
|
|
295
|
+
};
|
|
296
|
+
if (inspected.snapshot.head !== input.expectedHead)
|
|
297
|
+
return {
|
|
298
|
+
status: 'failed',
|
|
299
|
+
changed: false,
|
|
300
|
+
snapshot: inspected.snapshot,
|
|
301
|
+
operations: [],
|
|
302
|
+
error: { code: 'GIT_HEAD_MISMATCH', message: `Expected HEAD ${input.expectedHead}, received ${inspected.snapshot.head}` }
|
|
303
|
+
};
|
|
304
|
+
const currentTree = gitText(input.cwd, ['rev-parse', 'HEAD^{tree}']);
|
|
305
|
+
if (currentTree !== input.expectedTree)
|
|
306
|
+
return {
|
|
307
|
+
status: 'failed',
|
|
308
|
+
changed: false,
|
|
309
|
+
snapshot: inspected.snapshot,
|
|
310
|
+
operations: [],
|
|
311
|
+
error: { code: 'GIT_TREE_MISMATCH', message: `Expected current tree ${input.expectedTree}, received ${currentTree}` }
|
|
312
|
+
};
|
|
212
313
|
return {
|
|
213
|
-
status: '
|
|
314
|
+
status: 'no-op',
|
|
214
315
|
changed: false,
|
|
215
316
|
snapshot: inspected.snapshot,
|
|
216
|
-
operations: [],
|
|
217
|
-
error:
|
|
317
|
+
operations: [{ name: 'commit', status: 'no-op' }],
|
|
318
|
+
error: null
|
|
218
319
|
};
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
: commitExplicitPaths({
|
|
228
|
-
cwd: input.cwd,
|
|
229
|
-
paths: input.paths,
|
|
230
|
-
message: input.message,
|
|
231
|
-
expectedHead: input.expectedHead,
|
|
232
|
-
expectedTree: input.expectedTree
|
|
233
|
-
});
|
|
320
|
+
})()
|
|
321
|
+
: commitExplicitPaths({
|
|
322
|
+
cwd: input.cwd,
|
|
323
|
+
paths: input.paths,
|
|
324
|
+
message: input.message,
|
|
325
|
+
expectedHead: input.expectedHead,
|
|
326
|
+
expectedTree: input.expectedTree
|
|
327
|
+
});
|
|
234
328
|
if (committed.status === 'failed')
|
|
235
329
|
return {
|
|
236
330
|
...committed,
|
|
@@ -241,42 +335,42 @@ function executeUnlocked(input, task, mode) {
|
|
|
241
335
|
snapshot: committed.snapshot
|
|
242
336
|
};
|
|
243
337
|
const warnings = [];
|
|
244
|
-
if (orchestrationCompletion) {
|
|
245
|
-
try {
|
|
246
|
-
commitOrchestrationStageCompletion(orchestrationCompletion);
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
warnings.push(warning('ORCHESTRATION_COMPLETION_FAILED', error instanceof Error ? error.message : String(error), true, 'orchestration', task?.taskId ?? 'orchestration', 'ACTION_REQUIRED'));
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
338
|
let status = committed.status;
|
|
253
339
|
let result = committed.status === 'applied' ? 'committed' : 'no_op';
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
340
|
+
if (delivery.mode === 'push') {
|
|
341
|
+
const target = `${delivery.remote}:${delivery.refs[0]}`;
|
|
342
|
+
const decision = commitPushDecision({ branch }, target);
|
|
343
|
+
if (!decision.shouldPush) {
|
|
344
|
+
warnings.push(decision.warning);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const pushed = pushGitRefs({
|
|
348
|
+
cwd: input.cwd, remote: delivery.remote, refs: delivery.refs,
|
|
349
|
+
expectedSha: committed.snapshot?.head ?? gitText(input.cwd, ['rev-parse', 'HEAD'])
|
|
350
|
+
});
|
|
351
|
+
if (pushed.status !== 'applied')
|
|
352
|
+
warnings.push(warning('COMMIT_PUSH_FAILED', pushed.error?.message ?? 'Git push failed', true, 'push', target, 'ACTION_REQUIRED'));
|
|
353
|
+
}
|
|
268
354
|
}
|
|
269
355
|
if (warnings.length > 0) {
|
|
270
356
|
status = 'applied';
|
|
271
357
|
result = 'committed_with_warnings';
|
|
272
358
|
}
|
|
273
359
|
if (task && (committed.status === 'applied' || committed.status === 'no-op')) {
|
|
274
|
-
const
|
|
360
|
+
const committedHead = checkpointCommittedHead ?? committed.snapshot?.head ?? gitText(task.repoRoot, ['rev-parse', 'HEAD']);
|
|
361
|
+
const taskWarning = syncTaskCommit(input, task, committedHead);
|
|
275
362
|
if (taskWarning) {
|
|
276
363
|
warnings.push(taskWarning);
|
|
277
364
|
status = 'applied';
|
|
278
365
|
result = 'committed_with_warnings';
|
|
279
366
|
}
|
|
367
|
+
if (delivery.mode === 'local' && !taskWarning && pendingIntent && checkpointCommittedHead) {
|
|
368
|
+
const synced = updateCheckpointIntent(pendingIntent, {
|
|
369
|
+
state: 'synced', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
|
|
370
|
+
});
|
|
371
|
+
writeCheckpointIntent(task.repoRoot, synced);
|
|
372
|
+
removeCheckpointIntent(task.repoRoot, task.taskId);
|
|
373
|
+
}
|
|
280
374
|
}
|
|
281
375
|
return {
|
|
282
376
|
status,
|
package/dist/lib/task/create.js
CHANGED
|
@@ -4,6 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { isValidAgentInfraVersion, VERSION } from '../version.js';
|
|
5
5
|
import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
|
|
6
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
7
|
+
import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.js';
|
|
8
|
+
import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.js';
|
|
7
9
|
const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'];
|
|
8
10
|
const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'];
|
|
9
11
|
const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'];
|
|
@@ -15,12 +17,15 @@ const TASK_INPUT_KEYS = [
|
|
|
15
17
|
function payloadError(message) {
|
|
16
18
|
throw new Error(`TASK_CREATE_PAYLOAD_INVALID: ${message}`);
|
|
17
19
|
}
|
|
18
|
-
function exactKeys(value, expected, label) {
|
|
20
|
+
function exactKeys(value, expected, label, optional = []) {
|
|
19
21
|
const actual = Object.keys(value).sort();
|
|
20
22
|
const wanted = [...expected].sort();
|
|
21
|
-
|
|
23
|
+
const allowed = new Set([...expected, ...optional]);
|
|
24
|
+
if (actual.some((key) => !allowed.has(key))) {
|
|
22
25
|
payloadError(`${label} has unknown or missing fields`);
|
|
23
26
|
}
|
|
27
|
+
if (expected.some((key) => !actual.includes(key)))
|
|
28
|
+
payloadError(`${label} has unknown or missing fields`);
|
|
24
29
|
}
|
|
25
30
|
function cleanString(value, label, maxBytes, oneLine = false) {
|
|
26
31
|
if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > maxBytes) {
|
|
@@ -43,7 +48,7 @@ function validateTaskCreateCandidate(value) {
|
|
|
43
48
|
exactKeys(candidate, [
|
|
44
49
|
'version', 'idempotencyKey', 'agent', 'title', 'type', 'branchSlug',
|
|
45
50
|
'priority', 'effort', 'description', 'taskInput'
|
|
46
|
-
], 'candidate');
|
|
51
|
+
], 'candidate', ['deliveryRemote', 'deliveryBaseRef']);
|
|
47
52
|
if (candidate.version !== 1)
|
|
48
53
|
payloadError('version must be 1');
|
|
49
54
|
const idempotencyKey = cleanString(candidate.idempotencyKey, 'idempotencyKey', 64, true);
|
|
@@ -58,6 +63,16 @@ function validateTaskCreateCandidate(value) {
|
|
|
58
63
|
payloadError('branchSlug must be lowercase ASCII kebab-case with length 3-64');
|
|
59
64
|
}
|
|
60
65
|
const description = cleanString(candidate.description, 'description', 8 * 1024);
|
|
66
|
+
const deliveryRemote = candidate.deliveryRemote === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: cleanString(candidate.deliveryRemote, 'deliveryRemote', 200, true);
|
|
69
|
+
const deliveryBaseRef = candidate.deliveryBaseRef === undefined
|
|
70
|
+
? undefined
|
|
71
|
+
: cleanString(candidate.deliveryBaseRef, 'deliveryBaseRef', 200, true);
|
|
72
|
+
if (deliveryRemote !== undefined && !validateRemote(deliveryRemote))
|
|
73
|
+
payloadError('deliveryRemote is invalid');
|
|
74
|
+
if (deliveryBaseRef !== undefined && !validateBaseRef(deliveryBaseRef))
|
|
75
|
+
payloadError('deliveryBaseRef is invalid');
|
|
61
76
|
if (!candidate.taskInput || typeof candidate.taskInput !== 'object' || Array.isArray(candidate.taskInput)) {
|
|
62
77
|
payloadError('taskInput must be an object');
|
|
63
78
|
}
|
|
@@ -79,6 +94,8 @@ function validateTaskCreateCandidate(value) {
|
|
|
79
94
|
priority: enumValue(candidate.priority, PRIORITIES, 'priority'),
|
|
80
95
|
effort: enumValue(candidate.effort, EFFORTS, 'effort'),
|
|
81
96
|
description,
|
|
97
|
+
...(deliveryRemote === undefined ? {} : { deliveryRemote }),
|
|
98
|
+
...(deliveryBaseRef === undefined ? {} : { deliveryBaseRef }),
|
|
82
99
|
taskInput
|
|
83
100
|
};
|
|
84
101
|
}
|
|
@@ -94,6 +111,8 @@ function canonicalTaskCreateCandidate(candidate) {
|
|
|
94
111
|
priority: valid.priority,
|
|
95
112
|
effort: valid.effort,
|
|
96
113
|
description: valid.description,
|
|
114
|
+
deliveryRemote: valid.deliveryRemote ?? null,
|
|
115
|
+
deliveryBaseRef: valid.deliveryBaseRef ?? null,
|
|
97
116
|
taskInput: Object.fromEntries(TASK_INPUT_KEYS.map((key) => [key, valid.taskInput[key]]))
|
|
98
117
|
});
|
|
99
118
|
}
|
|
@@ -145,7 +164,11 @@ function renderTask(params) {
|
|
|
145
164
|
'target_date:',
|
|
146
165
|
'current_step: requirement-analysis',
|
|
147
166
|
`assigned_to: ${candidate.agent}`,
|
|
148
|
-
|
|
167
|
+
`pr_delivery_fact: ${JSON.stringify(encodePrDeliveryFact(buildUnboundFact()))}`,
|
|
168
|
+
`delivery_remote: ${candidate.deliveryRemote ?? params.delivery.remote}`,
|
|
169
|
+
`delivery_base_ref: ${candidate.deliveryBaseRef ?? params.delivery.baseRef}`,
|
|
170
|
+
'checkpoint_commit:',
|
|
171
|
+
'delivery_remote_head:',
|
|
149
172
|
`task_create_key_digest: ${params.keyDigest}`,
|
|
150
173
|
`task_create_candidate_digest: ${params.candidateDigest}`,
|
|
151
174
|
'---'
|
|
@@ -175,7 +198,10 @@ function readProject(repoRoot) {
|
|
|
175
198
|
if (typeof config.project !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.project)) {
|
|
176
199
|
throw new Error('TASK_CREATE_CONFIG_INVALID: project is missing or invalid');
|
|
177
200
|
}
|
|
178
|
-
|
|
201
|
+
const delivery = readDeliveryDefaults(repoRoot);
|
|
202
|
+
if (!delivery.ok)
|
|
203
|
+
throw new Error(`TASK_CREATE_CONFIG_INVALID: ${delivery.message}`);
|
|
204
|
+
return { project: config.project, delivery: delivery.value };
|
|
179
205
|
}
|
|
180
206
|
function assertRealDirectory(directory, code) {
|
|
181
207
|
const stat = fs.lstatSync(directory);
|
|
@@ -269,7 +295,7 @@ function createLocalTask(value, options) {
|
|
|
269
295
|
ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
|
|
270
296
|
ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
271
297
|
ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
272
|
-
const
|
|
298
|
+
const projectConfig = readProject(repoRoot);
|
|
273
299
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
274
300
|
const candidateDigest = sha256(canonical);
|
|
275
301
|
const keyDigest = sha256(candidate.idempotencyKey);
|
|
@@ -312,7 +338,7 @@ function createLocalTask(value, options) {
|
|
|
312
338
|
if (!templateStat.isFile() || templateStat.isSymbolicLink())
|
|
313
339
|
throw new Error('TASK_CREATE_TEMPLATE_INVALID');
|
|
314
340
|
const rendered = renderTask({
|
|
315
|
-
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project, timestamp,
|
|
341
|
+
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project: projectConfig.project, delivery: projectConfig.delivery, timestamp,
|
|
316
342
|
agentInfraVersion, keyDigest, candidateDigest
|
|
317
343
|
});
|
|
318
344
|
const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
|
|
@@ -109,31 +109,8 @@ function resolveDecisionDetail(content, rowId, anchor) {
|
|
|
109
109
|
}
|
|
110
110
|
return { status: 'found', block: matches[0] };
|
|
111
111
|
}
|
|
112
|
-
const INFORMAL_SHORT_HEADING = /^(?:AN|PL|CD|HD)-[1-9]\d*[::]\s*(?:上一轮)?简短复核$/i;
|
|
113
|
-
const INFORMAL_SHORT_FRAGMENT = /^简短(?:结论|说明)$/;
|
|
114
|
-
function isInformalShortBlock(block) {
|
|
115
|
-
if (block.canonical || block.content.length > 600)
|
|
116
|
-
return false;
|
|
117
|
-
if (!INFORMAL_SHORT_HEADING.test(block.heading))
|
|
118
|
-
return false;
|
|
119
|
-
const scanned = scanVisibleMarkdown(block.content);
|
|
120
|
-
const bodyLines = scanned.lines
|
|
121
|
-
.filter((line) => !scanned.headings.some((heading) => heading.start === line.start) && line.text.trim())
|
|
122
|
-
.map((line) => line.text);
|
|
123
|
-
if (bodyLines.length === 0 || bodyLines.length > 2)
|
|
124
|
-
return false;
|
|
125
|
-
const fragments = bodyLines.map((line) => line.match(/^\s*[-*]\s+([^\s].*?)\s*$/)?.[1] ?? null);
|
|
126
|
-
if (fragments.some((fragment) => fragment === null))
|
|
127
|
-
return false;
|
|
128
|
-
return fragments.every((fragment) => INFORMAL_SHORT_FRAGMENT.test(fragment));
|
|
129
|
-
}
|
|
130
|
-
function hasDirectPrecedingAnchor(block, scanned) {
|
|
131
|
-
return scanned.anchors.some((anchor) => (anchor.end < block.start
|
|
132
|
-
&& !scanned.lines.some((line) => line.start > anchor.end && line.start < block.start && line.text.trim())));
|
|
133
|
-
}
|
|
134
112
|
function inspectDecisionDetailDuplicates(content) {
|
|
135
113
|
const blocks = parseDecisionDetailBlocks(content);
|
|
136
|
-
const scanned = scanVisibleMarkdown(content);
|
|
137
114
|
const grouped = new Map();
|
|
138
115
|
for (const block of blocks) {
|
|
139
116
|
const key = block.id.toUpperCase();
|
|
@@ -145,15 +122,13 @@ function inspectDecisionDetailDuplicates(content) {
|
|
|
145
122
|
for (const group of grouped.values()) {
|
|
146
123
|
if (group.length < 2)
|
|
147
124
|
continue;
|
|
148
|
-
|
|
149
|
-
const repairable = canonical.length === 1 && group.every((block) => (block.canonical || (!hasDirectPrecedingAnchor(block, scanned) && isInformalShortBlock(block))));
|
|
150
|
-
duplicates.push({ id: canonical[0]?.id ?? group[0].id, blocks: group, repairable });
|
|
125
|
+
duplicates.push({ id: group[0].id, blocks: group });
|
|
151
126
|
}
|
|
152
127
|
if (duplicates.length === 0)
|
|
153
128
|
return { ok: true, blocks };
|
|
154
129
|
const summary = duplicates.map((duplicate) => {
|
|
155
130
|
const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
|
|
156
|
-
return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types}
|
|
131
|
+
return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types})`;
|
|
157
132
|
}).join(', ');
|
|
158
133
|
return {
|
|
159
134
|
ok: false,
|
|
@@ -163,26 +138,6 @@ function inspectDecisionDetailDuplicates(content) {
|
|
|
163
138
|
duplicates
|
|
164
139
|
};
|
|
165
140
|
}
|
|
166
|
-
function repairDecisionDetailDuplicates(content) {
|
|
167
|
-
const inspection = inspectDecisionDetailDuplicates(content);
|
|
168
|
-
if (inspection.ok)
|
|
169
|
-
return { ok: true, changed: false, content };
|
|
170
|
-
const unsafe = inspection.duplicates.filter((duplicate) => !duplicate.repairable);
|
|
171
|
-
if (unsafe.length > 0) {
|
|
172
|
-
return {
|
|
173
|
-
ok: false,
|
|
174
|
-
code: 'DECISION_DETAIL_AMBIGUOUS',
|
|
175
|
-
message: `Cannot safely repair duplicate decision detail ids: ${unsafe.map((duplicate) => duplicate.id).join(', ')}`,
|
|
176
|
-
content
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
const removals = inspection.duplicates.flatMap((duplicate) => duplicate.blocks.filter((block) => !block.canonical));
|
|
180
|
-
let repaired = content;
|
|
181
|
-
for (const block of removals.sort((left, right) => right.start - left.start)) {
|
|
182
|
-
repaired = repaired.slice(0, block.start) + repaired.slice(block.end);
|
|
183
|
-
}
|
|
184
|
-
return { ok: true, changed: repaired !== content, content: repaired };
|
|
185
|
-
}
|
|
186
141
|
const FIELD_KEYS = {
|
|
187
142
|
'为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
|
|
188
143
|
'要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
|
|
@@ -259,5 +214,5 @@ function normalizeDecisionDetailForDisplay(block) {
|
|
|
259
214
|
ordered.push(byKey.get('implementation'));
|
|
260
215
|
return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
|
|
261
216
|
}
|
|
262
|
-
export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence,
|
|
217
|
+
export { inspectDecisionDetailDuplicates, normalizeDecisionDetailForDisplay, parseDecisionDetailBlocks, parseDecisionEvidence, resolveDecisionDetail };
|
|
263
218
|
//# sourceMappingURL=decision-details.js.map
|