@fitlab-ai/agent-infra 0.11.3 → 0.11.4

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.
Files changed (102) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -1
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-finalization.js +41 -1
  7. package/dist/lib/internal/task-ledger.js +13 -5
  8. package/dist/lib/internal/task-operation-registry.js +1 -0
  9. package/dist/lib/platform/completion-backfill.js +63 -17
  10. package/dist/lib/sandbox/control/server.js +26 -143
  11. package/dist/lib/sandbox/task-cleanup.js +13 -20
  12. package/dist/lib/task/artifact-command.js +11 -4
  13. package/dist/lib/task/artifact-lifecycle.js +217 -85
  14. package/dist/lib/task/artifact-operations.js +1 -26
  15. package/dist/lib/task/artifact-receipts.js +4 -1
  16. package/dist/lib/task/artifact-schema.js +1 -0
  17. package/dist/lib/task/artifact-selection.js +25 -0
  18. package/dist/lib/task/capabilities.js +158 -39
  19. package/dist/lib/task/commands/status.js +9 -2
  20. package/dist/lib/task/control-authority.js +3 -2
  21. package/dist/lib/task/decision-intents.js +12 -0
  22. package/dist/lib/task/delivery.js +1 -43
  23. package/dist/lib/task/events.js +80 -64
  24. package/dist/lib/task/finalization.js +301 -143
  25. package/dist/lib/task/ledger-intents.js +157 -15
  26. package/dist/lib/task/lifecycle-path.js +75 -0
  27. package/dist/lib/task/lifecycle.js +10 -0
  28. package/dist/lib/task/local-artifact-finalization.js +1 -20
  29. package/dist/lib/task/orchestration.js +17 -7
  30. package/dist/lib/task/review-finalization.js +3 -16
  31. package/dist/lib/task/rework-intent.js +70 -4
  32. package/dist/lib/task/verification-engine.js +20 -30
  33. package/dist/lib/task/verification.js +1 -0
  34. package/dist/lib/task/workflow-warning-intents.js +21 -6
  35. package/dist/lib/task/workspace-snapshot.js +7 -2
  36. package/dist/lifecycle-build-manifest.json +3 -3
  37. package/lib/internal/cli-route-inventory.ts +1 -1
  38. package/lib/internal/task-artifact.ts +1 -1
  39. package/lib/internal/task-finalization.ts +40 -1
  40. package/lib/internal/task-ledger.ts +12 -5
  41. package/lib/internal/task-operation-registry.ts +1 -0
  42. package/lib/platform/completion-backfill.ts +78 -16
  43. package/lib/sandbox/control/server.ts +21 -146
  44. package/lib/sandbox/task-cleanup.ts +12 -20
  45. package/lib/task/artifact-command.ts +14 -4
  46. package/lib/task/artifact-lifecycle.ts +236 -85
  47. package/lib/task/artifact-operations.ts +1 -26
  48. package/lib/task/artifact-receipts.ts +4 -1
  49. package/lib/task/artifact-schema.ts +1 -0
  50. package/lib/task/artifact-selection.ts +53 -0
  51. package/lib/task/capabilities.ts +154 -40
  52. package/lib/task/commands/status.ts +11 -2
  53. package/lib/task/control-authority.ts +3 -1
  54. package/lib/task/decision-intents.ts +9 -0
  55. package/lib/task/delivery.ts +1 -40
  56. package/lib/task/events.ts +82 -64
  57. package/lib/task/finalization.ts +269 -111
  58. package/lib/task/ledger-intents.ts +155 -15
  59. package/lib/task/lifecycle-path.ts +98 -0
  60. package/lib/task/lifecycle.ts +13 -0
  61. package/lib/task/local-artifact-finalization.ts +2 -25
  62. package/lib/task/orchestration.ts +26 -8
  63. package/lib/task/review-finalization.ts +3 -16
  64. package/lib/task/rework-intent.ts +75 -5
  65. package/lib/task/verification-engine.ts +19 -30
  66. package/lib/task/verification.ts +2 -1
  67. package/lib/task/workflow-warning-intents.ts +18 -6
  68. package/lib/task/workspace-snapshot.ts +3 -2
  69. package/package.json +1 -1
  70. package/templates/.agents/rules/review-handshake.en.md +4 -4
  71. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  72. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  73. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  74. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  75. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  76. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  77. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  78. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  79. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  82. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  85. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  86. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  87. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  88. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  89. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  90. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  91. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  92. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  96. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  97. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  98. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  99. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  100. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  101. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  102. package/lib/sandbox/control/finalization-response.ts +0 -35
@@ -7,7 +7,10 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SE
7
7
  import { resolveTaskRef } from './resolve-ref.js';
8
8
  import { writeTask } from './write.js';
9
9
  import { allowsManualOverride } from './guard-override.js';
