coding-agent-harness 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/dist/build-dist.mjs +13 -0
  4. package/dist/check-dist-observation.mjs +24 -7
  5. package/dist/check-no-ts-nocheck.mjs +88 -0
  6. package/dist/check-type-boundaries.mjs +23 -6
  7. package/dist/commands/preset-command.mjs +23 -1
  8. package/dist/commands/task-command.mjs +2 -1
  9. package/dist/harness.mjs +2 -1
  10. package/dist/lib/capability-registry.mjs +2 -2
  11. package/dist/lib/harness-core.mjs +1 -0
  12. package/dist/lib/preset-engine.mjs +1 -0
  13. package/dist/lib/preset-registry.mjs +13 -1
  14. package/dist/lib/preset-runner.mjs +294 -0
  15. package/dist/lib/task-index.mjs +1 -0
  16. package/dist/lib/task-lifecycle.mjs +3 -1
  17. package/dist/lib/task-review-model.mjs +2 -0
  18. package/dist/lib/task-scanner.mjs +1 -0
  19. package/dist/lib/task-tombstone-commands.mjs +12 -0
  20. package/docs-release/README.md +1 -1
  21. package/package.json +6 -2
  22. package/presets/release-closeout/checks/check-release-package.mjs +24 -0
  23. package/presets/release-closeout/preset.yaml +100 -0
  24. package/presets/release-closeout/scripts/generate-release-package.mjs +210 -0
  25. package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
  26. package/presets/release-closeout/templates/findings.seed.md +5 -0
  27. package/presets/release-closeout/templates/review.seed.md +3 -0
  28. package/presets/release-closeout/templates/task_plan.append.md +24 -0
  29. package/references/pull-request-standard.md +2 -2
  30. package/templates/reference/pull-request-standard.md +2 -2
  31. package/templates-zh-CN/reference/pull-request-standard.md +1 -1
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ const context = JSON.parse(fs.readFileSync(process.env.HARNESS_PRESET_CONTEXT, "utf8"));
7
+ const release = safeRelease(context.inputs.release);
8
+ if (!release) {
9
+ console.error("release-closeout requires inputs.release");
10
+ process.exit(2);
11
+ }
12
+
13
+ const tasksRoot = path.join(context.targetRoot, "coding-agent-harness/planning/tasks");
14
+ const releaseRoot = path.join(context.outputRoot, "release");
15
+ fs.mkdirSync(releaseRoot, { recursive: true });
16
+
17
+ const tasks = collectTasks(tasksRoot)
18
+ .filter((task) => task.id !== context.task.id && task.preset !== "release-closeout")
19
+ .sort((a, b) => a.id.localeCompare(b.id));
20
+ const eligibleArchive = tasks.filter((task) => task.state === "done" && task.deletionState !== "superseded");
21
+ const blockedArchive = tasks.filter((task) => task.state === "blocked" || task.queue === "blocked");
22
+
23
+ const aggregate = {
24
+ schemaVersion: "release-closeout-aggregate/v1",
25
+ release,
26
+ generatedAt: new Date().toISOString(),
27
+ summary: {
28
+ totalTasks: tasks.length,
29
+ doneTasks: tasks.filter((task) => task.state === "done").length,
30
+ blockedTasks: tasks.filter((task) => task.state === "blocked").length,
31
+ archiveEligibleTasks: eligibleArchive.length,
32
+ },
33
+ tasks: tasks.map((task) => ({
34
+ id: task.id,
35
+ title: task.title,
36
+ state: task.state,
37
+ preset: task.preset || "none",
38
+ deletionState: task.deletionState,
39
+ archiveEligible: eligibleArchive.some((candidate) => candidate.id === task.id),
40
+ archiveBlockedReason: task.state === "blocked" ? "blocked tasks cannot be archived" : "",
41
+ })),
42
+ };
43
+
44
+ const index = `# Release Closeout Package: ${release}
45
+
46
+ Generated by \`presets/release-closeout\` through the generic preset runner.
47
+
48
+ | Document | Purpose |
49
+ | --- | --- |
50
+ | \`task-aggregate.json\` | Machine-readable task inventory for this release closeout. |
51
+ | \`task-archive-plan.md\` | Preset-owned archive eligibility plan. |
52
+ | \`public-summary.md\` | Redacted public-facing summary. |
53
+ | \`public-redaction-report.json\` | Redaction evidence for public materialization. |
54
+
55
+ ## Summary
56
+
57
+ - Total tasks: ${aggregate.summary.totalTasks}
58
+ - Done tasks: ${aggregate.summary.doneTasks}
59
+ - Blocked tasks: ${aggregate.summary.blockedTasks}
60
+ - Archive eligible tasks: ${aggregate.summary.archiveEligibleTasks}
61
+ `;
62
+
63
+ const archivePlan = `# Release ${release} Task Archive Plan
64
+
65
+ This plan is generated by the release-closeout preset. It does not archive tasks by itself.
66
+
67
+ ## Eligible Tasks
68
+
69
+ ${eligibleArchive.length ? eligibleArchive.map((task) => `- ${task.id} - ${task.title}`).join("\n") : "- none"}
70
+
71
+ ## Not Eligible
72
+
73
+ ${blockedArchive.length ? blockedArchive.map((task) => `- ${task.id} - blocked tasks cannot be archived`).join("\n") : "- none"}
74
+ `;
75
+
76
+ const publicSummaryRaw = `# Release ${release} Public Summary
77
+
78
+ This release closeout aggregates ${aggregate.summary.totalTasks} tasks, including ${aggregate.summary.doneTasks} done tasks and ${aggregate.summary.blockedTasks} blocked tasks.
79
+
80
+ Recent evidence snippets:
81
+
82
+ ${tasks.slice(0, 20).map((task) => `- ${task.id}: ${task.evidenceSnippet || "no evidence snippet"}`).join("\n")}
83
+ `;
84
+ const publicSummary = redactPublic(publicSummaryRaw);
85
+ const redactionReport = {
86
+ schemaVersion: "public-redaction-report/v1",
87
+ status: leaksLocalPath(publicSummary) ? "fail" : "pass",
88
+ rules: ["local-absolute-paths", "file-urls"],
89
+ checkedFiles: ["public-summary.md"],
90
+ findings: leaksLocalPath(publicSummary) ? ["public-summary contains local absolute path"] : [],
91
+ generatedAt: new Date().toISOString(),
92
+ };
93
+ if (redactionReport.status !== "pass") {
94
+ console.error("public redaction failed");
95
+ process.exit(3);
96
+ }
97
+
98
+ write("INDEX.md", index);
99
+ write("task-archive-plan.md", archivePlan);
100
+ write("task-aggregate.json", `${JSON.stringify(aggregate, null, 2)}\n`);
101
+ write("public-summary.md", publicSummary);
102
+ write("public-redaction-report.json", `${JSON.stringify(redactionReport, null, 2)}\n`);
103
+
104
+ const destinationRoot = `coding-agent-harness/governance/releases/${release}`;
105
+ fs.writeFileSync(context.materializationManifestPath, `${JSON.stringify({
106
+ schemaVersion: "preset-materialization/v1",
107
+ status: "ok",
108
+ publicRedactionReport: { source: "release/public-redaction-report.json" },
109
+ writes: [
110
+ { source: "release/INDEX.md", destination: `${destinationRoot}/INDEX.md`, type: "text" },
111
+ { source: "release/task-archive-plan.md", destination: `${destinationRoot}/task-archive-plan.md`, type: "text" },
112
+ { source: "release/task-aggregate.json", destination: `${destinationRoot}/task-aggregate.json`, type: "json" },
113
+ { source: "release/public-summary.md", destination: `${destinationRoot}/public-summary.md`, type: "text", visibility: "public" },
114
+ { source: "release/public-redaction-report.json", destination: `${destinationRoot}/public-redaction-report.json`, type: "json" },
115
+ ],
116
+ }, null, 2)}\n`);
117
+
118
+ function write(name, content) {
119
+ fs.writeFileSync(path.join(releaseRoot, name), content.endsWith("\n") ? content : `${content}\n`);
120
+ }
121
+
122
+ function collectTasks(root) {
123
+ if (!fs.existsSync(root)) return [];
124
+ const taskPlans = walk(root).filter((file) => path.basename(file) === "task_plan.md");
125
+ return taskPlans.map((taskPlanPath) => {
126
+ const taskDir = path.dirname(taskPlanPath);
127
+ const taskPlan = read(taskPlanPath);
128
+ const progress = read(path.join(taskDir, "progress.md"));
129
+ const tombstone = parseTombstone(taskPlan);
130
+ return {
131
+ id: path.basename(taskDir),
132
+ title: titleFrom(taskPlan, path.basename(taskDir)),
133
+ preset: metadataLine(taskPlan, ["Task Preset", "Preset"]).toLowerCase(),
134
+ state: parseState(progress),
135
+ deletionState: tombstone.state || "active",
136
+ queue: parseState(progress) === "blocked" ? "blocked" : "active",
137
+ evidenceSnippet: progress.split(/\r?\n/).find((line) => /\/Users\/|\/Volumes\/|file:\/\//.test(line)) || "",
138
+ tombstone,
139
+ };
140
+ });
141
+ }
142
+
143
+ function walk(root) {
144
+ const files = [];
145
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
146
+ const full = path.join(root, entry.name);
147
+ if (entry.isDirectory()) files.push(...walk(full));
148
+ else if (entry.isFile()) files.push(full);
149
+ }
150
+ return files;
151
+ }
152
+
153
+ function read(filePath) {
154
+ try {
155
+ return fs.readFileSync(filePath, "utf8");
156
+ } catch {
157
+ return "";
158
+ }
159
+ }
160
+
161
+ function titleFrom(content, fallback) {
162
+ const match = String(content || "").match(/^#\s+(.+)$/m);
163
+ return match ? match[1].trim() : fallback;
164
+ }
165
+
166
+ function metadataLine(content, labels) {
167
+ const escaped = labels.map((label) => label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
168
+ const match = String(content || "").match(new RegExp(`^(?:${escaped})\\s*[::]\\s*([^\\n]+)`, "im"));
169
+ return match ? match[1].replace(/`/g, "").trim() : "";
170
+ }
171
+
172
+ function parseState(progress) {
173
+ const match = String(progress || "").match(/^##\s*(?:Current Status|Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
174
+ const raw = (match ? match[1] : "").trim().toLowerCase().replaceAll("-", "_").replaceAll(" ", "_");
175
+ return ["not_started", "planned", "in_progress", "review", "blocked", "done"].includes(raw) ? raw : "unknown";
176
+ }
177
+
178
+ function parseTombstone(content) {
179
+ const match = String(content || "").match(/^##\s*(?:Task Tombstone|任务墓碑)\s*$([\s\S]*?)(?=^##\s+|(?![\s\S]))/im);
180
+ if (!match) return { state: "active", fields: {} };
181
+ const fields = {};
182
+ for (const line of match[1].split(/\r?\n/)) {
183
+ const row = line.trim();
184
+ if (!row.startsWith("|") || /---/.test(row) || /Field\s*\|\s*Value/i.test(row)) continue;
185
+ const cells = row.slice(1, -1).split("|").map((cell) => cell.trim().toLowerCase());
186
+ if (cells.length >= 2) fields[cells[0]] = cells.slice(1).join("|").trim();
187
+ }
188
+ return { state: fields.state || "soft-deleted", fields };
189
+ }
190
+
191
+ function safeRelease(value) {
192
+ const release = String(value || "").trim();
193
+ return /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(release) ? release : "";
194
+ }
195
+
196
+ function redactPublic(value) {
197
+ return String(value || "")
198
+ .replace(/file:\/\/\/[^\s)"'`<>\]]+/g, "LOCAL_FILE_URL_REDACTED")
199
+ .replaceAll("file://", "LOCAL_FILE_URL_REDACTED")
200
+ .replace(/\/Users\/[^/\s)"'`<>\]]+(?:\/[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED")
201
+ .replace(/\/Volumes\/[^\s)"'`<>\]]+(?:\/[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED")
202
+ .replace(/\/(?:private\/)?tmp\/[^\s)"'`<>\]]+(?:\/[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED")
203
+ .replace(/\/var\/folders\/[^\s)"'`<>\]]+(?:\/[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED")
204
+ .replace(/\/home\/[^/\s)"'`<>\]]+(?:\/[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED")
205
+ .replace(/[A-Za-z]:\\[^\s)"'`<>\]]+(?:\\[^\s)"'`<>\]]*)*/g, "LOCAL_PATH_REDACTED");
206
+ }
207
+
208
+ function leaksLocalPath(value) {
209
+ return /(?:^|[\s"'(])(?:\/Users\/|\/Volumes\/|\/tmp\/|\/private\/tmp\/|\/var\/folders\/|\/home\/|[A-Za-z]:\\|file:\/\/)/.test(String(value || ""));
210
+ }
@@ -0,0 +1,7 @@
1
+ ## Release Closeout Strategy
2
+
3
+ | Phase ID | Depends On | State | Completion | Output | Required Evidence | Evidence Status | Blocking Risk | Owner / Handoff |
4
+ | --- | --- | --- | ---: | --- | --- | --- | --- | --- |
5
+ | RC-PLAN | none | planned | 0 | Release task inventory and archive eligibility plan | `harness preset run release-closeout plan --task {{taskId}} .` | missing | none | coordinator |
6
+ | RC-SCAFFOLD | RC-PLAN | planned | 0 | Version package materialized under governance releases | `harness preset run release-closeout scaffold --task {{taskId}} .` | missing | none | coordinator |
7
+ | RC-CHECK | RC-SCAFFOLD | planned | 0 | Release package validation report | `harness preset run release-closeout check --task {{taskId}} .` | missing | none | coordinator |
@@ -0,0 +1,5 @@
1
+ ## Release Closeout Findings
2
+
3
+ | Severity | Finding | Open | Blocks Release | Disposition | Waiver By |
4
+ | --- | --- | --- | --- | --- | --- |
5
+ | P2 | Release package has not been generated yet. | yes | no | pending `release-closeout scaffold` | n/a |
@@ -0,0 +1,3 @@
1
+ ## Release Closeout Review
2
+
3
+ Review the generated release package, archive plan, and public redaction report before marking this task complete.
@@ -0,0 +1,24 @@
1
+ ## Release Closeout
2
+
3
+ Release Version: {{release}}
4
+ Previous Release: {{previousRelease}}
5
+ Next Release: {{nextRelease}}
6
+ Task Query: {{taskQuery}}
7
+
8
+ ## Release Package Workflow
9
+
10
+ Run these preset entrypoints from the target root. The task scaffold only records the release intent; version package files are generated by the generic preset runner.
11
+
12
+ 1. `harness preset run release-closeout plan --task {{taskId}} .`
13
+ 2. `harness preset run release-closeout scaffold --task {{taskId}} .`
14
+ 3. `harness preset run release-closeout check --task {{taskId}} .`
15
+
16
+ ## Release Package Outputs
17
+
18
+ | Output | Owner |
19
+ | --- | --- |
20
+ | `coding-agent-harness/governance/releases/{{release}}/INDEX.md` | `presets/release-closeout` |
21
+ | `coding-agent-harness/governance/releases/{{release}}/task-aggregate.json` | `presets/release-closeout` |
22
+ | `coding-agent-harness/governance/releases/{{release}}/task-archive-plan.md` | `presets/release-closeout` |
23
+ | `coding-agent-harness/governance/releases/{{release}}/public-summary.md` | `presets/release-closeout` |
24
+ | `coding-agent-harness/governance/releases/{{release}}/public-redaction-report.json` | `presets/release-closeout` |
@@ -25,8 +25,8 @@ The PR body must include:
25
25
 
26
26
  ## Content Rules
27
27
 
28
- - State the target version explicitly. If `package.json` changes from `1.0.3`
29
- to `1.0.4`, say so in Version Impact.
28
+ - State the target version explicitly. If `package.json` changes from one
29
+ version to another, say so in Version Impact.
30
30
  - List changed surfaces by user-visible area or module, not by dumping every
31
31
  file path.
32
32
  - Verification must name the real commands, browser checks, CI runs, or
@@ -25,8 +25,8 @@ then the localized section follows after the English section.
25
25
 
26
26
  ## Content Rules
27
27
 
28
- - State the target version explicitly. If `package.json` changes from `1.0.3`
29
- to `1.0.4`, say so in Version Impact.
28
+ - State the target version explicitly. If `package.json` changes from one
29
+ version to another, say so in Version Impact.
30
30
  - List changed surfaces by user-visible area or module, not by dumping every
31
31
  file path.
32
32
  - Verification must name the real commands, browser checks, CI runs, or
@@ -20,7 +20,7 @@ PR body 必须包含:
20
20
 
21
21
  ## 内容规则
22
22
 
23
- - 必须明确目标版本。如果 `package.json` 从 `1.0.2` 变为 `1.0.3`,就在版本影响里写清楚。
23
+ - 必须明确目标版本。如果 `package.json` 从一个版本变为另一个版本,就在版本影响里写清楚。
24
24
  - 改动内容按用户可见面或模块总结,不要只堆文件路径。
25
25
  - 验证必须列真实命令、浏览器检查、CI run 或证据产物。没有跑的检查必须说明原因。
26
26
  - 审查证据必须说明自查、subagent 审查、人工审查或代码质量审查状态。release-blocking finding 必须在 merge 前关闭或路由。