@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
5
- import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.js';
4
+ import { parseArtifactName } from './artifact-name.js';
5
+ import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
6
6
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
7
7
  import { finalizeReviewSummaryContent } from './review-artifacts.js';
8
8
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
@@ -14,9 +14,9 @@ import { getArtifactSchema } from './artifact-schema.js';
14
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
15
  import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
16
16
  const STAGES = {
17
- analysis: { family: 'review-analysis', action: 'Review Analysis' },
18
- plan: { family: 'review-plan', action: 'Review Plan' },
19
- code: { family: 'review-code', action: 'Review Code' }
17
+ analysis: { family: 'review-analysis' },
18
+ plan: { family: 'review-plan' },
19
+ code: { family: 'review-code' }
20
20
  };
21
21
  const DEFAULT_FILE_SYSTEM = {
22
22
  readFileSync: (file) => fs.readFileSync(file, 'utf8'),
@@ -44,13 +44,6 @@ function failed(request, code, message, taskId = null, stageStatus = null, extra
44
44
  ...extra
45
45
  };
46
46
  }
47
- function openReviewRound(content, action, round) {
48
- const section = locateActivityLog(content);
49
- if (!section)
50
- return false;
51
- const expected = `${action} (Round ${round})`;
52
- return startedBackedRows(pairEntries(section.entries)).filter((row) => (row.step === expected && !row.done)).length === 1;
53
- }
54
47
  function cleanupTemp(fileSystem, tempPath) {
55
48
  try {
56
49
  fileSystem.unlinkSync(tempPath);
@@ -59,240 +52,179 @@ function cleanupTemp(fileSystem, tempPath) {
59
52
  // Best-effort cleanup preserves the primary failure.
60
53
  }
61
54
  }
62
- function finalizeReviewSummaryUnlocked(request, options = {}) {
55
+ /** Prepare domain validation, summary bytes and provenance before any publication. */
56
+ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
57
+ let taskId = null;
58
+ let stageStatus = null;
59
+ const reject = (code, message, extra = {}) => ({
60
+ result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
61
+ });
63
62
  const stage = request.stage;
64
63
  const spec = STAGES[stage];
65
64
  if (!spec)
66
- return failed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
67
- if (!request.taskRef || !request.artifact) {
68
- return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
69
- }
65
+ return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
66
+ if (!request.taskRef || !request.artifact)
67
+ return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
70
68
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
69
+ taskId = resolved.taskId ?? null;
71
70
  if (!resolved.ok)
72
- return failed(request, resolved.code, resolved.message, resolved.taskId);
71
+ return reject(resolved.code, resolved.message);
73
72
  if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
74
- return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
75
- }
76
- const parsedArtifact = parseArtifactName(request.artifact);
77
- if (!parsedArtifact || parsedArtifact.family !== spec.family) {
78
- return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`, resolved.taskId);
79
- }
80
- const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact, parsedArtifact.round);
81
- if (!validated.ok) {
82
- const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
83
- ? 'REVIEW_ARTIFACT_NOT_REGULAR'
84
- : 'REVIEW_ARTIFACT_IDENTITY_INVALID';
85
- return failed(request, code, validated.error.message, resolved.taskId);
86
- }
87
- const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
73
+ return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
74
+ }
75
+ const parsed = parseArtifactName(request.artifact);
76
+ if (!parsed || parsed.family !== spec.family)
77
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
78
+ const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
79
+ if (publicationError)
80
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
88
81
  let taskContent;
89
- let artifactContent;
90
82
  try {
91
- taskContent = fileSystem.readFileSync(resolved.taskMdPath);
92
- artifactContent = fileSystem.readFileSync(validated.artifact.path);
83
+ taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath);
93
84
  }
94
85
  catch (error) {
95
- return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
86
+ return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
96
87
  }
97
- const schema = getArtifactSchema(spec.family);
98
- const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
99
88
  const artifactSha256 = sha256Content(artifactContent);
100
- const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
89
+ const semanticDigest = canonicalSemanticDigest(artifactContent);
90
+ const digests = { artifactSha256, semanticDigest };
101
91
  let repairIntent;
102
92
  try {
103
- repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
93
+ repairIntent = readArtifactRepairIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
104
94
  }
105
95
  catch (error) {
106
- return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
107
- artifactSha256,
108
- semanticDigest: artifactSemanticDigest
109
- });
96
+ return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
110
97
  }
111
- if (structure && !structure.ok) {
98
+ const schema = getArtifactSchema(spec.family);
99
+ const structure = inspectArtifactContract(artifactContent, schema);
100
+ if (!structure.ok) {
112
101
  const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
113
- if (repairable) {
114
- if (repairIntent && (repairIntent.state !== 'awaiting-repair' ||
115
- repairIntent.artifactSha256 !== artifactSha256 ||
116
- repairIntent.semanticDigest !== structure.semanticDigest)) {
117
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
118
- artifactSha256,
119
- semanticDigest: structure.semanticDigest
120
- });
121
- }
122
- try {
123
- if (!repairIntent) {
124
- writeArtifactRepairIntent(resolved.repoRoot, {
125
- version: 1,
126
- taskId: resolved.taskId,
127
- family: spec.family,
128
- artifact: request.artifact,
129
- state: 'awaiting-repair',
130
- baselineSemanticDigest: structure.semanticDigest,
131
- artifactSha256,
132
- semanticDigest: structure.semanticDigest
133
- });
134
- }
135
- }
136
- catch (error) {
137
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
138
- artifactSha256,
139
- semanticDigest: structure.semanticDigest,
140
- operation: structure.repair
141
- });
142
- }
102
+ if (repairable && repairIntent && (repairIntent.state !== 'awaiting-repair'
103
+ || repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== structure.semanticDigest)) {
104
+ return reject('REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', digests);
143
105
  }
144
- return failed(request, 'REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), resolved.taskId, null, {
145
- artifactSha256,
146
- semanticDigest: structure.semanticDigest,
147
- repairable,
148
- operation: structure.repair
149
- });
106
+ return {
107
+ ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
108
+ artifactSha256, semanticDigest: structure.semanticDigest, repairable, operation: structure.repair
109
+ }),
110
+ ...(repairable && !repairIntent ? { provenance: {
111
+ version: 2, taskId: taskId, family: spec.family, artifact: request.artifact,
112
+ state: 'awaiting-repair', baselineSemanticDigest: structure.semanticDigest,
113
+ artifactSha256, semanticDigest: structure.semanticDigest,
114
+ recoveryOperationId: null, phase: null, authorityDigest: null,
115
+ requestId: `review-finalize:${taskId}`, createdAt: Date.now(), updatedAt: Date.now()
116
+ }, expectedIntent: null } : {})
117
+ };
150
118
  }
151
- if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
152
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
153
- artifactSha256,
154
- semanticDigest: artifactSemanticDigest
155
- });
119
+ if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== semanticDigest) {
120
+ return reject('REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', digests);
156
121
  }
157
122
  if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
158
- && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
159
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
160
- artifactSha256,
161
- semanticDigest: artifactSemanticDigest
162
- });
123
+ && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== semanticDigest)) {
124
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
163
125
  }
164
- if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
165
- return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
126
+ if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
127
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
166
128
  }
167
129
  const execution = validateLifecycleExecution(request.taskRef, {
168
130
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
169
- identity: {
170
- stage: spec.family,
171
- round: parsedArtifact.round,
172
- artifact: request.artifact,
173
- role: 'reviewer'
174
- },
131
+ identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
175
132
  dryRun: request.dryRun
176
133
  }, { repoRoot: options.repoRoot });
177
134
  if (!execution.ok) {
178
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
135
+ return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
179
136
  }
180
- let rows;
181
137
  try {
182
138
  const ledger = parseLedgerDocument(taskContent);
183
- if (!ledger.present) {
184
- return failed(request, 'REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
185
- }
186
- rows = ledger.rows;
139
+ if (!ledger.present)
140
+ return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
141
+ const error = validateLedgerRows(ledger.rows);
142
+ if (error)
143
+ return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
144
+ stageStatus = summarizeLedgerStage(ledger.rows, stage);
187
145
  }
188
146
  catch (error) {
189
- return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
147
+ return reject('REVIEW_LEDGER_INVALID', String(error));
190
148
  }
191
- const ledgerError = validateLedgerRows(rows);
192
- if (ledgerError) {
193
- return failed(request, 'REVIEW_LEDGER_INVALID', `${ledgerError.code}: ${ledgerError.message}`, resolved.taskId);
149
+ const detail = inspectDecisionDetailDuplicates(artifactContent);
150
+ if (!detail.ok)
151
+ return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
152
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
153
+ if (!transformed.ok)
154
+ return reject(transformed.code, transformed.message);
155
+ const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
156
+ return {
157
+ content: transformed.content,
158
+ result: {
159
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
160
+ status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
161
+ changed: transformed.changed,
162
+ operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
163
+ error: null
164
+ },
165
+ ...(repairIntent?.state === 'awaiting-repair' ? {
166
+ provenance: { ...repairIntent, state: 'passed', ...finalDigests },
167
+ expectedIntent: repairIntent
168
+ } : {})
169
+ };
170
+ }
171
+ function commitReviewSummaryProvenance(prepared, repoRoot) {
172
+ try {
173
+ if (prepared.provenance) {
174
+ writeArtifactRepairIntent(repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
175
+ }
176
+ return prepared.result;
194
177
  }
195
- const stageStatus = summarizeLedgerStage(rows, stage);
196
- const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
197
- if (!detailInspection.ok) {
198
- return failed(request, 'REVIEW_DECISION_DETAIL_INVALID', `${detailInspection.code}: ${detailInspection.message}`, resolved.taskId, stageStatus);
178
+ catch (error) {
179
+ return { ...prepared.result, status: 'failed', error: {
180
+ code: 'REVIEW_PROVENANCE_INVALID', message: `cannot record review provenance: ${String(error)}`
181
+ } };
199
182
  }
200
- const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
201
- if (!transformed.ok) {
202
- return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
183
+ }
184
+ function finalizeReviewSummaryUnlocked(request, options = {}) {
185
+ const spec = STAGES[request.stage];
186
+ if (!spec || !request.taskRef || !request.artifact)
187
+ return prepareReviewSummaryCandidate(request, '', options).result;
188
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
189
+ if (!resolved.ok)
190
+ return failed(request, resolved.code, resolved.message, resolved.taskId);
191
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
192
+ if (!validated.ok) {
193
+ return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
203
194
  }
204
- if (!transformed.changed) {
205
- if (repairIntent?.state === 'awaiting-repair') {
206
- try {
207
- writeArtifactRepairIntent(resolved.repoRoot, {
208
- ...repairIntent,
209
- state: 'passed',
210
- artifactSha256,
211
- semanticDigest: artifactSemanticDigest
212
- });
213
- }
214
- catch (error) {
215
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
216
- artifactSha256,
217
- semanticDigest: artifactSemanticDigest
218
- });
219
- }
220
- }
221
- return {
222
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
223
- artifactSha256,
224
- semanticDigest: artifactSemanticDigest
225
- }),
226
- status: 'no-op',
227
- error: null
228
- };
195
+ const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
196
+ let content;
197
+ try {
198
+ content = fileSystem.readFileSync(validated.artifact.path);
229
199
  }
230
- const operations = [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }];
231
- if (request.dryRun) {
232
- return {
233
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
234
- artifactSha256: sha256Content(transformed.content),
235
- semanticDigest: canonicalSemanticDigest(transformed.content)
236
- }),
237
- status: 'planned',
238
- changed: true,
239
- operations,
240
- error: null
241
- };
200
+ catch (error) {
201
+ return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
242
202
  }
203
+ const prepared = prepareReviewSummaryCandidate(request, content, options);
204
+ if (request.dryRun)
205
+ return prepared.result;
206
+ if (prepared.result.status !== 'applied')
207
+ return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
243
208
  const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
244
- let mode;
245
209
  try {
246
- mode = fileSystem.statModeSync(validated.artifact.path);
247
- fileSystem.writeFileSync(tempPath, transformed.content, mode);
210
+ fileSystem.writeFileSync(tempPath, prepared.content, fileSystem.statModeSync(validated.artifact.path));
248
211
  }
249
212
  catch (error) {
250
213
  cleanupTemp(fileSystem, tempPath);
251
- return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
214
+ return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
252
215
  }
253
216
  try {
254
- if (fileSystem.readFileSync(validated.artifact.path) !== artifactContent) {
217
+ if (fileSystem.readFileSync(validated.artifact.path) !== content) {
255
218
  cleanupTemp(fileSystem, tempPath);
256
- return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, stageStatus);
219
+ return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, prepared.result.stageStatus);
257
220
  }
258
221
  fileSystem.renameSync(tempPath, validated.artifact.path);
259
222
  }
260
223
  catch (error) {
261
224
  cleanupTemp(fileSystem, tempPath);
262
- return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
225
+ return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
263
226
  }
264
- if (repairIntent?.state === 'awaiting-repair') {
265
- try {
266
- writeArtifactRepairIntent(resolved.repoRoot, {
267
- ...repairIntent,
268
- state: 'passed',
269
- artifactSha256: sha256Content(transformed.content),
270
- semanticDigest: canonicalSemanticDigest(transformed.content)
271
- });
272
- }
273
- catch (error) {
274
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
275
- artifactSha256: sha256Content(transformed.content),
276
- semanticDigest: canonicalSemanticDigest(transformed.content)
277
- });
278
- }
279
- }
280
- return {
281
- status: 'applied',
282
- changed: true,
283
- intent: 'finalize-summary',
284
- requestRef: request.taskRef,
285
- taskId: resolved.taskId,
286
- stage,
287
- artifact: request.artifact,
288
- stageStatus,
289
- artifactSha256: sha256Content(transformed.content),
290
- semanticDigest: canonicalSemanticDigest(transformed.content),
291
- repairable: false,
292
- operation: null,
293
- operations,
294
- error: null
295
- };
227
+ return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
296
228
  }
297
229
  function finalizeReviewSummary(request, options = {}) {
298
230
  if (request.dryRun || options.lockAlreadyHeld)
@@ -309,5 +241,5 @@ function finalizeReviewSummary(request, options = {}) {
309
241
  return failed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
310
242
  }
311
243
  }
312
- export { finalizeReviewSummary };
244
+ export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
313
245
  //# sourceMappingURL=review-finalization.js.map
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { artifactName, maxRound } from "./review-artifacts.js";
3
+ import { artifactName, maxArtifactRound } from "./artifact-name.js";
4
4
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.js";
5
5
  const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
6
6
  export function parseReviewedGitTree(value) {
@@ -29,7 +29,7 @@ export function resolvePostReviewGlobs(config = {}, reviewConfig = {}) {
29
29
  }
30
30
  export function findAuthoritativeReviewCodeArtifact(taskDir) {
31
31
  const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
32
- const round = maxRound(entries, "review-code");
32
+ const round = maxArtifactRound(entries, "review-code");
33
33
  if (round === 0) {
34
34
  return { ok: false, round: 0, fileName: null, path: null, error: null };
35
35
  }
@@ -1,3 +1,4 @@
1
+ import { scanVisibleMarkdown } from './markdown.js';
1
2
  function escapeRegExp(value) {
2
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3
4
  }
@@ -27,10 +28,13 @@ function parseTable(content, input) {
27
28
  throw new DocumentMutationError('TASK_DOCUMENT_INVALID', 'table section is ambiguous');
28
29
  }
29
30
  const section = sections[0];
31
+ const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
30
32
  const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
31
33
  const firstContent = sectionLines.findIndex((line) => line.text.trim() !== '');
32
34
  const tables = [];
33
35
  for (let index = 0; index + 1 < sectionLines.length; index += 1) {
36
+ if (!visibleStarts.has(sectionLines[index].start) || !visibleStarts.has(sectionLines[index + 1].start))
37
+ continue;
34
38
  const header = splitTableCells(sectionLines[index].text);
35
39
  const separator = splitTableCells(sectionLines[index + 1].text);
36
40
  if (!header || !separator || header.cells.length !== input.columns.length)
@@ -97,24 +101,23 @@ function documentEol(content) {
97
101
  }
98
102
  function matchingSections(content, aliases) {
99
103
  const allowed = new Set(aliases);
100
- return linesOf(content)
101
- .map((line, index, lines) => {
102
- const match = /^(#{2,3})\s+(.+?)\s*$/.exec(line.text);
103
- if (!match?.[2] || !allowed.has(match[2]))
104
+ const lines = new Map(linesOf(content).map((line) => [line.start, line]));
105
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
106
+ return headings
107
+ .map((heading, index) => {
108
+ if (!allowed.has(heading.text))
104
109
  return null;
105
- const level = match[1].length;
106
- let end = content.length;
107
- for (let i = index + 1; i < lines.length; i += 1) {
108
- const nextHeading = /^(#{2,3})\s+/.exec(lines[i].text);
109
- if (nextHeading && nextHeading[1].length <= level) {
110
- end = lines[i].start;
111
- break;
112
- }
113
- }
114
- return { heading: match[2], line, bodyStart: line.end, end };
110
+ const next = headings.slice(index + 1).find((candidate) => candidate.level <= heading.level);
111
+ const line = lines.get(heading.start);
112
+ return { heading: heading.text, level: heading.level, line, bodyStart: line.end, end: next?.start ?? content.length };
115
113
  })
116
114
  .filter((match) => match !== null);
117
115
  }
116
+ // Select the first visible section at the requested level, in document order.
117
+ function findSectionRange(content, aliases, level = 2) {
118
+ const section = matchingSections(content, aliases).find((candidate) => candidate.level === level);
119
+ return section ? { start: section.line.start, bodyStart: section.bodyStart, end: section.end, heading: section.heading } : null;
120
+ }
118
121
  function validateAliases(aliases, name) {
119
122
  if (!Array.isArray(aliases) || aliases.length === 0 || aliases.some((alias) => !alias || /[\r\n]/.test(alias))) {
120
123
  throw new DocumentMutationError('MUTATION_INVALID', `${name} must contain headings`);
@@ -261,9 +264,12 @@ function mutateTableRow(content, mutation) {
261
264
  throw new DocumentMutationError(sections.length === 0 ? 'TABLE_NOT_FOUND' : 'TASK_DOCUMENT_INVALID', sections.length === 0 ? 'table section not found' : 'table section is ambiguous');
262
265
  }
263
266
  const section = sections[0];
267
+ const visibleStarts = new Set(scanVisibleMarkdown(content).lines.map((line) => line.start));
264
268
  const sectionLines = linesOf(content).filter((line) => line.start >= section.bodyStart && line.start < section.end);
265
269
  const tables = [];
266
270
  for (let i = 0; i + 1 < sectionLines.length; i += 1) {
271
+ if (!visibleStarts.has(sectionLines[i].start) || !visibleStarts.has(sectionLines[i + 1].start))
272
+ continue;
267
273
  const header = splitTableCells(sectionLines[i].text);
268
274
  const separator = splitTableCells(sectionLines[i + 1].text);
269
275
  if (!header || !separator || header.cells.length !== mutation.columns.length)
@@ -368,25 +374,12 @@ function mutateTableRow(content, mutation) {
368
374
  * lines are trimmed. Returns '' when no alias heading is present.
369
375
  */
370
376
  function extractSection(content, aliases) {
371
- const lines = content.split('\n');
372
- let start = -1;
373
- for (let i = 0; i < lines.length; i += 1) {
374
- const line = lines[i].trim();
375
- if (aliases.some((alias) => new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`).test(line))) {
376
- start = i + 1;
377
- break;
378
- }
379
- }
380
- if (start === -1)
377
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
378
+ const index = headings.findIndex((heading) => aliases.includes(heading.text));
379
+ if (index === -1)
381
380
  return '';