10
- import { parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.js';
10
+ import { parseLegacyReworkIntentDocument, parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } from './rework-intent.js';
11
+ import { extractSection, extractSubSection } from './sections.js';
12
+ import { parseLifecyclePathDecision } from './lifecycle-path.js';
13
+ import { scanVisibleMarkdown } from './markdown.js';
11
14
  const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
12
15
  const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
13
16
  const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
@@ -23,15 +26,50 @@ function ledgerReadErrorCode(error) {
23
26
  return code;
24
27
  return code === 'TABLE_DUPLICATE_KEY' ? 'LEDGER_DUPLICATE_ID' : 'LEDGER_DOCUMENT_INVALID';
25
28
  }
26
- function maxHandshakeRounds(repoRoot) {
27
- try {
28
- const parsed = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
29
- const value = parsed.review?.maxHandshakeRounds;
30
- return Number.isInteger(value) && Number(value) > 0 ? Number(value) : 3;
31
- }
32
- catch {
33
- return 3;
29
+ const CLASSIFICATION_TARGET = {
30
+ 'scope-or-requirement': 'analysis', design: 'plan', implementation: 'code',
31
+ 'human-decision': 'pause', 'insufficient-evidence': 'pause'
32
+ };
33
+ function semanticDigest(value) {
34
+ return createHash('sha256').update(value.normalize('NFKC').replace(/\s+/g, ' ').trim()).digest('hex');
35
+ }
36
+ function normalizedTaskFact(value) {
37
+ return value.normalize('NFKC').replace(/\s+/g, ' ').trim();
38
+ }
39
+ function semanticFindingEvidence(reviewContent, heading, end) {
40
+ const title = heading.text.replace(/^\d+[.、::]\s*/, '').trim();
41
+ return `${title}\n${reviewContent.slice(heading.end, end)}`;
42
+ }
43
+ function findingEvidence(reviewContent, evidence) {
44
+ const anchor = evidence.split('#')[1];
45
+ if (!anchor)
46
+ return null;
47
+ const markdown = scanVisibleMarkdown(reviewContent);
48
+ const explicit = markdown.anchors.find((candidate) => candidate.id === anchor);
49
+ if (explicit) {
50
+ const heading = markdown.headings.find((candidate) => candidate.start > explicit.start);
51
+ const next = heading && markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
52
+ const end = next?.start ?? reviewContent.length;
53
+ return heading ? semanticFindingEvidence(reviewContent, heading, end) : reviewContent.slice(explicit.end, end);
34
54
  }
55
+ const heading = markdown.headings.find((candidate) => {
56
+ const escapedAnchor = anchor.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
+ return candidate.text === anchor || new RegExp(`^${escapedAnchor}(?:\\s|[.、::])`).test(candidate.text);
58
+ });
59
+ if (!heading)
60
+ return null;
61
+ const next = markdown.headings.find((candidate) => candidate.start > heading.start && candidate.level <= heading.level);
62
+ return semanticFindingEvidence(reviewContent, heading, next?.start ?? reviewContent.length);
63
+ }
64
+ function taskFactDigest(taskDir, content) {
65
+ const analysisNames = fs.readdirSync(taskDir).filter((name) => /^analysis(?:-r\d+)?\.md$/.test(name));
66
+ const latest = analysisNames.map((name) => ({ name, round: parseArtifactName(name)?.round ?? 0 })).sort((a, b) => b.round - a.round)[0];
67
+ const flow = latest ? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, latest.name), 'utf8')) : null;
68
+ return semanticDigest(JSON.stringify({
69
+ taskInput: normalizedTaskFact(extractSection(content, ['任务输入', 'Task Input'])),
70
+ requirements: normalizedTaskFact(extractSection(content, ['需求', 'Requirements'])),
71
+ flow: flow?.status === 'valid' ? flow.decision.semanticDigest : flow?.status ?? 'missing'
72
+ }));
35
73
  }
36
74
  function nextFindingId(rows, stage) {
37
75
  const prefix = PREFIX[stage];
@@ -89,12 +127,15 @@ function applyLedgerIntent(intent, options = {}) {
89
127
  return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: nextHdId(rows), before: null, after: null, operations: [], error: null };
90
128
  }
91
129
  if (intent.kind === 'rework-intent-upsert') {
92
- if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !['analysis', 'plan', 'code'].includes(intent.target) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
93
- return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, target, or source hash is invalid', resolved.taskId, intent.intentId);
130
+ if (!/^RI-[1-9]\d*$/.test(intent.intentId) || !/^(AN|PL|CD)-[1-9]\d*$/.test(intent.findingId) || !(intent.classification in CLASSIFICATION_TARGET) || !/^[a-f0-9]{64}$/.test(intent.sourceSha256)) {
131
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rework intent identity, classification, or source hash is invalid', resolved.taskId, intent.intentId);
94
132
  }
95
133
  const finding = rows.find((row) => row.id === intent.findingId);
96
134
  if (!finding || finding.severity === 'decision')
97
135
  return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
136
+ if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
137
+ return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId, intent.findingId);
138
+ }
98
139
  const sourceFromEvidence = finding.evidence.split('#')[0];
