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
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
|
-
allowedTaskStates,
|
|
5
|
-
allowedTaskBudgets,
|
|
6
4
|
visualMapFile,
|
|
7
5
|
legacyVisualRoadmapFile,
|
|
8
6
|
lessonCandidatesFile,
|
|
9
7
|
longRunningTaskContractFile,
|
|
10
|
-
taskContractMarker,
|
|
11
8
|
toPosix,
|
|
12
9
|
readFileSafe,
|
|
10
|
+
readJsonSafe,
|
|
13
11
|
walkFiles,
|
|
14
12
|
titleFromMarkdown,
|
|
15
13
|
} from "./core-shared.mjs";
|
|
@@ -19,6 +17,23 @@ import {
|
|
|
19
17
|
splitList,
|
|
20
18
|
splitDependencies,
|
|
21
19
|
} from "./markdown-utils.mjs";
|
|
20
|
+
import {
|
|
21
|
+
normalizePhaseActor,
|
|
22
|
+
normalizePhaseKind,
|
|
23
|
+
phaseCompletionAverage,
|
|
24
|
+
} from "./phase-kind.mjs";
|
|
25
|
+
import {
|
|
26
|
+
legacyAuditIssues,
|
|
27
|
+
parseTaskAuditMetadata,
|
|
28
|
+
scaffoldProvenanceSummaryFromTaskAudit,
|
|
29
|
+
taskAuditMaterialIssues,
|
|
30
|
+
} from "./task-audit-metadata.mjs";
|
|
31
|
+
import {
|
|
32
|
+
parseTaskBudget,
|
|
33
|
+
parseTaskContractInfo,
|
|
34
|
+
parseTaskMetadata,
|
|
35
|
+
parseTaskStateInfo,
|
|
36
|
+
} from "./task-metadata.mjs";
|
|
22
37
|
import {
|
|
23
38
|
isLessonCandidateDecisionComplete,
|
|
24
39
|
parseLessonCandidateStatus,
|
|
@@ -40,6 +55,13 @@ import {
|
|
|
40
55
|
taskReviewStatus,
|
|
41
56
|
taskScannerVersion,
|
|
42
57
|
} from "./task-review-model.mjs";
|
|
58
|
+
export {
|
|
59
|
+
parseTaskBudget,
|
|
60
|
+
parseTaskContractInfo,
|
|
61
|
+
parseTaskMetadata,
|
|
62
|
+
parseTaskState,
|
|
63
|
+
parseTaskStateInfo,
|
|
64
|
+
} from "./task-metadata.mjs";
|
|
43
65
|
export {
|
|
44
66
|
collectReviewRisks,
|
|
45
67
|
deriveLifecycleState,
|
|
@@ -53,6 +75,9 @@ export {
|
|
|
53
75
|
taskReviewStatus,
|
|
54
76
|
taskScannerVersion,
|
|
55
77
|
} from "./task-review-model.mjs";
|
|
78
|
+
export {
|
|
79
|
+
parseTaskAuditMetadata,
|
|
80
|
+
} from "./task-audit-metadata.mjs";
|
|
56
81
|
export {
|
|
57
82
|
allowedLessonCandidateRowStatuses,
|
|
58
83
|
allowedLessonCandidateTaskStatuses,
|
|
@@ -61,139 +86,33 @@ export {
|
|
|
61
86
|
reviewCompleteLessonCandidateStatuses,
|
|
62
87
|
} from "./task-lesson-candidates.mjs";
|
|
63
88
|
|
|
64
|
-
export function parseTaskState(progressContent) {
|
|
65
|
-
return parseTaskStateInfo(progressContent).state;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function parseTaskBudget(taskPlanContent) {
|
|
69
|
-
const match =
|
|
70
|
-
String(taskPlanContent || "").match(/^Selected budget\s*[::]\s*([^\n]+)/im) ||
|
|
71
|
-
String(taskPlanContent || "").match(/^选择预算\s*[::]\s*([^\n]+)/im);
|
|
72
|
-
if (!match) return "standard";
|
|
73
|
-
const raw = match[1].replace(/`/g, "").trim().toLowerCase();
|
|
74
|
-
const normalized = raw.replaceAll("_", "-").replace(/\s+/g, "-");
|
|
75
|
-
if (allowedTaskBudgets.has(normalized)) return normalized;
|
|
76
|
-
if (["long-running", "longrunning", "module-parallel"].includes(normalized)) return "complex";
|
|
77
|
-
return "standard";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function parseMetadataLine(content, labels) {
|
|
81
|
-
const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
82
|
-
const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
|
|
83
|
-
return match ? match[1].replace(/`/g, "").trim() : "";
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function normalizeMetadataValue(value, fallback = "") {
|
|
87
|
-
const normalized = String(value || "")
|
|
88
|
-
.replace(/`/g, "")
|
|
89
|
-
.trim()
|
|
90
|
-
.toLowerCase()
|
|
91
|
-
.replaceAll("_", "-")
|
|
92
|
-
.replace(/\s+/g, "-");
|
|
93
|
-
return normalized || fallback;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function parseTaskMetadata(taskPlanContent) {
|
|
97
|
-
const content = String(taskPlanContent || "");
|
|
98
|
-
const kind = normalizeMetadataValue(parseMetadataLine(content, ["Task Kind", "任务类型"]), "general");
|
|
99
|
-
const preset = normalizeMetadataValue(parseMetadataLine(content, ["Task Preset", "Preset", "任务预设"]), "none");
|
|
100
|
-
const presetVersion = parseMetadataLine(content, ["Preset Version", "预设版本"]);
|
|
101
|
-
const migrationTargetLevel = normalizeMetadataValue(
|
|
102
|
-
parseMetadataLine(content, ["Migration Target Level", "Target Level", "迁移目标等级", "目标等级"]),
|
|
103
|
-
"",
|
|
104
|
-
);
|
|
105
|
-
const migrationAchievedLevel = normalizeMetadataValue(
|
|
106
|
-
parseMetadataLine(content, ["Migration Achieved Level", "Achieved Level", "迁移实际完成等级", "实际完成等级"]),
|
|
107
|
-
"",
|
|
108
|
-
);
|
|
109
|
-
const evidenceBundle = parseMetadataLine(content, ["Evidence Bundle", "证据包"]);
|
|
110
|
-
return {
|
|
111
|
-
kind,
|
|
112
|
-
preset,
|
|
113
|
-
presetVersion,
|
|
114
|
-
migrationTargetLevel,
|
|
115
|
-
migrationAchievedLevel,
|
|
116
|
-
evidenceBundle,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function parseTaskContractInfo(taskPlanContent) {
|
|
121
|
-
const content = String(taskPlanContent || "");
|
|
122
|
-
const explicit =
|
|
123
|
-
content.match(/^Task Contract\s*[::]\s*`?([^`\n]+)`?\s*$/im) ||
|
|
124
|
-
content.match(/^任务合同\s*[::]\s*`?([^`\n]+)`?\s*$/im);
|
|
125
|
-
const version = explicit ? explicit[1].trim() : "";
|
|
126
|
-
return {
|
|
127
|
-
version,
|
|
128
|
-
generated: version === "harness-task/v1" || content.includes(taskContractMarker),
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function parseTaskStateInfo(progressContent) {
|
|
133
|
-
const match = progressContent.match(/^##\s*(?:Current Status|Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
134
|
-
if (!match) return inferLegacyTaskState(progressContent);
|
|
135
|
-
const raw = match[1].replace(/`/g, "").trim();
|
|
136
|
-
if (!raw || raw.includes("|") || /^[-*]\s+/.test(raw)) return inferLegacyTaskState(progressContent);
|
|
137
|
-
const aliases = new Map([
|
|
138
|
-
["进行中", "in_progress"],
|
|
139
|
-
["已完成", "done"],
|
|
140
|
-
["未开始", "not_started"],
|
|
141
|
-
["计划中", "planned"],
|
|
142
|
-
["审查中", "review"],
|
|
143
|
-
["已阻塞", "blocked"],
|
|
144
|
-
["pending", "planned"],
|
|
145
|
-
]);
|
|
146
|
-
const normalized = aliases.get(raw) || raw.toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
|
|
147
|
-
return allowedTaskStates.has(normalized)
|
|
148
|
-
? { state: normalized, source: "explicit", raw }
|
|
149
|
-
: { state: "unknown", source: "invalid", raw };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function inferLegacyTaskState(progressContent) {
|
|
153
|
-
const { header, rows } = tableAfterHeading(progressContent, /^(Status|状态)$/i);
|
|
154
|
-
const statusIndex = firstColumn(header, ["Status", "状态"]);
|
|
155
|
-
if (statusIndex < 0 || rows.length === 0) return { state: "unknown", source: "missing", raw: "" };
|
|
156
|
-
const states = rows.map((row) => normalizeLegacyState(row[statusIndex])).filter(Boolean);
|
|
157
|
-
if (states.includes("blocked")) return { state: "blocked", source: "legacy-table", raw: "blocked" };
|
|
158
|
-
if (states.includes("in_progress")) return { state: "in_progress", source: "legacy-table", raw: "in_progress" };
|
|
159
|
-
if (states.includes("review")) return { state: "review", source: "legacy-table", raw: "review" };
|
|
160
|
-
if (states.length > 0 && states.every((state) => state === "done")) return { state: "done", source: "legacy-table", raw: "done" };
|
|
161
|
-
if (states.some((state) => ["planned", "not_started"].includes(state))) return { state: "planned", source: "legacy-table", raw: "planned" };
|
|
162
|
-
return { state: "unknown", source: "missing", raw: "" };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function normalizeLegacyState(value) {
|
|
166
|
-
const raw = String(value || "").replace(/`/g, "").trim().toLowerCase();
|
|
167
|
-
if (!raw || /^(none|n\/a|na|-|—|–|无)$/.test(raw)) return "";
|
|
168
|
-
if (/block|阻塞|blocked/.test(raw)) return "blocked";
|
|
169
|
-
if (/in[-_\s]?progress|doing|active|进行中|当前|working/.test(raw)) return "in_progress";
|
|
170
|
-
if (/review|审查|审核|验证中/.test(raw)) return "review";
|
|
171
|
-
if (/done|complete|completed|merged|closed|完成|已完成/.test(raw)) return "done";
|
|
172
|
-
if (/pending|planned|todo|not[-_\s]?started|未开始|计划/.test(raw)) return "planned";
|
|
173
|
-
return "";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
89
|
export function parsePhases(taskPlanContent) {
|
|
177
90
|
const { header, rows } = tableAfterHeading(taskPlanContent, /^Phase ID$/i);
|
|
178
91
|
if (rows.length === 0) return [];
|
|
179
92
|
const indexes = {
|
|
180
93
|
id: firstColumn(header, ["Phase ID", "阶段 ID"]),
|
|
94
|
+
kind: firstColumn(header, ["Kind", "阶段类型", "类型"]),
|
|
181
95
|
dependsOn: firstColumn(header, ["Depends On", "依赖"]),
|
|
182
96
|
state: firstColumn(header, ["State", "状态"]),
|
|
183
97
|
completion: firstColumn(header, ["Completion", "完成度"]),
|
|
184
98
|
output: firstColumn(header, ["Output", "产出"]),
|
|
185
99
|
requiredEvidence: firstColumn(header, ["Required Evidence", "必要证据"]),
|
|
100
|
+
exitCommand: firstColumn(header, ["Exit Command", "出口命令", "退出命令"]),
|
|
101
|
+
actor: firstColumn(header, ["Actor", "执行者", "角色"]),
|
|
186
102
|
evidenceStatus: firstColumn(header, ["Evidence Status", "证据状态"]),
|
|
187
103
|
blockingRisk: firstColumn(header, ["Blocking Risk", "阻塞风险"]),
|
|
188
104
|
owner: firstColumn(header, ["Owner / Handoff", "负责人 / 交接"]),
|
|
189
105
|
};
|
|
190
106
|
return rows.map((row) => ({
|
|
191
107
|
id: row[indexes.id] || "",
|
|
108
|
+
kind: normalizePhaseKind(row[indexes.kind]),
|
|
192
109
|
dependsOn: splitDependencies(row[indexes.dependsOn] || ""),
|
|
193
110
|
state: row[indexes.state] || "planned",
|
|
194
111
|
completion: Number.parseInt(String(row[indexes.completion] || "0").replace("%", ""), 10) || 0,
|
|
195
112
|
output: row[indexes.output] || "",
|
|
196
113
|
requiredEvidence: splitList(row[indexes.requiredEvidence] || ""),
|
|
114
|
+
exitCommand: row[indexes.exitCommand] || "",
|
|
115
|
+
actor: normalizePhaseActor(row[indexes.actor]),
|
|
197
116
|
evidenceStatus: row[indexes.evidenceStatus] || "missing",
|
|
198
117
|
blockingRisk: row[indexes.blockingRisk] || "",
|
|
199
118
|
owner: row[indexes.owner] || "",
|
|
@@ -310,12 +229,15 @@ export function taskCutoverCounters(tasks) {
|
|
|
310
229
|
};
|
|
311
230
|
}
|
|
312
231
|
|
|
313
|
-
export function collectTasks(target) {
|
|
314
|
-
|
|
232
|
+
export function collectTasks(target, { requireGeneratedScaffoldProvenance = false, taskPlanPaths, closeoutContent } = {}) {
|
|
233
|
+
const paths = taskPlanPaths || listTaskPlanPaths(target);
|
|
234
|
+
const closeout = closeoutContent ?? readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
235
|
+
return paths.map((taskPlanPath) => {
|
|
315
236
|
const taskDir = path.dirname(taskPlanPath);
|
|
316
237
|
const taskPlan = readFileSafe(taskPlanPath);
|
|
317
238
|
const brief = readTaskContractFile(taskDir, "brief.md", "");
|
|
318
239
|
const executionStrategyPath = path.join(taskDir, "execution_strategy.md");
|
|
240
|
+
const indexPath = path.join(taskDir, "INDEX.md");
|
|
319
241
|
const progressPath = path.join(taskDir, "progress.md");
|
|
320
242
|
const reviewPath = path.join(taskDir, "review.md");
|
|
321
243
|
const findingsPath = path.join(taskDir, "findings.md");
|
|
@@ -324,19 +246,14 @@ export function collectTasks(target) {
|
|
|
324
246
|
const visualMap = readVisualMapContractFile(taskDir, taskPlan);
|
|
325
247
|
const progress = readFileSafe(progressPath);
|
|
326
248
|
const review = readFileSafe(reviewPath);
|
|
249
|
+
const indexContent = readFileSafe(indexPath);
|
|
327
250
|
const parsedLessonCandidates = parseLessonCandidateStatus(readFileSafe(lessonCandidatesPath));
|
|
328
251
|
const lessonDetailIssues = validateLessonCandidateDetailArtifacts(target, taskDir, parsedLessonCandidates);
|
|
329
252
|
const lessonCandidates = lessonDetailIssues.length
|
|
330
253
|
? { ...parsedLessonCandidates, issues: [...parsedLessonCandidates.issues, ...lessonDetailIssues] }
|
|
331
254
|
: parsedLessonCandidates;
|
|
332
255
|
const phases = parsePhases(visualMap.content);
|
|
333
|
-
const completion =
|
|
334
|
-
phases.length > 0
|
|
335
|
-
? Math.round(
|
|
336
|
-
phases.filter((phase) => phase.state !== "skipped").reduce((sum, phase) => sum + phase.completion, 0) /
|
|
337
|
-
Math.max(1, phases.filter((phase) => phase.state !== "skipped").length),
|
|
338
|
-
)
|
|
339
|
-
: 0;
|
|
256
|
+
const completion = phaseCompletionAverage(phases);
|
|
340
257
|
const relative = toPosix(path.relative(target.projectRoot, taskDir));
|
|
341
258
|
const id = taskIdForDirectory(target, taskDir);
|
|
342
259
|
const identity = parseTaskIdentity(taskPlan, id);
|
|
@@ -346,6 +263,10 @@ export function collectTasks(target) {
|
|
|
346
263
|
const budget = parseTaskBudget(taskPlan);
|
|
347
264
|
const metadata = parseTaskMetadata(taskPlan);
|
|
348
265
|
const taskContract = parseTaskContractInfo(taskPlan);
|
|
266
|
+
const taskAudit = parseTaskAuditMetadata(indexContent, {
|
|
267
|
+
required: requireGeneratedScaffoldProvenance && taskContract.generated,
|
|
268
|
+
});
|
|
269
|
+
const scaffoldProvenance = { summary: scaffoldProvenanceSummaryFromTaskAudit(taskAudit) };
|
|
349
270
|
const explicitModule = id.startsWith("MODULES/") ? id.split("/")[1] : null;
|
|
350
271
|
const legacyCandidate = brief.source !== "standalone" || visualMap.status === "legacy-only" || !fs.existsSync(executionStrategyPath);
|
|
351
272
|
const classification = inferTaskClassification({ id, title, relative, explicitModule, legacyCandidate });
|
|
@@ -355,14 +276,19 @@ export function collectTasks(target) {
|
|
|
355
276
|
const reviewSubmission = parseAgentReviewSubmission(review, { taskKey: identity.taskKey });
|
|
356
277
|
const reviewConfirmation = parseReviewConfirmation(review, {
|
|
357
278
|
taskKey: identity.taskKey,
|
|
279
|
+
taskAudit,
|
|
358
280
|
projectRoot: target.projectRoot,
|
|
359
281
|
taskDir,
|
|
282
|
+
indexPath,
|
|
360
283
|
reviewPath,
|
|
361
284
|
progressPath,
|
|
362
285
|
});
|
|
363
286
|
const reviewStatus = taskReviewStatus({ reviewContent: review, risks, confirmation: reviewConfirmation, submission: reviewSubmission });
|
|
364
|
-
const closeoutInfo = taskCloseoutInfo(target, taskPlanPath);
|
|
365
|
-
const
|
|
287
|
+
const closeoutInfo = taskCloseoutInfo(target, taskPlanPath, closeout);
|
|
288
|
+
const effectiveCloseoutStatus = budget === "simple" && stateInfo.state === "done" && completion === 100
|
|
289
|
+
? "closed"
|
|
290
|
+
: closeoutInfo.status;
|
|
291
|
+
const lifecycleState = deriveLifecycleState({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, budget });
|
|
366
292
|
const materialReadiness = assessMaterialsReadiness({
|
|
367
293
|
budget,
|
|
368
294
|
taskDir,
|
|
@@ -376,19 +302,24 @@ export function collectTasks(target) {
|
|
|
376
302
|
reviewSurfaceRequired: requiresReviewMaterials({
|
|
377
303
|
state: stateInfo.state,
|
|
378
304
|
lifecycleState,
|
|
379
|
-
closeoutStatus:
|
|
305
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
380
306
|
}),
|
|
381
307
|
});
|
|
382
|
-
const
|
|
308
|
+
const materialIssues = [
|
|
309
|
+
...materialReadiness.issues,
|
|
310
|
+
...taskAuditMaterialIssues(target, taskDir, taskAudit),
|
|
311
|
+
...legacyAuditIssues(target, taskDir, { briefContent: brief.content, reviewContent: review }),
|
|
312
|
+
];
|
|
313
|
+
const stateConflicts = collectStateConflicts({ state: stateInfo.state, reviewStatus, closeoutStatus: effectiveCloseoutStatus, lifecycleState, budget });
|
|
383
314
|
const reviewQueueState = deriveReviewQueueState({
|
|
384
315
|
state: stateInfo.state,
|
|
385
316
|
lifecycleState,
|
|
386
317
|
reviewStatus,
|
|
387
|
-
closeoutStatus:
|
|
318
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
388
319
|
budget,
|
|
389
320
|
walkthroughPath: closeoutInfo.walkthroughPath,
|
|
390
321
|
lessonCandidateDecisionComplete: isLessonCandidateDecisionComplete(lessonCandidates),
|
|
391
|
-
materialsReady:
|
|
322
|
+
materialsReady: materialIssues.length === 0,
|
|
392
323
|
deletionState: tombstone.deletionState,
|
|
393
324
|
});
|
|
394
325
|
const queueModel = deriveTaskQueues({
|
|
@@ -400,11 +331,11 @@ export function collectTasks(target) {
|
|
|
400
331
|
reviewSubmission,
|
|
401
332
|
reviewConfirmation,
|
|
402
333
|
reviewQueueState,
|
|
403
|
-
materialIssues
|
|
334
|
+
materialIssues,
|
|
404
335
|
risks,
|
|
405
336
|
stateConflicts,
|
|
406
337
|
lessonCandidates,
|
|
407
|
-
closeoutStatus:
|
|
338
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
408
339
|
tombstone,
|
|
409
340
|
taskDir,
|
|
410
341
|
target,
|
|
@@ -450,18 +381,20 @@ export function collectTasks(target) {
|
|
|
450
381
|
migrationAchievedLevel: metadata.migrationAchievedLevel,
|
|
451
382
|
evidenceBundle: formatEvidenceBundle(metadata.evidenceBundle),
|
|
452
383
|
migrationSnapshot: collectMigrationSnapshot(target, metadata),
|
|
384
|
+
scaffoldProvenance: scaffoldProvenance.summary,
|
|
385
|
+
taskAudit: taskAudit.summary,
|
|
453
386
|
lifecycleState,
|
|
454
387
|
reviewStatus,
|
|
455
388
|
reviewSubmitted: Boolean(reviewSubmission?.submitted),
|
|
456
389
|
reviewSubmission,
|
|
457
390
|
reviewQueueState,
|
|
458
391
|
reviewConfirmation,
|
|
459
|
-
materialsReady:
|
|
460
|
-
materialIssues
|
|
392
|
+
materialsReady: materialIssues.length === 0,
|
|
393
|
+
materialIssues,
|
|
461
394
|
taskQueues: queueModel.taskQueues,
|
|
462
395
|
queueReasons: queueModel.queueReasons,
|
|
463
396
|
repairPrompt: queueModel.repairPrompt,
|
|
464
|
-
closeoutStatus:
|
|
397
|
+
closeoutStatus: effectiveCloseoutStatus,
|
|
465
398
|
walkthroughPath: closeoutInfo.walkthroughPath ? `TARGET:${closeoutInfo.walkthroughPath}` : "",
|
|
466
399
|
lessonCandidatePath: fs.existsSync(lessonCandidatesPath)
|
|
467
400
|
? `TARGET:${toPosix(path.relative(target.projectRoot, lessonCandidatesPath))}`
|
|
@@ -505,12 +438,7 @@ function collectMigrationSnapshot(target, metadata) {
|
|
|
505
438
|
const evidenceBundle = String(metadata.evidenceBundle || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
|
|
506
439
|
const bundlePath = evidenceBundle ? path.join(target.projectRoot, evidenceBundle) : "";
|
|
507
440
|
const sessionPath = bundlePath ? path.join(bundlePath, "session.json") : "";
|
|
508
|
-
|
|
509
|
-
try {
|
|
510
|
-
session = sessionPath && fs.existsSync(sessionPath) ? JSON.parse(fs.readFileSync(sessionPath, "utf8")) : null;
|
|
511
|
-
} catch {
|
|
512
|
-
session = null;
|
|
513
|
-
}
|
|
441
|
+
const session = sessionPath && fs.existsSync(sessionPath) ? readJsonSafe(sessionPath, null) : null;
|
|
514
442
|
const summary = session?.plan?.summary || {};
|
|
515
443
|
return {
|
|
516
444
|
targetLevel: metadata.migrationTargetLevel || "",
|
|
@@ -536,8 +464,7 @@ function formatEvidenceBundle(value) {
|
|
|
536
464
|
return normalized ? `TARGET:${normalized}` : "";
|
|
537
465
|
}
|
|
538
466
|
|
|
539
|
-
function taskCloseoutInfo(target, taskPlanPath) {
|
|
540
|
-
const closeout = readFileSafe(path.join(target.docsRoot, "10-WALKTHROUGH/Closeout-SSoT.md"));
|
|
467
|
+
function taskCloseoutInfo(target, taskPlanPath, closeout) {
|
|
541
468
|
if (!closeout.trim()) return { status: "missing", walkthroughPath: "" };
|
|
542
469
|
const docsRelative = `docs/${toPosix(path.relative(target.docsRoot, taskPlanPath))}`;
|
|
543
470
|
const projectRelative = toPosix(path.relative(target.projectRoot, taskPlanPath));
|
|
@@ -30,3 +30,14 @@ State the user-visible or project-visible result this task must deliver.
|
|
|
30
30
|
## First Next Step
|
|
31
31
|
|
|
32
32
|
Replace this line with the first concrete action before implementation starts.
|
|
33
|
+
|
|
34
|
+
## Scaffold Provenance
|
|
35
|
+
|
|
36
|
+
| Field | Value |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| Created By | {{SCAFFOLD_CREATED_BY}} |
|
|
39
|
+
| Command Shape | {{SCAFFOLD_COMMAND}} |
|
|
40
|
+
| Created At | {{SCAFFOLD_CREATED_AT}} |
|
|
41
|
+
| Budget | {{SCAFFOLD_BUDGET}} |
|
|
42
|
+
| Template Source | {{SCAFFOLD_TEMPLATE_SOURCE}} |
|
|
43
|
+
| Exception Reason | {{SCAFFOLD_EXCEPTION_REASON}} |
|
|
@@ -16,10 +16,11 @@ Keep it as a charter and routing index. Detailed rules belong in
|
|
|
16
16
|
|
|
17
17
|
1. Preserve architecture boundaries documented in `docs/11-REFERENCE/engineering-standard.md`.
|
|
18
18
|
2. Do not commit secrets, credentials, private endpoints, or user data.
|
|
19
|
-
3. Start non-trivial work with a task directory under `docs/09-PLANNING/TASKS/`.
|
|
19
|
+
3. Start non-trivial work with a Harness CLI-created task directory under `docs/09-PLANNING/TASKS/`.
|
|
20
20
|
4. Record evidence before claiming completion.
|
|
21
21
|
5. Protect unrelated working-tree changes; never revert files outside the assigned scope.
|
|
22
22
|
6. Commit verified, meaningful work slices proactively. Do not leave completed work only as dirty files unless the user explicitly pauses commits, checks fail, dirty ownership is unclear, or a security boundary prevents a clean commit; in those cases record the no-commit reason, owner, and next step in progress, handoff, or closeout notes. Never mix unrelated dirty changes into a task commit.
|
|
23
|
+
7. Before the final response, inspect the current task `visual_map.md` gate phase. If the current `Exit Command` has `Actor: agent`, run it or record the blocker. Never run a `human` gate such as `review-confirm` unless the user has explicitly performed or delegated that human confirmation.
|
|
23
24
|
|
|
24
25
|
## Reading Matrix
|
|
25
26
|
|
|
@@ -33,7 +34,7 @@ Read the smallest context that can answer the task.
|
|
|
33
34
|
| API, event, webhook, SDK, or third-party contract | `docs/06-INTEGRATIONS/README.md` and the related contract file |
|
|
34
35
|
| Tests, smoke, or regression | `docs/11-REFERENCE/testing-standard.md`, `docs/05-TEST-QA/Regression-SSoT.md` |
|
|
35
36
|
| Execution, commit, PR, or release work | `docs/11-REFERENCE/execution-workflow-standard.md`, `docs/11-REFERENCE/repo-governance-standard.md`, `docs/11-REFERENCE/pull-request-standard.md`, `docs/11-REFERENCE/ci-cd-standard.md` |
|
|
36
|
-
| Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use
|
|
37
|
+
| Creating or advancing a task | Current task directory under `docs/09-PLANNING/TASKS/`; use `harness new-task` / lifecycle commands when this project has the Harness CLI configured |
|
|
37
38
|
| Brief, Execution Strategy, or Visual Map | Current task `brief.md`, `execution_strategy.md`, and `visual_map.md` |
|
|
38
39
|
| Long-running task | `docs/11-REFERENCE/long-running-task-standard.md` |
|
|
39
40
|
| Reviewer, subagent, or adversarial review | `docs/11-REFERENCE/review-routing-standard.md`, `docs/11-REFERENCE/adversarial-review-standard.md` |
|
|
@@ -48,7 +49,7 @@ Read the smallest context that can answer the task.
|
|
|
48
49
|
## Standard Execution Flow
|
|
49
50
|
|
|
50
51
|
1. Confirm the request, scope, and affected files.
|
|
51
|
-
2. For non-trivial work, create or update the task plan before editing code.
|
|
52
|
+
2. For non-trivial work, create or update the task plan before editing code. Use `harness new-task`; if the CLI is unavailable, keep Task Audit Metadata in the task `INDEX.md` and set `Created By` to `manual-exception` with a concrete reason.
|
|
52
53
|
3. Assess delegation from the user's goal; users do not need to know or ask for subagents.
|
|
53
54
|
4. Load only the reference documents required by the Reading Matrix.
|
|
54
55
|
5. Preserve existing project facts; merge new context instead of overwriting history.
|
|
@@ -56,8 +57,9 @@ Read the smallest context that can answer the task.
|
|
|
56
57
|
7. Implement only within the approved scope.
|
|
57
58
|
8. Run the relevant checks and capture evidence in the task records.
|
|
58
59
|
9. Route required review and close blocking findings.
|
|
59
|
-
10.
|
|
60
|
-
11.
|
|
60
|
+
10. Follow the current `visual_map.md` lifecycle gate and its `Exit Command` / `Actor`.
|
|
61
|
+
11. Write or update the walkthrough and closeout records.
|
|
62
|
+
12. Update SSoT / ledger files that the task actually touched.
|
|
61
63
|
|
|
62
64
|
## Coordination Rules
|
|
63
65
|
|
|
@@ -12,6 +12,18 @@ const state = {
|
|
|
12
12
|
taskGroupPage: 1,
|
|
13
13
|
warningFilter: "all",
|
|
14
14
|
warningPage: 1,
|
|
15
|
+
presetQuery: "",
|
|
16
|
+
presetSourceFilter: "all",
|
|
17
|
+
selectedPresetKey: "",
|
|
18
|
+
selectedPresetId: "",
|
|
19
|
+
presetActionResult: null,
|
|
20
|
+
presetInstallSource: "",
|
|
21
|
+
presetInstallScope: "project",
|
|
22
|
+
presetInstallForce: false,
|
|
23
|
+
presetSeedScope: "project",
|
|
24
|
+
presetSeedForce: false,
|
|
25
|
+
presetUninstallScope: "project",
|
|
26
|
+
presetUninstallConfirm: "",
|
|
15
27
|
renderMode: "rendered",
|
|
16
28
|
theme: localStorage.getItem("harness.theme") || "system",
|
|
17
29
|
taskLayout: localStorage.getItem("harness.taskLayout") || "list",
|
|
@@ -29,6 +29,7 @@ function shell() {
|
|
|
29
29
|
${routeLink("#/tasks", t("taskIndex"), "tasks")}
|
|
30
30
|
${routeLink("#/review", t("reviewQueue"), "review")}
|
|
31
31
|
${routeLink("#/modules", t("moduleView"), "modules")}
|
|
32
|
+
${routeLink("#/presets", t("presetCatalog"), "presets")}
|
|
32
33
|
<button data-language-toggle>${locale === "zh" ? "EN" : "中文"}</button>
|
|
33
34
|
<button data-theme-toggle>${themeLabel()}</button>
|
|
34
35
|
</div>
|
|
@@ -55,6 +56,7 @@ function renderRoute() {
|
|
|
55
56
|
if (route.name === "reviewTask") return reviewWorkspace(route);
|
|
56
57
|
if (route.name === "review") return reviewQueue();
|
|
57
58
|
if (route.name === "modules") return modulesView(route.id);
|
|
59
|
+
if (route.name === "presets") return presetsView();
|
|
58
60
|
if (route.name === "tasks") return taskIndex();
|
|
59
61
|
return overview();
|
|
60
62
|
}
|
|
@@ -66,6 +68,7 @@ function currentRoute() {
|
|
|
66
68
|
if (parts[0] === "review" && parts[1]) return { name: "reviewTask", id: parts[1] };
|
|
67
69
|
if (parts[0] === "review") return { name: "review" };
|
|
68
70
|
if (parts[0] === "modules") return { name: "modules", id: parts[1] || "" };
|
|
71
|
+
if (parts[0] === "presets") return { name: "presets" };
|
|
69
72
|
if (parts[0] === "tasks") return { name: "tasks" };
|
|
70
73
|
return { name: "overview" };
|
|
71
74
|
}
|
|
@@ -16,6 +16,9 @@ function overview() {
|
|
|
16
16
|
|
|
17
17
|
function statusStrip() {
|
|
18
18
|
const status = bundle.status?.checkState?.status || "unknown";
|
|
19
|
+
const validationMode = bundle.status?.checkState?.validationMode || "validated";
|
|
20
|
+
const dataOnly = validationMode === "data-only";
|
|
21
|
+
const displayState = dataOnly ? "snapshot" : status;
|
|
19
22
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
20
23
|
const warnings = bundle.status?.checkState?.warnings || 0;
|
|
21
24
|
const tasks = bundle.status?.tasks || [];
|
|
@@ -23,9 +26,9 @@ function statusStrip() {
|
|
|
23
26
|
const visual = summary.visualMapCoverage || {};
|
|
24
27
|
const withBrief = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
25
28
|
return `<section class="status-card-group">
|
|
26
|
-
<div class="status-primary ${
|
|
27
|
-
<span>${t("readiness")}</span>
|
|
28
|
-
<strong>${label(status)}</strong>
|
|
29
|
+
<div class="status-primary ${displayState}">
|
|
30
|
+
<span>${dataOnly ? t("snapshotStatus") : t("readiness")}</span>
|
|
31
|
+
<strong>${dataOnly ? t("snapshot") : label(status)}</strong>
|
|
29
32
|
<p>${nextActionText()}</p>
|
|
30
33
|
</div>
|
|
31
34
|
<div class="metrics-grid">
|
|
@@ -46,6 +49,7 @@ function metric(labelText, value) {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function nextActionText() {
|
|
52
|
+
if ((bundle.status?.checkState?.validationMode || "validated") === "data-only") return t("snapshotNotValidated");
|
|
49
53
|
const failures = bundle.status?.checkState?.failures || 0;
|
|
50
54
|
if (failures > 0) return t("resolveBlockers");
|
|
51
55
|
const missingBriefs = (bundle.status?.tasks || []).filter((task) => task.briefSource !== "standalone").length;
|
|
@@ -72,17 +72,57 @@ function taskQueueReasonSummary(task) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function phaseTimeline(task) {
|
|
75
|
+
const knownKinds = new Set(["init", "execution", "gate"]);
|
|
76
|
+
const groups = [
|
|
77
|
+
["init", "Init"],
|
|
78
|
+
["execution", "Execution"],
|
|
79
|
+
["gate", "Gate"],
|
|
80
|
+
["other", "Other / Invalid"],
|
|
81
|
+
];
|
|
82
|
+
const phases = task.phases || [];
|
|
83
|
+
const grouped = groups
|
|
84
|
+
.map(([kind, label]) => {
|
|
85
|
+
const items = kind === "other"
|
|
86
|
+
? phases.filter((phase) => !knownKinds.has(phase.kind || "execution"))
|
|
87
|
+
: phases.filter((phase) => (phase.kind || "execution") === kind);
|
|
88
|
+
if (!items.length) return "";
|
|
89
|
+
return `<div class="phase-kind-group ${escapeAttr(kind)}">
|
|
90
|
+
<h3>${escapeHtml(label)}</h3>
|
|
91
|
+
${items.map(phaseStep).join("")}
|
|
92
|
+
</div>`;
|
|
93
|
+
})
|
|
94
|
+
.join("");
|
|
75
95
|
return `<section class="phase-timeline">
|
|
76
96
|
<h2>${t("phaseTimeline")}</h2>
|
|
77
|
-
${
|
|
78
|
-
<strong>${escapeHtml(phase.id)}</strong>
|
|
79
|
-
<span>${phase.completion}%</span>
|
|
80
|
-
<p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
|
|
81
|
-
${progressBar(phase.completion)}
|
|
82
|
-
</div>`).join("") || emptyState(t("noPhaseData"))}
|
|
97
|
+
${grouped || emptyState(t("noPhaseData"))}
|
|
83
98
|
</section>`;
|
|
84
99
|
}
|
|
85
100
|
|
|
101
|
+
function phaseStep(phase) {
|
|
102
|
+
const kind = phase.kind || "execution";
|
|
103
|
+
const actor = phase.actor || "agent";
|
|
104
|
+
const knownKind = ["init", "execution", "gate"].includes(kind);
|
|
105
|
+
const kindLabel = knownKind ? escapeHtml(kind) : `<span class="tag warn">${escapeHtml(kind)}</span>`;
|
|
106
|
+
const phaseKindClass = knownKind ? kind : "other";
|
|
107
|
+
return `<div class="phase-step ${escapeAttr(phase.state)} ${escapeAttr(phaseKindClass)}">
|
|
108
|
+
<div class="phase-step-head">
|
|
109
|
+
<strong>${escapeHtml(phase.id)}</strong>
|
|
110
|
+
<span>${kindLabel} · ${phase.completion}%</span>
|
|
111
|
+
</div>
|
|
112
|
+
<p>${escapeHtml(phase.output || phase.blockingRisk || phase.state)}</p>
|
|
113
|
+
${progressBar(phase.completion)}
|
|
114
|
+
<div class="phase-meta">
|
|
115
|
+
${phaseMetaTag(actor)}
|
|
116
|
+
${tag(phase.evidenceStatus || "missing")}
|
|
117
|
+
</div>
|
|
118
|
+
${phase.exitCommand ? `<code class="phase-exit-command">${escapeHtml(phase.exitCommand)}</code>` : ""}
|
|
119
|
+
</div>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function phaseMetaTag(value) {
|
|
123
|
+
return `<span class="tag">${escapeHtml(String(value || "unknown").replaceAll("_", " "))}</span>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
86
126
|
function taskDocSection(task, fileName, title, required) {
|
|
87
127
|
const doc = taskDocument(task, fileName);
|
|
88
128
|
if (!doc && !required) return "";
|