coding-agent-harness 1.0.4 → 1.0.5
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/CHANGELOG.md +7 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +33 -1
- package/README.zh-CN.md +23 -1
- package/SKILL.md +9 -8
- package/docs-release/architecture/overview.md +1 -1
- package/docs-release/architecture/overview.zh-CN.md +1 -1
- package/docs-release/architecture/system-explainer/01-system-overview.md +217 -0
- package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +239 -0
- package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +303 -0
- package/docs-release/architecture/system-explainer/README.md +67 -0
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +226 -0
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +250 -0
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +323 -0
- package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
- package/docs-release/guides/agent-installation.en-US.md +8 -7
- package/docs-release/guides/agent-installation.md +9 -7
- package/docs-release/guides/preset-development.md +26 -2
- package/docs-release/guides/task-state-machine.en-US.md +30 -13
- package/docs-release/guides/task-state-machine.md +30 -13
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
- package/package.json +3 -2
- package/references/harness-ledger.md +1 -1
- package/scripts/commands/migration-command.mjs +30 -0
- package/scripts/commands/task-command.mjs +26 -25
- package/scripts/harness.mjs +7 -3
- package/scripts/lib/capability-registry.mjs +17 -21
- package/scripts/lib/check-module-parallel.mjs +9 -16
- package/scripts/lib/check-profiles.mjs +35 -81
- package/scripts/lib/check-task-contracts.mjs +13 -5
- package/scripts/lib/core-shared.mjs +55 -2
- package/scripts/lib/dashboard-data.mjs +126 -18
- package/scripts/lib/dashboard-workbench.mjs +80 -1
- package/scripts/lib/dashboard-writer.mjs +6 -2
- package/scripts/lib/git-status-summary.mjs +1 -1
- package/scripts/lib/governance-sync.mjs +180 -83
- package/scripts/lib/harness-core.mjs +1 -0
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-engine.mjs +5 -8
- package/scripts/lib/preset-registry.mjs +188 -39
- package/scripts/lib/review-confirm-git-gate.mjs +1 -1
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +7 -4
- package/scripts/lib/task-audit-metadata.mjs +385 -0
- package/scripts/lib/task-audit-migration.mjs +350 -0
- package/scripts/lib/task-completion-consistency.mjs +11 -1
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +67 -0
- package/scripts/lib/task-lifecycle/phase-sync.mjs +88 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +40 -29
- package/scripts/lib/task-lifecycle/review-gates.mjs +13 -10
- package/scripts/lib/task-lifecycle/review-submission.mjs +63 -0
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +49 -0
- package/scripts/lib/task-lifecycle/template-files.mjs +53 -0
- package/scripts/lib/task-lifecycle.mjs +114 -147
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +54 -68
- package/scripts/lib/task-scanner.mjs +70 -143
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
- package/templates/AGENTS.md.template +7 -5
- package/templates/dashboard/assets/app-src/00-state.js +12 -0
- package/templates/dashboard/assets/app-src/10-router.js +3 -0
- package/templates/dashboard/assets/app-src/20-overview.js +7 -3
- package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
- package/templates/dashboard/assets/app-src/55-presets.js +375 -0
- package/templates/dashboard/assets/app-src/60-shared.js +3 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
- package/templates/dashboard/assets/app.css +583 -0
- package/templates/dashboard/assets/app.css.manifest.json +1 -0
- package/templates/dashboard/assets/app.js +578 -10
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/i18n.js +140 -2
- package/templates/planning/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/review.md +0 -18
- package/templates/planning/task_plan.md +4 -43
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +29 -2
- package/templates-zh-CN/AGENTS.md.template +7 -5
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +0 -18
- package/templates-zh-CN/planning/task_plan.md +3 -63
- package/templates-zh-CN/planning/visual_map.md +14 -7
- package/templates-zh-CN/planning/visual_map.simple.md +48 -0
- package/templates-zh-CN/reference/execution-workflow-standard.md +31 -6
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
allowedTaskStates,
|
|
3
|
+
allowedTaskBudgets,
|
|
4
|
+
taskContractMarker,
|
|
5
|
+
} from "./core-shared.mjs";
|
|
6
|
+
import { tableAfterHeading, firstColumn } from "./markdown-utils.mjs";
|
|
7
|
+
|
|
8
|
+
export function parseTaskState(progressContent) {
|
|
9
|
+
return parseTaskStateInfo(progressContent).state;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function parseTaskBudget(taskPlanContent) {
|
|
13
|
+
const match =
|
|
14
|
+
String(taskPlanContent || "").match(/^Selected budget\s*[::]\s*([^\n]+)/im) ||
|
|
15
|
+
String(taskPlanContent || "").match(/^选择预算\s*[::]\s*([^\n]+)/im);
|
|
16
|
+
if (!match) return "standard";
|
|
17
|
+
const raw = match[1].replace(/`/g, "").trim().toLowerCase();
|
|
18
|
+
const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
|
|
19
|
+
if (allowedTaskBudgets.has(normalized)) return normalized;
|
|
20
|
+
if (["long-running", "longrunning", "module-parallel"].includes(normalized)) return "complex";
|
|
21
|
+
return "standard";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseMetadataLine(content, labels) {
|
|
25
|
+
const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
26
|
+
const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
|
|
27
|
+
return match ? match[1].replace(/`/g, "").trim() : "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeMetadataValue(value, fallback = "") {
|
|
31
|
+
const normalized = String(value || "")
|
|
32
|
+
.replace(/`/g, "")
|
|
33
|
+
.trim()
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replaceAll("_", "-")
|
|
36
|
+
.replace(/\s+/g, "-");
|
|
37
|
+
return normalized || fallback;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseTaskMetadata(taskPlanContent) {
|
|
41
|
+
const content = String(taskPlanContent || "");
|
|
42
|
+
const kind = normalizeMetadataValue(parseMetadataLine(content, ["Task Kind", "任务类型"]), "general");
|
|
43
|
+
const preset = normalizeMetadataValue(parseMetadataLine(content, ["Task Preset", "Preset", "任务预设"]), "none");
|
|
44
|
+
const presetVersion = parseMetadataLine(content, ["Preset Version", "预设版本"]);
|
|
45
|
+
const migrationTargetLevel = normalizeMetadataValue(
|
|
46
|
+
parseMetadataLine(content, ["Migration Target Level", "Target Level", "迁移目标等级", "目标等级"]),
|
|
47
|
+
"",
|
|
48
|
+
);
|
|
49
|
+
const migrationAchievedLevel = normalizeMetadataValue(
|
|
50
|
+
parseMetadataLine(content, ["Migration Achieved Level", "Achieved Level", "迁移实际完成等级", "实际完成等级"]),
|
|
51
|
+
"",
|
|
52
|
+
);
|
|
53
|
+
const evidenceBundle = parseMetadataLine(content, ["Evidence Bundle", "证据包"]);
|
|
54
|
+
return {
|
|
55
|
+
kind,
|
|
56
|
+
preset,
|
|
57
|
+
presetVersion,
|
|
58
|
+
migrationTargetLevel,
|
|
59
|
+
migrationAchievedLevel,
|
|
60
|
+
evidenceBundle,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function parseTaskContractInfo(taskPlanContent) {
|
|
65
|
+
const content = String(taskPlanContent || "");
|
|
66
|
+
const explicit =
|
|
67
|
+
content.match(/^Task Contract\s*[::]\s*`?([^`\n]+)`?\s*$/im) ||
|
|
68
|
+
content.match(/^任务合同\s*[::]\s*`?([^`\n]+)`?\s*$/im);
|
|
69
|
+
const version = explicit ? explicit[1].trim() : "";
|
|
70
|
+
return {
|
|
71
|
+
version,
|
|
72
|
+
generated: version === "harness-task/v1" || content.includes(taskContractMarker),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function parseTaskStateInfo(progressContent) {
|
|
77
|
+
const match = progressContent.match(/^##\s*(?:Current Status|Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
78
|
+
if (!match) return inferLegacyTaskState(progressContent);
|
|
79
|
+
const raw = match[1].replace(/`/g, "").trim();
|
|
80
|
+
if (!raw || raw.includes("|") || /^[-*]\s+/.test(raw)) return inferLegacyTaskState(progressContent);
|
|
81
|
+
const aliases = new Map([
|
|
82
|
+
["进行中", "in_progress"],
|
|
83
|
+
["已完成", "done"],
|
|
84
|
+
["未开始", "not_started"],
|
|
85
|
+
["计划中", "planned"],
|
|
86
|
+
["审查中", "review"],
|
|
87
|
+
["已阻塞", "blocked"],
|
|
88
|
+
["pending", "planned"],
|
|
89
|
+
]);
|
|
90
|
+
const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
91
|
+
return allowedTaskStates.has(normalized)
|
|
92
|
+
? { state: normalized, source: "explicit", raw }
|
|
93
|
+
: { state: "unknown", source: "invalid", raw };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function inferLegacyTaskState(progressContent) {
|
|
97
|
+
const { header, rows } = tableAfterHeading(progressContent, /^(Status|状态)$/i);
|
|
98
|
+
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
99
|
+
if (statusIndex < 0 || rows.length === 0) return { state: "unknown", source: "missing", raw: "" };
|
|
100
|
+
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(Boolean);
|
|
101
|
+
if (states.includes("blocked")) return { state: "blocked", source: "legacy-table", raw: "blocked" };
|
|
102
|
+
if (states.includes("in_progress")) return { state: "in_progress", source: "legacy-table", raw: "in_progress" };
|
|
103
|
+
if (states.includes("review")) return { state: "review", source: "legacy-table", raw: "review" };
|
|
104
|
+
if (states.length > 0 && states.every((state) => state === "done")) return { state: "done", source: "legacy-table", raw: "done" };
|
|
105
|
+
if (states.some((state) => ["planned", "not_started"].includes(state))) return { state: "planned", source: "legacy-table", raw: "planned" };
|
|
106
|
+
return { state: "unknown", source: "missing", raw: "" };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeLegacyState(value) {
|
|
110
|
+
const raw = String(value || "").replace(/`/g, "").trim().toLowerCase();
|
|
111
|
+
if (!raw || /^(none|n\/a|na|-|—|–|无)$/.test(raw)) return "";
|
|
112
|
+
if (/block|阻塞|blocked/.test(raw)) return "blocked";
|
|
113
|
+
if (/in[-_\s]?progress|doing|active|进行中|当前|working/.test(raw)) return "in_progress";
|
|
114
|
+
if (/review|审查|审核|验证中/.test(raw)) return "review";
|
|
115
|
+
if (/done|complete|completed|merged|closed|完成|已完成/.test(raw)) return "done";
|
|
116
|
+
if (/pending|planned|todo|not[-_\s]?started|未开始|计划/.test(raw)) return "planned";
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
@@ -12,10 +12,15 @@ import {
|
|
|
12
12
|
splitMarkdownRow,
|
|
13
13
|
tableAfterHeading,
|
|
14
14
|
} from "./markdown-utils.mjs";
|
|
15
|
+
import {
|
|
16
|
+
implementationPhases,
|
|
17
|
+
phaseHasRecordedProgress,
|
|
18
|
+
} from "./phase-kind.mjs";
|
|
15
19
|
import { validateReviewConfirmationGitAudit } from "./review-confirm-git-gate.mjs";
|
|
16
20
|
import { isLessonCandidateDecisionComplete } from "./task-lesson-candidates.mjs";
|
|
21
|
+
import { reviewConfirmationFromTaskAudit } from "./task-audit-metadata.mjs";
|
|
17
22
|
|
|
18
|
-
export const taskScannerVersion = "task-scanner/2026-05-
|
|
23
|
+
export const taskScannerVersion = "task-scanner/2026-05-25-phase-kind";
|
|
19
24
|
export const reviewFindingColumns = {
|
|
20
25
|
severity: ["Severity", "严重级别", "优先级"],
|
|
21
26
|
finding: ["Finding", "发现"],
|
|
@@ -158,15 +163,12 @@ export function assessMaterialsReadiness({ budget, taskDir, brief, visualMap, re
|
|
|
158
163
|
if (!isLessonCandidateDecisionComplete(lessonCandidates)) {
|
|
159
164
|
addIssue("missing-lesson-decision", `Lesson candidate decision is not complete: ${lessonCandidates.status}.`, `TARGET:${lessonCandidatesFile}`);
|
|
160
165
|
}
|
|
161
|
-
const actionablePhases = (phases || [])
|
|
162
|
-
const hasPhaseEvidence = actionablePhases.some(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
);
|
|
168
|
-
if (actionablePhases.length > 0 && !hasPhaseEvidence) {
|
|
169
|
-
addIssue("phase-incomplete", "Visual Map has no phase progress or evidence yet.", `TARGET:${visualMapFile}`);
|
|
166
|
+
const actionablePhases = implementationPhases(phases || []);
|
|
167
|
+
const hasPhaseEvidence = actionablePhases.some(phaseHasRecordedProgress);
|
|
168
|
+
if ((phases || []).length > 0 && actionablePhases.length === 0) {
|
|
169
|
+
addIssue("missing-execution-phase", "Visual Map has no non-skipped execution phase.", `TARGET:${visualMapFile}`);
|
|
170
|
+
} else if (actionablePhases.length > 0 && !hasPhaseEvidence) {
|
|
171
|
+
addIssue("phase-incomplete", "Visual Map has no execution phase progress or evidence yet.", `TARGET:${visualMapFile}`);
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
return { ready: issues.length === 0, issues };
|
|
@@ -181,7 +183,7 @@ export function requiresReviewMaterials({ state = "unknown", lifecycleState = "u
|
|
|
181
183
|
);
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
export function deriveTaskQueues({ id, title, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target }) {
|
|
186
|
+
export function deriveTaskQueues({ id, title, state, budget, reviewStatus, reviewSubmission, reviewConfirmation, reviewQueueState, materialIssues, risks, stateConflicts, lessonCandidates, closeoutStatus, tombstone, taskDir, target }) {
|
|
185
187
|
const queueReasons = [];
|
|
186
188
|
const pushReason = (reason) => {
|
|
187
189
|
queueReasons.push({
|
|
@@ -227,17 +229,26 @@ export function deriveTaskQueues({ id, title, reviewStatus, reviewSubmission, re
|
|
|
227
229
|
message: "Agent Review Submission was generated by a stale scanner version.",
|
|
228
230
|
});
|
|
229
231
|
}
|
|
232
|
+
if (budget !== "simple" && reviewSubmission?.submitted && reviewQueueState === "needs-material" && !queueReasons.some((reason) => reason.queue === "missing-materials")) {
|
|
233
|
+
pushReason({
|
|
234
|
+
code: "review-closeout-materials-incomplete",
|
|
235
|
+
queue: "missing-materials",
|
|
236
|
+
sourcePath: "TARGET:docs/10-WALKTHROUGH/Closeout-SSoT.md",
|
|
237
|
+
message: "Agent review was submitted, but closeout materials are not ready for human confirmation.",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
230
240
|
const hasLessonWork = lessonCandidates?.status === "needs-promotion" || lessonCandidates?.promotionState === "queued" || lessonCandidates?.openCount > 0;
|
|
231
241
|
const taskQueues = [];
|
|
232
242
|
if (tombstone.deletionState !== "active") {
|
|
233
243
|
taskQueues.push("soft-deleted-superseded");
|
|
234
244
|
} else {
|
|
235
|
-
if ((materialIssues || []).length > 0) taskQueues.push("missing-materials");
|
|
245
|
+
if ((materialIssues || []).length > 0 || queueReasons.some((reason) => reason.queue === "missing-materials")) taskQueues.push("missing-materials");
|
|
236
246
|
if (queueReasons.some((reason) => reason.queue === "blocked")) taskQueues.push("blocked");
|
|
237
247
|
if (reviewSubmission?.submitted && reviewQueueState === "ready-to-confirm" && !reviewConfirmation?.confirmed && !hasLessonWork && !taskQueues.includes("blocked") && !taskQueues.includes("missing-materials")) {
|
|
238
248
|
taskQueues.push("review");
|
|
239
249
|
}
|
|
240
250
|
if (hasLessonWork) taskQueues.push("lessons");
|
|
251
|
+
if (budget === "simple" && state === "done" && closeoutStatus === "closed") taskQueues.push("finalized");
|
|
241
252
|
if (reviewStatus === "confirmed") taskQueues.push(closeoutStatus === "closed" ? "finalized" : "confirmed");
|
|
242
253
|
}
|
|
243
254
|
if (taskQueues.length === 0) taskQueues.push("active");
|
|
@@ -248,60 +259,34 @@ export function deriveTaskQueues({ id, title, reviewStatus, reviewSubmission, re
|
|
|
248
259
|
};
|
|
249
260
|
}
|
|
250
261
|
|
|
251
|
-
export function parseReviewConfirmation(reviewContent, { taskKey = "", projectRoot = "", taskDir = "", reviewPath = "", progressPath = "" } = {}) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
...(taskKeyMismatch ? ["Task Key match"] : []),
|
|
278
|
-
...(confirmTextMismatch ? ["Confirm Text match"] : []),
|
|
279
|
-
...(commitShaInvalid ? ["Commit SHA valid"] : []),
|
|
280
|
-
...(auditStatusInvalid ? ["Audit Status committed"] : []),
|
|
281
|
-
...(gitAuditInvalid ? ["Commit SHA git audit"] : []),
|
|
282
|
-
];
|
|
283
|
-
if (fields.size > 0) {
|
|
284
|
-
return {
|
|
285
|
-
confirmed: missing.length === 0 && invalidFields.length === 0,
|
|
286
|
-
missingFields: [...missing, ...invalidFields],
|
|
287
|
-
confirmationId: fields.get("confirmation id") || "",
|
|
288
|
-
confirmedAt: fields.get("confirmed at") || "",
|
|
289
|
-
reviewer: fields.get("reviewer") || "",
|
|
290
|
-
reviewerEmail: fields.get("reviewer email") || "",
|
|
291
|
-
taskKey: confirmedTaskKey,
|
|
292
|
-
taskKeyMismatch,
|
|
293
|
-
confirmText,
|
|
294
|
-
confirmTextMismatch,
|
|
295
|
-
evidenceChecked: fields.get("evidence checked") || "",
|
|
296
|
-
commitSha,
|
|
297
|
-
commitShaInvalid,
|
|
298
|
-
auditStatus,
|
|
299
|
-
auditStatusInvalid,
|
|
300
|
-
gitAudit,
|
|
301
|
-
gitAuditInvalid,
|
|
302
|
-
};
|
|
262
|
+
export function parseReviewConfirmation(reviewContent, { taskKey = "", taskAudit = null, projectRoot = "", taskDir = "", indexPath = "", reviewPath = "", progressPath = "" } = {}) {
|
|
263
|
+
if (taskAudit) {
|
|
264
|
+
const confirmation = reviewConfirmationFromTaskAudit(taskAudit, { taskKey });
|
|
265
|
+
if (
|
|
266
|
+
confirmation?.confirmed &&
|
|
267
|
+
confirmation.auditSource !== "migrated-legacy-review" &&
|
|
268
|
+
projectRoot &&
|
|
269
|
+
(indexPath || taskDir) &&
|
|
270
|
+
confirmation.commitSha
|
|
271
|
+
) {
|
|
272
|
+
const gitAudit = validateReviewConfirmationGitAudit({
|
|
273
|
+
projectRoot,
|
|
274
|
+
taskId: taskKey,
|
|
275
|
+
reviewPath: indexPath || path.join(taskDir, "INDEX.md"),
|
|
276
|
+
progressPath: "",
|
|
277
|
+
commitSha: confirmation.commitSha,
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
...confirmation,
|
|
281
|
+
confirmed: confirmation.confirmed && gitAudit.valid,
|
|
282
|
+
missingFields: gitAudit.valid ? confirmation.missingFields : [...confirmation.missingFields, "Review Commit SHA git audit"],
|
|
283
|
+
gitAudit,
|
|
284
|
+
gitAuditInvalid: !gitAudit.valid,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return confirmation;
|
|
303
288
|
}
|
|
304
|
-
return
|
|
289
|
+
return null;
|
|
305
290
|
}
|
|
306
291
|
|
|
307
292
|
export function taskReviewStatus({ reviewContent = "", risks = [], confirmation = null, submission = null } = {}) {
|
|
@@ -327,8 +312,9 @@ export function isBlockingReviewRisk(risk) {
|
|
|
327
312
|
return /^P[0-2]$/i.test(risk?.severity || "") && (risk.open || risk.blocksRelease);
|
|
328
313
|
}
|
|
329
314
|
|
|
330
|
-
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing" } = {}) {
|
|
315
|
+
export function deriveLifecycleState({ state = "unknown", reviewStatus = "missing", closeoutStatus = "missing", budget = "standard" } = {}) {
|
|
331
316
|
if (reviewStatus === "blocked-open-findings") return "review-blocked";
|
|
317
|
+
if (budget === "simple" && closeoutStatus === "closed") return "closed";
|
|
332
318
|
if (closeoutStatus === "closed" && reviewStatus !== "confirmed") return "closed-review-pending";
|
|
333
319
|
if (closeoutStatus === "closed") return "closed";
|
|
334
320
|
if (state === "blocked") return "blocked";
|
|
@@ -354,12 +340,12 @@ export function deriveReviewQueueState({ state = "unknown", lifecycleState = "un
|
|
|
354
340
|
return "ready-to-confirm";
|
|
355
341
|
}
|
|
356
342
|
|
|
357
|
-
export function collectStateConflicts({ state, reviewStatus, closeoutStatus, lifecycleState }) {
|
|
343
|
+
export function collectStateConflicts({ state, reviewStatus, closeoutStatus, lifecycleState, budget = "standard" }) {
|
|
358
344
|
const conflicts = [];
|
|
359
345
|
if (state === "done" && closeoutStatus !== "closed") {
|
|
360
346
|
conflicts.push({ code: "done-without-closeout", severity: "warn", message: "Task state is done, but closeout is still missing or pending." });
|
|
361
347
|
}
|
|
362
|
-
if (closeoutStatus === "closed" && reviewStatus !== "confirmed") {
|
|
348
|
+
if (closeoutStatus === "closed" && reviewStatus !== "confirmed" && budget !== "simple") {
|
|
363
349
|
conflicts.push({ code: "closed-without-human-review", severity: "warn", message: "Task is closed, but human review confirmation is still missing." });
|
|
364
350
|
}
|
|
365
351
|
if (reviewStatus === "blocked-open-findings") {
|