99
140
  if (sourceFromEvidence !== intent.sourceArtifact || parseArtifactName(intent.sourceArtifact)?.family !== `review-${finding.stage}`) {
100
141
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
@@ -106,12 +147,35 @@ function applyLedgerIntent(intent, options = {}) {
106
147
  const parsed = parseReworkIntentDocument(content);
107
148
  if (!parsed.ok)
108
149
  return failed(intent, parsed.code, parsed.message, resolved.taskId, intent.intentId);
109
- const nextIntent = {
150
+ const reviewContent = fs.readFileSync(path.join(resolved.taskDir, intent.sourceArtifact), 'utf8');
151
+ const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
152
+ if (!evidenceBlock)
153
+ return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId, intent.intentId);
154
+ const evidenceDigest = semanticDigest(evidenceBlock);
155
+ const factsDigest = taskFactDigest(resolved.taskDir, content);
156
+ const requestedIntent = {
110
157
  intentId: intent.intentId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact,
111
- sourceSha256: intent.sourceSha256, target: intent.target, status: 'pending',
158
+ sourceSha256: intent.sourceSha256, target: CLASSIFICATION_TARGET[intent.classification], classification: intent.classification,
159
+ evidenceDigest, taskFactDigest: factsDigest, status: 'pending',
112
160
  declaredAt: new Date().toISOString(), consumedAt: ''
113
161
  };
114
- const next = upsertReworkIntent(parsed.intents, nextIntent);
162
+ if (parsed.intents.some((candidate) => candidate.intentId === intent.intentId)) {
163
+ const replay = upsertReworkIntent(parsed.intents, requestedIntent);
164
+ if (!replay.changed) {
165
+ return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
166
+ }
167
+ }
168
+ const same = parsed.intents.find((candidate) => candidate.findingId === intent.findingId
169
+ && candidate.evidenceDigest === evidenceDigest && candidate.taskFactDigest === factsDigest
170
+ && candidate.classification === intent.classification && candidate.target === CLASSIFICATION_TARGET[intent.classification]);
171
+ const classification = same ? 'insufficient-evidence' : intent.classification;
172
+ const nextIntent = {
173
+ ...requestedIntent, target: CLASSIFICATION_TARGET[classification], classification
174
+ };
175
+ const now = nextIntent.declaredAt;
176
+ const superseded = parsed.intents.map((candidate) => candidate.status === 'pending'
177
+ ? { ...candidate, status: 'superseded', consumedAt: now } : candidate);
178
+ const next = upsertReworkIntent(superseded, nextIntent);
115
179
  if (!next.changed)
116
180
  return { status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: intent.intentId, before: null, after: null, operations: [], error: null };
117
181
  const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next.intents)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
@@ -123,6 +187,84 @@ function applyLedgerIntent(intent, options = {}) {
123
187
  return failed(intent, 'LEDGER_IDENTITY_CONFLICT', error instanceof Error ? error.message : String(error), resolved.taskId, intent.intentId);
124
188
  }
125
189
  }
