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,121 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
+ function parseArgs(argv) {
7
+ let repoRoot = defaultRepoRoot;
8
+ for (let index = 0; index < argv.length; index += 1) {
9
+ const arg = argv[index];
10
+ if (arg === "--repo-root") {
11
+ const value = argv[index + 1];
12
+ if (!value)
13
+ throw new Error("--repo-root requires a value");
14
+ repoRoot = path.resolve(value);
15
+ index += 1;
16
+ continue;
17
+ }
18
+ throw new Error(`Unknown argument: ${arg}`);
19
+ }
20
+ return { repoRoot };
21
+ }
22
+ export function checkLiteForbiddenSurfaces(repoRoot = defaultRepoRoot) {
23
+ const forbiddenPath = path.join(repoRoot, "docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt");
24
+ const patterns = readForbiddenPatterns(forbiddenPath);
25
+ const scannedFiles = collectLiteProductFiles(repoRoot);
26
+ const violations = [];
27
+ for (const relativeFile of scannedFiles) {
28
+ const absoluteFile = path.join(repoRoot, relativeFile);
29
+ const lines = fs.readFileSync(absoluteFile, "utf8").split(/\r?\n/);
30
+ for (const [lineIndex, line] of lines.entries()) {
31
+ for (const pattern of patterns) {
32
+ pattern.pattern.lastIndex = 0;
33
+ if (pattern.pattern.test(line)) {
34
+ violations.push({
35
+ file: relativeFile,
36
+ line: lineIndex + 1,
37
+ pattern: pattern.source,
38
+ text: line.trim(),
39
+ });
40
+ }
41
+ }
42
+ }
43
+ }
44
+ return { ok: violations.length === 0, violations, scannedFiles };
45
+ }
46
+ function readForbiddenPatterns(forbiddenPath) {
47
+ if (!fs.existsSync(forbiddenPath)) {
48
+ throw new Error(`Missing Lite forbidden-surface list: ${path.relative(process.cwd(), forbiddenPath)}`);
49
+ }
50
+ const lines = fs.readFileSync(forbiddenPath, "utf8").split(/\r?\n/);
51
+ return lines
52
+ .map((line) => line.trim())
53
+ .filter((line) => line && !line.startsWith("#"))
54
+ .map((line) => {
55
+ if (line.startsWith("literal:")) {
56
+ const literal = line.slice("literal:".length);
57
+ return { source: line, pattern: new RegExp(escapeRegExp(literal), "i") };
58
+ }
59
+ if (line.startsWith("regex:")) {
60
+ const source = line.slice("regex:".length);
61
+ return { source: line, pattern: new RegExp(source, "i") };
62
+ }
63
+ return { source: line, pattern: new RegExp(escapeRegExp(line), "i") };
64
+ });
65
+ }
66
+ function collectLiteProductFiles(repoRoot) {
67
+ const files = new Set();
68
+ const explicitFiles = [
69
+ "docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md",
70
+ "skills/coding-agent-harness-lite/SKILL.md",
71
+ ];
72
+ for (const relativeFile of explicitFiles) {
73
+ if (fs.existsSync(path.join(repoRoot, relativeFile)))
74
+ files.add(relativeFile);
75
+ }
76
+ for (const relativeDir of ["skill-sources/products/lite", "skill-sources/document-kernel/products/lite"]) {
77
+ const absoluteDir = path.join(repoRoot, relativeDir);
78
+ if (!fs.existsSync(absoluteDir))
79
+ continue;
80
+ for (const relativeFile of walkTextFiles(absoluteDir, repoRoot))
81
+ files.add(relativeFile);
82
+ }
83
+ return [...files].sort();
84
+ }
85
+ function walkTextFiles(current, repoRoot) {
86
+ const stat = fs.lstatSync(current);
87
+ if (stat.isSymbolicLink())
88
+ return [];
89
+ if (stat.isFile()) {
90
+ return /\.(md|txt|template)$/.test(current) ? [toPosix(path.relative(repoRoot, current))] : [];
91
+ }
92
+ const files = [];
93
+ for (const entry of fs.readdirSync(current)) {
94
+ files.push(...walkTextFiles(path.join(current, entry), repoRoot));
95
+ }
96
+ return files;
97
+ }
98
+ function toPosix(value) {
99
+ return value.split(path.sep).join("/");
100
+ }
101
+ function escapeRegExp(value) {
102
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
103
+ }
104
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
105
+ try {
106
+ const { repoRoot } = parseArgs(process.argv.slice(2));
107
+ const result = checkLiteForbiddenSurfaces(repoRoot);
108
+ if (!result.ok) {
109
+ console.error([
110
+ "Lite forbidden-surface check failed:",
111
+ ...result.violations.map((violation) => `${violation.file}:${violation.line}: ${violation.pattern}: ${violation.text}`),
112
+ ].join("\n"));
113
+ process.exit(1);
114
+ }
115
+ console.log(`Lite forbidden-surface check passed (${result.scannedFiles.length} files scanned)`);
116
+ }
117
+ catch (error) {
118
+ console.error(error instanceof Error ? error.message : String(error));
119
+ process.exit(1);
120
+ }
121
+ }
@@ -4,9 +4,9 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
6
  const sourceRoots = ["scripts", "tests"];
