@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.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- 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/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -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/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- 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 {
|
|
5
|
-
import {
|
|
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'
|
|
18
|
-
plan: { family: 'review-plan'
|
|
19
|
-
code: { family: '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
|
-
|
|
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
|
|
67
|
-
if (!request.taskRef || !request.artifact)
|
|
68
|
-
return
|
|
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
|
|
71
|
+
return reject(resolved.code, resolved.message);
|
|
73
72
|
if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
|
89
|
+
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
90
|
+
const digests = { artifactSha256, semanticDigest };
|
|
101
91
|
let repairIntent;
|
|
102
92
|
try {
|
|
103
|
-
repairIntent = readArtifactRepairIntent(resolved.repoRoot,
|
|
93
|
+
repairIntent = readArtifactRepairIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
|
|
104
94
|
}
|
|
105
95
|
catch (error) {
|
|
106
|
-
return
|
|
107
|
-
artifactSha256,
|
|
108
|
-
semanticDigest: artifactSemanticDigest
|
|
109
|
-
});
|
|
96
|
+
return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
|
|
110
97
|
}
|
|
111
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 !==
|
|
152
|
-
return
|
|
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 !==
|
|
159
|
-
return
|
|
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 (!
|
|
165
|
-
return
|
|
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
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
147
|
+
return reject('REVIEW_LEDGER_INVALID', String(error));
|
|
190
148
|
}
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
193
|
-
return
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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) !==
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
|
|
398
|
-
|
|
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
|
|
413
|
-
const headRe = new RegExp(
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
424
|
-
|
|
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
|