@fitlab-ai/agent-infra 0.11.5 → 0.11.7
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/dist/lib/git/primary-worktree.js +58 -0
- package/dist/lib/merge.js +62 -10
- package/dist/lib/platform/github-provider.js +9 -14
- package/dist/lib/platform/github-release-notes.js +80 -11
- package/dist/lib/platform/none-provider.js +1 -1
- package/dist/lib/platform/provider-contract.d.ts +39 -16
- package/dist/lib/platform/provider-contract.js +1 -1
- package/dist/lib/platform/provider-validation.js +83 -13
- package/dist/lib/platform/pull-requests.js +29 -3
- package/dist/lib/platform/release-notes.js +7 -8
- package/dist/lib/sandbox/capture.js +1 -1
- package/dist/lib/sandbox/commands/create.js +46 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/start.js +1 -1
- package/dist/lib/sandbox/config.js +10 -0
- package/dist/lib/sandbox/control/lifecycle.js +18 -13
- package/dist/lib/sandbox/control/server.js +6 -2
- package/dist/lib/sandbox/task-resolver.js +62 -2
- package/dist/lib/server/config.js +7 -27
- package/dist/lib/server/daemon.js +4 -3
- package/dist/lib/server/process-control.js +4 -2
- package/dist/lib/server/runner.js +4 -3
- package/dist/lib/task/artifact-lifecycle.js +42 -10
- package/dist/lib/task/artifact-receipts.js +62 -22
- package/dist/lib/task/capabilities.js +22 -4
- package/dist/lib/task/completion-facts.js +56 -0
- package/dist/lib/task/create-service.js +6 -2
- package/dist/lib/task/create.js +46 -82
- package/dist/lib/task/events.js +268 -195
- package/dist/lib/task/finalization-handoff.js +2 -1
- package/dist/lib/task/invalidation-command.js +4 -2
- package/dist/lib/task/invalidation.js +38 -9
- package/dist/lib/task/local-artifact-finalization.js +17 -3
- package/dist/lib/task/qualification-audit.js +23 -105
- package/dist/lib/task/resolve-ref.js +3 -3
- package/dist/lib/task/review-finalization.js +7 -0
- package/dist/lib/task/review-identity.js +69 -0
- package/dist/lib/task/sections.js +6 -5
- package/dist/lib/task/task-execution-lock.js +6 -3
- package/dist/lib/task/verification-engine.js +14 -63
- package/dist/lib/task/verification.js +35 -38
- package/dist/lib/task/write.js +121 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/git/primary-worktree.ts +62 -0
- package/lib/merge.ts +59 -10
- package/lib/platform/github-provider.ts +11 -18
- package/lib/platform/github-release-notes.ts +89 -14
- package/lib/platform/none-provider.ts +1 -1
- package/lib/platform/provider-contract.ts +38 -12
- package/lib/platform/provider-validation.ts +83 -12
- package/lib/platform/pull-requests.ts +27 -3
- package/lib/platform/release-notes.ts +11 -12
- package/lib/sandbox/capture.ts +1 -1
- package/lib/sandbox/commands/create.ts +52 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/start.ts +1 -1
- package/lib/sandbox/config.ts +12 -0
- package/lib/sandbox/control/lifecycle.ts +22 -13
- package/lib/sandbox/control/server.ts +5 -2
- package/lib/sandbox/task-resolver.ts +51 -2
- package/lib/server/config.ts +7 -29
- package/lib/server/daemon.ts +5 -3
- package/lib/server/process-control.ts +4 -2
- package/lib/server/runner.ts +7 -4
- package/lib/server/server.schema.json +0 -4
- package/lib/task/artifact-lifecycle.ts +35 -11
- package/lib/task/artifact-receipts.ts +55 -15
- package/lib/task/capabilities.ts +17 -3
- package/lib/task/completion-facts.ts +66 -0
- package/lib/task/create-service.ts +7 -3
- package/lib/task/create.ts +44 -71
- package/lib/task/events.ts +248 -182
- package/lib/task/finalization-handoff.ts +2 -1
- package/lib/task/invalidation-command.ts +4 -2
- package/lib/task/invalidation.ts +35 -10
- package/lib/task/local-artifact-finalization.ts +13 -3
- package/lib/task/qualification-audit.ts +21 -114
- package/lib/task/resolve-ref.ts +3 -3
- package/lib/task/review-finalization.ts +7 -0
- package/lib/task/review-identity.ts +85 -0
- package/lib/task/sections.ts +6 -6
- package/lib/task/task-execution-lock.ts +7 -3
- package/lib/task/verification-engine.ts +13 -86
- package/lib/task/verification.ts +34 -38
- package/lib/task/write.ts +115 -1
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/decision-qualification.en.md +4 -3
- package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
- package/templates/.agents/rules/evidence-reporting.en.md +1 -2
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
- package/templates/.agents/rules/validation-output.en.md +19 -0
- package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
- package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
- package/templates/.agents/skills/review-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- 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/watch-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
|
@@ -4,14 +4,13 @@ import path from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
7
|
+
import { extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
|
|
8
8
|
import { check as checkPlatformSync } from "../platform/verification-sync.js";
|
|
9
9
|
import { check as checkRequiredChecks } from "../platform/verification-required.js";
|
|
10
10
|
import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
|
|
11
11
|
import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
|
|
12
12
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
|
|
13
13
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
|
|
14
|
-
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
15
14
|
import { parseTypedTaskFrontmatter } from "./frontmatter.js";
|
|
16
15
|
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
17
16
|
import { isValidAgentInfraVersion } from "../version.js";
|
|
@@ -20,11 +19,9 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
|
|
|
20
19
|
import { parseImplementationInputs } from "./implementation-inputs.js";
|
|
21
20
|
import { scanVisibleMarkdown } from "./markdown.js";
|
|
22
21
|
import { loadVerificationConfig } from "./verification-config.js";
|
|
23
|
-
import { snapshotReview } from "../git/review-snapshot.js";
|
|
24
22
|
import { inspectActivityLog } from "./activity-log.js";
|
|
25
23
|
import { parseWorkflowWarnings } from "./workflow-warnings.js";
|
|
26
24
|
import { OrchestrationStateError, readRun } from "./orchestration.js";
|
|
27
|
-
import { resolveDeliveryTarget } from "./delivery-target.js";
|
|
28
25
|
import { readPrDeliveryFact } from "./pr-delivery-fact.js";
|
|
29
26
|
import { validateLocalArtifact } from "./local-artifact-finalization.js";
|
|
30
27
|
import { getArtifactSchema } from "./artifact-schema.js";
|
|
@@ -35,6 +32,8 @@ import { readManualValidationCompletion } from "./manual-validation-completion.j
|
|
|
35
32
|
import { sha256File } from "./artifact-receipts.js";
|
|
36
33
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
37
34
|
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
35
|
+
import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
|
|
36
|
+
import { inspectReviewIdentity } from './review-identity.js';
|
|
38
37
|
const TASK_ENUMS = {
|
|
39
38
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
40
39
|
workflow: ["feature-development", "bug-fix", "refactoring"],
|
|
@@ -1014,12 +1013,16 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1014
1013
|
return failResult("review-fact", task.message);
|
|
1015
1014
|
}
|
|
1016
1015
|
const content = fs.readFileSync(resolvedArtifact.path, "utf8");
|
|
1016
|
+
if (!hasArtifactCompletionFact(task.content, resolvedArtifact.path, 'review-code.completed')) {
|
|
1017
|
+
return failResult("review-fact", `Review Code completion fact does not match ${path.basename(resolvedArtifact.path)}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (!hasArtifactCompletionLog(task.content, path.basename(resolvedArtifact.path), 'review-code.completed')) {
|
|
1020
|
+
return failResult("review-fact", `Review Code completion entry does not reference ${path.basename(resolvedArtifact.path)}`);
|
|
1021
|
+
}
|
|
1017
1022
|
const verdict = parseReviewVerdict(content);
|
|
1018
1023
|
const reviewTargetHead = extractReviewTargetHead(content);
|
|
1019
1024
|
const reviewReviewedHead = extractReviewedHead(content);
|
|
1020
1025
|
const reviewDiffBase = extractReviewDiffBase(content);
|
|
1021
|
-
const reviewedFingerprint = extractReviewDiffFingerprint(content);
|
|
1022
|
-
const reviewedTree = extractReviewedSnapshotTree(content);
|
|
1023
1026
|
if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
|
|
1024
1027
|
return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
|
|
1025
1028
|
}
|
|
@@ -1031,71 +1034,19 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1031
1034
|
if (!reviewTargetHead || !reviewReviewedHead || !reviewDiffBase) {
|
|
1032
1035
|
return failResult("review-fact", "Review fact must record target head, reviewed head, and diff base");
|
|
1033
1036
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
let targetHead;
|
|
1038
|
-
let diffBase;
|
|
1039
|
-
try {
|
|
1040
|
-
const taskRepositoryRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
1041
|
-
gitRoot = reviewWorktreeForTask(taskRepositoryRoot, String(task.metadata.branch || "").trim()) ?? taskRepositoryRoot;
|
|
1042
|
-
head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
1043
|
-
const target = resolveDeliveryTarget(gitRoot, { remote: deliveryRemote, baseRef: deliveryBaseRef });
|
|
1044
|
-
if (!target.ok)
|
|
1045
|
-
throw new Error(target.message);
|
|
1046
|
-
const reviewedRef = reviewReviewedHead;
|
|
1047
|
-
baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewedRef}^{commit}`], { encoding: "utf8" }).trim();
|
|
1048
|
-
targetHead = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewTargetHead}^{commit}`], { encoding: "utf8" }).trim();
|
|
1049
|
-
const computed = execFileSync("git", ["-C", gitRoot, "merge-base", baseline, targetHead], { encoding: "utf8" }).trim();
|
|
1050
|
-
diffBase = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewDiffBase}^{commit}`], { encoding: "utf8" }).trim();
|
|
1051
|
-
if (diffBase !== computed)
|
|
1052
|
-
throw new Error('saved review diff base does not match merge-base(reviewed head, target head)');
|
|
1053
|
-
}
|
|
1054
|
-
catch {
|
|
1055
|
-
return blockedResult("review-fact", `Unable to resolve saved review commits in the task repository; re-run review-code`);
|
|
1056
|
-
}
|
|
1057
|
-
if (baseline !== head) {
|
|
1058
|
-
return failResult("review-fact", `Reviewed head ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`);
|
|
1059
|
-
}
|
|
1060
|
-
let actualSnapshot;
|
|
1061
|
-
try {
|
|
1062
|
-
actualSnapshot = snapshotReview({
|
|
1063
|
-
cwd: gitRoot,
|
|
1064
|
-
mode: "worktree",
|
|
1065
|
-
baseline,
|
|
1066
|
-
diffBase,
|
|
1067
|
-
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
catch {
|
|
1071
|
-
return blockedResult("review-fact", `Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1072
|
-
}
|
|
1073
|
-
if (actualSnapshot.fingerprint !== reviewedFingerprint) {
|
|
1074
|
-
return failResult("review-fact", `Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1075
|
-
}
|
|
1076
|
-
if (!reviewedTree || actualSnapshot.tree !== reviewedTree) {
|
|
1077
|
-
return failResult("review-fact", `Reviewed snapshot tree does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1037
|
+
const identity = inspectReviewIdentity(taskDir, content, repositoryRoot);
|
|
1038
|
+
if (identity.status !== 'matched' || !identity.reviewedHead) {
|
|
1039
|
+
return failResult('review-fact', `Review identity is ${identity.status}: ${identity.message}; re-run review-code`);
|
|
1078
1040
|
}
|
|
1041
|
+
const baseline = identity.reviewedHead;
|
|
1079
1042
|
if (["通过", "Approved"].includes(verdict)) {
|
|
1080
1043
|
const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
|
|
1081
|
-
|
|
1082
|
-
if (cleanSnapshot && lastReviewedCommit !== baseline) {
|
|
1044
|
+
if (lastReviewedCommit !== baseline) {
|
|
1083
1045
|
return failResult("review-fact", `Approved clean review must set task last_reviewed_commit to reviewed head ${baseline.slice(0, 8)}`);
|
|
1084
1046
|
}
|
|
1085
|
-
if (!cleanSnapshot && lastReviewedCommit) {
|
|
1086
|
-
return failResult("review-fact", "Approved review with uncommitted changes must remain unanchored until commit");
|
|
1087
|
-
}
|
|
1088
1047
|
}
|
|
1089
1048
|
return passResult("review-fact", `Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`);
|
|
1090
1049
|
}
|
|
1091
|
-
function reviewWorktreeForTask(repositoryRoot, branch) {
|
|
1092
|
-
if (!branch)
|
|
1093
|
-
return null;
|
|
1094
|
-
const worktree = resolveBranchWorktree(repositoryRoot, branch);
|
|
1095
|
-
if (worktree)
|
|
1096
|
-
return worktree;
|
|
1097
|
-
throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
|
|
1098
|
-
}
|
|
1099
1050
|
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
|
|
1100
1051
|
if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
|
|
1101
1052
|
return { ok: true, sha: lastReviewedCommit };
|
|
@@ -89,47 +89,44 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
89
89
|
function statusLabel(status) {
|
|
90
90
|
return status === 'fail' ? 'FAIL' : status === 'blocked' ? 'BLOCKED' : 'pass';
|
|
91
91
|
}
|
|
92
|
-
function renderPayload(payload) {
|
|
93
|
-
const lines = [];
|
|
94
|
-
if (Array.isArray(payload.checks)) {
|
|
95
|
-
const gateMode = typeof payload.gate === 'string';
|
|
96
|
-
lines.push(gateMode
|
|
97
|
-
? `Verification: ${payload.gate} | Skill: ${payload.skill}`
|
|
98
|
-
: `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
99
|
-
for (const check of payload.checks) {
|
|
100
|
-
const raw = statusLabel(check.status);
|
|
101
|
-
const effective = statusLabel(check.effectiveStatus ?? check.status);
|
|
102
|
-
const reason = check.reason ?? 'CHECK_FAILED';
|
|
103
|
-
const message = check.message ?? reason;
|
|
104
|
-
lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
|
|
105
|
-
}
|
|
106
|
-
const summary = payload.summary ?? (() => {
|
|
107
|
-
const checks = payload.checks;
|
|
108
|
-
const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
|
|
109
|
-
return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
|
|
110
|
-
})();
|
|
111
|
-
lines.push('', `Result: ${summary} - ${payload.action}`);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
115
|
-
const raw = statusLabel(payload.status);
|
|
116
|
-
const effective = statusLabel(payload.effectiveStatus ?? payload.status);
|
|
117
|
-
const reason = payload.reason ?? 'CHECK_FAILED';
|
|
118
|
-
lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
|
|
119
|
-
const status = payload.effectiveStatus ?? payload.status;
|
|
120
|
-
const summary = status === 'pass' ? '1 passed, 0 failed'
|
|
121
|
-
: status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
|
|
122
|
-
const action = status === 'pass' ? 'Requested check passed'
|
|
123
|
-
: status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
|
|
124
|
-
: `Fix ${payload.type} issues and re-run check`;
|
|
125
|
-
lines.push('', `Result: ${summary} - ${action}`);
|
|
126
|
-
}
|
|
127
|
-
return `${lines.join('\n')}\n`;
|
|
128
|
-
}
|
|
129
92
|
function renderTaskVerification(result) {
|
|
130
93
|
if (result.status === 'failed')
|
|
131
94
|
return `Verification failed: ${result.error?.code ?? 'VERIFY_FAILED'} - ${result.error?.message ?? 'unknown error'}\n`;
|
|
132
|
-
|
|
95
|
+
const target = `${result.event}${result.artifact ? ` (${result.artifact})` : ''}`;
|
|
96
|
+
const lines = [`Verification: ${result.status} | Target: ${target} | Skill: ${result.skill}`];
|
|
97
|
+
const checks = result.invocations.flatMap(({ payload }) => {
|
|
98
|
+
if (Array.isArray(payload.checks))
|
|
99
|
+
return payload.checks;
|
|
100
|
+
return [payload];
|
|
101
|
+
});
|
|
102
|
+
const counts = {
|
|
103
|
+
pass: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'pass').length,
|
|
104
|
+
fail: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'fail').length,
|
|
105
|
+
blocked: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'blocked').length
|
|
106
|
+
};
|
|
107
|
+
const summary = `${counts.pass} passed, ${counts.fail} failed${counts.blocked > 0 ? `, ${counts.blocked} blocked` : ''}`;
|
|
108
|
+
lines.push('', `Result: ${summary}`);
|
|
109
|
+
for (const check of checks) {
|
|
110
|
+
const rawStatus = check.status;
|
|
111
|
+
const effectiveStatus = check.effectiveStatus ?? rawStatus;
|
|
112
|
+
const isSoftWarning = (rawStatus === 'fail' || rawStatus === 'blocked') && effectiveStatus === 'pass';
|
|
113
|
+
const reason = check.reason ?? 'CHECK_FAILED';
|
|
114
|
+
const message = check.message ?? reason;
|
|
115
|
+
const action = check.action ?? 'Review validation output';
|
|
116
|
+
const id = check.checkId ?? check.type;
|
|
117
|
+
const isHumanExemptionNotice = id === 'post-review-commit'
|
|
118
|
+
&& typeof message === 'string'
|
|
119
|
+
&& /\bhuman-decided (?:post-review )?exemption\b/i.test(message)
|
|
120
|
+
&& /\bPRC-\d+\s*:/.test(message);
|
|
121
|
+
if (effectiveStatus === 'pass' && !isSoftWarning && !isHumanExemptionNotice)
|
|
122
|
+
continue;
|
|
123
|
+
lines.push(isSoftWarning
|
|
124
|
+
? `Warning: ${id} (${rawStatus === 'blocked' ? `raw BLOCKED; ` : ''}${reason}) - ${message}; ${action}`
|
|
125
|
+
: isHumanExemptionNotice
|
|
126
|
+
? `Notice: ${id} - ${message}; ${action}`
|
|
127
|
+
: ` [${statusLabel(effectiveStatus)}] ${id} (${reason}) - ${message}; ${action}`);
|
|
128
|
+
}
|
|
129
|
+
return `${lines.join('\n')}\n`;
|
|
133
130
|
}
|
|
134
131
|
export { VERIFICATION_CATALOG, renderTaskVerification, verifyTaskEvent };
|
|
135
132
|
//# sourceMappingURL=verification.js.map
|
package/dist/lib/task/write.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import { isValidAgentInfraVersion, VERSION } from '../version.js';
|
|
4
5
|
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
5
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
@@ -53,7 +54,7 @@ function errorDetails(error, fallback) {
|
|
|
53
54
|
message: error instanceof Error ? error.message : String(error)
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
|
-
function
|
|
57
|
+
function writeTaskCore(request, options = {}) {
|
|
57
58
|
const resolved = options.taskLocation
|
|
58
59
|
? {
|
|
59
60
|
ok: true,
|
|
@@ -258,5 +259,124 @@ function writeTask(request, options = {}) {
|
|
|
258
259
|
}
|
|
259
260
|
return { ...successBase, status: 'applied', changed: true };
|
|
260
261
|
}
|
|
262
|
+
function archiveContentsHash(localDir) {
|
|
263
|
+
const files = [];
|
|
264
|
+
const walk = (dir) => {
|
|
265
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
266
|
+
if (entry.name.includes('\n') || entry.name.includes('\r') || entry.name === 'manifest.md') {
|
|
267
|
+
throw new Error(`Archive source path is not valid: ${path.join(dir, entry.name)}`);
|
|
268
|
+
}
|
|
269
|
+
const target = path.join(dir, entry.name);
|
|
270
|
+
const stat = fs.lstatSync(target);
|
|
271
|
+
if (stat.isSymbolicLink())
|
|
272
|
+
throw new Error(`Archive source cannot contain symbolic links: ${target}`);
|
|
273
|
+
if (stat.isDirectory())
|
|
274
|
+
walk(target);
|
|
275
|
+
else if (stat.isFile()) {
|
|
276
|
+
if (entry.name !== 'contents.sha256')
|
|
277
|
+
files.push(target);
|
|
278
|
+
}
|
|
279
|
+
else
|
|
280
|
+
throw new Error(`Archive source cannot contain special files: ${target}`);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
walk(localDir);
|
|
284
|
+
const rows = files
|
|
285
|
+
.map((file) => ({ file, relative: path.relative(localDir, file).split(path.sep).join('/') }))
|
|
286
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.relative), Buffer.from(right.relative)))
|
|
287
|
+
.map(({ file, relative }) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${relative}`);
|
|
288
|
+
return `${rows.join('\n')}${rows.length ? '\n' : ''}`;
|
|
289
|
+
}
|
|
290
|
+
function writeArchiveContentsHash(localDir) {
|
|
291
|
+
const contents = archiveContentsHash(localDir);
|
|
292
|
+
const hashPath = path.join(localDir, 'contents.sha256');
|
|
293
|
+
const tempPath = path.join(localDir, `.contents.sha256.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
294
|
+
try {
|
|
295
|
+
fs.writeFileSync(tempPath, contents, { flag: 'wx' });
|
|
296
|
+
fs.renameSync(tempPath, hashPath);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
try {
|
|
300
|
+
fs.unlinkSync(tempPath);
|
|
301
|
+
}
|
|
302
|
+
catch (cleanupError) {
|
|
303
|
+
if (cleanupError.code !== 'ENOENT') {
|
|
304
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function restoreArchivedTask(taskMdPath, contents, mode) {
|
|
311
|
+
const tempPath = `${taskMdPath}.${process.pid}.${Math.random().toString(36).slice(2)}.rollback`;
|
|
312
|
+
fs.writeFileSync(tempPath, contents, { flag: 'wx', mode });
|
|
313
|
+
try {
|
|
314
|
+
fs.renameSync(tempPath, taskMdPath);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
try {
|
|
318
|
+
fs.unlinkSync(tempPath);
|
|
319
|
+
}
|
|
320
|
+
catch (cleanupError) {
|
|
321
|
+
if (cleanupError.code !== 'ENOENT') {
|
|
322
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function writeTask(request, options = {}) {
|
|
329
|
+
const resolved = options.taskLocation
|
|
330
|
+
? {
|
|
331
|
+
ok: true,
|
|
332
|
+
repoRoot: options.taskLocation.repoRoot,
|
|
333
|
+
taskId: options.taskLocation.taskId,
|
|
334
|
+
taskDir: path.dirname(options.taskLocation.taskMdPath),
|
|
335
|
+
taskMdPath: options.taskLocation.taskMdPath,
|
|
336
|
+
state: options.taskLocation.state
|
|
337
|
+
}
|
|
338
|
+
: resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
339
|
+
if (!resolved.ok) {
|
|
340
|
+
return failure(request, { taskId: resolved.taskId, taskMdPath: null, actualState: null }, resolved.code, resolved.message);
|
|
341
|
+
}
|
|
342
|
+
if (resolved.state !== 'archive' || request.expectedState !== 'archive' || request.dryRun) {
|
|
343
|
+
return writeTaskCore(request, { ...options, taskLocation: {
|
|
344
|
+
repoRoot: resolved.repoRoot,
|
|
345
|
+
taskId: resolved.taskId,
|
|
346
|
+
taskMdPath: resolved.taskMdPath,
|
|
347
|
+
state: resolved.state
|
|
348
|
+
} });
|
|
349
|
+
}
|
|
350
|
+
const identity = { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, actualState: resolved.state };
|
|
351
|
+
try {
|
|
352
|
+
const originalTask = fs.readFileSync(resolved.taskMdPath);
|
|
353
|
+
const originalMode = fs.statSync(resolved.taskMdPath).mode;
|
|
354
|
+
archiveContentsHash(resolved.taskDir);
|
|
355
|
+
const result = writeTaskCore(request, { ...options, taskLocation: {
|
|
356
|
+
repoRoot: resolved.repoRoot,
|
|
357
|
+
taskId: resolved.taskId,
|
|
358
|
+
taskMdPath: resolved.taskMdPath,
|
|
359
|
+
state: resolved.state
|
|
360
|
+
} });
|
|
361
|
+
if (result.status === 'applied') {
|
|
362
|
+
try {
|
|
363
|
+
writeArchiveContentsHash(resolved.taskDir);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
try {
|
|
367
|
+
restoreArchivedTask(resolved.taskMdPath, originalTask, originalMode);
|
|
368
|
+
}
|
|
369
|
+
catch (rollbackError) {
|
|
370
|
+
throw new Error(`Archive checksum update failed: ${error instanceof Error ? error.message : String(error)}; task rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
371
|
+
}
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
return failure(request, identity, 'TASK_READ_FAILED', error instanceof Error ? error.message : String(error));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
261
381
|
export { writeTask, captureTaskWriteMetadata, canonicalTimestamp };
|
|
262
382
|
//# sourceMappingURL=write.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.7",
|
|
4
|
+
"sourceInputHash": "095f0cf30aa29fcd2dd45123dc755e5097d7a9f81de33420461fd943cff122b2",
|
|
5
|
+
"internalExecutableBuildHash": "a956f2ed366e025a111a87bc1f542b87d83d4025e068304f95b1aae704709d3f"
|
|
6
6
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import { sameFilesystemEntry } from './worktree-identity.ts';
|
|
5
|
+
|
|
6
|
+
function gitText(repositoryRoot: string, args: string[]): string {
|
|
7
|
+
return execFileSync('git', ['-C', repositoryRoot, ...args], {
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
10
|
+
}).replace(/\r?\n$/, '');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isPrimaryWorktree(candidate: string, expectedCommonDirectory: string): boolean {
|
|
14
|
+
try {
|
|
15
|
+
const root = fs.realpathSync.native(candidate);
|
|
16
|
+
if (!fs.statSync(root).isDirectory()) return false;
|
|
17
|
+
if (gitText(root, ['rev-parse', '--show-prefix']) !== '') return false;
|
|
18
|
+
|
|
19
|
+
const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
20
|
+
const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
|
|
21
|
+
return sameFilesystemEntry(commonDirectory, expectedCommonDirectory) &&
|
|
22
|
+
sameFilesystemEntry(gitDirectory, expectedCommonDirectory);
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function worktreePaths(repositoryRoot: string): string[] {
|
|
29
|
+
const output = execFileSync('git', ['-C', repositoryRoot, 'worktree', 'list', '--porcelain', '-z'], {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return output
|
|
35
|
+
.split('\0\0')
|
|
36
|
+
.flatMap((record) => record.split('\0'))
|
|
37
|
+
.filter((field) => field.startsWith('worktree '))
|
|
38
|
+
.map((field) => field.slice('worktree '.length));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolvePrimaryWorktreeRoot(repositoryRoot: string): string {
|
|
42
|
+
try {
|
|
43
|
+
const root = fs.realpathSync.native(repositoryRoot);
|
|
44
|
+
if (!fs.statSync(root).isDirectory() || gitText(root, ['rev-parse', '--show-prefix']) !== '') {
|
|
45
|
+
throw new Error('repository root is not a worktree root');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
49
|
+
const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
|
|
50
|
+
if (sameFilesystemEntry(gitDirectory, commonDirectory)) return root;
|
|
51
|
+
|
|
52
|
+
const matches = worktreePaths(root).filter((candidate) => isPrimaryWorktree(candidate, commonDirectory));
|
|
53
|
+
if (matches.length !== 1) {
|
|
54
|
+
throw new Error(matches.length === 0 ? 'primary worktree metadata was not found' : 'primary worktree metadata is ambiguous');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return fs.realpathSync.native(matches[0]!);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
60
|
+
throw new Error(`server: unable to locate primary worktree: ${detail}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
package/lib/merge.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import { removeDirRecursive } from './remove-dir.ts';
|
|
4
5
|
import { validateCurrentTaskContract } from './task/current-contract.ts';
|
|
5
6
|
|
|
@@ -107,7 +108,7 @@ function extractTitle(content: string): string | null {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
function normalizeTaskRecord(taskDir: string, taskFile: string, dateParts: DateParts): TaskRecord {
|
|
110
|
-
const taskId = path.basename(taskDir);
|
|
111
|
+
const taskId = path.basename(taskDir) === 'local' ? path.basename(path.dirname(taskDir)) : path.basename(taskDir);
|
|
111
112
|
const content = fs.readFileSync(taskFile, 'utf8');
|
|
112
113
|
const completedAt = extractField(content, 'completed_at');
|
|
113
114
|
const updatedAt = extractField(content, 'updated_at');
|
|
@@ -155,7 +156,7 @@ function scanSourceTasks(sourceDir: string): TaskRecord[] {
|
|
|
155
156
|
continue;
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
const taskDir = path.join(dayDir, taskEntry.name);
|
|
159
|
+
const taskDir = path.join(dayDir, taskEntry.name, 'local');
|
|
159
160
|
const taskFile = path.join(taskDir, 'task.md');
|
|
160
161
|
if (!fs.existsSync(taskFile)) {
|
|
161
162
|
continue;
|
|
@@ -251,7 +252,7 @@ function collectArchiveEntries(archiveDir: string): ArchiveManifestEntry[] {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
const taskDir = path.join(dayDir, taskEntry.name);
|
|
254
|
-
const taskFile = path.join(taskDir, 'task.md');
|
|
255
|
+
const taskFile = path.join(taskDir, 'local', 'task.md');
|
|
255
256
|
const relativePath = `${yearEntry.name}/${monthEntry.name}/${dayEntry.name}/${taskEntry.name}/`;
|
|
256
257
|
let title = taskEntry.name;
|
|
257
258
|
let type = 'unknown';
|
|
@@ -624,10 +625,12 @@ function validateSourceRoot(sourceRoot: string): void {
|
|
|
624
625
|
function validateTaskDirectory(
|
|
625
626
|
sourceRoot: string,
|
|
626
627
|
taskDir: string,
|
|
627
|
-
seenTaskIds: Map<string, string
|
|
628
|
+
seenTaskIds: Map<string, string>,
|
|
629
|
+
archived = false
|
|
628
630
|
): void {
|
|
629
631
|
const taskId = path.basename(taskDir);
|
|
630
|
-
const
|
|
632
|
+
const contentDir = archived ? path.join(taskDir, 'local') : taskDir;
|
|
633
|
+
const taskFile = path.join(contentDir, 'task.md');
|
|
631
634
|
const taskDirStat = sourcePathStat(sourceRoot, taskDir);
|
|
632
635
|
if (!taskDirStat || !taskDirStat.isDirectory()) {
|
|
633
636
|
invalidSource(`${sourceRelativePath(sourceRoot, taskDir)} must be a directory`);
|
|
@@ -646,6 +649,33 @@ function validateTaskDirectory(
|
|
|
646
649
|
invalidSource(`${sourceRelativePath(sourceRoot, taskFile)} id '${String(contract.metadata.id ?? 'missing')}' does not match directory '${taskId}'`);
|
|
647
650
|
}
|
|
648
651
|
validateTaskTree(sourceRoot, taskDir);
|
|
652
|
+
if (archived) {
|
|
653
|
+
for (const entry of fs.readdirSync(taskDir, { withFileTypes: true })) {
|
|
654
|
+
if (!['local', 'github', 'derived'].includes(entry.name) || !entry.isDirectory()) {
|
|
655
|
+
invalidSource(`${sourceRelativePath(sourceRoot, path.join(taskDir, entry.name))} is not an allowed archive source directory`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
validateArchiveSource(sourceRoot, contentDir);
|
|
659
|
+
const githubDir = path.join(taskDir, 'github');
|
|
660
|
+
if (fs.existsSync(githubDir)) {
|
|
661
|
+
for (const capture of fs.readdirSync(githubDir, { withFileTypes: true })) {
|
|
662
|
+
const captureDir = path.join(githubDir, capture.name);
|
|
663
|
+
if (!capture.isDirectory() || !/^capture-[A-Za-z0-9._-]+$/.test(capture.name) || !fs.existsSync(path.join(captureDir, 'capture.md'))) {
|
|
664
|
+
invalidSource(`${sourceRelativePath(sourceRoot, captureDir)} is not a valid GitHub capture`);
|
|
665
|
+
}
|
|
666
|
+
validateArchiveSource(sourceRoot, captureDir);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const derivedDir = path.join(taskDir, 'derived');
|
|
670
|
+
if (fs.existsSync(derivedDir)) {
|
|
671
|
+
for (const view of fs.readdirSync(derivedDir, { withFileTypes: true })) {
|
|
672
|
+
const viewDir = path.join(derivedDir, view.name);
|
|
673
|
+
if (!view.isDirectory() || !fs.existsSync(path.join(viewDir, 'inputs.md'))) {
|
|
674
|
+
invalidSource(`${sourceRelativePath(sourceRoot, viewDir)} requires inputs.md`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
649
679
|
|
|
650
680
|
const previousPath = seenTaskIds.get(taskId);
|
|
651
681
|
if (previousPath) {
|
|
@@ -654,6 +684,26 @@ function validateTaskDirectory(
|
|
|
654
684
|
seenTaskIds.set(taskId, sourceRelativePath(sourceRoot, taskDir));
|
|
655
685
|
}
|
|
656
686
|
|
|
687
|
+
function validateArchiveSource(sourceRoot: string, sourceDir: string): void {
|
|
688
|
+
const hashFile = path.join(sourceDir, 'contents.sha256');
|
|
689
|
+
const hashStat = sourcePathStat(sourceRoot, hashFile);
|
|
690
|
+
if (!hashStat?.isFile()) invalidSource(`${sourceRelativePath(sourceRoot, sourceDir)} requires contents.sha256`);
|
|
691
|
+
const files: string[] = [];
|
|
692
|
+
const walk = (dir: string) => {
|
|
693
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
694
|
+
const target = path.join(dir, entry.name);
|
|
695
|
+
if (entry.name === 'manifest.md') invalidSource(`${sourceRelativePath(sourceRoot, target)} is forbidden inside a TASK`);
|
|
696
|
+
if (entry.isDirectory()) walk(target);
|
|
697
|
+
else if (entry.isFile() && target !== hashFile) files.push(target);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
walk(sourceDir);
|
|
701
|
+
const expected = files.sort((a, b) => Buffer.compare(Buffer.from(path.relative(sourceDir, a).split(path.sep).join('/')), Buffer.from(path.relative(sourceDir, b).split(path.sep).join('/'))))
|
|
702
|
+
.map((file) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${path.relative(sourceDir, file).split(path.sep).join('/')}`);
|
|
703
|
+
const actual = fs.readFileSync(hashFile, 'utf8').trimEnd().split(/\r?\n/).filter(Boolean);
|
|
704
|
+
if (JSON.stringify(expected) !== JSON.stringify(actual)) invalidSource(`${sourceRelativePath(sourceRoot, hashFile)} does not match local files`);
|
|
705
|
+
}
|
|
706
|
+
|
|
657
707
|
function validateMutableSection(
|
|
658
708
|
sourceRoot: string,
|
|
659
709
|
section: MutableSection,
|
|
@@ -730,9 +780,6 @@ function validateArchiveSection(
|
|
|
730
780
|
const monthDir = monthPath;
|
|
731
781
|
for (const dayEntry of fs.readdirSync(monthDir, { withFileTypes: true })) {
|
|
732
782
|
const dayPath = path.join(monthDir, dayEntry.name);
|
|
733
|
-
if (allowManifest(monthDir, dayEntry)) {
|
|
734
|
-
continue;
|
|
735
|
-
}
|
|
736
783
|
const dayStat = sourcePathStat(sourceRoot, dayPath);
|
|
737
784
|
if (!dayStat?.isDirectory() || !/^\d{2}$/.test(dayEntry.name)) {
|
|
738
785
|
invalidSource(`${sourceRelativePath(sourceRoot, dayPath)} is not a DD directory or manifest.md`);
|
|
@@ -745,7 +792,7 @@ function validateArchiveSection(
|
|
|
745
792
|
if (!taskStat?.isDirectory() || !TASK_ID_RE.test(taskEntry.name)) {
|
|
746
793
|
invalidSource(`${sourceRelativePath(sourceRoot, taskPath)} is not a TASK-YYYYMMDD-HHMMSS directory`);
|
|
747
794
|
}
|
|
748
|
-
validateTaskDirectory(sourceRoot, taskPath, seenTaskIds);
|
|
795
|
+
validateTaskDirectory(sourceRoot, taskPath, seenTaskIds, true);
|
|
749
796
|
}
|
|
750
797
|
}
|
|
751
798
|
}
|
|
@@ -904,9 +951,10 @@ function mergeArchiveSection(sourceArchive: string, localArchive: string, report
|
|
|
904
951
|
continue;
|
|
905
952
|
}
|
|
906
953
|
|
|
954
|
+
const sourceTaskDir = path.dirname(task.taskDir);
|
|
907
955
|
const destinationDir = path.join(localArchive, task.relativePath);
|
|
908
956
|
fs.mkdirSync(path.dirname(destinationDir), { recursive: true });
|
|
909
|
-
fs.cpSync(
|
|
957
|
+
fs.cpSync(sourceTaskDir, destinationDir, { recursive: true });
|
|
910
958
|
recordArchive(report, 'copied', {
|
|
911
959
|
action: 'copied',
|
|
912
960
|
symbol: '✓',
|
|
@@ -1045,6 +1093,7 @@ async function cmdMerge(args: string[]): Promise<void> {
|
|
|
1045
1093
|
const report = createReport(resolvedSource, backupRootRelative);
|
|
1046
1094
|
detectSourceMode(resolvedSource);
|
|
1047
1095
|
validateSourceWorkspace(resolvedSource);
|
|
1096
|
+
validateArchiveSection(workspaceDir, new Map());
|
|
1048
1097
|
|
|
1049
1098
|
for (const section of ALL_SECTIONS) {
|
|
1050
1099
|
fs.mkdirSync(path.join(workspaceDir, section), { recursive: true });
|
|
@@ -923,30 +923,23 @@ function createGitHubOperations(client: GitHubClient): Pick<PlatformProvider, 'i
|
|
|
923
923
|
if (collected.status === 'failed' || collected.status === 'blocked' || !('pullRequests' in collected)) {
|
|
924
924
|
return { ok: false, error: collected.error || { code: 'RELEASE_NOTES_COLLECTION_FAILED', message: 'Release notes could not be collected', retryable: false } };
|
|
925
925
|
}
|
|
926
|
-
const mergedPullRequests = collected.pullRequests.map((item: any)
|
|
927
|
-
id: String(item.number), identity: { kind: 'number', value: Number(item.number) }, number: Number(item.number),
|
|
928
|
-
|
|
926
|
+
const mergedPullRequests = collected.pullRequests.map((item: any) => ({
|
|
927
|
+
id: String(item.number), identity: { kind: 'number' as const, value: Number(item.number) }, number: Number(item.number),
|
|
928
|
+
title: String(item.title || ''), body: String(item.body || ''),
|
|
929
929
|
mergedAt: new Date(String(item.mergedAt)).toISOString(), displayUrl: String(item.url || ''),
|
|
930
930
|
labels: Array.isArray(item.labels) ? item.labels.map((label: any) => String(label.name || label)) : [],
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const closingIssues: IssueSnapshot[] = collected.pullRequests.flatMap((item: any) =>
|
|
934
|
-
Array.isArray(item.closingIssuesReferences) ? item.closingIssuesReferences.map((issue: any) => ({
|
|
931
|
+
author: item.author,
|
|
932
|
+
closingIssues: item.closingIssues.map((issue: any) => ({
|
|
935
933
|
id: String(issue.number), identity: { kind: 'number', value: Number(issue.number) }, number: Number(issue.number),
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
);
|
|
940
|
-
const actors = [...collected.authors.values()].flatMap((items) => items.map((item: any) => ({
|
|
941
|
-
...(item.login ? { id: String(item.login) } : {}), ...(item.name ? { name: String(item.name) } : {})
|
|
942
|
-
})));
|
|
934
|
+
title: String(issue.title || ''), displayUrl: String(issue.url || ''), author: issue.author
|
|
935
|
+
}))
|
|
936
|
+
}));
|
|
943
937
|
return {
|
|
944
938
|
ok: true,
|
|
945
939
|
value: {
|
|
946
|
-
history:
|
|
940
|
+
history: collected.history,
|
|
941
|
+
commits: collected.commits,
|
|
947
942
|
mergedPullRequests,
|
|
948
|
-
closingIssues,
|
|
949
|
-
actors
|
|
950
943
|
}
|
|
951
944
|
};
|
|
952
945
|
}
|
|
@@ -1116,7 +1109,7 @@ function createGitHubProvider(
|
|
|
1116
1109
|
): PlatformProvider {
|
|
1117
1110
|
return {
|
|
1118
1111
|
type: input.providerType,
|
|
1119
|
-
contractVersion:
|
|
1112
|
+
contractVersion: 2,
|
|
1120
1113
|
identity: { issue: 'number', 'pull-request': 'number', comment: 'number', release: 'key' },
|
|
1121
1114
|
...createGitHubOperations(client),
|
|
1122
1115
|
context: {
|