coding-agent-harness 1.0.2 → 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 +32 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +661 -21
- package/LICENSE-EXCEPTION.md +37 -0
- package/README.md +244 -87
- package/README.zh-CN.md +77 -35
- package/SKILL.md +32 -24
- package/docs-release/README.md +9 -5
- package/docs-release/architecture/overview.md +17 -5
- package/docs-release/architecture/overview.zh-CN.md +9 -5
- 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/assets/dashboard-overview.png +0 -0
- package/docs-release/guides/agent-installation.en-US.md +39 -15
- package/docs-release/guides/agent-installation.md +43 -16
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +3 -2
- package/docs-release/guides/document-audience-and-surfaces.md +3 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +2 -2
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +2 -2
- package/docs-release/guides/legacy-migration-agent-prompt.md +0 -11
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +0 -11
- package/docs-release/guides/migration-playbook.en-US.md +14 -15
- package/docs-release/guides/migration-playbook.md +14 -15
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +7 -5
- package/docs-release/guides/parent-control-repository-pattern.md +7 -5
- package/docs-release/guides/preset-development.md +238 -0
- package/docs-release/guides/repository-operating-models.en-US.md +5 -4
- package/docs-release/guides/repository-operating-models.md +5 -4
- package/docs-release/guides/task-state-machine.en-US.md +224 -0
- package/docs-release/guides/task-state-machine.md +231 -0
- package/docs-release/intl/en-US.md +1 -1
- package/docs-release/intl/zh-CN.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/INDEX.md +60 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/findings.md +7 -0
- package/package.json +10 -4
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +2 -2
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +6 -7
- package/references/harness-ledger.md +53 -96
- package/references/lessons-governance.md +88 -93
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +12 -6
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +11 -2
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/walkthrough-closeout.md +7 -7
- package/scripts/check-harness.mjs +40 -301
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +126 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +328 -0
- package/scripts/harness.mjs +59 -260
- package/scripts/lib/capability-registry.mjs +82 -28
- package/scripts/lib/check-module-parallel.mjs +230 -0
- package/scripts/lib/check-profiles.mjs +90 -228
- package/scripts/lib/check-task-contracts.mjs +55 -0
- package/scripts/lib/core-shared.mjs +65 -2
- package/scripts/lib/dashboard-data.mjs +155 -24
- package/scripts/lib/dashboard-workbench.mjs +131 -12
- package/scripts/lib/dashboard-writer.mjs +20 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +611 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +6 -0
- package/scripts/lib/lesson-maintenance.mjs +36 -29
- package/scripts/lib/markdown-utils.mjs +33 -0
- package/scripts/lib/migration-planner.mjs +4 -6
- package/scripts/lib/migration-support.mjs +1 -1
- package/scripts/lib/phase-kind.mjs +50 -0
- package/scripts/lib/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +494 -0
- package/scripts/lib/preset-registry.mjs +776 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-builder.mjs +88 -0
- package/scripts/lib/status-dashboard-renderer.mjs +105 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- 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 +26 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- 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 +112 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +73 -0
- 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/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +338 -477
- package/scripts/lib/task-metadata.mjs +118 -0
- package/scripts/lib/task-review-model.mjs +455 -0
- package/scripts/lib/task-scanner.mjs +193 -372
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +43 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/skills/preset-creator/references/complex-task-skeleton/visual_map.md +50 -0
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +24 -18
- package/templates/dashboard/assets/app-src/00-state.js +13 -0
- package/templates/dashboard/assets/app-src/10-router.js +5 -1
- package/templates/dashboard/assets/app-src/20-overview.js +18 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +92 -246
- package/templates/dashboard/assets/app-src/35-task-detail.js +286 -0
- package/templates/dashboard/assets/app-src/45-review.js +241 -22
- package/templates/dashboard/assets/app-src/50-migration.js +24 -10
- 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 +302 -29
- package/templates/dashboard/assets/app.css +1501 -376
- package/templates/dashboard/assets/app.css.manifest.json +10 -0
- package/templates/dashboard/assets/app.js +1240 -101
- package/templates/dashboard/assets/app.manifest.json +2 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +346 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +489 -0
- package/templates/dashboard/assets/css-src/45-presets.css +516 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +263 -23
- package/templates/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/INDEX.md +87 -0
- package/templates/planning/brief.md +1 -1
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +18 -6
- package/templates/planning/module_session_prompt.md +1 -0
- package/templates/planning/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +41 -0
- package/templates/planning/task_plan.md +5 -21
- package/templates/planning/visual_map.md +13 -9
- package/templates/planning/visual_map.simple.md +52 -0
- package/templates/reference/execution-workflow-standard.md +31 -3
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +7 -6
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +25 -19
- package/templates-zh-CN/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/INDEX.md +87 -0
- package/templates-zh-CN/planning/brief.md +1 -1
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +18 -6
- package/templates-zh-CN/planning/module_session_prompt.md +1 -0
- package/templates-zh-CN/planning/review.md +41 -1
- package/templates-zh-CN/planning/task_plan.md +4 -44
- 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/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +1 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +33 -7
- package/templates-zh-CN/reference/harness-ledger-standard.md +2 -2
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -3
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +3 -2
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +1 -1
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/scripts/smoke-dashboard.mjs +0 -92
- package/scripts/test-harness.mjs +0 -1395
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { checkModuleParallelStructure } from "./lib/check-module-parallel.mjs";
|
|
5
6
|
|
|
6
7
|
const targetRoot = path.resolve(process.argv[2] || process.cwd());
|
|
7
8
|
const requireGlobalModuleSync = process.env.HARNESS_REQUIRE_GLOBAL_MODULE_SYNC === "1";
|
|
@@ -23,7 +24,6 @@ const requiredFiles = [
|
|
|
23
24
|
"docs/10-WALKTHROUGH/Closeout-SSoT.md",
|
|
24
25
|
"docs/05-TEST-QA/Regression-SSoT.md",
|
|
25
26
|
"docs/05-TEST-QA/Cadence-Ledger.md",
|
|
26
|
-
"docs/01-GOVERNANCE/Lessons-SSoT.md",
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
const legacyPlanningFiles = [
|
|
@@ -41,7 +41,6 @@ const agAgentsRefs = [
|
|
|
41
41
|
"adversarial-review-standard.md",
|
|
42
42
|
"review-routing-standard.md",
|
|
43
43
|
"walkthrough-standard.md",
|
|
44
|
-
"Lessons-SSoT.md",
|
|
45
44
|
"harness-ledger-standard.md",
|
|
46
45
|
"Closeout-SSoT.md",
|
|
47
46
|
];
|
|
@@ -109,7 +108,18 @@ function checkRequiredFiles() {
|
|
|
109
108
|
|
|
110
109
|
function checkPlanningStructure() {
|
|
111
110
|
if (exists("docs/09-PLANNING/Module-Registry.md")) {
|
|
112
|
-
checkModuleParallelStructure(
|
|
111
|
+
checkModuleParallelStructure({
|
|
112
|
+
exists,
|
|
113
|
+
fail,
|
|
114
|
+
filePath,
|
|
115
|
+
markdownTable,
|
|
116
|
+
read,
|
|
117
|
+
rel,
|
|
118
|
+
requireFile,
|
|
119
|
+
requireGlobalModuleSync,
|
|
120
|
+
targetRoot,
|
|
121
|
+
warn,
|
|
122
|
+
});
|
|
113
123
|
return;
|
|
114
124
|
}
|
|
115
125
|
for (const legacyFile of legacyPlanningFiles) {
|
|
@@ -117,239 +127,6 @@ function checkPlanningStructure() {
|
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
function stripMarkdownCode(value) {
|
|
121
|
-
return String(value || "").replace(/`/g, "").trim();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function modulePromptBlock(content, key) {
|
|
125
|
-
const heading = `## Module: ${key}`;
|
|
126
|
-
const start = content.indexOf(heading);
|
|
127
|
-
if (start < 0) return "";
|
|
128
|
-
const rest = content.slice(start + heading.length);
|
|
129
|
-
const next = rest.search(/\n## Module: /);
|
|
130
|
-
return next >= 0 ? rest.slice(0, next) : rest;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function checkModuleParallelStructure() {
|
|
134
|
-
if (!exists("docs/09-PLANNING/Module-Registry.md")) return;
|
|
135
|
-
|
|
136
|
-
requireFile("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
137
|
-
const hasPromptPack = exists("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md");
|
|
138
|
-
for (const templateFile of [
|
|
139
|
-
"docs/09-PLANNING/MODULES/_task-template/task_plan.md",
|
|
140
|
-
"docs/09-PLANNING/MODULES/_task-template/progress.md",
|
|
141
|
-
"docs/09-PLANNING/MODULES/_task-template/findings.md",
|
|
142
|
-
"docs/09-PLANNING/MODULES/_task-template/review.md",
|
|
143
|
-
]) {
|
|
144
|
-
requireFile(templateFile);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const registryContent = read("docs/09-PLANNING/Module-Registry.md");
|
|
148
|
-
for (const term of ["PREFIX", "Current Step", "Status", "Write Scope"]) {
|
|
149
|
-
if (!registryContent.includes(term)) {
|
|
150
|
-
fail(`docs/09-PLANNING/Module-Registry.md missing registry column or section: ${term}`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const registryRows = markdownTable(registryContent)
|
|
155
|
-
.filter((cells) => cells.length >= 6)
|
|
156
|
-
.filter((cells) => /^(_shared|[a-z][a-z0-9-]*)$/.test(cells[0] || "") && /^[A-Z]{2,5}$/.test(cells[2] || ""));
|
|
157
|
-
|
|
158
|
-
if (registryRows.length === 0) {
|
|
159
|
-
fail("docs/09-PLANNING/Module-Registry.md has no active module rows");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const promptPack = hasPromptPack ? read("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md") : "";
|
|
163
|
-
if (hasPromptPack && !/Subagent Worker Invariant|worker[\s\S]{0,120}worktree[\s\S]{0,120}commit SHA/i.test(promptPack)) {
|
|
164
|
-
fail("docs/09-PLANNING/MODULES/Session-Prompt-Pack.md missing subagent worker worktree/commit handoff rule");
|
|
165
|
-
}
|
|
166
|
-
for (const cells of registryRows) {
|
|
167
|
-
const [key, , prefix, branch, currentStep, status] = cells;
|
|
168
|
-
requireFile(`docs/09-PLANNING/MODULES/${key}/module_plan.md`);
|
|
169
|
-
if (!/^(planned|in-progress|paused|completed)$/.test(status)) {
|
|
170
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} has invalid status: ${status}`);
|
|
171
|
-
}
|
|
172
|
-
if (currentStep !== `${prefix}-00` && !currentStep.startsWith(`${prefix}-`)) {
|
|
173
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} current step does not match prefix ${prefix}: ${currentStep}`);
|
|
174
|
-
}
|
|
175
|
-
const branchName = stripMarkdownCode(branch);
|
|
176
|
-
if (!branchName.startsWith("codex/")) {
|
|
177
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${key} branch must use codex/ prefix: ${branch}`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const block = modulePromptBlock(promptPack, key);
|
|
181
|
-
if (!block) {
|
|
182
|
-
if (!exists(`docs/09-PLANNING/MODULES/${key}/session_prompt.md`)) {
|
|
183
|
-
fail(`missing module session prompt for ${key}`);
|
|
184
|
-
}
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
for (const term of [
|
|
188
|
-
"Current Step",
|
|
189
|
-
branchName,
|
|
190
|
-
"Preflight:",
|
|
191
|
-
"Before code edits:",
|
|
192
|
-
"Write scope:",
|
|
193
|
-
"Forbidden without coordination:",
|
|
194
|
-
"Shared Coordination:",
|
|
195
|
-
"Verification:",
|
|
196
|
-
"Closeout:",
|
|
197
|
-
"Stop conditions:",
|
|
198
|
-
]) {
|
|
199
|
-
if (!block.includes(term)) {
|
|
200
|
-
fail(`module session prompt for ${key} missing required term: ${term}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
checkModuleTaskSsotIndex(registryRows);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function checkModuleTaskSsotIndex(registryRows) {
|
|
208
|
-
const registryByModule = new Map(registryRows.map((cells) => [cells[0], cells]));
|
|
209
|
-
const ledgerContent = exists("docs/Harness-Ledger.md") ? read("docs/Harness-Ledger.md") : "";
|
|
210
|
-
const taskPlans = listModuleTaskPlans();
|
|
211
|
-
|
|
212
|
-
for (const taskPlanPath of taskPlans) {
|
|
213
|
-
const parsed = parseModuleTaskPath(taskPlanPath);
|
|
214
|
-
if (!parsed) continue;
|
|
215
|
-
const { moduleKey, taskDir } = parsed;
|
|
216
|
-
const modulePlanPath = `docs/09-PLANNING/MODULES/${moduleKey}/module_plan.md`;
|
|
217
|
-
if (!exists(modulePlanPath)) continue;
|
|
218
|
-
|
|
219
|
-
const taskPlan = read(taskPlanPath);
|
|
220
|
-
const taskProgress = readTaskProgress(taskPlanPath);
|
|
221
|
-
const taskProgressStatus = readTaskProgressStatus(taskPlanPath);
|
|
222
|
-
const taskIsActive = isActiveModuleTaskStatus(taskProgressStatus);
|
|
223
|
-
const stepId = extractStepId(taskPlan, taskDir);
|
|
224
|
-
if (!stepId) {
|
|
225
|
-
if (taskIsActive) {
|
|
226
|
-
fail(`${taskPlanPath} does not expose a Step ID and task directory does not start with <PREFIX-NN>`);
|
|
227
|
-
}
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const modulePlan = read(modulePlanPath);
|
|
232
|
-
const moduleRelativeTaskPlan = `TASKS/${taskDir}/task_plan.md`;
|
|
233
|
-
if (!modulePlan.includes(stepId) || !modulePlan.includes(moduleRelativeTaskPlan)) {
|
|
234
|
-
fail(`${modulePlanPath} does not index ${stepId} task plan ${moduleRelativeTaskPlan}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!taskIsActive) continue;
|
|
238
|
-
|
|
239
|
-
const registryRow = registryByModule.get(moduleKey);
|
|
240
|
-
const reviewPath = taskPlanPath.replace(/task_plan\.md$/, "review.md");
|
|
241
|
-
const registrySynced = Boolean(registryRow && registryRow[4] === stepId);
|
|
242
|
-
const ledgerSynced = ledgerContent.includes(taskPlanPath) && (!exists(reviewPath) || ledgerContent.includes(reviewPath));
|
|
243
|
-
if (registrySynced && ledgerSynced) continue;
|
|
244
|
-
|
|
245
|
-
if (requireGlobalModuleSync) {
|
|
246
|
-
if (!registryRow) {
|
|
247
|
-
fail(`docs/09-PLANNING/Module-Registry.md does not include active module ${moduleKey} for ${taskPlanPath}`);
|
|
248
|
-
} else if (registryRow[4] !== stepId) {
|
|
249
|
-
fail(`docs/09-PLANNING/Module-Registry.md row ${moduleKey} current step is ${registryRow[4]}, but active task is ${stepId}`);
|
|
250
|
-
}
|
|
251
|
-
if (!ledgerContent.includes(taskPlanPath)) {
|
|
252
|
-
fail(`docs/Harness-Ledger.md does not index active module task plan ${taskPlanPath}`);
|
|
253
|
-
}
|
|
254
|
-
if (exists(reviewPath) && !ledgerContent.includes(reviewPath)) {
|
|
255
|
-
fail(`docs/Harness-Ledger.md does not index active module review ${reviewPath}`);
|
|
256
|
-
}
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (hasPendingCoordinatorHandoff(taskPlan, taskProgress)) {
|
|
261
|
-
warn(`${taskPlanPath} has pending coordinator handoff; run coordinator pass before final integration or set HARNESS_REQUIRE_GLOBAL_MODULE_SYNC=1 for strict gate`);
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
fail(`${taskPlanPath} is active but is neither globally synced nor marked with Coordinator Handoff: pending-coordinator-pass`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function listModuleTaskPlans() {
|
|
269
|
-
const modulesRoot = filePath("docs/09-PLANNING/MODULES");
|
|
270
|
-
if (!fs.existsSync(modulesRoot)) return [];
|
|
271
|
-
const results = [];
|
|
272
|
-
function walk(dir) {
|
|
273
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
274
|
-
const full = path.join(dir, entry);
|
|
275
|
-
const relativePath = rel(path.relative(targetRoot, full));
|
|
276
|
-
const stat = fs.statSync(full);
|
|
277
|
-
if (stat.isDirectory()) {
|
|
278
|
-
if (relativePath.includes("/_archive/") || relativePath.endsWith("/_task-template")) continue;
|
|
279
|
-
walk(full);
|
|
280
|
-
} else if (/\/TASKS\/[^/]+\/task_plan\.md$/.test(relativePath)) {
|
|
281
|
-
results.push(relativePath);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
walk(modulesRoot);
|
|
286
|
-
return results;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function parseModuleTaskPath(taskPlanPath) {
|
|
290
|
-
const match = taskPlanPath.match(/^docs\/09-PLANNING\/MODULES\/([^/]+)\/TASKS\/([^/]+)\/task_plan\.md$/);
|
|
291
|
-
if (!match) return null;
|
|
292
|
-
return { moduleKey: match[1], taskDir: match[2] };
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function extractStepId(taskPlanContent, taskDir) {
|
|
296
|
-
const fromPlan = taskPlanContent.match(/^- Step ID:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
297
|
-
if (fromPlan) return fromPlan[1];
|
|
298
|
-
const fromModuleSection = taskPlanContent.match(/^- Step:\s*`?([A-Z]{2,5}-\d{2})`?/m);
|
|
299
|
-
if (fromModuleSection) return fromModuleSection[1];
|
|
300
|
-
const fromDir = taskDir.match(/^([A-Z]{2,5}-\d{2})-/);
|
|
301
|
-
return fromDir ? fromDir[1] : "";
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function readTaskProgress(taskPlanPath) {
|
|
305
|
-
const progressPath = taskPlanPath.replace(/task_plan\.md$/, "progress.md");
|
|
306
|
-
return exists(progressPath) ? read(progressPath) : "";
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function readTaskProgressStatus(taskPlanPath) {
|
|
310
|
-
const progress = readTaskProgress(taskPlanPath);
|
|
311
|
-
if (!progress) return "";
|
|
312
|
-
const match = progress.match(/^##\s*(?:Status|状态)\s*[::]?\s*(?:\n\s*)?([^\n]+)/im);
|
|
313
|
-
return match ? normalizeModuleTaskStatus(stripMarkdownCode(match[1])) : "";
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function normalizeModuleTaskStatus(status) {
|
|
317
|
-
const value = String(status || "").trim().toLowerCase();
|
|
318
|
-
const aliases = new Map([
|
|
319
|
-
["未开始", "not-started"],
|
|
320
|
-
["未启动", "not-started"],
|
|
321
|
-
["进行中", "in-progress"],
|
|
322
|
-
["开发中", "in-progress"],
|
|
323
|
-
["规划审查", "planning-review"],
|
|
324
|
-
["已完成", "completed"],
|
|
325
|
-
["完成", "completed"],
|
|
326
|
-
["已关闭", "closed"],
|
|
327
|
-
["关闭", "closed"],
|
|
328
|
-
["已阻塞", "blocked"],
|
|
329
|
-
["阻塞", "blocked"],
|
|
330
|
-
]);
|
|
331
|
-
return aliases.get(value) || value;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function isActiveModuleTaskStatus(status) {
|
|
335
|
-
if (!status) return false;
|
|
336
|
-
return !new Set([
|
|
337
|
-
"not-started",
|
|
338
|
-
"blocked-not-started",
|
|
339
|
-
"complete",
|
|
340
|
-
"completed",
|
|
341
|
-
"closed",
|
|
342
|
-
"closed-with-residual",
|
|
343
|
-
"closed-local-only",
|
|
344
|
-
"superseded",
|
|
345
|
-
]).has(status);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function hasPendingCoordinatorHandoff(taskPlanContent, progressContent) {
|
|
349
|
-
const combined = `${taskPlanContent}\n${progressContent}`;
|
|
350
|
-
return /Coordinator Handoff/i.test(combined) && /pending-coordinator-pass/i.test(combined);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
130
|
function checkAgentsIndex() {
|
|
354
131
|
if (!exists("AGENTS.md")) return;
|
|
355
132
|
const content = read("AGENTS.md");
|
|
@@ -517,10 +294,6 @@ function findHeaderIndex(rows, pattern) {
|
|
|
517
294
|
return rows.findIndex((cells) => cells.some((cell) => pattern.test(cell)));
|
|
518
295
|
}
|
|
519
296
|
|
|
520
|
-
function columnIndex(header, pattern) {
|
|
521
|
-
return header.findIndex((cell) => pattern.test(cell));
|
|
522
|
-
}
|
|
523
|
-
|
|
524
297
|
function contentIncludesAny(content, terms) {
|
|
525
298
|
return terms.some((term) => (term instanceof RegExp ? term.test(content) : content.includes(term)));
|
|
526
299
|
}
|
|
@@ -602,7 +375,7 @@ function checkCloseoutSsot() {
|
|
|
602
375
|
if (!createdMatch && !candidateMatch && !lessonsNonePattern.test(lessonsCheck)) {
|
|
603
376
|
fail(`${closeoutPath} row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
|
|
604
377
|
} else if (createdMatch && !lessonIds.has(createdMatch[1])) {
|
|
605
|
-
fail(`${closeoutPath} row ${id} references missing
|
|
378
|
+
fail(`${closeoutPath} row ${id} references missing lesson detail doc id: ${createdMatch[1]}`);
|
|
606
379
|
} else if (candidateMatch && !lessonCandidateIds.has(candidateMatch[1])) {
|
|
607
380
|
fail(`${closeoutPath} row ${id} references missing lesson candidate id: ${candidateMatch[1]}`);
|
|
608
381
|
}
|
|
@@ -615,7 +388,7 @@ function checkCloseoutSsot() {
|
|
|
615
388
|
if (!ledgerCreatedMatch && !ledgerCandidateMatch && !lessonsNonePattern.test(ledgerLessonsCheck)) {
|
|
616
389
|
fail(`docs/Harness-Ledger.md row ${id} needs Lessons Check value: checked-created:<lesson-id>, checked-candidate:<candidate-id>, queued-promotion:<candidate-id>, or checked-none:<reason>`);
|
|
617
390
|
} else if (ledgerCreatedMatch && !lessonIds.has(ledgerCreatedMatch[1])) {
|
|
618
|
-
fail(`docs/Harness-Ledger.md row ${id} references missing
|
|
391
|
+
fail(`docs/Harness-Ledger.md row ${id} references missing lesson detail doc id: ${ledgerCreatedMatch[1]}`);
|
|
619
392
|
} else if (ledgerCandidateMatch && !lessonCandidateIds.has(ledgerCandidateMatch[1])) {
|
|
620
393
|
fail(`docs/Harness-Ledger.md row ${id} references missing lesson candidate id: ${ledgerCandidateMatch[1]}`);
|
|
621
394
|
}
|
|
@@ -624,18 +397,20 @@ function checkCloseoutSsot() {
|
|
|
624
397
|
}
|
|
625
398
|
|
|
626
399
|
function collectLessonIds() {
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
400
|
+
const root = filePath("docs/01-GOVERNANCE/lessons");
|
|
401
|
+
const ids = new Set();
|
|
402
|
+
if (!fs.existsSync(root)) return ids;
|
|
403
|
+
for (const entry of fs.readdirSync(root)) {
|
|
404
|
+
if (!entry.endsWith(".md")) continue;
|
|
405
|
+
const full = path.join(root, entry);
|
|
406
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
407
|
+
const content = fs.readFileSync(full, "utf8");
|
|
408
|
+
const pathMatch = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
|
|
409
|
+
const titleMatch = content.match(/#\s*(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i);
|
|
410
|
+
const id = titleMatch?.[1] || pathMatch?.[1];
|
|
411
|
+
if (id) ids.add(id);
|
|
412
|
+
}
|
|
413
|
+
return ids;
|
|
639
414
|
}
|
|
640
415
|
|
|
641
416
|
function collectLessonCandidateIds() {
|
|
@@ -662,52 +437,16 @@ function collectLessonCandidateIds() {
|
|
|
662
437
|
return ids;
|
|
663
438
|
}
|
|
664
439
|
|
|
665
|
-
function
|
|
666
|
-
const
|
|
667
|
-
if (!
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const headerIndex = findHeaderIndex(table, /^ID$/i);
|
|
676
|
-
const header = headerIndex >= 0 ? table[headerIndex] : [];
|
|
677
|
-
const detailColumn = columnIndexAny(header, [/^(Detail Doc|Detail)$/i, /^详情文档$/i, /^详情文档\s*\/\s*Detail Doc$/i]);
|
|
678
|
-
const idColumn = columnIndex(header, /^ID$/i);
|
|
679
|
-
const statusColumn = columnIndexAny(header, [/^Status$/i, /^状态$/i, /^状态\s*\/\s*Status$/i]);
|
|
680
|
-
if (idColumn < 0) fail(`${lessonsPath} missing ID column`);
|
|
681
|
-
if (detailColumn < 0) fail(`${lessonsPath} missing Detail Doc column`);
|
|
682
|
-
if (statusColumn < 0) fail(`${lessonsPath} missing Status column`);
|
|
683
|
-
if (idColumn < 0 || detailColumn < 0 || statusColumn < 0) return;
|
|
684
|
-
|
|
685
|
-
const lessonRows = table.filter((cells) => /^L-\d{4}(-\d{2}-\d{2})?-\d+/i.test(cells[idColumn] || ""));
|
|
686
|
-
for (const cells of lessonRows) {
|
|
687
|
-
const id = cells[idColumn] || "";
|
|
688
|
-
const status = cells[statusColumn] || "";
|
|
689
|
-
const detail = cells[detailColumn] || "";
|
|
690
|
-
if (!/pending|approved|merged|rejected|superseded|🟡|🟢|✅|❌|🔀/i.test(status)) {
|
|
691
|
-
fail(`${lessonsPath} row ${id} has unrecognized status: ${status}`);
|
|
692
|
-
}
|
|
693
|
-
const detailMatch = detail.match(/docs\/01-GOVERNANCE\/lessons\/[^|\s`]+\.md/);
|
|
694
|
-
if (!detailMatch) {
|
|
695
|
-
fail(`${lessonsPath} row ${id} Detail Doc must point to docs/01-GOVERNANCE/lessons/*.md`);
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
const detailPath = detailMatch[0];
|
|
699
|
-
if (!exists(detailPath)) {
|
|
700
|
-
fail(`${lessonsPath} row ${id} Detail Doc missing file: ${detailPath}`);
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
const detailContent = read(detailPath);
|
|
704
|
-
if (!detailContent.includes(id)) {
|
|
705
|
-
fail(`${detailPath} does not include lesson id ${id}`);
|
|
706
|
-
}
|
|
707
|
-
for (const requiredTerm of ["背景", "冲突声明"]) {
|
|
708
|
-
if (!detailContent.includes(requiredTerm)) {
|
|
709
|
-
fail(`${detailPath} missing required lesson section: ${requiredTerm}`);
|
|
710
|
-
}
|
|
440
|
+
function checkLessonDetailDocs() {
|
|
441
|
+
const root = filePath("docs/01-GOVERNANCE/lessons");
|
|
442
|
+
if (!fs.existsSync(root)) return;
|
|
443
|
+
for (const entry of fs.readdirSync(root)) {
|
|
444
|
+
if (!entry.endsWith(".md")) continue;
|
|
445
|
+
const relativePath = `docs/01-GOVERNANCE/lessons/${entry}`;
|
|
446
|
+
const content = read(relativePath);
|
|
447
|
+
const id = entry.match(/(L-\d{4}(?:-\d{2}-\d{2})?-\d+)/i)?.[1];
|
|
448
|
+
if (id && !content.includes(id)) {
|
|
449
|
+
fail(`${relativePath} does not include lesson id ${id}`);
|
|
711
450
|
}
|
|
712
451
|
}
|
|
713
452
|
}
|
|
@@ -748,7 +487,7 @@ function main() {
|
|
|
748
487
|
checkReviewTemplate();
|
|
749
488
|
checkHarnessLedger();
|
|
750
489
|
checkCloseoutSsot();
|
|
751
|
-
|
|
490
|
+
checkLessonDetailDocs();
|
|
752
491
|
checkWalkthroughTemplate();
|
|
753
492
|
checkReferencePlaceholders();
|
|
754
493
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
serveDashboardWorkbench,
|
|
5
|
+
writeDashboardFolder,
|
|
6
|
+
writeDashboardSingleFile,
|
|
7
|
+
} from "../lib/harness-core.mjs";
|
|
8
|
+
|
|
9
|
+
export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
10
|
+
const watch = takeFlag("--watch");
|
|
11
|
+
const workbench = takeFlag("--workbench");
|
|
12
|
+
const out = takeOption("--out", path.join("tmp", "harness-dashboard.html"));
|
|
13
|
+
const outDir = takeOption("--out-dir", "");
|
|
14
|
+
const host = takeOption("--host", "127.0.0.1");
|
|
15
|
+
const port = takeOption("--port", "0");
|
|
16
|
+
const localeOverride = takeOption("--locale", "");
|
|
17
|
+
const opts = localeOverride ? { localeOverride } : {};
|
|
18
|
+
if (workbench) {
|
|
19
|
+
if (!outDir) {
|
|
20
|
+
console.error("dashboard --workbench requires --out-dir so regenerated data has a stable folder");
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
await serveDashboardWorkbench(outDir, targetArg(), { ...opts, host, port });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error.message);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (watch) {
|
|
31
|
+
if (!outDir) {
|
|
32
|
+
console.error("dashboard --watch requires --out-dir so updates are written to a stable folder");
|
|
33
|
+
process.exit(2);
|
|
34
|
+
}
|
|
35
|
+
const target = targetArg();
|
|
36
|
+
const docsRoot = path.basename(path.resolve(target)) === "docs" ? path.resolve(target) : path.join(path.resolve(target), "docs");
|
|
37
|
+
const regenerate = () => {
|
|
38
|
+
try {
|
|
39
|
+
console.log(writeDashboardFolder(outDir, target, opts));
|
|
40
|
+
console.log(`dashboard regenerated: ${new Date().toISOString()}`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`dashboard regeneration failed: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
regenerate();
|
|
46
|
+
let timer = null;
|
|
47
|
+
const watcher = fs.watch(docsRoot, { recursive: true }, () => {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
timer = setTimeout(regenerate, 300);
|
|
50
|
+
});
|
|
51
|
+
const close = () => {
|
|
52
|
+
watcher.close();
|
|
53
|
+
clearTimeout(timer);
|
|
54
|
+
process.exit(0);
|
|
55
|
+
};
|
|
56
|
+
process.on("SIGINT", close);
|
|
57
|
+
process.on("SIGTERM", close);
|
|
58
|
+
console.log(`watching ${docsRoot}`);
|
|
59
|
+
await new Promise(() => {});
|
|
60
|
+
}
|
|
61
|
+
if (outDir) {
|
|
62
|
+
console.log(writeDashboardFolder(outDir, targetArg(), opts));
|
|
63
|
+
} else {
|
|
64
|
+
console.log(writeDashboardSingleFile(out, targetArg(), opts));
|
|
65
|
+
}
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildMigrationPlan,
|
|
3
|
+
runMigration,
|
|
4
|
+
verifyMigrationSession,
|
|
5
|
+
} from "../lib/harness-core.mjs";
|
|
6
|
+
import {
|
|
7
|
+
applyTaskAuditIndexMigration,
|
|
8
|
+
planTaskAuditIndexMigration,
|
|
9
|
+
} from "../lib/task-audit-migration.mjs";
|
|
10
|
+
|
|
11
|
+
export function runMigrationCommand(command, { args, takeFlag, takeOption, targetArg }) {
|
|
12
|
+
if (command === "migrate-task-audit-index") {
|
|
13
|
+
const json = takeFlag("--json");
|
|
14
|
+
const apply = takeFlag("--apply");
|
|
15
|
+
const planOnly = takeFlag("--plan");
|
|
16
|
+
try {
|
|
17
|
+
const result = apply && !planOnly
|
|
18
|
+
? applyTaskAuditIndexMigration(targetArg())
|
|
19
|
+
: planTaskAuditIndexMigration(targetArg());
|
|
20
|
+
if (json) {
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
} else {
|
|
23
|
+
console.log(`Task audit INDEX migration ${result.result}: ${result.target}`);
|
|
24
|
+
console.log(`actions: ${result.summary.actions}`);
|
|
25
|
+
console.log(`legacy audit blocks: ${result.summary.legacyAuditBlocks}`);
|
|
26
|
+
console.log(`failures: ${result.summary.failures}`);
|
|
27
|
+
for (const action of result.actions || []) console.log(`- ${action.taskId}: ${action.legacyBlocks.join(", ")}`);
|
|
28
|
+
for (const failure of result.failures || []) console.error(`Failure: ${failure.taskId}: ${failure.failure}`);
|
|
29
|
+
}
|
|
30
|
+
process.exit(result.failures?.length ? 1 : 0);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (json && error.plan) console.error(JSON.stringify(error.plan, null, 2));
|
|
33
|
+
else console.error(error.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (command === "migrate-plan") {
|
|
39
|
+
const json = takeFlag("--json");
|
|
40
|
+
const limit = Number.parseInt(takeOption("--limit", "20"), 10) || 20;
|
|
41
|
+
try {
|
|
42
|
+
const plan = buildMigrationPlan(targetArg(), { limit });
|
|
43
|
+
if (json) {
|
|
44
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(`Migration Plan: ${plan.target}`);
|
|
47
|
+
console.log(`mode: ${plan.mode}`);
|
|
48
|
+
console.log(`warnings: ${plan.summary.warnings}`);
|
|
49
|
+
console.log(`task actions: ${plan.summary.taskActions}`);
|
|
50
|
+
console.log(`visual map actions: ${plan.summary.visualMapActions}`);
|
|
51
|
+
console.log(`legacy visual-only tasks: ${plan.summary.legacyVisualOnly}`);
|
|
52
|
+
console.log(`weak briefs: ${plan.summary.weakBrief}`);
|
|
53
|
+
console.log(`unknown classifications: ${plan.summary.unknownClassification}`);
|
|
54
|
+
console.log(`full cutover eligible: ${plan.summary.fullCutoverEligible ? "yes" : "no"}`);
|
|
55
|
+
console.log(`review actions: ${plan.summary.reviewSchemaGaps}`);
|
|
56
|
+
console.log(`legacy actions: ${plan.summary.legacyReferenceGaps}`);
|
|
57
|
+
console.log(`legacy residuals: ${plan.summary.legacyResiduals}`);
|
|
58
|
+
console.log(`recommended capabilities: ${plan.summary.recommendedCapabilities.join(", ") || "none"}`);
|
|
59
|
+
console.log("\nPhases:");
|
|
60
|
+
for (const phase of plan.phases) console.log(`- ${phase.id}: ${phase.title}`);
|
|
61
|
+
console.log("\nTop task actions:");
|
|
62
|
+
for (const action of plan.taskActions) console.log(`- ${action.taskId}: add ${action.files.join(", ")}`);
|
|
63
|
+
console.log("\nTop review actions:");
|
|
64
|
+
for (const action of plan.reviewActions) console.log(`- ${action.path}: add ${action.missing.join(", ")}`);
|
|
65
|
+
console.log("\nTop legacy residuals:");
|
|
66
|
+
for (const action of plan.legacyResiduals) console.log(`- ${action.taskId}: ${action.missing} (${action.reason})`);
|
|
67
|
+
console.log("\nNext commands:");
|
|
68
|
+
for (const next of plan.nextCommands) console.log(`- ${next}`);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(error.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (command === "migrate-run") {
|
|
78
|
+
const locale = takeOption("--locale", "");
|
|
79
|
+
const assumeLocale = takeFlag("--assume-locale");
|
|
80
|
+
const allowDirty = takeFlag("--allow-dirty");
|
|
81
|
+
const planOnly = takeFlag("--plan-only");
|
|
82
|
+
const outDir = takeOption("--out-dir", "");
|
|
83
|
+
const sessionDir = takeOption("--session-dir", "");
|
|
84
|
+
try {
|
|
85
|
+
console.log(
|
|
86
|
+
JSON.stringify(
|
|
87
|
+
runMigration(targetArg(), {
|
|
88
|
+
locale,
|
|
89
|
+
assumeLocale,
|
|
90
|
+
allowDirty,
|
|
91
|
+
planOnly,
|
|
92
|
+
outDir,
|
|
93
|
+
sessionDir,
|
|
94
|
+
}),
|
|
95
|
+
null,
|
|
96
|
+
2,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(error.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (command === "migrate-verify") {
|
|
107
|
+
const json = takeFlag("--json");
|
|
108
|
+
const fullCutover = takeFlag("--full-cutover");
|
|
109
|
+
const sessionPath = args.shift();
|
|
110
|
+
if (!sessionPath) {
|
|
111
|
+
console.error("Missing session.json path");
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const result = verifyMigrationSession(sessionPath, { fullCutover });
|
|
115
|
+
if (json) {
|
|
116
|
+
console.log(JSON.stringify(result, null, 2));
|
|
117
|
+
} else {
|
|
118
|
+
for (const failure of result.failures) console.error(`Failure: ${failure}`);
|
|
119
|
+
for (const warning of result.warnings) console.log(`Warning: ${warning}`);
|
|
120
|
+
console.log(`Migration verify ${result.status}: ${result.sessionPath}`);
|
|
121
|
+
}
|
|
122
|
+
process.exit(result.status === "pass" ? 0 : 1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`Unsupported migration command: ${command}`);
|
|
126
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkPresetPackage,
|
|
3
|
+
inspectPresetPackage,
|
|
4
|
+
installPresetPackage,
|
|
5
|
+
listPresetPackages,
|
|
6
|
+
seedBundledPresets,
|
|
7
|
+
uninstallPresetPackage,
|
|
8
|
+
} from "../lib/harness-core.mjs";
|
|
9
|
+
|
|
10
|
+
export function runPresetCommand({ args, takeFlag, targetArg }) {
|
|
11
|
+
const subcommand = args.shift() || "list";
|
|
12
|
+
const json = takeFlag("--json");
|
|
13
|
+
const project = takeFlag("--project");
|
|
14
|
+
try {
|
|
15
|
+
if (subcommand === "list") {
|
|
16
|
+
const target = targetArg();
|
|
17
|
+
const presets = listPresetPackages({ targetInput: target }).map((preset) => ({
|
|
18
|
+
id: preset.id,
|
|
19
|
+
version: preset.version,
|
|
20
|
+
purpose: preset.purpose,
|
|
21
|
+
compatibleBudgets: preset.compatibleBudgets,
|
|
22
|
+
source: preset.source,
|
|
23
|
+
manifestPath: preset.manifestRelativePath,
|
|
24
|
+
}));
|
|
25
|
+
if (json) console.log(JSON.stringify({ presets }, null, 2));
|
|
26
|
+
else for (const preset of presets) console.log(`${preset.id}@${preset.version} [${preset.source}] ${preset.compatibleBudgets.join(",")} - ${preset.purpose}`);
|
|
27
|
+
} else if (subcommand === "inspect") {
|
|
28
|
+
const id = args.shift();
|
|
29
|
+
if (!id) throw new Error("Missing preset id");
|
|
30
|
+
const preset = inspectPresetPackage(id, { targetInput: targetArg() });
|
|
31
|
+
if (json) console.log(JSON.stringify(preset, null, 2));
|
|
32
|
+
else console.log(`${preset.id}@${preset.version}\n${preset.purpose}`);
|
|
33
|
+
} else if (subcommand === "check") {
|
|
34
|
+
const id = args.shift();
|
|
35
|
+
if (!id) throw new Error("Missing preset id");
|
|
36
|
+
const report = checkPresetPackage(id, { targetInput: targetArg() });
|
|
37
|
+
if (json) console.log(JSON.stringify(report, null, 2));
|
|
38
|
+
else {
|
|
39
|
+
for (const failure of report.failures) console.error(`Failure: ${failure}`);
|
|
40
|
+
for (const warning of report.warnings) console.log(`Warning: ${warning}`);
|
|
41
|
+
console.log(`Preset check ${report.status}: ${report.id}@${report.version}`);
|
|
42
|
+
}
|
|
43
|
+
process.exit(report.status === "pass" ? 0 : 1);
|
|
44
|
+
} else if (subcommand === "install") {
|
|
45
|
+
const force = takeFlag("--force");
|
|
46
|
+
const source = args.shift();
|
|
47
|
+
if (!source) throw new Error("Missing preset source");
|
|
48
|
+
const result = installPresetPackage(source, { force, scope: project ? "project" : "user", targetInput: targetArg() });
|
|
49
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
50
|
+
else console.log(`Installed preset ${result.id}@${result.version} to ${result.destination}`);
|
|
51
|
+
} else if (subcommand === "seed") {
|
|
52
|
+
const force = takeFlag("--force");
|
|
53
|
+
const dryRun = takeFlag("--dry-run");
|
|
54
|
+
const result = seedBundledPresets({ force, dryRun, scope: project ? "project" : "user", targetInput: targetArg() });
|
|
55
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
56
|
+
else {
|
|
57
|
+
console.log(`Seeded bundled presets to ${result.target}`);
|
|
58
|
+
for (const preset of result.presets) console.log(`${preset.action}: ${preset.id}@${preset.version}`);
|
|
59
|
+
}
|
|
60
|
+
} else if (subcommand === "uninstall") {
|
|
61
|
+
const id = args.shift();
|
|
62
|
+
if (!id) throw new Error("Missing preset id");
|
|
63
|
+
const result = uninstallPresetPackage(id, { scope: project ? "project" : "user", targetInput: targetArg() });
|
|
64
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
else console.log(`${result.removed ? "Removed" : "Preset not installed"}: ${result.id}`);
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error(`Unknown preset subcommand: ${subcommand}`);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(error.message);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|