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.
Files changed (232) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/CONTRIBUTING.md +8 -4
  3. package/README.md +12 -2
  4. package/README.zh-CN.md +10 -2
  5. package/SKILL.md +14 -3
  6. package/dist/build-dist.mjs +19 -6
  7. package/dist/check-dist-observation.mjs +57 -29
  8. package/dist/check-harness.mjs +0 -1
  9. package/dist/check-import-graph.mjs +44 -27
  10. package/dist/check-lite-forbidden-surfaces.mjs +121 -0
  11. package/dist/check-no-ts-nocheck.mjs +7 -7
  12. package/dist/check-runtime-emit.mjs +10 -3
  13. package/dist/check-type-boundaries.mjs +51 -9
  14. package/dist/commands/dashboard-command.mjs +52 -14
  15. package/dist/commands/migration-command.mjs +18 -8
  16. package/dist/commands/module-command.mjs +142 -0
  17. package/dist/commands/preset-command.mjs +51 -12
  18. package/dist/commands/registry.mjs +483 -0
  19. package/dist/commands/task-command.mjs +109 -52
  20. package/dist/harness.mjs +6 -304
  21. package/dist/lib/capability-registry.mjs +229 -53
  22. package/dist/lib/check-module-parallel.mjs +1 -6
  23. package/dist/lib/check-profiles.mjs +39 -46
  24. package/dist/lib/check-task-contracts.mjs +6 -4
  25. package/dist/lib/command-registry.mjs +248 -0
  26. package/dist/lib/core-shared.mjs +78 -3
  27. package/dist/lib/dashboard-data.mjs +203 -22
  28. package/dist/lib/dashboard-workbench.mjs +245 -21
  29. package/dist/lib/dashboard-writer.mjs +4 -1
  30. package/dist/lib/git-status-summary.mjs +0 -1
  31. package/dist/lib/governance-index-generator.mjs +7 -5
  32. package/dist/lib/governance-sync.mjs +46 -121
  33. package/dist/lib/governance-table-boundary.mjs +1 -14
  34. package/dist/lib/harness-core.mjs +4 -1
  35. package/dist/lib/harness-paths.mjs +115 -1
  36. package/dist/lib/impact-classifier.mjs +420 -0
  37. package/dist/lib/lesson-maintenance.mjs +1 -2
  38. package/dist/lib/markdown-utils.mjs +50 -1
  39. package/dist/lib/migration-planner.mjs +31 -16
  40. package/dist/lib/migration-support.mjs +5 -4
  41. package/dist/lib/module-registry.mjs +296 -0
  42. package/dist/lib/preset-audit-contracts.mjs +24 -1
  43. package/dist/lib/preset-engine.mjs +67 -29
  44. package/dist/lib/preset-registry.mjs +361 -71
  45. package/dist/lib/preset-runner.mjs +292 -26
  46. package/dist/lib/review-confirm-git-gate.mjs +73 -19
  47. package/dist/lib/status-builder.mjs +23 -8
  48. package/dist/lib/structure-migration.mjs +6 -4
  49. package/dist/lib/subagent-authorization-audit.mjs +8 -2
  50. package/dist/lib/task-archive-eligibility.mjs +65 -0
  51. package/dist/lib/task-audit-metadata.mjs +25 -11
  52. package/dist/lib/task-audit-migration.mjs +21 -14
  53. package/dist/lib/task-discovery-contract.mjs +32 -0
  54. package/dist/lib/task-index.mjs +3 -2
  55. package/dist/lib/task-lesson-candidates.mjs +1 -2
  56. package/dist/lib/task-lesson-sedimentation.mjs +310 -9
  57. package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
  58. package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
  59. package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
  60. package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
  61. package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
  62. package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
  63. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
  64. package/dist/lib/task-lifecycle/template-files.mjs +2 -5
  65. package/dist/lib/task-lifecycle.mjs +116 -160
  66. package/dist/lib/task-metadata.mjs +10 -5
  67. package/dist/lib/task-preset-contract-drift.mjs +45 -0
  68. package/dist/lib/task-repository.mjs +192 -0
  69. package/dist/lib/task-review-model.mjs +36 -17
  70. package/dist/lib/task-scanner.mjs +74 -23
  71. package/dist/lib/task-template-materials.mjs +131 -0
  72. package/dist/lib/task-tombstone-commands.mjs +186 -29
  73. package/dist/lib/types/check-profiles.js +1 -0
  74. package/dist/lib/types/impact.js +1 -0
  75. package/dist/lib/types/preset.js +1 -0
  76. package/dist/lib/types/task-lifecycle.js +1 -0
  77. package/dist/lib/types/task-scanner.js +1 -0
  78. package/dist/postinstall.mjs +2 -2
  79. package/dist/run-built-tests.mjs +10 -3
  80. package/docs-release/README.md +1 -0
  81. package/docs-release/architecture/document-contract-kernel/README.md +150 -0
  82. package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
  83. package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
  84. package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
  85. package/docs-release/architecture/overview.md +2 -2
  86. package/docs-release/architecture/overview.zh-CN.md +2 -2
  87. package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
  88. package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
  89. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
  90. package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
  91. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
  92. package/docs-release/architecture/system-explainer/README.md +1 -1
  93. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
  94. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
  95. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
  96. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  97. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
  98. package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
  99. package/docs-release/guides/agent-installation.en-US.md +4 -6
  100. package/docs-release/guides/agent-installation.md +11 -8
  101. package/docs-release/guides/contributing.md +10 -3
  102. package/docs-release/guides/contributing.zh-CN.md +10 -3
  103. package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
  104. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
  105. package/docs-release/guides/migration-playbook.en-US.md +9 -6
  106. package/docs-release/guides/migration-playbook.md +9 -6
  107. package/docs-release/guides/preset-development.md +68 -2
  108. package/docs-release/guides/task-state-machine.en-US.md +8 -8
  109. package/docs-release/guides/task-state-machine.md +7 -7
  110. package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
  111. package/package.json +16 -12
  112. package/postinstall.mjs +37 -0
  113. package/presets/legacy-migration/preset.yaml +5 -5
  114. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  115. package/presets/lesson-sedimentation/preset.yaml +3 -3
  116. package/presets/module/preset.yaml +2 -2
  117. package/presets/module/templates/execution_strategy.append.md +1 -1
  118. package/presets/module/templates/task_plan.append.md +3 -3
  119. package/presets/release-closeout/checks/check-release-package.mjs +6 -1
  120. package/presets/release-closeout/preset.yaml +9 -9
  121. package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
  122. package/presets/release-closeout/templates/task_plan.append.md +5 -5
  123. package/presets/standard-task/preset.yaml +2 -2
  124. package/references/agents-md-pattern.md +23 -17
  125. package/references/lessons-governance.md +2 -2
  126. package/references/module-parallel-standard.md +3 -6
  127. package/references/ssot-governance.md +2 -2
  128. package/references/taskr-gap-analysis.md +3 -3
  129. package/run-dist.mjs +34 -0
  130. package/skills/preset-creator/SKILL.md +40 -8
  131. package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
  132. package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
  133. package/skills/preset-creator/references/structure-aware-paths.md +112 -0
  134. package/templates/AGENTS.md.template +28 -26
  135. package/templates/architecture/README.md +2 -2
  136. package/templates/architecture/service-catalog.md +2 -2
  137. package/templates/architecture/services/service-template.md +1 -1
  138. package/templates/dashboard/assets/app-src/00-state.js +5 -1
  139. package/templates/dashboard/assets/app-src/10-router.js +7 -0
  140. package/templates/dashboard/assets/app-src/20-overview.js +8 -8
  141. package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
  142. package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
  143. package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
  144. package/templates/dashboard/assets/app-src/40-modules.js +257 -41
  145. package/templates/dashboard/assets/app-src/45-review.js +127 -1
  146. package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
  147. package/templates/dashboard/assets/app.css +928 -53
  148. package/templates/dashboard/assets/app.css.manifest.json +2 -0
  149. package/templates/dashboard/assets/app.js +1071 -98
  150. package/templates/dashboard/assets/app.manifest.json +1 -0
  151. package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
  152. package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
  153. package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
  154. package/templates/dashboard/assets/css-src/31-archive.css +94 -0
  155. package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
  156. package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
  158. package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
  159. package/templates/dashboard/assets/i18n.js +166 -2
  160. package/templates/development/README.md +9 -9
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +1 -1
  163. package/templates/development/external-source-packs/README.md +2 -2
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +1 -1
  166. package/templates/integrations/event-contract.md +1 -1
  167. package/templates/integrations/third-party/vendor-template.md +1 -1
  168. package/templates/integrations/webhook-contract.md +1 -1
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/modules/module_brief.md +50 -0
  171. package/templates/modules/module_plan.md +49 -0
  172. package/templates/modules/registry_view.md +9 -0
  173. package/templates/modules/session_prompt_pack.md +55 -0
  174. package/templates/planning/brief.md +32 -8
  175. package/templates/planning/module_brief.md +28 -3
  176. package/templates/planning/module_plan.md +26 -11
  177. package/templates/planning/module_session_prompt.md +11 -2
  178. package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
  179. package/templates/planning/review.md +1 -1
  180. package/templates/planning/visual_map.md +1 -1
  181. package/templates/reference/docs-library-standard.md +7 -7
  182. package/templates/reference/execution-workflow-standard.md +13 -0
  183. package/templates/reference/external-source-intake-standard.md +10 -10
  184. package/templates/reference/repo-governance-standard.md +1 -1
  185. package/templates/reference/review-routing-standard.md +4 -0
  186. package/templates/ssot/Module-Registry.md +4 -38
  187. package/templates/walkthrough/walkthrough-template.md +1 -1
  188. package/templates-zh-CN/AGENTS.md.template +27 -25
  189. package/templates-zh-CN/CLAUDE.md.template +1 -1
  190. package/templates-zh-CN/architecture/README.md +2 -2
  191. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  192. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  193. package/templates-zh-CN/development/README.md +9 -9
  194. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  195. package/templates-zh-CN/development/external-context/service-template.md +1 -1
  196. package/templates-zh-CN/development/external-source-packs/README.md +2 -2
  197. package/templates-zh-CN/integrations/README.md +4 -4
  198. package/templates-zh-CN/integrations/api-contract.md +1 -1
  199. package/templates-zh-CN/integrations/event-contract.md +1 -1
  200. package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
  201. package/templates-zh-CN/integrations/webhook-contract.md +1 -1
  202. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  203. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  204. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  205. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  206. package/templates-zh-CN/modules/module_brief.md +47 -0
  207. package/templates-zh-CN/modules/module_plan.md +48 -0
  208. package/templates-zh-CN/modules/registry_view.md +9 -0
  209. package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
  210. package/templates-zh-CN/planning/INDEX.md +1 -0
  211. package/templates-zh-CN/planning/brief.md +26 -7
  212. package/templates-zh-CN/planning/module_brief.md +24 -2
  213. package/templates-zh-CN/planning/module_plan.md +35 -29
  214. package/templates-zh-CN/planning/module_session_prompt.md +15 -11
  215. package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
  216. package/templates-zh-CN/planning/review.md +1 -1
  217. package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
  218. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  219. package/templates-zh-CN/reference/docs-library-standard.md +27 -27
  220. package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
  221. package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
  222. package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
  223. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  224. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  225. package/templates-zh-CN/reference/review-routing-standard.md +3 -0
  226. package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
  227. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  228. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  229. package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
  230. package/templates-zh-CN/ssot/Module-Registry.md +5 -44
  231. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  232. 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 = String(task.evidenceBundle || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
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?.message || "unknown parse error"}`);
35
- if (declaration.validateOperation && value.operation !== declaration.validateOperation) {
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 && value.planOnly)
38
+ if (declaration.rejectPlanOnly && sessionInput.planOnly)
39
39
  throw new Error(`${preset.id} preset cannot use plan-only session evidence`);
40
- if (declaration.requireTarget && (!value.target || !fs.existsSync(value.target)))
41
- throw new Error(`Preset input target missing: ${value.target || "(none)"}`);
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 = value.target || targetFromInput;
44
- inputs[name] = { ...value, sourcePath: filePath };
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, taskDir, context });
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
- if (!preset.writeScopes.some((scope) => normalizedPresetScopes(scope.path).some((candidate) => matchesScope(candidate, normalized)))) {
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 normalizedPresetScopes(scopePath) {
175
- const scope = toPosix(path.normalize(String(scopePath || "")));
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 && typeof value === "object" && value.operation === "migrate-run");
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 && typeof value === "object" && value.operation === "migrate-run");
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
- return String(key).split(".").reduce((cursor, part) => (cursor && Object.prototype.hasOwnProperty.call(cursor, part) ? cursor[part] : undefined), values);
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
+ }