7
- const tsNocheckPattern = /^\s*\/\/\s*@ts-nocheck\b/;
7
+ const tsNocheckPattern = new RegExp(String.raw `^\s*//\s*` + "@ts" + String.raw `-nocheck\b`);
8
8
  export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = path.join(repoRoot, "scripts/ts-nocheck-allowlist.json"), } = {}) {
9
- const files = collectMtsFiles(repoRoot);
9
+ const files = collectTypeScriptFiles(repoRoot);
10
10
  const allowlist = readAllowlist(allowlistPath);
11
11
  const violations = [];
12
12
  const observed = new Set();
@@ -22,7 +22,7 @@ export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = p
22
22
  code: "unlisted-ts-nocheck",
23
23
  file,
24
24
  line: lineIndex + 1,
25
- message: `${file}:${lineIndex + 1} has @ts-nocheck but is not in scripts/ts-nocheck-allowlist.json`,
25
+ message: `${file}:${lineIndex + 1} has ${"@ts"}-nocheck but is not in scripts/ts-nocheck-allowlist.json`,
26
26
  });
27
27
  }
28
28
  }
@@ -31,13 +31,13 @@ export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = p
31
31
  violations.push({
32
32
  code: "stale-ts-nocheck-allowlist",
33
33
  file,
34
- message: `${file} is listed in scripts/ts-nocheck-allowlist.json but no longer has @ts-nocheck`,
34
+ message: `${file} is listed in scripts/ts-nocheck-allowlist.json but no longer has ${"@ts"}-nocheck`,
35
35
  });
36
36
  }
37
37
  }
38
38
  return { ok: violations.length === 0, violations };
39
39
  }
