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
@@ -1,11 +1,11 @@
1
- // @ts-nocheck
2
1
  // Task contract checks depend on dynamic task scan metadata until checker domain types are modeled.
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { lessonCandidatesFile, readFileSafe, toPosix, visualMapFile, } from "./core-shared.mjs";
6
- import { listTaskPlanPaths, parseTaskBudget, parseTaskContractInfo, } from "./task-scanner.mjs";
5
+ import { parseTaskBudget, parseTaskContractInfo } from "./task-metadata.mjs";
6
+ import { createScannerTaskRepository, taskPlanPathFromRecord } from "./task-repository.mjs";
7
7
  import { parseTaskAuditMetadata } from "./task-audit-metadata.mjs";
8
- export function validatePlanContracts(target, { strict = true, taskPlanPaths } = {}) {
8
+ export function validatePlanContracts(target, { strict = true, tasks } = {}) {
9
9
  const failures = [];
10
10
  const warnings = [];
11
11
  const report = (message) => {
@@ -14,7 +14,9 @@ export function validatePlanContracts(target, { strict = true, taskPlanPaths } =
14
14
  else
15
15
  warnings.push(`adoption-needed: ${message}`);
16
16
  };
17
- for (const taskPlanPath of taskPlanPaths || listTaskPlanPaths(target)) {
17
+ const taskRecords = tasks || createScannerTaskRepository(target).list();
18
+ for (const task of taskRecords) {
19
+ const taskPlanPath = taskPlanPathFromRecord(target, task);
18
20
  const taskDir = path.dirname(taskPlanPath);
19
21
  const relativeDir = toPosix(path.relative(target.projectRoot, taskDir));
20
22
  const taskPlanContent = readFileSafe(taskPlanPath);
@@ -0,0 +1,248 @@
1
+ const HELP_NOTES = `If init runs in an interactive terminal and --locale is omitted, it asks for a
2
+ language. Non-interactive init defaults to en-US.
3
+
4
+ Human review confirmation is available only through local Dashboard workbench.
5
+
6
+ Preset discovery:
7
+ Project presets live in <target>/.coding-agent-harness/presets/<preset-id>/.
8
+ User presets live in ~/.coding-agent-harness/presets/<preset-id>/.
9
+ Harness discovers project presets first when a target is supplied, then user
10
+ presets, then bundled package presets under presets/<preset-id>/.
11
+ "harness init" seeds bundled presets into the target project. "harness
12
+ install-user" and npm postinstall seed bundled presets into the user root.
13
+ Use "harness preset seed" to repair or re-run preset seeding.
14
+ Use "harness preset install" with a local preset folder, .zip archive, or
15
+ bundled preset id. Preset archives must contain preset.yaml at the archive
16
+ root or inside one top-level folder.
17
+ Use "harness preset list --json" to see available presets, their source,
18
+ purpose, compatible budgets, and manifest path. Use "harness preset inspect
19
+ <id> --json" for the full preset manifest summary.
20
+
21
+ Module workflow:
22
+ Registered modules are stored in the root harness.yaml under modules.items.
23
+ "harness module register" writes YAML, creates module brief.md/module_plan.md,
24
+ and regenerates planning/modules/Module-Registry.md as a read-only view.
25
+ "harness module scaffold" repairs only module-owned root docs; task execution
26
+ files stay under planning/modules/<key>/tasks/<task-id>/.
27
+ "harness new-task --module <key>" creates a module task and applies the module
28
+ task preset by default. Unknown modules must be registered first, or use
29
+ --register-module with --module-title/--module-prefix/--module-scope.`;
30
+ export function dispatchCommand(registry, argv) {
31
+ if (isTopLevelHelpRequest(argv)) {
32
+ console.log(generateCommandHelp(registry));
33
+ return;
34
+ }
35
+ const resolved = resolveCommand(registry, argv);
36
+ if (!resolved) {
37
+ console.log(generateCommandHelp(registry));
38
+ if (isUnresolvedCommandHelpRequest(argv))
39
+ return;
40
+ process.exit(2);
41
+ }
42
+ if (isCommandHelpRequest(resolved.raw)) {
43
+ console.log(generateCommandHelp(registry));
44
+ return;
45
+ }
46
+ return resolved.definition.handler(createCommandContext(resolved.definition, resolved.raw));
47
+ }
48
+ export function resolveCommand(registry, argv) {
49
+ const sorted = [...registry].sort((left, right) => commandWordCount(right.name) - commandWordCount(left.name));
50
+ for (const definition of sorted) {
51
+ const words = definition.name.split(" ");
52
+ if (words.every((word, index) => argv[index] === word)) {
53
+ return { definition, raw: [...argv.slice(words.length)] };
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ export function createCommandContext(definition, raw) {
59
+ const mutableArgs = [...raw];
60
+ const readers = createArgReaders(mutableArgs);
61
+ const parsed = parseCommandArgs(definition.flags || [], raw);
62
+ return {
63
+ definition,
64
+ args: parsed,
65
+ raw: [...raw],
66
+ target: definition.hasTarget === false ? "." : targetFromArgsAfterKnownFlags(definition.flags || [], raw),
67
+ ...readers,
68
+ };
69
+ }
70
+ export function createArgReaders(args) {
71
+ return {
72
+ takeFlag(name, fallback = false) {
73
+ const index = args.indexOf(name);
74
+ if (index < 0)
75
+ return fallback;
76
+ args.splice(index, 1);
77
+ return true;
78
+ },
79
+ takeOption(name, fallback = "") {
80
+ return takeOptionFromArgs(args, name, fallback);
81
+ },
82
+ targetArg() {
83
+ return trailingTargetArg(args);
84
+ },
85
+ };
86
+ }
87
+ export function parseCommandArgs(flags, argv) {
88
+ const result = { flags: {}, options: {}, repeated: {}, positionals: [] };
89
+ for (const flag of flags) {
90
+ const key = flagKey(flag.name);
91
+ if (flag.default === undefined)
92
+ continue;
93
+ if (flag.type === "boolean")
94
+ result.flags[key] = Boolean(flag.default);
95
+ else if (flag.type === "string[]")
96
+ result.repeated[key] = Array.isArray(flag.default) ? flag.default.map(String) : [];
97
+ else
98
+ result.options[key] = String(flag.default);
99
+ }
100
+ for (let index = 0; index < argv.length; index += 1) {
101
+ const arg = argv[index];
102
+ const definition = flags.find((flag) => flag.name === arg || flag.alias === arg);
103
+ if (!definition) {
104
+ if (!arg.startsWith("-"))
105
+ result.positionals.push(arg);
106
+ continue;
107
+ }
108
+ const key = flagKey(definition.name);
109
+ if (definition.type === "boolean") {
110
+ result.flags[key] = true;
111
+ continue;
112
+ }
113
+ const fallback = definition.default === undefined || Array.isArray(definition.default) ? "" : String(definition.default);
114
+ const value = argv[index + 1] || fallback;
115
+ if (definition.type === "string[]") {
116
+ result.repeated[key] = result.repeated[key] || [];
117
+ result.repeated[key].push(value);
118
+ }
119
+ else {
120
+ result.options[key] = value;
121
+ }
122
+ index += 1;
123
+ }
124
+ return result;
125
+ }
126
+ export function takeOptionFromArgs(args, name, fallback = "") {
127
+ const index = args.indexOf(name);
128
+ if (index < 0)
129
+ return fallback;
130
+ const value = args[index + 1] || fallback;
131
+ args.splice(index, 2);
132
+ return value;
133
+ }
134
+ export function takeRepeatedOptionsFromArgs(args, flag) {
135
+ const values = [];
136
+ for (let index = 0; index < args.length;) {
137
+ if (args[index] !== flag) {
138
+ index += 1;
139
+ continue;
140
+ }
141
+ const value = args[index + 1] || "";
142
+ args.splice(index, 2);
143
+ if (!value)
144
+ throw new Error(`${flag} requires a value`);
145
+ values.push(value);
146
+ }
147
+ return values;
148
+ }
149
+ export function generateCommandHelp(registry) {
150
+ const usageLines = [];
151
+ const seenUsages = new Set();
152
+ for (const command of registry) {
153
+ const usage = command.usage || buildUsage(command);
154
+ if (seenUsages.has(usage))
155
+ continue;
156
+ seenUsages.add(usage);
157
+ usageLines.push(` ${usage}`);
158
+ }
159
+ const lines = [
160
+ "Coding Agent Harness",
161
+ "",
162
+ "Usage:",
163
+ ...usageLines,
164
+ "",
165
+ HELP_NOTES,
166
+ ];
167
+ return `${lines.join("\n")}\n`;
168
+ }
169
+ export function validateCommandRegistry(registry) {
170
+ const issues = [];
171
+ const seenCommands = new Set();
172
+ for (const command of registry) {
173
+ if (!command.name.trim())
174
+ issues.push("command has empty name");
175
+ if (seenCommands.has(command.name))
176
+ issues.push(`duplicate command name: ${command.name}`);
177
+ seenCommands.add(command.name);
178
+ if (!command.description.trim())
179
+ issues.push(`${command.name}: missing description`);
180
+ if (typeof command.handler !== "function")
181
+ issues.push(`${command.name}: missing handler`);
182
+ const seenFlags = new Set();
183
+ for (const flag of command.flags || []) {
184
+ if (!flag.name.startsWith("--"))
185
+ issues.push(`${command.name}: flag must use -- prefix: ${flag.name}`);
186
+ if (seenFlags.has(flag.name))
187
+ issues.push(`${command.name}: duplicate flag ${flag.name}`);
188
+ seenFlags.add(flag.name);
189
+ if (flag.alias) {
190
+ if (seenFlags.has(flag.alias))
191
+ issues.push(`${command.name}: duplicate flag alias ${flag.alias}`);
192
+ seenFlags.add(flag.alias);
193
+ }
194
+ if (!flag.description.trim())
195
+ issues.push(`${command.name}: ${flag.name} missing description`);
196
+ }
197
+ }
198
+ return issues;
199
+ }
200
+ function isTopLevelHelpRequest(argv) {
201
+ return argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h";
202
+ }
203
+ function isCommandHelpRequest(raw) {
204
+ return raw[0] === "help" || raw.includes("--help") || raw.includes("-h");
205
+ }
206
+ function isUnresolvedCommandHelpRequest(argv) {
207
+ return argv[1] === "help" || argv.includes("--help") || argv.includes("-h");
208
+ }
209
+ function buildUsage(command) {
210
+ const positionals = (command.positionals || []).map((positional) => `<${positional}>`);
211
+ const flags = (command.flags || []).map((flag) => `[${flag.name}${flag.type === "boolean" ? "" : " value"}]`);
212
+ const target = command.hasTarget === false ? [] : ["[target]"];
213
+ return ["harness", command.name, ...positionals, ...flags, ...target].join(" ");
214
+ }
215
+ function targetFromArgsAfterKnownFlags(flags, argv) {
216
+ const args = [...argv];
217
+ for (const flag of flags) {
218
+ if (flag.type === "boolean") {
219
+ removeBooleanFlag(args, flag.name);
220
+ if (flag.alias)
221
+ removeBooleanFlag(args, flag.alias);
222
+ }
223
+ else {
224
+ removeOption(args, flag.name);
225
+ if (flag.alias)
226
+ removeOption(args, flag.alias);
227
+ }
228
+ }
229
+ return trailingTargetArg(args);
230
+ }
231
+ function removeBooleanFlag(args, name) {
232
+ for (let index = args.indexOf(name); index >= 0; index = args.indexOf(name))
233
+ args.splice(index, 1);
234
+ }
235
+ function removeOption(args, name) {
236
+ for (let index = args.indexOf(name); index >= 0; index = args.indexOf(name))
237
+ args.splice(index, 2);
238
+ }
239
+ function trailingTargetArg(args) {
240
+ const candidate = args[args.length - 1] || "";
241
+ return candidate && !candidate.startsWith("-") ? candidate : ".";
242
+ }
243
+ function flagKey(name) {
244
+ return name.replace(/^-+/, "");
245
+ }
246
+ function commandWordCount(name) {
247
+ return name.split(" ").length;
248
+ }
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import fs from "node:fs";
3
2
  import os from "node:os";
4
3
  import path from "node:path";
@@ -18,6 +17,18 @@ export function userPresetRootForHome(home = "") {
18
17
  return path.join(path.resolve(home || os.homedir()), ".coding-agent-harness/presets");
19
18
  }
20
19
  export const userPresetRoot = userPresetRootForHome();
20
+ export const harnessPathTemplateFields = [
21
+ "harnessRoot",
22
+ "planningRoot",
23
+ "tasksRoot",
24
+ "modulesRoot",
25
+ "externalRoot",
26
+ "governanceRoot",
27
+ "generatedRoot",
28
+ "regressionRoot",
29
+ "ledgerPath",
30
+ "closeoutIndexPath",
31
+ ];
21
32
  export const supportedLocales = new Set(["zh-CN", "en-US"]);
22
33
  export const allowedReviewDispositions = new Set([
23
34
  "open",
@@ -95,6 +106,70 @@ export function readBundledTemplate(source) {
95
106
  throw new Error(`Bundled template is empty: ${source}`);
96
107
  return content;
97
108
  }
109
+ export function harnessPathContext(targetOrPaths) {
110
+ const paths = (targetOrPaths?.harness || targetOrPaths || {});
111
+ const projectRoot = String(paths.projectRoot || targetOrPaths?.projectRoot || "");
112
+ const result = {};
113
+ for (const field of harnessPathTemplateFields) {
114
+ const value = String(paths[field] || "");
115
+ if (!value)
116
+ continue;
117
+ result[field] = projectRoot && path.isAbsolute(value)
118
+ ? toPosix(path.relative(projectRoot, value))
119
+ : toPosix(value);
120
+ }
121
+ return result;
122
+ }
123
+ export function absoluteHarnessPathContext(targetOrPaths) {
124
+ const paths = (targetOrPaths?.harness || targetOrPaths || {});
125
+ const projectRoot = String(paths.projectRoot || targetOrPaths?.projectRoot || "");
126
+ const result = {};
127
+ for (const field of harnessPathTemplateFields) {
128
+ const value = String(paths[field] || "");
129
+ if (!value)
130
+ continue;
131
+ result[field] = path.isAbsolute(value) ? value : path.join(projectRoot, value);
132
+ }
133
+ return result;
134
+ }
135
+ export function renderHarnessTemplate(content, context = {}, { strict = false, missing = "preserve" } = {}) {
136
+ return String(content).replace(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g, (match, key) => {
137
+ const value = getTemplateValue(context, key);
138
+ if (value == null) {
139
+ if (strict)
140
+ throw new Error(`Unknown template token: ${key}`);
141
+ return missing === "empty" ? "" : match;
142
+ }
143
+ return String(value);
144
+ });
145
+ }
146
+ export function validateHarnessPathTemplateTokens(content, label = "template") {
147
+ const failures = [];
148
+ for (const match of String(content || "").matchAll(/\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g)) {
149
+ const key = match[1];
150
+ if (!key.startsWith("paths."))
151
+ continue;
152
+ const field = key.slice("paths.".length);
153
+ if (!harnessPathTemplateFields.includes(field))
154
+ failures.push(`${label} uses unknown path token: ${key}`);
155
+ }
156
+ return failures;
157
+ }
158
+ export function resolveHarnessPathTemplate(value, target, label = "path") {
159
+ const rendered = renderHarnessTemplate(String(value || ""), { paths: harnessPathContext(target) }, { strict: true });
160
+ const normalized = toPosix(path.normalize(rendered));
161
+ if (!rendered.trim())
162
+ throw new Error(`${label} resolved to an empty path`);
163
+ if (path.isAbsolute(rendered) || normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
164
+ throw new Error(`${label} escapes target root: ${value}`);
165
+ }
166
+ return normalized;
167
+ }
168
+ function getTemplateValue(context, key) {
169
+ return String(key).split(".").reduce((cursor, part) => (cursor && typeof cursor === "object" && Object.prototype.hasOwnProperty.call(cursor, part)
170
+ ? cursor[part]
171
+ : undefined), context);
172
+ }
98
173
  export function walkFiles(root, options = {}) {
99
174
  const results = [];
100
175
  if (!fs.existsSync(root))
@@ -196,7 +271,7 @@ export function nowTimestamp() {
196
271
  export function normalizeTaskId(value) {
197
272
  return slug(value || "task");
198
273
  }
199
- export function renderTaskTemplate(content, { taskId, title, locale, budget = "standard", moduleKey = "", preset = "none", presetVersion = "", evidenceBundle = "", longRunning = false, scaffoldProvenance = {}, taskAudit = {} }) {
274
+ export function renderTaskTemplate(content, { taskId, title, locale, budget = "standard", moduleKey = "", preset = "none", presetVersion = "", evidenceBundle = "", longRunning = false, scaffoldProvenance = {}, taskAudit = {}, target, paths = target ? harnessPathContext(target) : {}, }) {
200
275
  const date = todayDate();
201
276
  const provenance = {
202
277
  createdBy: scaffoldProvenance.createdBy || "harness new-task",
@@ -206,7 +281,7 @@ export function renderTaskTemplate(content, { taskId, title, locale, budget = "s
206
281
  templateSource: scaffoldProvenance.templateSource || "templates/planning/brief.md",
207
282
  exceptionReason: scaffoldProvenance.exceptionReason || "n/a",
208
283
  };
209
- return String(content)
284
+ return renderHarnessTemplate(String(content), { paths })
210
285
  .replaceAll("{{TASK_ID}}", taskId)
211
286
  .replaceAll("{{TASK_TITLE}}", title)
212
287
  .replaceAll("{{DATE}}", date)