382
- let end = lines.length;
383
- for (let i = start; i < lines.length; i += 1) {
384
- if (/^##\s+/.test(lines[i])) {
385
- end = i;
386
- break;
387
- }
388
- }
389
- return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
381
+ return content.slice(headings[index].end + 1, headings[index + 1]?.start ?? content.length)
382
+ .replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
390
383
  }
391
384
  /**
392
385
  * Return the heading alias that actually appears as a `## {alias}` line, so a
@@ -394,11 +387,8 @@ function extractSection(content, aliases) {
394
387
  * alias when none is present.
395
388
  */
396
389
  function findSectionHeading(content, aliases) {
397
- for (const alias of aliases) {
398
- if (new RegExp(`^##\\s+${escapeRegExp(alias)}\\s*$`, 'm').test(content))
399
- return alias;
400
- }
401
- return aliases[0];
390
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2);
391
+ return aliases.find((alias) => headings.some((heading) => heading.text === alias)) ?? aliases[0];
402
392
  }
403
393
  /**
404
394
  * Return the body of the first `### {headingPrefix}` sub-section, from the
@@ -409,25 +399,13 @@ function findSectionHeading(content, aliases) {
409
399
  * and trailing blank lines are trimmed. Returns '' when no match is present.
410
400
  */
411
401
  function extractSubSection(content, headingPrefix) {
412
- const lines = content.split('\n');
413
- const headRe = new RegExp(`^###\\s+${escapeRegExp(headingPrefix)}(?![\\w-])`);
414
- let start = -1;
415
- for (let i = 0; i < lines.length; i += 1) {
416
- if (headRe.test(lines[i].trim())) {
417
- start = i;
418
- break;
419
- }
420
- }
421
- if (start === -1)
402
+ const headings = scanVisibleMarkdown(content).headings.filter((heading) => heading.level === 2 || heading.level === 3);
403
+ const headRe = new RegExp(`^${escapeRegExp(headingPrefix)}(?![\\w-])`);
404
+ const index = headings.findIndex((heading) => heading.level === 3 && headRe.test(heading.text));
405
+ if (index === -1)
422
406
  return '';
423
- let end = lines.length;
424
- for (let i = start + 1; i < lines.length; i += 1) {
425
- if (/^###?\s+/.test(lines[i])) {
426
- end = i;
427
- break;
428
- }
429
- }
430
- return lines.slice(start, end).join('\n').replace(/^\n+/, '').replace(/\n+$/, '');
407
+ return content.slice(headings[index].start, headings[index + 1]?.start ?? content.length)
408
+ .replace(/^(?:\r?\n)+|(?:\r?\n)+$/g, '');
431
409
  }
432
- export { extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
410
+ export { findSectionRange, extractSection, findSectionHeading, extractSubSection, parseTable, upsertSection, mutateTableRow, DocumentMutationError };
433
411
  //# sourceMappingURL=sections.js.map