40
- function collectMtsFiles(repoRoot) {
40
+ function collectTypeScriptFiles(repoRoot) {
41
41
  const files = [];
42
42
  for (const root of sourceRoots) {
43
43
  const absoluteRoot = path.join(repoRoot, root);
@@ -59,7 +59,7 @@ function walk(current, files, repoRoot) {
59
59
  walk(path.join(current, entry), files, repoRoot);
60
60
  return;
61
61
  }
62
- if (stat.isFile() && current.endsWith(".mts")) {
62
+ if (stat.isFile() && /\.(mts|ts)$/.test(current)) {
63
63
  files.push(path.relative(repoRoot, current).split(path.sep).join("/"));
64
64
  }
65
65
  }
@@ -84,5 +84,5 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
84
84
  console.error(result.violations.map((violation) => violation.message).join("\n"));
85
85
  process.exit(1);
86
86
  }
87
- console.log("No @ts-nocheck gate passed");
87
+ console.log(`No ${"@ts"}-nocheck gate passed`);
88
88
  }
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from "node:fs";
4
3
  import os from "node:os";
5
4
  import path from "node:path";
@@ -75,11 +74,19 @@ export function checkRuntimeEmitContract({ projectRoot = defaultRepoRoot, config
75
74
  };
76
75
  }
77
76
  function runTypeScriptEmit({ projectRoot, configPath, outDir }) {
78
- return spawnSync("npm", ["exec", "--yes", "--package", `typescript@${typescriptVersion}`, "--", "tsc", "-p", configPath, "--outDir", outDir, "--noCheck"], {
77
+ const npmArgs = ["exec", "--yes", "--package", `typescript@${typescriptVersion}`, "--", "tsc", "-p", configPath, "--outDir", outDir, "--noCheck"];
78
+ const npmCommand = resolveNpmCommand(npmArgs);
79
+ return spawnSync(npmCommand.command, npmCommand.args, {
79
80
  cwd: projectRoot,
80
81
  encoding: "utf8",
81
82
  });
82
83
  }
84
+ function resolveNpmCommand(npmArgs) {
85
+ const npmExecPath = process.env.npm_execpath;
86
+ if (npmExecPath)
87
+ return { command: process.execPath, args: [npmExecPath, ...npmArgs] };
88
+ return { command: process.platform === "win32" ? "npm.cmd" : "npm", args: npmArgs };
89
+ }
83
90
  function compareDirectories({ expectedDir, actualDir, violations }) {
84
91
  const expectedFiles = collectFiles(expectedDir).filter((file) => file.endsWith(".mjs")).sort();
85
92
  const actualFiles = collectFiles(actualDir).filter((file) => file.endsWith(".mjs")).sort();
@@ -129,7 +136,7 @@ function collectFiles(directory) {
129
136
  walk(directory, files, () => true);
130
137
  return files.sort();
131
138
  }
132
- function walk(current, files, predicate, sourceRoot) {
139
+ function walk(current, files, predicate, sourceRoot = "") {
133
140
  const stat = fs.lstatSync(current);
134
141
  if (stat.isSymbolicLink())
135
142
  return;
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
5
  const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
6
  const sourceRoots = ["scripts", "tests"];
8
7
  const importPattern = /\b(import|export)\s+(type\s+)?(?:[^'"]*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
9
- const tsEscapePattern = /@(ts-ignore|ts-expect-error)\b|\bas\s+unknown\s+as\b|\bRecord\s*<\s*string\s*,\s*any\s*>|(?:^|[^A-Za-z0-9_$])(?:as\s+any|:\s*any\b)/;
8
+ const tsEscapePattern = /@(ts-ignore|ts-expect-error|ts-nocheck)\b|\bas\s+unknown\s+as\b|<[^>\n]*\bany\b[^>\n]*>|(?:^|[^A-Za-z0-9_$])(?:as\s+any|:\s*any\b)/;
9
+ const crossLineUnknownCastPattern = /\bas\s+unknown\s+as\b/;
10
10
  export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlistPath = path.join(repoRoot, "scripts/type-escape-allowlist.json"), } = {}) {
11
11
  const files = collectSourceFiles(repoRoot);
12
12
  const violations = [];
13
13
  const escapeAllowlist = readEscapeAllowlist(escapeAllowlistPath);
14
+ const observedEscapes = new Set();
14
15
  for (const file of files) {
15
16
  const absolutePath = path.join(repoRoot, file);
16
17
  const content = fs.readFileSync(absolutePath, "utf8");
@@ -25,10 +26,23 @@ export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlis
25
26
  line: index + 1,
26
27
  message: `${file}:${index + 1} uses a TypeScript escape hatch that requires review`,
27
28
  };
29
+ observedEscapes.add(escapeKey(violation));
28
30
  if (!isEscapeAllowed(escapeAllowlist, violation))
29
31
  violations.push(violation);
30
32
  }
31
33
  }
34
+ if (crossLineUnknownCastPattern.test(content) && !lines.some((line) => /\bas\s+unknown\s+as\b/.test(line))) {
35
+ const line = lineForOffset(content, content.search(crossLineUnknownCastPattern));
36
+ const violation = {
37
+ code: "ts-escape-hatch",
38
+ file,
39
+ line,
40
+ message: `${file}:${line} uses a cross-line TypeScript escape hatch that requires review`,
41
+ };
42
+ observedEscapes.add(escapeKey(violation));
43
+ if (!isEscapeAllowed(escapeAllowlist, violation))
44
+ violations.push(violation);
45
+ }
32
46
  }
33
47
  for (const imported of imports) {
34
48
  if (!isLocalSpecifier(imported.specifier))
@@ -52,6 +66,16 @@ export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlis
52
66
  }
53
67
  }
54
68
  }
69
+ for (const allowed of escapeAllowlist.values()) {
70
+ if (observedEscapes.has(allowed.key))
71
+ continue;
72
+ violations.push({
73
+ code: "stale-ts-escape-allowlist",
74
+ file: allowed.file,
75
+ line: allowed.line,
76
+ message: `${allowed.file}:${allowed.line} is listed in ${path.relative(repoRoot, escapeAllowlistPath)} but no matching TypeScript escape hatch was found`,
77
+ });
78
+ }
55
79
  return { ok: violations.length === 0, violations };
56
80
  }
57
81
  function collectSourceFiles(repoRoot) {
@@ -86,7 +110,7 @@ function parseImports(content) {
86
110
  imports.push({
87
111
  kind: match[1] || "import",
88
112
  typeOnly: match[2] === "type ",
89
- specifier: match[3] || match[4],
113
+ specifier: match[3] || match[4] || "",
90
114
  });
91
115
  }
92
116
  return imports;
@@ -134,17 +158,35 @@ function isTypeOnlyTypeScriptImport(file, imported) {
134
158
  }
135
159
  function readEscapeAllowlist(allowlistPath) {
136
160
  if (!allowlistPath || !fs.existsSync(allowlistPath))
137
- return new Set();
161
+ return new Map();
138
162
  const parsed = JSON.parse(fs.readFileSync(allowlistPath, "utf8"));
139
163
  const entries = Array.isArray(parsed) ? parsed : parsed.escapes || [];
140
- return new Set(entries.map((entry) => {
141
- if (typeof entry === "string")
142
- return entry;
143
- return `${entry.file}:${entry.line}:${entry.code || "ts-escape-hatch"}`;
164
+ return new Map(entries.map((entry) => {
165
+ if (typeof entry === "string") {
166
+ return [
167
+ entry,
168
+ {
169
+ key: entry,
170
+ file: entry.split(":")[0] || entry,
171
+ line: Number.parseInt(entry.split(":")[1] || "", 10) || undefined,
172
+ code: entry.split(":")[2] || "ts-escape-hatch",
173
+ },
174
+ ];
175
+ }
176
+ const key = `${entry.file}:${entry.line}:${entry.code || "ts-escape-hatch"}`;
177
+ return [key, { key, file: entry.file, line: entry.line, code: entry.code || "ts-escape-hatch" }];
144
178
  }));
145
179
  }
146
180
  function isEscapeAllowed(allowlist, violation) {
147
- return allowlist.has(`${violation.file}:${violation.line}:${violation.code}`) || allowlist.has(`${violation.file}:${violation.line}`);
181
+ return allowlist.has(escapeKey(violation)) || allowlist.has(`${violation.file}:${violation.line}`);
182
+ }
183
+ function escapeKey(violation) {
184
+ return `${violation.file}:${violation.line}:${violation.code}`;
185
+ }
186
+ function lineForOffset(content, offset) {
187
+ if (offset < 0)
188
+ return 1;
189
+ return content.slice(0, offset).split(/\r?\n/).length;
148
190
  }
149
191
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
150
192
  const result = checkTypeBoundaries();
@@ -1,9 +1,31 @@
1
- // @ts-nocheck
2
1
  // Dashboard command parsing stays behavior-first until command handler types are modeled.
3
2
  import fs from "node:fs";
3
+ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { normalizeTarget, serveDashboardWorkbench, writeDashboardFolder, writeDashboardSingleFile, } from "../lib/harness-core.mjs";
6
6
  import { dashboardWatchRoots } from "../lib/harness-paths.mjs";
7
+ export async function runDevDashboardCommand({ takeFlag, takeOption, targetArg }) {
8
+ const open = !takeFlag("--no-open");
9
+ const outDir = takeOption("--out-dir", "");
10
+ const host = takeOption("--host", "127.0.0.1");
11
+ const port = Number(takeOption("--port", "0"));
12
+ const localeOverride = takeOption("--locale", "");
13
+ const target = targetArg();
14
+ const usesDefaultOutDir = !outDir;
15
+ const dashboardOutDir = outDir || defaultDevOutDir(target);
16
+ const opts = {
17
+ ...(localeOverride ? { localeOverride } : {}),
18
+ recoverGeneratedDashboard: usesDefaultOutDir,
19
+ replaceExistingDashboardOutput: usesDefaultOutDir,
20
+ };
21
+ try {
22
+ await serveDashboardWorkbench(dashboardOutDir, target, { ...opts, host, port, autoRefresh: true, open, label: "harness dev" });
23
+ }
24
+ catch (error) {
25
+ console.error(errorMessage(error));
26
+ process.exit(1);
27
+ }
28
+ }
7
29
  export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
8
30
  const watch = takeFlag("--watch");
9
31
  const workbench = takeFlag("--workbench");
@@ -19,11 +41,11 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
19
41
  process.exit(2);
20
42
  }
21
43
  try {
22
- assertV2DashboardTarget(targetArg());
23
- await serveDashboardWorkbench(outDir, targetArg(), { ...opts, host, port });
44
+ requireV2DashboardTarget(targetArg());
45
+ await serveDashboardWorkbench(outDir, targetArg(), { ...opts, host, port: Number(port) });
24
46
  }
25
47
  catch (error) {
26
- console.error(error.message);
48
+ console.error(errorMessage(error));
27
49
  process.exit(1);
28
50
  }
29
51
  }
@@ -33,28 +55,29 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
33
55
  process.exit(2);
34
56
  }
35
57
  const target = targetArg();
36
- assertV2DashboardTarget(target);
37
- const normalizedTarget = normalizeTarget(target);
38
- const watchRoots = dashboardWatchRoots(normalizedTarget.harness);
58
+ const harnessPaths = requireV2DashboardTarget(target);
59
+ const watchRoots = dashboardWatchRoots(harnessPaths);
39
60
  const regenerate = () => {
40
61
  try {
41
62
  console.log(writeDashboardFolder(outDir, target, opts));
42
63
  console.log(`dashboard regenerated: ${new Date().toISOString()}`);
43
64
  }
44
65
  catch (error) {
45
- console.error(`dashboard regeneration failed: ${error.message}`);
66
+ console.error(`dashboard regeneration failed: ${errorMessage(error)}`);
46
67
  }
47
68
  };
48
69
  regenerate();
49
70
  let timer = null;
50
71
  const watchers = watchRoots.map((watchRoot) => fs.watch(watchRoot, { recursive: true }, () => {
51
- clearTimeout(timer);
72
+ if (timer)
73
+ clearTimeout(timer);
52
74
  timer = setTimeout(regenerate, 300);
53
75
  }));
54
76
  const close = () => {
55
77
  for (const watcher of watchers)
56
78
  watcher.close();
57
- clearTimeout(timer);
79
+ if (timer)
80
+ clearTimeout(timer);
58
81
  process.exit(0);
59
82
  };
60
83
  process.on("SIGINT", close);
@@ -62,7 +85,7 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
62
85
  console.log(`watching ${watchRoots.join(", ")}`);
63
86
  await new Promise(() => { });
64
87
  }
65
- assertV2DashboardTarget(targetArg());
88
+ requireV2DashboardTarget(targetArg());
66
89
  if (outDir) {
67
90
  console.log(writeDashboardFolder(outDir, targetArg(), opts));
68
91
  }
@@ -71,10 +94,25 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
71
94
  }
