coding-agent-harness 1.0.8 → 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 +10 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +19 -6
- package/dist/check-dist-observation.mjs +57 -29
- 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 +7 -7
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +51 -9
- 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 +51 -12
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +109 -52
- package/dist/harness.mjs +6 -304
- 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 +4 -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 +67 -29
- package/dist/lib/preset-registry.mjs +361 -71
- package/dist/lib/preset-runner.mjs +292 -26
- 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 +3 -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 +116 -160
- 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 +36 -17
- package/dist/lib/task-scanner.mjs +74 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +186 -29
- 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 +1 -0
- 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 +16 -12
- 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 +6 -1
- package/presets/release-closeout/preset.yaml +9 -9
- package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
- package/presets/release-closeout/templates/task_plan.append.md +5 -5
- 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/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/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/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,14 +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,
|
|
97
101
|
resolvedInputs,
|
|
98
102
|
});
|
|
99
103
|
const context = {
|
|
100
|
-
kind: evaluatedValues.kind || preset.task?.kind || "general",
|
|
104
|
+
kind: String(evaluatedValues.kind || preset.task?.kind || "general"),
|
|
101
105
|
preset: preset.id,
|
|
102
106
|
presetVersion: String(preset.version),
|
|
103
107
|
presetPackage: preset,
|
|
@@ -114,8 +118,8 @@ export function buildPresetContext(preset, { target, taskDir, taskId, taskTitle,
|
|
|
114
118
|
migrationAchievedLevel: evaluatedValues.migrationAchievedLevel || "",
|
|
115
119
|
evidenceBundle,
|
|
116
120
|
};
|
|
117
|
-
context.evidenceFiles = generateEvidenceFiles(preset, { target,
|
|
118
|
-
const resources = generateResourceFiles(preset, { context });
|
|
121
|
+
context.evidenceFiles = generateEvidenceFiles(preset, { target, context });
|
|
122
|
+
const resources = generateResourceFiles(preset, { target, context });
|
|
119
123
|
context.resourceFiles = resources.files;
|
|
120
124
|
context.resourceIndexRows = resources.indexRows;
|
|
121
125
|
return context;
|
|
@@ -138,7 +142,7 @@ export function renderPresetTaskTemplate(destination, content, presetContext) {
|
|
|
138
142
|
"review.md": "reviewSeed",
|
|
139
143
|
[visualMapFile]: "visualMapAppend",
|
|
140
144
|
}[destination];
|
|
141
|
-
const templatePath = presetContext.presetPackage?.newTaskTemplates?.[templateKey];
|
|
145
|
+
const templatePath = templateKey ? presetContext.presetPackage?.newTaskTemplates?.[templateKey] : "";
|
|
142
146
|
if (templatePath) {
|
|
143
147
|
next = `${next.trimEnd()}\n\n${renderPresetTemplate(presetContext.presetPackage, templatePath, presetContext.values).trimEnd()}\n`;
|
|
144
148
|
}
|
|
@@ -162,17 +166,36 @@ export function renderPresetResourceIndex(content, kind, rows) {
|
|
|
162
166
|
}
|
|
163
167
|
return `${String(content || "").trimEnd()}\n${renderedRows.join("\n")}\n`;
|
|
164
168
|
}
|
|
165
|
-
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) {
|
|
166
173
|
const normalized = toPosix(path.normalize(relativePath));
|
|
167
174
|
if (normalized.startsWith("../") || path.isAbsolute(normalized)) {
|
|
168
175
|
throw new Error(`Preset write scope violation for ${relativePath}`);
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
const scopes = target ? resolvePresetScopes(preset, target).writeScopes : preset.writeScopes.flatMap((scope) => normalizedPresetScopes(scope.path));
|
|
178
|
+
if (!scopes.some((candidate) => matchesScope(candidate, normalized))) {
|
|
171
179
|
throw new Error(`Preset write scope violation for ${normalized}`);
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
|
-
function
|
|
175
|
-
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));
|
|
176
199
|
const taskRoot = legacyPath(legacyTaskRoot);
|
|
177
200
|
const planningRoot = legacyPath(legacyPlanningRoot);
|
|
178
201
|
const scopes = [scope];
|
|
@@ -199,7 +222,7 @@ function inputValue(declaration, { cliArgs, fromSession }) {
|
|
|
199
222
|
}
|
|
200
223
|
function computedValues(preset, inputs) {
|
|
201
224
|
const values = {};
|
|
202
|
-
const migrationSession = Object.values(inputs).find((value) => value &&
|
|
225
|
+
const migrationSession = Object.values(inputs).find((value) => isRecord(value) && value.operation === "migrate-run");
|
|
203
226
|
if (migrationSession) {
|
|
204
227
|
values.migrationTargetLevel = preset.task?.migrationTargetLevel || "migration-baseline";
|
|
205
228
|
values.migrationAchievedLevel = migrationSession.strictDeferred ? "migration-deferred" : migrationSession.result === "complete" ? "migration-full-cutover" : "migration-baseline";
|
|
@@ -222,7 +245,7 @@ function presetEvidenceBundle(preset, { target, taskDir, evaluatedValues }) {
|
|
|
222
245
|
function generateEvidenceFiles(preset, { target, context }) {
|
|
223
246
|
const files = [];
|
|
224
247
|
const add = (relativePath, source, content) => {
|
|
225
|
-
assertPresetWriteScope(preset, relativePath);
|
|
248
|
+
assertPresetWriteScope(preset, relativePath, target);
|
|
226
249
|
files.push({ relativePath, source, content });
|
|
227
250
|
};
|
|
228
251
|
const evidenceFiles = preset.evidence?.files || {};
|
|
@@ -236,11 +259,11 @@ function generateEvidenceFiles(preset, { target, context }) {
|
|
|
236
259
|
}
|
|
237
260
|
return files;
|
|
238
261
|
}
|
|
239
|
-
function generateResourceFiles(preset, { context }) {
|
|
262
|
+
function generateResourceFiles(preset, { target, context }) {
|
|
240
263
|
const files = [];
|
|
241
264
|
const indexRows = { references: [], artifacts: [] };
|
|
242
265
|
const add = (relativePath, source, content) => {
|
|
243
|
-
assertPresetWriteScope(preset, relativePath);
|
|
266
|
+
assertPresetWriteScope(preset, relativePath, target);
|
|
244
267
|
files.push({ relativePath, source, content });
|
|
245
268
|
};
|
|
246
269
|
for (const resource of Object.values(preset.resources?.references || {})) {
|
|
@@ -365,7 +388,7 @@ function renderPresetMetadata(content, context) {
|
|
|
365
388
|
...declaredMetadataLines(context),
|
|
366
389
|
].filter(Boolean).join("\n");
|
|
367
390
|
let next = String(content).replace(new RegExp(`^(${escapeRegExp(taskContractMarker)}\\s*)$`, "im"), `$1\n${metadata}`);
|
|
368
|
-
const outcome = context.presetPackage.task?.defaultOutcome || "";
|
|
391
|
+
const outcome = String(context.presetPackage.task?.defaultOutcome || "");
|
|
369
392
|
if (outcome) {
|
|
370
393
|
next = next
|
|
371
394
|
.replace("[State the outcome this task must deliver in one sentence.]", outcome)
|
|
@@ -391,19 +414,19 @@ function declaredMetadataLines(context) {
|
|
|
391
414
|
const label = declaration.label || name;
|
|
392
415
|
let value = "";
|
|
393
416
|
if (Object.prototype.hasOwnProperty.call(declaration, "from")) {
|
|
394
|
-
value = getPath(base, declaration.from);
|
|
417
|
+
value = String(getPath(base, declaration.from) || "");
|
|
395
418
|
}
|
|
396
419
|
else if (Object.prototype.hasOwnProperty.call(declaration, "value")) {
|
|
397
|
-
value = declaration.value;
|
|
420
|
+
value = String(declaration.value || "");
|
|
398
421
|
}
|
|
399
422
|
else if (Object.prototype.hasOwnProperty.call(declaration, "default")) {
|
|
400
|
-
value = declaration.default;
|
|
423
|
+
value = String(declaration.default || "");
|
|
401
424
|
}
|
|
402
425
|
return value == null || value === "" ? "" : `${label}: ${value}`;
|
|
403
426
|
});
|
|
404
427
|
}
|
|
405
428
|
function migrationSession(context) {
|
|
406
|
-
const session = Object.values(context.resolvedInputs || {}).find((value) => value &&
|
|
429
|
+
const session = Object.values(context.resolvedInputs || {}).find((value) => isRecord(value) && value.operation === "migrate-run");
|
|
407
430
|
if (!session)
|
|
408
431
|
throw new Error("Preset evidence requires migrate-run session input");
|
|
409
432
|
return session;
|
|
@@ -486,7 +509,7 @@ function targetCommit(projectRoot) {
|
|
|
486
509
|
}
|
|
487
510
|
function packageVersion() {
|
|
488
511
|
try {
|
|
489
|
-
return readJsonSafe(path.join(repoRoot, "package.json"), {}).version || "unknown";
|
|
512
|
+
return String(asRecord(readJsonSafe(path.join(repoRoot, "package.json"), {})).version || "unknown");
|
|
490
513
|
}
|
|
491
514
|
catch {
|
|
492
515
|
return "unknown";
|
|
@@ -495,7 +518,13 @@ function packageVersion() {
|
|
|
495
518
|
function getPath(values, key) {
|
|
496
519
|
if (!key)
|
|
497
520
|
return values;
|
|
498
|
-
|
|
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;
|
|
499
528
|
}
|
|
500
529
|
function renderInline(value, values) {
|
|
501
530
|
return String(value || "").replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (_match, key) => {
|
|
@@ -515,3 +544,12 @@ function presetIndexSkeleton(kind) {
|
|
|
515
544
|
function escapeRegExp(value) {
|
|
516
545
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
517
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
|
+
}
|