190
+ if (intent.kind === 'rework-intent-rebuild') {
191
+ const beforeSha256 = createHash('sha256').update(content).digest('hex');
192
+ const supplied = [intent.findingId, intent.sourceArtifact, intent.sourceSha256, intent.classification].filter((value) => value !== undefined).length;
193
+ if (supplied !== 0 && supplied !== 4)
194
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild binding options must be provided together', resolved.taskId);
195
+ const current = parseReworkIntentDocument(content);
196
+ if (current.ok && current.present) {
197
+ if (supplied !== 0)
198
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'current rework intent format does not accept rebuild binding options', resolved.taskId);
199
+ return {
200
+ status: 'no-op', changed: false, intent: intent.kind, taskId: resolved.taskId, entityId: null,
201
+ before: null, after: null, operations: [], error: null,
202
+ rebuild: { mode: 'format-only', beforeSha256, afterSha256: beforeSha256, converted: current.intents.length, newIntentId: null }
203
+ };
204
+ }
205
+ const legacy = parseLegacyReworkIntentDocument(content);
206
+ if (!legacy.ok)
207
+ return failed(intent, legacy.code, legacy.message, resolved.taskId);
208
+ const pending = legacy.intents.filter((row) => row.status === 'pending');
209
+ if (pending.length === 0 && supplied !== 0)
210
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'format-only rebuild does not accept finding or classification options', resolved.taskId);
211
+ if (pending.length > 0 && supplied !== 4)
212
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'legacy pending intents require finding, review, hash, and classification', resolved.taskId);
213
+ const now = new Date().toISOString();
214
+ let next = legacy.intents.map((row) => row.status === 'pending' ? { ...row, status: 'superseded', consumedAt: now } : row);
215
+ let entityId = null;
216
+ if (pending.length > 0) {
217
+ const finding = rows.find((row) => row.id === intent.findingId);
218
+ if (!finding || finding.severity === 'decision' || finding.evidence.split('#')[0] !== intent.sourceArtifact)
219
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild binding does not match a review finding', resolved.taskId);
220
+ if (intent.classification === 'human-decision' && finding.status !== 'needs-human-decision') {
221
+ return failed(intent, 'LEDGER_TRANSITION_INVALID', 'human-decision classification requires a needs-human-decision finding', resolved.taskId);
222
+ }
223
+ const sourceIdentity = parseArtifactName(intent.sourceArtifact);
224
+ const expectedFamily = `review-${finding.stage}`;
225
+ const latestSource = fs.readdirSync(resolved.taskDir)
226
+ .map((name) => parseArtifactName(name))
227
+ .filter((identity) => identity?.family === expectedFamily)
228
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0];
229
+ if (!sourceIdentity || sourceIdentity.family !== expectedFamily || latestSource?.name !== sourceIdentity.name) {
230
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact is not the latest review for the finding stage', resolved.taskId);
231
+ }
232
+ const sourcePath = path.join(resolved.taskDir, intent.sourceArtifact);
233
+ const actualHash = createHash('sha256').update(fs.readFileSync(sourcePath)).digest('hex');
234
+ if (actualHash !== intent.sourceSha256)
235
+ return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rebuild source artifact hash does not match', resolved.taskId);
236
+ const max = next.reduce((value, row) => Math.max(value, Number(row.intentId.slice(3))), 0);
237
+ entityId = `RI-${max + 1}`;
238
+ const reviewContent = fs.readFileSync(sourcePath, 'utf8');
239
+ const classification = intent.classification;
240
+ if (!(classification in CLASSIFICATION_TARGET))
241
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'rebuild classification is invalid', resolved.taskId);
242
+ const evidenceBlock = findingEvidence(reviewContent, finding.evidence);
243
+ if (!evidenceBlock)
244
+ return failed(intent, 'LEDGER_EVIDENCE_INVALID', `finding evidence anchor '${finding.evidence}' was not found`, resolved.taskId);
245
+ next = [...next, {
246
+ intentId: entityId, findingId: intent.findingId, sourceArtifact: intent.sourceArtifact, sourceSha256: intent.sourceSha256,
247
+ target: CLASSIFICATION_TARGET[classification], classification,
248
+ evidenceDigest: semanticDigest(evidenceBlock),
249
+ taskFactDigest: taskFactDigest(resolved.taskDir, content), status: 'pending', declaredAt: now, consumedAt: ''
250
+ }];
251
+ }
252
+ const result = writeTask({ taskRef: intent.taskRef, expectedState: 'active', dryRun: intent.dryRun, mutations: [reworkIntentMutation(content, next)] }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
253
+ if (result.status === 'failed')
254
+ return failed(intent, result.error.code, result.error.message, result.taskId, entityId);
255
+ const afterContent = result.status === 'applied' || result.status === 'no-op' ? fs.readFileSync(resolved.taskMdPath, 'utf8') : content;
256
+ return {
257
+ status: result.status, changed: result.changed, intent: intent.kind, taskId: result.taskId, entityId,
258
+ before: null, after: null, operations: result.operations, error: null,
259
+ rebuild: {
260
+ mode: pending.length > 0 ? 'pending-reclassification' : 'format-only',
261
+ beforeSha256,
262
+ afterSha256: createHash('sha256').update(afterContent).digest('hex'),
263
+ converted: legacy.intents.length,
264
+ newIntentId: entityId
265
+ }
266
+ };
267
+ }
126
268
  let before = null;
127
269
  let after;