72
95
  process.exit(0);
73
96
  }
74
- function assertV2DashboardTarget(target) {
97
+ function requireV2DashboardTarget(target) {
75
98
  const normalizedTarget = normalizeTarget(target);
76
- if (normalizedTarget.harness?.version === 2)
77
- return;
99
+ const harnessPaths = readV2HarnessPaths(normalizedTarget);
100
+ if (harnessPaths)
101
+ return harnessPaths;
78
102
  console.error("dashboard requires v2 harness structure; run `harness migrate-structure --plan` then `harness migrate-structure --apply`");
79
103
  process.exit(1);
80
104
  }
105
+ function defaultDevOutDir(targetInput) {
106
+ const target = path.resolve(targetInput || ".");
107
+ return path.join(os.tmpdir(), "coding-agent-harness-dev", `${path.basename(target) || "project"}-${Buffer.from(target).toString("hex").slice(0, 16)}`);
108
+ }
109
+ function readV2HarnessPaths(target) {
110
+ const harness = target.harness;
111
+ return isRecord(harness) && harness.version === 2 ? harness : null;
112
+ }
113
+ function isRecord(value) {
114
+ return typeof value === "object" && value !== null;
115
+ }
116
+ function errorMessage(error) {
117
+ return error instanceof Error ? error.message : String(error);
118
+ }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { applyStructureMigration, buildMigrationPlan, planStructureMigration, runMigration, verifyMigrationSession, } from "../lib/harness-core.mjs";
3
2
  import { applyTaskAuditIndexMigration, planTaskAuditIndexMigration, } from "../lib/task-audit-migration.mjs";
4
3
  export function runMigrationCommand(command, { args, takeFlag, takeOption, targetArg }) {
@@ -8,6 +7,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
8
7
  const planOnly = takeFlag("--plan");
9
8
  const force = takeFlag("--force");
10
9
  try {
10
+ const shouldApply = apply && !planOnly;
11
11
  const result = apply && !planOnly
12
12
  ? applyStructureMigration(targetArg(), { force })
13
13
  : planStructureMigration(targetArg());
@@ -15,7 +15,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
15
15
  console.log(JSON.stringify(result, null, 2));
16
16
  }
17
17
  else {
18
- console.log(`Structure migration ${result.applied ? "applied" : "plan"}: ${result.target}`);
18
+ console.log(`Structure migration ${shouldApply ? "applied" : "plan"}: ${result.target}`);
19
19
  console.log(`manifest: ${result.manifest}`);
20
20
  console.log(`actions: ${result.summary.actions}`);
21
21
  for (const action of result.actions || [])
@@ -23,7 +23,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
23
23
  }
24
24
  }
25
25
  catch (error) {
26
- console.error(error.message);
26
+ console.error(errorMessage(error));
27
27
  process.exit(1);
28
28
  }
29
29
  return;
@@ -52,10 +52,11 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
52
52
  process.exit(result.failures?.length ? 1 : 0);
53
53
  }
