coding-agent-harness 1.0.7 → 1.1.0
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 +33 -0
- package/CONTRIBUTING.md +9 -5
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +32 -6
- package/dist/check-dist-observation.mjs +73 -28
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +88 -0
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +67 -8
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +65 -4
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +111 -53
- package/dist/harness.mjs +6 -303
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +5 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +68 -29
- package/dist/lib/preset-registry.mjs +374 -72
- package/dist/lib/preset-runner.mjs +560 -0
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +4 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +117 -159
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +38 -17
- package/dist/lib/task-scanner.mjs +75 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +187 -18
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +2 -1
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +19 -11
- package/postinstall.mjs +37 -0
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/release-closeout/checks/check-release-package.mjs +29 -0
- package/presets/release-closeout/preset.yaml +100 -0
- package/presets/release-closeout/scripts/generate-release-package.mjs +572 -0
- package/presets/release-closeout/templates/execution_strategy.append.md +7 -0
- package/presets/release-closeout/templates/findings.seed.md +5 -0
- package/presets/release-closeout/templates/review.seed.md +3 -0
- package/presets/release-closeout/templates/task_plan.append.md +24 -0
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/pull-request-standard.md +2 -2
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/pull-request-standard.md +2 -2
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/pull-request-standard.md +1 -1
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { collectTasks } from "./task-scanner.mjs";
|
|
4
|
+
import { normalizeLocale, normalizeTarget, readBundledTemplate, readFileSafe, renderTaskTemplate, todayDate, toPosix } from "./core-shared.mjs";
|
|
5
|
+
import { assertRenderableHarnessManifest, renderHarnessManifest } from "./harness-paths.mjs";
|
|
6
|
+
import { moduleTemplateFiles } from "./task-lifecycle/template-files.mjs";
|
|
7
|
+
export const allowedHarnessModuleStatuses = new Set([
|
|
8
|
+
"planned",
|
|
9
|
+
"in-progress",
|
|
10
|
+
"blocked",
|
|
11
|
+
"ready-for-sync",
|
|
12
|
+
"integrating",
|
|
13
|
+
"completed",
|
|
14
|
+
"paused",
|
|
15
|
+
"cancelled",
|
|
16
|
+
]);
|
|
17
|
+
export function normalizeHarnessModuleKey(value) {
|
|
18
|
+
const normalized = String(value || "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
19
|
+
if (!/^[a-z][a-z0-9-]*$/.test(normalized))
|
|
20
|
+
throw new Error(`Invalid module key: ${value || "<empty>"}`);
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
export function readHarnessModules(targetInput) {
|
|
24
|
+
const target = asHarnessTarget(targetInput);
|
|
25
|
+
return normalizeHarnessModules(target, target.harness.manifest?.modules || null);
|
|
26
|
+
}
|
|
27
|
+
export function registeredHarnessModule(targetInput, moduleKey) {
|
|
28
|
+
const key = normalizeHarnessModuleKey(moduleKey);
|
|
29
|
+
const modules = readHarnessModules(targetInput);
|
|
30
|
+
return modules.items[key] || null;
|
|
31
|
+
}
|
|
32
|
+
export function prepareModuleRegistration(targetInput, moduleKey, input, { dryRun = false, allowExisting = false } = {}) {
|
|
33
|
+
const target = asHarnessTarget(targetInput);
|
|
34
|
+
ensureV2Harness(target);
|
|
35
|
+
const key = normalizeHarnessModuleKey(moduleKey);
|
|
36
|
+
const modules = readHarnessModules(target);
|
|
37
|
+
const existed = Boolean(modules.items[key]);
|
|
38
|
+
if (existed && !allowExisting)
|
|
39
|
+
throw new Error(`Module already registered: ${key}`);
|
|
40
|
+
const module = normalizeModuleDefinition(target, key, { ...(modules.items[key] || {}), ...input });
|
|
41
|
+
modules.items[key] = module;
|
|
42
|
+
return writeModuleRegistryMutation(target, modules, key, { dryRun, action: existed ? "sync-module-registry" : "register-module", scaffold: true, locale: input.locale });
|
|
43
|
+
}
|
|
44
|
+
export function prepareModuleStepRegistrationUpdate(targetInput, moduleKey, { stepId, state, dryRun = false }) {
|
|
45
|
+
const target = asHarnessTarget(targetInput);
|
|
46
|
+
ensureV2Harness(target);
|
|
47
|
+
const key = normalizeHarnessModuleKey(moduleKey);
|
|
48
|
+
const modules = readHarnessModules(target);
|
|
49
|
+
const module = modules.items[key];
|
|
50
|
+
if (!module)
|
|
51
|
+
throw new Error(`Unknown module: ${key}. Register it first with: harness module register ${key} --title <title> --prefix <PREFIX> --scope <path> ${target.projectRoot}`);
|
|
52
|
+
module.currentStep = stepId;
|
|
53
|
+
module.status = mapStepStateToModuleStatus(state);
|
|
54
|
+
module.updated = todayDate();
|
|
55
|
+
return writeModuleRegistryMutation(target, modules, key, { dryRun, action: "sync-module-registry" });
|
|
56
|
+
}
|
|
57
|
+
export function prepareModuleUnregister(targetInput, moduleKey, { dryRun = false } = {}) {
|
|
58
|
+
const target = asHarnessTarget(targetInput);
|
|
59
|
+
ensureV2Harness(target);
|
|
60
|
+
const key = normalizeHarnessModuleKey(moduleKey);
|
|
61
|
+
const modules = readHarnessModules(target);
|
|
62
|
+
if (!modules.items[key])
|
|
63
|
+
throw new Error(`Module is not registered: ${key}`);
|
|
64
|
+
const blockers = moduleUnregisterBlockers(target, key);
|
|
65
|
+
if (blockers.length > 0)
|
|
66
|
+
throw new Error(`Cannot unregister module ${key}; references still exist:\n${blockers.map((item) => `- ${item}`).join("\n")}`);
|
|
67
|
+
delete modules.items[key];
|
|
68
|
+
const result = writeModuleRegistryMutation(target, modules, key, { dryRun, action: "unregister-module" });
|
|
69
|
+
return { moduleKey: key, changes: result.changes };
|
|
70
|
+
}
|
|
71
|
+
export function prepareModuleScaffold(targetInput, moduleKey, { dryRun = false, locale = "" } = {}) {
|
|
72
|
+
const target = asHarnessTarget(targetInput);
|
|
73
|
+
ensureV2Harness(target);
|
|
74
|
+
const key = normalizeHarnessModuleKey(moduleKey);
|
|
75
|
+
const modules = readHarnessModules(target);
|
|
76
|
+
const module = modules.items[key];
|
|
77
|
+
if (!module)
|
|
78
|
+
throw new Error(`Module is not registered: ${key}`);
|
|
79
|
+
return {
|
|
80
|
+
moduleKey: key,
|
|
81
|
+
changes: scaffoldModuleFiles(target, key, module, { dryRun, locale }),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function moduleRegistryViewPath(targetInput) {
|
|
85
|
+
const target = asHarnessTarget(targetInput);
|
|
86
|
+
const modules = readHarnessModules(target);
|
|
87
|
+
const generatedView = modules.generatedView || defaultGeneratedView(target.harness);
|
|
88
|
+
return path.join(target.projectRoot, generatedView);
|
|
89
|
+
}
|
|
90
|
+
export function renderModuleRegistryView(targetInput, modulesInput = null) {
|
|
91
|
+
const target = asHarnessTarget(targetInput);
|
|
92
|
+
const modules = modulesInput || readHarnessModules(target);
|
|
93
|
+
const rows = Object.entries(modules.items || {}).sort(([left], [right]) => left.localeCompare(right)).map(([key, module]) => [
|
|
94
|
+
`M-${String(module.prefix || key).toUpperCase().replace(/[^A-Z0-9]+/g, "-")}`,
|
|
95
|
+
key,
|
|
96
|
+
module.title || key,
|
|
97
|
+
module.prefix || "",
|
|
98
|
+
module.branch || "",
|
|
99
|
+
module.currentStep || "",
|
|
100
|
+
module.status || "planned",
|
|
101
|
+
module.owner || "coordinator",
|
|
102
|
+
(module.scope || []).join("<br>") || "none",
|
|
103
|
+
(module.shared || []).join("<br>") || "none",
|
|
104
|
+
(module.dependsOn || []).join(", ") || "none",
|
|
105
|
+
module.plan || "none",
|
|
106
|
+
module.brief || "none",
|
|
107
|
+
module.updated || todayDate(),
|
|
108
|
+
]);
|
|
109
|
+
return `# Module Registry
|
|
110
|
+
|
|
111
|
+
Generated from \`harness.yaml\` \`modules.items\`. Do not edit this view directly.
|
|
112
|
+
|
|
113
|
+
## Active Modules
|
|
114
|
+
|
|
115
|
+
| ID | Key | Title | Prefix | Branch | Current Step | Status | Owner | Scope | Shared | Depends On | Plan | Brief | Updated |
|
|
116
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
117
|
+
${rows.length ? rows.map((row) => `| ${row.map(escapeMarkdownCell).join(" | ")} |`).join("\n") : "| none | none | none | none | none | none | planned | none | none | none | none | none | none | none |"}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
export function moduleRegistryRelativePaths(targetInput) {
|
|
121
|
+
const target = asHarnessTarget(targetInput);
|
|
122
|
+
return [
|
|
123
|
+
toPosix(path.relative(target.projectRoot, target.harness.manifestPath)),
|
|
124
|
+
toPosix(path.relative(target.projectRoot, moduleRegistryViewPath(target))),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
function writeModuleRegistryMutation(target, modules, moduleKey, { dryRun, action, scaffold = false, locale = "" }) {
|
|
128
|
+
assertRenderableHarnessManifest(target.harness.manifest);
|
|
129
|
+
const manifest = target.harness.manifest;
|
|
130
|
+
if (!manifest)
|
|
131
|
+
throw new Error("Missing harness.yaml");
|
|
132
|
+
const manifestRelative = toPosix(path.relative(target.projectRoot, target.harness.manifestPath));
|
|
133
|
+
const viewPath = moduleRegistryViewPath(target);
|
|
134
|
+
const viewRelative = toPosix(path.relative(target.projectRoot, viewPath));
|
|
135
|
+
const module = modules.items[moduleKey] || {};
|
|
136
|
+
const scaffoldChanges = scaffold ? scaffoldModuleFiles(target, moduleKey, module, { dryRun, locale }) : [];
|
|
137
|
+
if (!dryRun) {
|
|
138
|
+
manifest.modules = modules;
|
|
139
|
+
fs.mkdirSync(path.dirname(target.harness.manifestPath), { recursive: true });
|
|
140
|
+
fs.writeFileSync(target.harness.manifestPath, renderHarnessManifest({
|
|
141
|
+
locale: manifest.locale,
|
|
142
|
+
capabilities: manifest.capabilities || [],
|
|
143
|
+
structure: manifest.structure,
|
|
144
|
+
modules,
|
|
145
|
+
}));
|
|
146
|
+
fs.mkdirSync(path.dirname(viewPath), { recursive: true });
|
|
147
|
+
fs.writeFileSync(viewPath, renderModuleRegistryView(target, modules));
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
moduleKey,
|
|
151
|
+
module,
|
|
152
|
+
changes: [
|
|
153
|
+
{ destination: manifestRelative, action: dryRun ? `would-${action}` : action, surface: "harness-manifest" },
|
|
154
|
+
{ destination: viewRelative, action: dryRun ? `would-${action}` : action, surface: "module-registry-view" },
|
|
155
|
+
...scaffoldChanges,
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function scaffoldModuleFiles(target, moduleKey, module, { dryRun, locale }) {
|
|
160
|
+
const moduleDir = path.join(target.harness.modulesRoot, moduleKey);
|
|
161
|
+
const normalizedLocale = normalizeLocale(locale || target.harness.manifest?.locale || "en-US");
|
|
162
|
+
const changes = [];
|
|
163
|
+
for (const [destination, source] of moduleTemplateFiles({ locale: normalizedLocale })) {
|
|
164
|
+
const destinationPath = path.join(moduleDir, destination);
|
|
165
|
+
if (fs.existsSync(destinationPath))
|
|
166
|
+
continue;
|
|
167
|
+
const relative = toPosix(path.relative(target.projectRoot, destinationPath));
|
|
168
|
+
changes.push({
|
|
169
|
+
destination: relative,
|
|
170
|
+
action: dryRun ? "would-create-module-file" : "create-module-file",
|
|
171
|
+
surface: "module-scaffold",
|
|
172
|
+
});
|
|
173
|
+
if (dryRun)
|
|
174
|
+
continue;
|
|
175
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
176
|
+
fs.writeFileSync(destinationPath, renderTaskTemplate(readBundledTemplate(source), {
|
|
177
|
+
taskId: moduleKey,
|
|
178
|
+
title: module.title || moduleKey,
|
|
179
|
+
locale: normalizedLocale,
|
|
180
|
+
budget: "standard",
|
|
181
|
+
moduleKey,
|
|
182
|
+
target,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
return changes;
|
|
186
|
+
}
|
|
187
|
+
function normalizeHarnessModules(target, modules) {
|
|
188
|
+
return {
|
|
189
|
+
schema: modules?.schema || "harness-modules/v1",
|
|
190
|
+
generatedView: modules?.generatedView || defaultGeneratedView(target.harness),
|
|
191
|
+
items: { ...(modules?.items || {}) },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function normalizeModuleDefinition(target, key, input) {
|
|
195
|
+
const scope = normalizeStringList(input.scope || []);
|
|
196
|
+
const title = String(input.title || "").trim();
|
|
197
|
+
const prefix = String(input.prefix || "").trim().toUpperCase();
|
|
198
|
+
if (!title)
|
|
199
|
+
throw new Error(`Module ${key} requires --title`);
|
|
200
|
+
if (!/^[A-Z][A-Z0-9-]{1,12}$/.test(prefix))
|
|
201
|
+
throw new Error(`Module ${key} requires --prefix with 2-13 uppercase letters, digits, or dashes`);
|
|
202
|
+
if (scope.length === 0)
|
|
203
|
+
throw new Error(`Module ${key} requires at least one --scope path`);
|
|
204
|
+
const status = normalizeModuleStatus(input.status || "planned");
|
|
205
|
+
const moduleDir = toPosix(path.relative(target.projectRoot, path.join(target.harness.modulesRoot, key)));
|
|
206
|
+
const module = {
|
|
207
|
+
title,
|
|
208
|
+
prefix,
|
|
209
|
+
status,
|
|
210
|
+
branch: String(input.branch || `codex/${key}`).trim(),
|
|
211
|
+
owner: String(input.owner || "coordinator").trim(),
|
|
212
|
+
currentStep: String(input.currentStep || "").trim(),
|
|
213
|
+
scope,
|
|
214
|
+
shared: normalizeStringList(input.shared || []),
|
|
215
|
+
dependsOn: normalizeStringList(input.dependsOn || []),
|
|
216
|
+
plan: String(input.plan || `${moduleDir}/module_plan.md`).trim(),
|
|
217
|
+
brief: String(input.brief || `${moduleDir}/brief.md`).trim(),
|
|
218
|
+
updated: String(input.updated || todayDate()).trim(),
|
|
219
|
+
};
|
|
220
|
+
validateModulePaths(target, key, module);
|
|
221
|
+
return module;
|
|
222
|
+
}
|
|
223
|
+
function normalizeModuleStatus(value) {
|
|
224
|
+
const normalized = String(value || "planned").trim().toLowerCase().replaceAll("_", "-");
|
|
225
|
+
if (!allowedHarnessModuleStatuses.has(normalized))
|
|
226
|
+
throw new Error(`Invalid module status: ${value}. Expected one of: ${[...allowedHarnessModuleStatuses].join(", ")}`);
|
|
227
|
+
return normalized;
|
|
228
|
+
}
|
|
229
|
+
function normalizeStringList(values) {
|
|
230
|
+
return [...new Set((values || []).flatMap((value) => String(value || "").split(",")).map((value) => value.trim()).filter(Boolean))];
|
|
231
|
+
}
|
|
232
|
+
function validateModulePaths(target, key, module) {
|
|
233
|
+
for (const [field, values] of Object.entries({ scope: module.scope || [], shared: module.shared || [], plan: [module.plan || ""], brief: [module.brief || ""] })) {
|
|
234
|
+
for (const value of values)
|
|
235
|
+
validateProjectRelativePath(target, `modules.items.${key}.${field}`, value);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function validateProjectRelativePath(target, field, value) {
|
|
239
|
+
const raw = String(value || "").trim();
|
|
240
|
+
if (!raw || raw === "none")
|
|
241
|
+
return;
|
|
242
|
+
if (path.isAbsolute(raw) || raw.split(/[\\/]+/).includes(".."))
|
|
243
|
+
throw new Error(`Invalid ${field}: path must stay project-relative: ${raw}`);
|
|
244
|
+
const resolved = path.resolve(target.projectRoot, raw.replace(/\*\*/g, "__glob__").replace(/\*/g, "__glob__"));
|
|
245
|
+
const relative = path.relative(target.projectRoot, resolved);
|
|
246
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
247
|
+
throw new Error(`Invalid ${field}: path escapes project root: ${raw}`);
|
|
248
|
+
}
|
|
249
|
+
function moduleUnregisterBlockers(target, key) {
|
|
250
|
+
const blockers = [];
|
|
251
|
+
const moduleDir = path.join(target.harness.modulesRoot, key);
|
|
252
|
+
const taskDir = path.join(moduleDir, "tasks");
|
|
253
|
+
if (fs.existsSync(taskDir) && fs.readdirSync(taskDir).length > 0)
|
|
254
|
+
blockers.push(toPosix(path.relative(target.projectRoot, taskDir)));
|
|
255
|
+
for (const task of collectTasks(target)) {
|
|
256
|
+
if (task.module === key)
|
|
257
|
+
blockers.push(String(task.id || task.taskPlanPath || key));
|
|
258
|
+
}
|
|
259
|
+
const ledger = readFileSafe(target.harness.ledgerPath);
|
|
260
|
+
if (ledger.includes(`| module | ${key} |`) || ledger.includes(`/modules/${key}/`))
|
|
261
|
+
blockers.push(toPosix(path.relative(target.projectRoot, target.harness.ledgerPath)));
|
|
262
|
+
return [...new Set(blockers)];
|
|
263
|
+
}
|
|
264
|
+
function defaultGeneratedView(paths) {
|
|
265
|
+
return toPosix(path.relative(paths.projectRoot, path.join(paths.modulesRoot, "Module-Registry.md")));
|
|
266
|
+
}
|
|
267
|
+
function mapStepStateToModuleStatus(state) {
|
|
268
|
+
if (state === "done")
|
|
269
|
+
return "completed";
|
|
270
|
+
if (state === "in-progress")
|
|
271
|
+
return "in-progress";
|
|
272
|
+
if (state === "blocked")
|
|
273
|
+
return "blocked";
|
|
274
|
+
if (state === "superseded")
|
|
275
|
+
return "cancelled";
|
|
276
|
+
return "planned";
|
|
277
|
+
}
|
|
278
|
+
function escapeMarkdownCell(value) {
|
|
279
|
+
return String(value || "").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
280
|
+
}
|
|
281
|
+
function ensureV2Harness(target) {
|
|
282
|
+
if (target.harness.version !== 2 || !target.harness.manifest)
|
|
283
|
+
throw new Error("Module registry requires a v2 harness.yaml manifest");
|
|
284
|
+
}
|
|
285
|
+
function asHarnessTarget(targetInput) {
|
|
286
|
+
const candidate = typeof targetInput === "string" ? normalizeTarget(targetInput) : targetInput;
|
|
287
|
+
if (isResolvedHarnessPaths(candidate.harness))
|
|
288
|
+
return { projectRoot: candidate.projectRoot, docsRoot: candidate.harness.docsRoot, harness: candidate.harness };
|
|
289
|
+
const normalized = normalizeTarget(candidate.projectRoot);
|
|
290
|
+
if (!isResolvedHarnessPaths(normalized.harness))
|
|
291
|
+
throw new Error("Could not resolve harness paths for module registry target");
|
|
292
|
+
return { projectRoot: normalized.projectRoot, docsRoot: normalized.harness.docsRoot, harness: normalized.harness };
|
|
293
|
+
}
|
|
294
|
+
function isResolvedHarnessPaths(value) {
|
|
295
|
+
return Boolean(value && typeof value === "object" && "version" in value && "manifestPath" in value && "modulesRoot" in value);
|
|
296
|
+
}
|
|
@@ -6,7 +6,7 @@ export function validateTaskPresetAuditSnapshot(target, task, presetPackage) {
|
|
|
6
6
|
const failures = [];
|
|
7
7
|
if (!presetPackage?.audit?.manifestRequired)
|
|
8
8
|
return failures;
|
|
9
|
-
const bundle =
|
|
9
|
+
const bundle = normalizeTargetRelativePath(task.evidenceBundle || "", "preset Evidence Bundle");
|
|
10
10
|
if (!bundle) {
|
|
11
11
|
failures.push(`${task.path} ${task.taskPreset} preset missing Evidence Bundle for manifest audit`);
|
|
12
12
|
return failures;
|
|
@@ -36,5 +36,28 @@ export function validateTaskPresetAuditSnapshot(target, task, presetPackage) {
|
|
|
36
36
|
else if (audit.manifestSha256 !== presetPackage.manifestSha256) {
|
|
37
37
|
failures.push(`${task.path} ${task.taskPreset} preset manifest hash mismatch: task audit ${audit.manifestSha256}, current ${presetPackage.manifestSha256}`);
|
|
38
38
|
}
|
|
39
|
+
const auditScriptSha256s = asRecord(audit.scriptSha256s);
|
|
40
|
+
const currentScriptSha256s = asRecord(presetPackage.scriptSha256s);
|
|
41
|
+
if (Object.keys(auditScriptSha256s).length > 0 && !recordsEqual(auditScriptSha256s, currentScriptSha256s)) {
|
|
42
|
+
failures.push(`${task.path} ${task.taskPreset} preset script hash mismatch`);
|
|
43
|
+
}
|
|
39
44
|
return failures;
|
|
40
45
|
}
|
|
46
|
+
function asRecord(value) {
|
|
47
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
48
|
+
}
|
|
49
|
+
function normalizeTargetRelativePath(value, label) {
|
|
50
|
+
const raw = String(value || "").replace(/^TARGET:/, "").replace(/^\/+/, "").trim();
|
|
51
|
+
if (!raw)
|
|
52
|
+
return "";
|
|
53
|
+
const normalized = toPosix(path.normalize(raw));
|
|
54
|
+
if (path.isAbsolute(raw) || normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
55
|
+
throw new Error(`${label} escapes target root: ${raw}`);
|
|
56
|
+
}
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
function recordsEqual(left, right) {
|
|
60
|
+
const leftEntries = Object.entries(left).map(([key, value]) => [key, String(value)]).sort(([a], [b]) => a.localeCompare(b));
|
|
61
|
+
const rightEntries = Object.entries(right).map(([key, value]) => [key, String(value)]).sort(([a], [b]) => a.localeCompare(b));
|
|
62
|
+
return JSON.stringify(leftEntries) === JSON.stringify(rightEntries);
|
|
63
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Preset task rendering stays behavior-first until preset/session domain types are modeled.
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import crypto from "node:crypto";
|
|
6
5
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { readJsonSafe, repoRoot, taskContractMarker, toPosix, visualMapFile } from "./core-shared.mjs";
|
|
6
|
+
import { harnessPathContext, readJsonSafe, repoRoot, resolveHarnessPathTemplate, taskContractMarker, toPosix, visualMapFile } from "./core-shared.mjs";
|
|
8
7
|
import { verifyMigrationSession } from "./migration-planner.mjs";
|
|
9
8
|
import { buildPresetAudit, renderPresetTemplate } from "./preset-registry.mjs";
|
|
10
9
|
import { legacyPath, legacyPlanningRoot, legacyTaskRoot, v2HarnessRoot, } from "./harness-paths.mjs";
|
|
@@ -31,17 +30,18 @@ export function resolvePresetInputs(preset, { cliArgs = [], fromSession = "", ta
|
|
|
31
30
|
let readError = null;
|
|
32
31
|
const value = readJsonSafe(filePath, null, { onError: (error) => { readError = error; } });
|
|
33
32
|
if (value === null)
|
|
34
|
-
throw new Error(`Invalid preset JSON input ${declaration.flag || name}: ${readError
|
|
35
|
-
|
|
33
|
+
throw new Error(`Invalid preset JSON input ${declaration.flag || name}: ${errorMessage(readError)}`);
|
|
34
|
+
const sessionInput = asRecord(value);
|
|
35
|
+
if (declaration.validateOperation && sessionInput.operation !== declaration.validateOperation) {
|
|
36
36
|
throw new Error(`${preset.id} preset requires ${declaration.flag || name} operation ${declaration.validateOperation}`);
|
|
37
37
|
}
|
|
38
|
-
if (declaration.rejectPlanOnly &&
|
|
38
|
+
if (declaration.rejectPlanOnly && sessionInput.planOnly)
|
|
39
39
|
throw new Error(`${preset.id} preset cannot use plan-only session evidence`);
|
|
40
|
-
if (declaration.requireTarget && (!
|
|
41
|
-
throw new Error(`Preset input target missing: ${
|
|
40
|
+
if (declaration.requireTarget && (!sessionInput.target || !fs.existsSync(String(sessionInput.target))))
|
|
41
|
+
throw new Error(`Preset input target missing: ${sessionInput.target || "(none)"}`);
|
|
42
42
|
if (declaration.targetFromSession)
|
|
43
|
-
targetFromInput =
|
|
44
|
-
inputs[name] = { ...
|
|
43
|
+
targetFromInput = String(sessionInput.target || targetFromInput);
|
|
44
|
+
inputs[name] = { ...sessionInput, sourcePath: filePath };
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
47
|
inputs[name] = rawValue == null || rawValue === "" ? declaration.default || "" : String(rawValue);
|
|
@@ -51,7 +51,7 @@ export function resolvePresetInputs(preset, { cliArgs = [], fromSession = "", ta
|
|
|
51
51
|
targetInput: targetFromInput || targetInput,
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
-
export function evaluateTemplateValues(preset, resolvedInputs, { taskId = "", taskTitle = "", moduleKey = "" } = {}) {
|
|
54
|
+
export function evaluateTemplateValues(preset, resolvedInputs, { taskId = "", taskTitle = "", moduleKey = "", target = null } = {}) {
|
|
55
55
|
const computed = computedValues(preset, resolvedInputs);
|
|
56
56
|
const base = {
|
|
57
57
|
inputs: resolvedInputs,
|
|
@@ -67,11 +67,13 @@ export function evaluateTemplateValues(preset, resolvedInputs, { taskId = "", ta
|
|
|
67
67
|
moduleKey,
|
|
68
68
|
kind: preset.task?.kind || "general",
|
|
69
69
|
},
|
|
70
|
+
paths: target ? harnessPathContext(target) : {},
|
|
70
71
|
};
|
|
71
72
|
const values = {
|
|
72
73
|
preset: preset.id,
|
|
73
74
|
presetVersion: String(preset.version),
|
|
74
75
|
kind: preset.task?.kind || "general",
|
|
76
|
+
paths: base.paths,
|
|
75
77
|
...computed,
|
|
76
78
|
};
|
|
77
79
|
for (const [name, declaration] of Object.entries(preset.templateValues || {})) {
|
|
@@ -90,13 +92,16 @@ export function evaluateTemplateValues(preset, resolvedInputs, { taskId = "", ta
|
|
|
90
92
|
export function buildPresetContext(preset, { target, taskDir, taskId, taskTitle, resolvedInputs, evaluatedValues }) {
|
|
91
93
|
const taskRelativeDir = toPosix(path.relative(target.projectRoot, taskDir));
|
|
92
94
|
const evidenceBundle = presetEvidenceBundle(preset, { target, taskDir, evaluatedValues });
|
|
95
|
+
const resolvedScopes = resolvePresetScopes(preset, target);
|
|
93
96
|
const audit = buildPresetAudit(preset, {
|
|
94
97
|
taskId,
|
|
95
98
|
targetRoot: target.projectRoot,
|
|
96
99
|
entrypoint: "newTask",
|
|
100
|
+
writeScopes: resolvedScopes.entrypoints.newTask || resolvedScopes.writeScopes,
|
|
101
|
+
resolvedInputs,
|
|
97
102
|
});
|
|
98
103
|
const context = {
|
|
99
|
-
kind: evaluatedValues.kind || preset.task?.kind || "general",
|
|
104
|
+
kind: String(evaluatedValues.kind || preset.task?.kind || "general"),
|
|
100
105
|
preset: preset.id,
|
|
101
106
|
presetVersion: String(preset.version),
|
|
102
107
|
presetPackage: preset,
|
|
@@ -113,8 +118,8 @@ export function buildPresetContext(preset, { target, taskDir, taskId, taskTitle,
|
|
|
113
118
|
migrationAchievedLevel: evaluatedValues.migrationAchievedLevel || "",
|
|
114
119
|
evidenceBundle,
|
|
115
120
|
};
|
|
116
|
-
context.evidenceFiles = generateEvidenceFiles(preset, { target,
|
|
117
|
-
const resources = generateResourceFiles(preset, { context });
|
|
121
|
+
context.evidenceFiles = generateEvidenceFiles(preset, { target, context });
|
|
122
|
+
const resources = generateResourceFiles(preset, { target, context });
|
|
118
123
|
context.resourceFiles = resources.files;
|
|
119
124
|
context.resourceIndexRows = resources.indexRows;
|
|
120
125
|
return context;
|
|
@@ -137,7 +142,7 @@ export function renderPresetTaskTemplate(destination, content, presetContext) {
|
|
|
137
142
|
"review.md": "reviewSeed",
|
|
138
143
|
[visualMapFile]: "visualMapAppend",
|
|
139
144
|
}[destination];
|
|
140
|
-
const templatePath = presetContext.presetPackage?.newTaskTemplates?.[templateKey];
|
|
145
|
+
const templatePath = templateKey ? presetContext.presetPackage?.newTaskTemplates?.[templateKey] : "";
|
|
141
146
|
if (templatePath) {
|
|
142
147
|
next = `${next.trimEnd()}\n\n${renderPresetTemplate(presetContext.presetPackage, templatePath, presetContext.values).trimEnd()}\n`;
|
|
143
148
|
}
|
|
@@ -161,17 +166,36 @@ export function renderPresetResourceIndex(content, kind, rows) {
|
|
|
161
166
|
}
|
|
162
167
|
return `${String(content || "").trimEnd()}\n${renderedRows.join("\n")}\n`;
|
|
163
168
|
}
|
|
164
|
-
export function assertPresetWriteScope(preset, relativePath) {
|
|
169
|
+
export function assertPresetWriteScope(preset, relativePath, target = null) {
|
|
170
|
+
return assertPresetWriteScopeForTarget(preset, relativePath, target);
|
|
171
|
+
}
|
|
172
|
+
export function assertPresetWriteScopeForTarget(preset, relativePath, target = null) {
|
|
165
173
|
const normalized = toPosix(path.normalize(relativePath));
|
|
166
174
|
if (normalized.startsWith("../") || path.isAbsolute(normalized)) {
|
|
167
175
|
throw new Error(`Preset write scope violation for ${relativePath}`);
|
|
168
176
|
}
|
|
169
|
-
|
|
177
|
+
const scopes = target ? resolvePresetScopes(preset, target).writeScopes : preset.writeScopes.flatMap((scope) => normalizedPresetScopes(scope.path));
|
|
178
|
+
if (!scopes.some((candidate) => matchesScope(candidate, normalized))) {
|
|
170
179
|
throw new Error(`Preset write scope violation for ${normalized}`);
|
|
171
180
|
}
|
|
172
181
|
}
|
|
173
|
-
function
|
|
174
|
-
const
|
|
182
|
+
export function resolvePresetScopes(preset, target) {
|
|
183
|
+
const writeScopes = preset.writeScopes.flatMap((scope) => normalizedPresetScopes(scope.path, target));
|
|
184
|
+
const entrypoints = {};
|
|
185
|
+
for (const [name, entrypoint] of Object.entries(preset.entrypoints || {})) {
|
|
186
|
+
entrypoints[name] = (entrypoint.writes || []).flatMap((scope) => normalizedPresetScopes(scope, target));
|
|
187
|
+
}
|
|
188
|
+
const reads = {};
|
|
189
|
+
for (const [name, entrypoint] of Object.entries(preset.entrypoints || {})) {
|
|
190
|
+
reads[name] = (entrypoint.reads || []).flatMap((scope) => normalizedPresetScopes(scope, target));
|
|
191
|
+
}
|
|
192
|
+
return { writeScopes, entrypoints, reads };
|
|
193
|
+
}
|
|
194
|
+
function normalizedPresetScopes(scopePath, target = null) {
|
|
195
|
+
const raw = String(scopePath || "");
|
|
196
|
+
const scope = target && raw.includes("{{")
|
|
197
|
+
? resolveHarnessPathTemplate(raw, target, "preset scope")
|
|
198
|
+
: toPosix(path.normalize(raw));
|
|
175
199
|
const taskRoot = legacyPath(legacyTaskRoot);
|
|
176
200
|
const planningRoot = legacyPath(legacyPlanningRoot);
|
|
177
201
|
const scopes = [scope];
|
|
@@ -198,7 +222,7 @@ function inputValue(declaration, { cliArgs, fromSession }) {
|
|
|
198
222
|
}
|
|
199
223
|
function computedValues(preset, inputs) {
|
|
200
224
|
const values = {};
|
|
201
|
-
const migrationSession = Object.values(inputs).find((value) => value &&
|
|
225
|
+
const migrationSession = Object.values(inputs).find((value) => isRecord(value) && value.operation === "migrate-run");
|
|
202
226
|
if (migrationSession) {
|
|
203
227
|
values.migrationTargetLevel = preset.task?.migrationTargetLevel || "migration-baseline";
|
|
204
228
|
values.migrationAchievedLevel = migrationSession.strictDeferred ? "migration-deferred" : migrationSession.result === "complete" ? "migration-full-cutover" : "migration-baseline";
|
|
@@ -221,7 +245,7 @@ function presetEvidenceBundle(preset, { target, taskDir, evaluatedValues }) {
|
|
|
221
245
|
function generateEvidenceFiles(preset, { target, context }) {
|
|
222
246
|
const files = [];
|
|
223
247
|
const add = (relativePath, source, content) => {
|
|
224
|
-
assertPresetWriteScope(preset, relativePath);
|
|
248
|
+
assertPresetWriteScope(preset, relativePath, target);
|
|
225
249
|
files.push({ relativePath, source, content });
|
|
226
250
|
};
|
|
227
251
|
const evidenceFiles = preset.evidence?.files || {};
|
|
@@ -235,11 +259,11 @@ function generateEvidenceFiles(preset, { target, context }) {
|
|
|
235
259
|
}
|
|
236
260
|
return files;
|
|
237
261
|
}
|
|
238
|
-
function generateResourceFiles(preset, { context }) {
|
|
262
|
+
function generateResourceFiles(preset, { target, context }) {
|
|
239
263
|
const files = [];
|
|
240
264
|
const indexRows = { references: [], artifacts: [] };
|
|
241
265
|
const add = (relativePath, source, content) => {
|
|
242
|
-
assertPresetWriteScope(preset, relativePath);
|
|
266
|
+
assertPresetWriteScope(preset, relativePath, target);
|
|
243
267
|
files.push({ relativePath, source, content });
|
|
244
268
|
};
|
|
245
269
|
for (const resource of Object.values(preset.resources?.references || {})) {
|
|
@@ -364,7 +388,7 @@ function renderPresetMetadata(content, context) {
|
|
|
364
388
|
...declaredMetadataLines(context),
|
|
365
389
|
].filter(Boolean).join("\n");
|
|
366
390
|
let next = String(content).replace(new RegExp(`^(${escapeRegExp(taskContractMarker)}\\s*)$`, "im"), `$1\n${metadata}`);
|
|
367
|
-
const outcome = context.presetPackage.task?.defaultOutcome || "";
|
|
391
|
+
const outcome = String(context.presetPackage.task?.defaultOutcome || "");
|
|
368
392
|
if (outcome) {
|
|
369
393
|
next = next
|
|
370
394
|
.replace("[State the outcome this task must deliver in one sentence.]", outcome)
|
|
@@ -390,19 +414,19 @@ function declaredMetadataLines(context) {
|
|
|
390
414
|
const label = declaration.label || name;
|
|
391
415
|
let value = "";
|
|
392
416
|
if (Object.prototype.hasOwnProperty.call(declaration, "from")) {
|
|
393
|
-
value = getPath(base, declaration.from);
|
|
417
|
+
value = String(getPath(base, declaration.from) || "");
|
|
394
418
|
}
|
|
395
419
|
else if (Object.prototype.hasOwnProperty.call(declaration, "value")) {
|
|
396
|
-
value = declaration.value;
|
|
420
|
+
value = String(declaration.value || "");
|
|
397
421
|
}
|
|
398
422
|
else if (Object.prototype.hasOwnProperty.call(declaration, "default")) {
|
|
399
|
-
value = declaration.default;
|
|
423
|
+
value = String(declaration.default || "");
|
|
400
424
|
}
|
|
401
425
|
return value == null || value === "" ? "" : `${label}: ${value}`;
|
|
402
426
|
});
|
|
403
427
|
}
|
|
404
428
|
function migrationSession(context) {
|
|
405
|
-
const session = Object.values(context.resolvedInputs || {}).find((value) => value &&
|
|
429
|
+
const session = Object.values(context.resolvedInputs || {}).find((value) => isRecord(value) && value.operation === "migrate-run");
|
|
406
430
|
if (!session)
|
|
407
431
|
throw new Error("Preset evidence requires migrate-run session input");
|
|
408
432
|
return session;
|
|
@@ -485,7 +509,7 @@ function targetCommit(projectRoot) {
|
|
|
485
509
|
}
|
|
486
510
|
function packageVersion() {
|
|
487
511
|
try {
|
|
488
|
-
return readJsonSafe(path.join(repoRoot, "package.json"), {}).version || "unknown";
|
|
512
|
+
return String(asRecord(readJsonSafe(path.join(repoRoot, "package.json"), {})).version || "unknown");
|
|
489
513
|
}
|
|
490
514
|
catch {
|
|
491
515
|
return "unknown";
|
|
@@ -494,7 +518,13 @@ function packageVersion() {
|
|
|
494
518
|
function getPath(values, key) {
|
|
495
519
|
if (!key)
|
|
496
520
|
return values;
|
|
497
|
-
|
|
521
|
+
let cursor = values;
|
|
522
|
+
for (const part of String(key).split(".")) {
|
|
523
|
+
if (!isRecord(cursor) || !Object.prototype.hasOwnProperty.call(cursor, part))
|
|
524
|
+
return undefined;
|
|
525
|
+
cursor = cursor[part];
|
|
526
|
+
}
|
|
527
|
+
return cursor;
|
|
498
528
|
}
|
|
499
529
|
function renderInline(value, values) {
|
|
500
530
|
return String(value || "").replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_match, key) => {
|
|
@@ -514,3 +544,12 @@ function presetIndexSkeleton(kind) {
|
|
|
514
544
|
function escapeRegExp(value) {
|
|
515
545
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
516
546
|
}
|
|
547
|
+
function isRecord(value) {
|
|
548
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
549
|
+
}
|
|
550
|
+
function asRecord(value) {
|
|
551
|
+
return isRecord(value) ? value : {};
|
|
552
|
+
}
|
|
553
|
+
function errorMessage(error) {
|
|
554
|
+
return error instanceof Error ? error.message : "unknown parse error";
|
|
555
|
+
}
|