128
270
  if (intent.kind === 'finding-upsert') {
@@ -199,7 +341,7 @@ function applyLedgerIntent(intent, options = {}) {
199
341
  ? new Set(['confirmed', 'open', 'needs-human-decision'])
200
342
  : before.status === 'cannot-judge' ? new Set(['open', 'needs-human-decision']) : new Set();
201
343
  const sameRoundMinorClose = before.status === 'open' && before.severity === 'minor' && intent.status === 'closed';
202
- if (((!sameRoundMinorClose && !allowed.has(intent.status)) || (intent.status === 'open' && Number(before.round) >= maxHandshakeRounds(resolved.repoRoot)))
344
+ if ((!sameRoundMinorClose && !allowed.has(intent.status))
203
345
  && !allowsManualOverride(options.manualOverride, 'ledger-intent', 'LEDGER_TRANSITION_INVALID')) {
204
346
  return failed(intent, 'LEDGER_TRANSITION_INVALID', `finding '${intent.id}' cannot transition from ${before.status} to ${intent.status}`, resolved.taskId, intent.id);
205
347
  }
@@ -0,0 +1,75 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { extractSection } from './sections.js';
3
+ const PATH_STAGES = {
4
+ streamlined: ['analysis', 'code', 'review-code'],
5
+ standard: ['analysis', 'plan', 'code', 'review-code'],
6
+ full: ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']
7
+ };
8
+ const FIELD_ALIASES = {
9
+ path: ['本任务路径', 'Path'],
10
+ basis: ['判定依据', 'Basis'],
11
+ unmetHigherConditions: ['未满足的更高路径条件', '未满足的较低路径条件', 'Unmet Higher-path Conditions'],
12
+ upgradeTriggers: ['升级触发条件', 'Upgrade Triggers']
13
+ };
14
+ function normalizeText(value) {
15
+ return value.normalize('NFKC').replace(/\s+/g, ' ').trim().replace(/[。.]$/, '');
16
+ }
17
+ function digest(value) {
18
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
19
+ }
20
+ function pathToken(value) {
21
+ const normalized = normalizeText(value).toLowerCase().replace(/[`*_]/g, '');
22
+ if (/^(?:streamlined|精简路径|精简)$/.test(normalized))
23
+ return 'streamlined';
24
+ if (/^(?:standard|标准路径|标准)$/.test(normalized))
25
+ return 'standard';
26
+ if (/^(?:full|完整路径|完整)$/.test(normalized))
27
+ return 'full';
28
+ return null;
29
+ }
30
+ function fieldValues(body, aliases) {
31
+ const wanted = new Set(aliases);
32
+ const values = [];
33
+ for (const line of body.split(/\r?\n/)) {
34
+ const match = /^\s*[-*]\s+\*\*([^*]+)\*\*[::]\s*(.*?)\s*$/.exec(line);
35
+ if (match && wanted.has(match[1].trim()))
36
+ values.push(match[2].trim());
37
+ }
38
+ return values;
39
+ }
40
+ function parseLifecyclePathDecision(analysisContent, sourceArtifact = '', sourceSha256 = '') {
41
+ const body = extractSection(analysisContent, ['流程裁定', 'Flow Decision']);
42
+ if (!body)
43
+ return { status: 'missing', decision: null, message: 'analysis flow decision section is missing' };
44
+ const fields = Object.fromEntries(Object.entries(FIELD_ALIASES).map(([key, aliases]) => [key, fieldValues(body, aliases)]));
45
+ const invalid = Object.entries(fields).find(([, values]) => values.length !== 1);
46
+ if (invalid)
47
+ return { status: 'invalid', decision: null, message: `flow decision field '${invalid[0]}' must appear exactly once` };
48
+ const path = pathToken(fields.path[0]);
49
+ if (!path)
50
+ return { status: 'invalid', decision: null, message: `unknown lifecycle path '${fields.path[0]}'` };
51
+ const basis = normalizeText(fields.basis[0]);
52
+ const unmetHigherConditions = normalizeText(fields.unmetHigherConditions[0]);
53
+ const upgradeTriggers = normalizeText(fields.upgradeTriggers[0]);
54
+ if (!basis || !unmetHigherConditions || !upgradeTriggers)
55
+ return { status: 'invalid', decision: null, message: 'flow decision fields must be non-empty' };
56
+ return {
57
+ status: 'valid',
58
+ message: null,
59
+ decision: {
60
+ path,
61
+ stages: PATH_STAGES[path],
62
+ basis,
63
+ unmetHigherConditions,
64
+ upgradeTriggers,
65
+ semanticDigest: digest({ path, basis, unmetHigherConditions, upgradeTriggers }),
66
+ sourceArtifact,
67
+ sourceSha256
68
+ }
69
+ };
70
+ }
71
+ function pathIncludes(state, stage) {
72
+ return state?.status === 'valid' && state.decision.stages.includes(stage);
73
+ }
74
+ export { PATH_STAGES, parseLifecyclePathDecision, pathIncludes };
75
+ //# sourceMappingURL=lifecycle-path.js.map
@@ -578,6 +578,16 @@ function applyTaskLifecycle(requestInput, options = {}) {
578
578
  return failed(request, { code: 'LIFECYCLE_JOURNAL_WRITE_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath, task: { operations: taskOperations }, changed: true, completedSteps: [...completed], timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion });
579
579
  }
580
580
  }
581
+ if (options.prepareOnly && !completed.has('directory-moved')) {
582
+ return {
583
+ ...failed(request, { code: '', message: '' }), status: 'applied', changed: true, error: null,
584
+ taskId, sourceState, targetState: spec.target, sourcePath, targetPath,
585
+ task: { operations: taskOperations }, directory: { effect: 'unchanged', changed: false },
586
+ shortId: { effect: 'unchanged', shortId: null, changed: false },
587
+ timestamp: journal.metadata.timestamp, agentInfraVersion: journal.metadata.agentInfraVersion,
588
+ journalPath, completedSteps: [...completed], pendingSteps: STEPS.filter((step) => !completed.has(step))
589
+ };
590
+ }
581
591
  if (fs.existsSync(targetPath) && !fs.existsSync(sourcePath))
582
592
  completed.add('directory-moved');
583
593
  if (!completed.has('directory-moved')) {
@@ -3,9 +3,7 @@ import path from 'node:path';
3
3
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
4
4
  import { scanVisibleMarkdown } from './markdown.js';
5
5
  import { parseArtifactName } from './artifact-name.js';
6
- import { hasOpenArtifactRound, } from './artifact-lifecycle.js';
7
6
  import { resolveTaskRef } from './resolve-ref.js';
8
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
9
7
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
10
8
  import { getArtifactSchema } from './artifact-schema.js';
11
9
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
@@ -57,21 +55,6 @@ function validateLocalArtifact(content, options) {
57
55
  const decisionDetails = inspectDecisionDetailDuplicates(content);
58
56
  if (!decisionDetails.ok)
59
57
  diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
60
- if (options.taskContent !== undefined) {
61
- const identity = options.artifact ? parseArtifactName(options.artifact) : null;
62
- const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
63
- ? expectedQualificationRelations(options.taskContent, options.family)
64
- : undefined;
65
- if (expected && !expected.ok)
66
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
67
- const qualification = validateQualificationAudit(options.taskContent, content, {
68
- family: options.family,
69
- artifact: options.artifact,
70
- expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
71
- });
72
- if (!qualification.ok)
73
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
74
- }
75
58
  return {
76
59
  ok: diagnostics.length === 0,
77
60
  family: options.family,
@@ -98,10 +81,8 @@ function prepareLocalArtifact(request, candidate) {
98
81
  const parsed = parseArtifactName(request.artifact);
99
82
  if (!parsed || parsed.family !== request.family)
100
83
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
101
- let taskContent;
102
84
  let content;
103
85
  try {
104
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
105
86
  const artifactPath = path.join(resolved.taskDir, request.artifact);
106
87
  if (!fs.statSync(artifactPath).isFile())
107
88
  return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
@@ -114,7 +95,7 @@ function prepareLocalArtifact(request, candidate) {
114
95
  return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
115
96
  }
116
97
  const validation = validateLocalArtifact(content, {
117
- family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
98
+ family: request.family, requiredSections: request.requiredSections
118
99
  });
119
100
  const artifactSha256 = sha256Content(content);
120
101
  const result = failedFinalization(request, {
@@ -22,6 +22,7 @@ import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js'
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
23
  import { reconcileTaskInvalidation } from './invalidation-command.js';
24
24
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
25
+ import { resolveArtifactContext } from './artifact-lifecycle.js';
25
26
  function supportsLifecycleDelegation(client) {
26
27
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
27
28
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -479,17 +480,13 @@ function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead)
479
480
  function routeFromFacts(facts) {
480
481
  const recommendation = recommendNext(facts);
481
482
  if (!recommendation.action)
482
- return { completion: true };
483
+ return recommendation.reasonCode === 'LIFECYCLE_REVIEWED' ? { completion: true } : null;
483
484
  const action = recommendation.action;
484
485
  if (action === 'manual-validation' || action === 'validation-run')
485
486
  return null;
486
- const family = action === 'analysis' ? 'analysis' : action;
487
- const round = maxArtifactRound([
488
- ...(facts.artifacts[family] ?? []), ...(facts.staleArtifacts?.[family] ?? [])
489
- ], family) + 1;
490
487
  const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
491
488
  const role = action.startsWith('review-') ? 'reviewer' : 'executor';
492
- return { action: command, role, stage: action, round, artifact: artifactName(family, round) };
489
+ return { action: command, role, stage: action };
493
490
  }
494
491
  function routeOrchestration(taskRef, options = {}) {
495
492
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
@@ -619,8 +616,19 @@ function routeOrchestration(taskRef, options = {}) {
619
616
  return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
620
617
  }
621
618
  const action = routed.stage;
619
+ const family = action === 'analysis' ? 'analysis' : action;
620
+ const artifactContext = resolveArtifactContext(taskRef, family, {
621
+ repoRoot: resolved.repoRoot
622
+ });
623
+ if (artifactContext.status !== 'ready' || !artifactContext.selection) {
624
+ return failed(artifactContext.error?.code ?? 'ORCHESTRATION_ARTIFACT_SELECTION_FAILED', artifactContext.error?.message ?? 'cannot determine artifact selection', resolved.taskId);
625
+ }
626
+ if (artifactContext.selection.disposition === 'reuse') {
627
+ return failed('ORCHESTRATION_REUSE_STALLED', `lifecycle still routes to ${action} after reusing ${artifactContext.selection.artifact.name}`, resolved.taskId);
628
+ }
629
+ const selected = artifactContext.selection.artifact;
622
630
  const capability = canStart(action, facts.facts, {
623
- initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${routed.round}`,
631
+ initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${selected.round}`,
624
632
  requestedAction: action, reasonCode: 'user-request', explicitRequest: true
625
633
  });
626
634
  if (!capability.allowed)
@@ -628,6 +636,8 @@ function routeOrchestration(taskRef, options = {}) {
628
636
  const policy = run ? rolePolicy(run, routed.role) : null;
629
637
  const next = {
630
638
  ...routed,
639
+ round: selected.round,
640
+ artifact: selected.name,
631
641
  requestedModel: policy?.model ?? null,
632
642
  requestedReasoningEffort: policy?.reasoningEffort ?? null
633
643
  };
@@ -12,7 +12,6 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
12
12
  import { allowsManualOverride } from './guard-override.js';
13
13
  import { getArtifactSchema } from './artifact-schema.js';
14
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
- import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
16
15
  const STAGES = {
17
16
  analysis: { family: 'review-analysis' },
18
17
  plan: { family: 'review-plan' },
@@ -66,10 +65,8 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
66
65
  const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
67
66
  if (publicationError)
68
67
  return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
69
- let taskContent;
70
68
  let content;
71
69
  try {
72
- taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
73
70
  const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
74
71
  if (!validated.ok)
75
72
  return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
@@ -89,22 +86,12 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
89
86
  const semanticDigest = canonicalSemanticDigest(content);
90
87
  const detail = inspectDecisionDetailDuplicates(content);
91
88
  const structure = inspectArtifactContract(content, getArtifactSchema(spec.family));
92
- const expected = expectedQualificationRelations(taskContent, spec.family);
93
- if (!expected.ok)
94
- return preflightFailed(request, 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', `${expected.code}: ${expected.message}`, resolved.taskId, { artifactSha256, semanticDigest });
95
- const qualification = validateQualificationAudit(taskContent, content, {
96
- family: spec.family,
97
- artifact: request.artifact,
98
- expectedUpstreamRelations: expected.relations
99
- });
100
- const qualificationError = qualification.ok ? null : qualification;
101
- if (!detail.ok || !structure.ok || qualificationError) {
89
+ if (!detail.ok || !structure.ok) {
102
90
  const diagnostics = [
103
91
  ...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
104
- ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
105
- ...(qualificationError ? [`${qualificationError.code}: ${qualificationError.message}`] : [])
92
+ ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`)
106
93
  ].join('; ');
107
- return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
94
+ return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : 'REVIEW_ARTIFACT_STRUCTURE_INVALID', diagnostics, resolved.taskId, {
108
95
  artifactSha256, semanticDigest,
109
96
  });
110
97
  }
@@ -1,5 +1,9 @@
1
1
  const REWORK_INTENT_HEADINGS = ['返工意图', 'Rework Intent'];
2
2
  const REWORK_INTENT_COLUMNS = [
3
+ 'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
+ 'classification', 'evidence_digest', 'task_fact_digest', 'status', 'declared_at', 'consumed_at'
5
+ ];
6
+ const LEGACY_REWORK_INTENT_COLUMNS = [
3
7
  'intent_id', 'finding_id', 'source_artifact', 'source_sha256', 'target',
4
8
  'status', 'declared_at', 'consumed_at'
5
9
  ];
@@ -55,12 +59,16 @@ function parseReworkIntentDocument(content) {
55
59
  throw new Error(`invalid intent id '${row.intent_id}'`);
56
60
  if (!/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
57
61
  throw new Error(`invalid finding id '${row.finding_id}'`);
58
- if (!['analysis', 'plan', 'code'].includes(row.target))
62
+ if (!['analysis', 'plan', 'code', 'pause'].includes(row.target))
59
63
  throw new Error(`invalid rework target '${row.target}'`);
64
+ if (!['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence'].includes(row.classification))
65
+ throw new Error(`invalid rework classification '${row.classification}'`);
60
66
  if (!['pending', 'consumed', 'superseded'].includes(row.status))
61
67
  throw new Error(`invalid rework status '${row.status}'`);
62
68
  if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
63
69
  throw new Error(`invalid source hash for '${row.intent_id}'`);
70
+ if (!/^[a-f0-9]{64}$/.test(row.evidence_digest) || !/^[a-f0-9]{64}$/.test(row.task_fact_digest))
71
+ throw new Error(`invalid semantic digest for '${row.intent_id}'`);
64
72
  if (!row.source_artifact || !row.declared_at)
65
73
  throw new Error(`rework intent '${row.intent_id}' has invalid required fields`);
66
74
  const terminal = row.status === 'consumed' || row.status === 'superseded';
@@ -69,6 +77,8 @@ function parseReworkIntentDocument(content) {
69
77
  return {
70
78
  intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact,
71
79
  sourceSha256: row.source_sha256, target: row.target,
80
+ classification: row.classification,
81
+ evidenceDigest: row.evidence_digest, taskFactDigest: row.task_fact_digest,
72
82
  status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
73
83
  };
74
84
  });
@@ -91,7 +101,10 @@ function renderReworkIntents(intents) {
91
101
  `| ${REWORK_INTENT_COLUMNS.map(() => '---').join(' | ')} |`,
92
102
  ...intents.map((intent) => `| ${[
93
103
  intent.intentId, intent.findingId, intent.sourceArtifact, intent.sourceSha256,
94
- intent.target, intent.status, intent.declaredAt, intent.consumedAt
104
+ intent.target, intent.classification ?? (intent.target === 'analysis' ? 'scope-or-requirement' : intent.target === 'plan' ? 'design' : intent.target === 'code' ? 'implementation' : 'insufficient-evidence'),
105
+ intent.evidenceDigest ?? requireDigest(`${intent.findingId}|${intent.sourceArtifact}|${intent.sourceSha256}`),
106
+ intent.taskFactDigest ?? requireDigest(`current|${intent.findingId}|${intent.target}`),
107
+ intent.status, intent.declaredAt, intent.consumedAt
95
108
  ].map(escape).join(' | ')} |`)
96
109
  ].join('\n');
97
110
  }
@@ -99,11 +112,63 @@ function upsertReworkIntent(intents, intent) {
99
112
  const existing = intents.find((candidate) => candidate.intentId === intent.intentId);
100
113
  if (!existing)
101
114
  return { changed: true, intents: [...intents, intent] };
102
- const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target];
115
+ const identity = (value) => [value.intentId, value.findingId, value.sourceArtifact, value.sourceSha256, value.target, value.classification, value.evidenceDigest, value.taskFactDigest];
103
116
  if (JSON.stringify(identity(existing)) !== JSON.stringify(identity(intent)))
104
117
  throw new Error(`intent '${intent.intentId}' identity conflicts with existing state`);
105
118
  return { changed: false, intents };
106
119
  }
120
+ function consumeHumanDecisionReworkIntents(intents, findingId, consumedAt) {
121
+ let changed = false;
122
+ const next = intents.map((intent) => {
123
+ if (intent.status !== 'pending' || intent.target !== 'pause' || intent.classification !== 'human-decision' || intent.findingId !== findingId)
124
+ return intent;
125
+ changed = true;
126
+ return { ...intent, status: 'consumed', consumedAt };
127
+ });
128
+ return { changed, intents: next };
129
+ }
130
+ function parseLegacyReworkIntentDocument(content) {
131
+ // TODO(compat): Remove this explicit decoder once no task contains the eight-column rework-intent header.
132
+ const body = sectionBody(content);
133
+ if (body === null)
134
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'legacy rework intent section is missing' };
135
+ const lines = body.split(/\r?\n/).filter((line) => line.trim());
136
+ const header = lines[0] ? cells(lines[0]) : null;
137
+ const separator = lines[1] ? cells(lines[1]) : null;
138
+ if (!header || !separator || header.length !== LEGACY_REWORK_INTENT_COLUMNS.length || !header.every((value, index) => value === LEGACY_REWORK_INTENT_COLUMNS[index])) {
139
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: 'rework intent section is not the known legacy table' };
140
+ }
141
+ try {
142
+ const intents = lines.slice(2).map((line) => {
143
+ const values = cells(line);
144
+ if (!values || values.length !== LEGACY_REWORK_INTENT_COLUMNS.length)
145
+ throw new Error('legacy rework intent row has the wrong column count');
146
+ const row = Object.fromEntries(LEGACY_REWORK_INTENT_COLUMNS.map((column, index) => [column, values[index]]));
147
+ if (!/^RI-[1-9]\d*$/.test(row.intent_id) || !/^(AN|PL|CD)-[1-9]\d*$/.test(row.finding_id))
148
+ throw new Error('legacy rework intent identity is invalid');
149
+ if (!['analysis', 'plan', 'code'].includes(row.target) || !['pending', 'consumed', 'superseded'].includes(row.status))
150
+ throw new Error('legacy rework intent state is invalid');
151
+ if (!/^[a-f0-9]{64}$/.test(row.source_sha256))
152
+ throw new Error('legacy rework intent source hash is invalid');
153
+ const legacyDigest = (value) => requireDigest(value);
154
+ const classification = row.target === 'analysis' ? 'scope-or-requirement' : row.target === 'plan' ? 'design' : 'implementation';
155
+ return {
156
+ intentId: row.intent_id, findingId: row.finding_id, sourceArtifact: row.source_artifact, sourceSha256: row.source_sha256,
157
+ target: row.target, classification,
158
+ evidenceDigest: legacyDigest(`${row.finding_id}|${row.source_artifact}|${row.source_sha256}`),
159
+ taskFactDigest: legacyDigest(`legacy|${row.finding_id}|${row.target}`),
160
+ status: row.status, declaredAt: row.declared_at, consumedAt: row.consumed_at
161
+ };
162
+ });
163
+ return { ok: true, present: true, intents };
164
+ }
165
+ catch (error) {
166
+ return { ok: false, code: 'TASK_REWORK_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) };
167
+ }
168
+ }
169
+ function requireDigest(value) {
170
+ return createHash('sha256').update(value).digest('hex');
171
+ }
107
172
  function consumeReworkIntents(intents, target, sourceHashes, consumedAt) {
108
173
  let changed = false;
109
174
  const next = intents.map((intent) => {
@@ -134,5 +199,6 @@ function reworkIntentMutation(content, intents) {
134
199
  body: renderReworkIntents(intents)
135
200
  };
136
201
  }
137
- export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent };
202
+ export { REWORK_INTENT_COLUMNS, REWORK_INTENT_HEADINGS, parseReworkIntentDocument, renderReworkIntents, reworkIntentMutation, consumeReworkIntents, supersedeReworkIntents, upsertReworkIntent, consumeHumanDecisionReworkIntents, parseLegacyReworkIntentDocument };
203
+ import { createHash } from 'node:crypto';
138
204
  //# sourceMappingURL=rework-intent.js.map