54
54
  catch (error) {
55
- if (json && error.plan)
56
- console.error(JSON.stringify(error.plan, null, 2));
55
+ const plan = readProperty(error, "plan");
56
+ if (json && plan)
57
+ console.error(JSON.stringify(plan, null, 2));
57
58
  else
58
- console.error(error.message);
59
+ console.error(errorMessage(error));
59
60
  process.exit(1);
60
61
  }
61
62
  }
@@ -99,7 +100,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
99
100
  }
100
101
  }
101
102
  catch (error) {
102
- console.error(error.message);
103
+ console.error(errorMessage(error));
103
104
  process.exit(1);
104
105
  }
105
106
  return;
@@ -122,7 +123,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
122
123
  }), null, 2));
123
124
  }
124
125
  catch (error) {
125
- console.error(error.message);
126
+ console.error(errorMessage(error));
126
127
  process.exit(1);
127
128
  }
128
129
  return;
@@ -150,3 +151,12 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
150
151
  }
151
152
  throw new Error(`Unsupported migration command: ${command}`);
152
153
  }
154
+ function readProperty(value, key) {
155
+ return isRecord(value) ? value[key] : undefined;
156
+ }
157
+ function isRecord(value) {
158
+ return typeof value === "object" && value !== null;
159
+ }
160
+ function errorMessage(error) {
161
+ return error instanceof Error ? error.message : String(error);
162
+ }
@@ -0,0 +1,142 @@
1
+ import { normalizeTarget, prepareModuleRegistration, prepareModuleScaffold, prepareModuleUnregister, readHarnessModules, } from "../lib/harness-core.mjs";
2
+ import { takeRepeatedOptionsFromArgs } from "../lib/command-registry.mjs";
3
+ import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync } from "../lib/governance-sync.mjs";
4
+ export function runModuleCommand({ args, takeFlag, takeOption, targetArg }) {
5
+ const subcommand = args.shift() || "list";
6
+ const json = takeFlag("--json");
7
+ if (subcommand === "list") {
8
+ const modules = readHarnessModules(targetArg());
9
+ const items = Object.entries(modules.items || {}).map(([key, module]) => ({ key, ...module }));
10
+ if (json)
11
+ console.log(JSON.stringify({ schema: modules.schema, generatedView: modules.generatedView, modules: items }, null, 2));
12
+ else
13
+ for (const item of items)
14
+ console.log(`${item.key}\t${item.status || "planned"}\t${item.title || item.key}`);
15
+ return;
16
+ }
17
+ if (subcommand === "inspect") {
18
+ const moduleKey = args.shift();
19
+ if (!moduleKey) {
20
+ console.error("Missing module key");
21
+ process.exit(2);
22
+ }
23
+ const modules = readHarnessModules(targetArg());
24
+ const module = modules.items[moduleKey];
25
+ if (!module) {
26
+ console.error(`Module is not registered: ${moduleKey}`);
27
+ process.exit(1);
28
+ }
29
+ console.log(JSON.stringify({ key: moduleKey, ...module }, null, 2));
30
+ return;
31
+ }
32
+ if (subcommand === "register") {
33
+ const dryRun = takeFlag("--dry-run");
34
+ const moduleKey = args.shift();
35
+ if (!moduleKey) {
36
+ console.error("Missing module key");
37
+ process.exit(2);
38
+ }
39
+ const input = {
40
+ title: takeOption("--title", ""),
41
+ prefix: takeOption("--prefix", ""),
42
+ status: takeOption("--status", "planned"),
43
+ branch: takeOption("--branch", ""),
44
+ owner: takeOption("--owner", "coordinator"),
45
+ currentStep: takeOption("--current-step", ""),
46
+ locale: takeOption("--locale", ""),
47
+ scope: takeRepeatedOptionsFromArgs(args, "--scope"),
48
+ shared: takeRepeatedOptionsFromArgs(args, "--shared"),
49
+ dependsOn: takeRepeatedOptionsFromArgs(args, "--depends-on"),
50
+ };
51
+ const target = normalizeTarget(targetArg());
52
+ const planned = prepareModuleRegistration(target, moduleKey, input, { dryRun: true });
53
+ const context = beginGovernanceSync(target, {
54
+ operation: `module register ${moduleKey}`,
55
+ dryRun,
56
+ allowDirtyWorktree: true,
57
+ allowedRelativePaths: governanceRelativePaths(planned.changes),
58
+ });
59
+ try {
60
+ const result = prepareModuleRegistration(target, moduleKey, input, { dryRun });
61
+ const commit = commitGovernanceSync(context, governanceRelativePaths(result.changes), { message: `chore(harness): register module ${result.moduleKey}` });
62
+ console.log(JSON.stringify({ ...result, governance: { commit } }, null, 2));
63
+ }
64
+ catch (error) {
65
+ console.error(errorMessage(error));
66
+ process.exit(1);
67
+ }
68
+ finally {
69
+ releaseGovernanceSync(context);
70
+ }
71
+ return;
72
+ }
73
+ if (subcommand === "unregister") {
74
+ const dryRun = takeFlag("--dry-run");
75
+ const moduleKey = args.shift();
76
+ if (!moduleKey) {
77
+ console.error("Missing module key");
78
+ process.exit(2);
79
+ }
80
+ const target = normalizeTarget(targetArg());
81
+ const planned = prepareModuleUnregister(target, moduleKey, { dryRun: true });
82
+ const context = beginGovernanceSync(target, {
83
+ operation: `module unregister ${moduleKey}`,
84
+ dryRun,
85
+ allowDirtyWorktree: true,
86
+ allowedRelativePaths: governanceRelativePaths(planned.changes),
87
+ });
88
+ try {
89
+ const result = prepareModuleUnregister(target, moduleKey, { dryRun });
90
+ const commit = commitGovernanceSync(context, governanceRelativePaths(result.changes), { message: `chore(harness): unregister module ${result.moduleKey}` });
91
+ console.log(JSON.stringify({ ...result, governance: { commit } }, null, 2));
92
+ }
93
+ catch (error) {
94
+ console.error(errorMessage(error));
95
+ process.exit(1);
96
+ }
97
+ finally {
98
+ releaseGovernanceSync(context);
99
+ }
100
+ return;
101
+ }
102
+ if (subcommand === "scaffold") {
103
+ const dryRun = takeFlag("--dry-run");
104
+ const all = takeFlag("--all");
105
+ const locale = takeOption("--locale", "");
106
+ const moduleKey = all ? "" : args.shift();
107
+ if (!all && !moduleKey) {
108
+ console.error("Missing module key");
109
+ process.exit(2);
110
+ }
111
+ const target = normalizeTarget(targetArg());
112
+ const modules = readHarnessModules(target);
113
+ const keys = all ? Object.keys(modules.items || {}).sort() : [moduleKey || ""];
114
+ const plannedChanges = keys.flatMap((key) => prepareModuleScaffold(target, key, { dryRun: true, locale }).changes);
115
+ const context = beginGovernanceSync(target, {
116
+ operation: all ? "module scaffold --all" : `module scaffold ${moduleKey}`,
117
+ dryRun,
118
+ allowDirtyWorktree: true,
119
+ allowedRelativePaths: governanceRelativePaths(plannedChanges),
120
+ allowDirtyWriteScope: true,
121
+ });
122
+ try {
123
+ const results = keys.map((key) => prepareModuleScaffold(target, key, { dryRun, locale }));
124
+ const changes = results.flatMap((result) => result.changes);
125
+ const commit = commitGovernanceSync(context, governanceRelativePaths(changes), { message: all ? "chore(harness): scaffold registered modules" : `chore(harness): scaffold module ${moduleKey}` });
126
+ console.log(JSON.stringify({ modules: results, changes, governance: { commit } }, null, 2));
127
+ }
128
+ catch (error) {
129
+ console.error(errorMessage(error));
130
+ process.exit(1);
131
+ }
132
+ finally {
133
+ releaseGovernanceSync(context);
134
+ }
135
+ return;
136
+ }
137
+ console.error(`Unknown module subcommand: ${subcommand}`);
138
+ process.exit(2);
139
+ }
140
+ function errorMessage(error) {
141
+ return error instanceof Error ? error.message : String(error);
142
+ }