coding-agent-harness 1.0.4 → 1.0.6

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 (279) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/CONTRIBUTING.md +2 -2
  3. package/LICENSE +661 -21
  4. package/LICENSE-EXCEPTION.md +37 -0
  5. package/README.md +96 -4
  6. package/README.zh-CN.md +75 -4
  7. package/SKILL.md +52 -51
  8. package/dist/build-dist.mjs +189 -0
  9. package/dist/check-dist-observation.mjs +428 -0
  10. package/dist/check-harness.mjs +489 -0
  11. package/dist/check-import-graph.mjs +511 -0
  12. package/dist/check-runtime-emit.mjs +304 -0
  13. package/dist/check-type-boundaries.mjs +139 -0
  14. package/dist/commands/dashboard-command.mjs +80 -0
  15. package/dist/commands/migration-command.mjs +152 -0
  16. package/dist/commands/preset-command.mjs +91 -0
  17. package/dist/commands/task-command.mjs +324 -0
  18. package/dist/harness.mjs +304 -0
  19. package/dist/lib/capability-registry.mjs +643 -0
  20. package/dist/lib/check-module-parallel.mjs +227 -0
  21. package/dist/lib/check-profiles.mjs +414 -0
  22. package/dist/lib/check-task-contracts.mjs +54 -0
  23. package/dist/lib/core-shared.mjs +254 -0
  24. package/dist/lib/dashboard-data.mjs +608 -0
  25. package/dist/lib/dashboard-workbench.mjs +334 -0
  26. package/dist/lib/dashboard-writer.mjs +200 -0
  27. package/dist/lib/git-status-summary.mjs +45 -0
  28. package/dist/lib/governance-index-generator.mjs +236 -0
  29. package/dist/lib/governance-sync.mjs +617 -0
  30. package/dist/lib/governance-table-boundary.mjs +161 -0
  31. package/{scripts → dist}/lib/harness-core.mjs +3 -0
  32. package/dist/lib/harness-paths.mjs +338 -0
  33. package/dist/lib/lesson-maintenance.mjs +139 -0
  34. package/dist/lib/markdown-utils.mjs +193 -0
  35. package/dist/lib/migration-planner.mjs +439 -0
  36. package/dist/lib/migration-support.mjs +317 -0
  37. package/dist/lib/phase-kind.mjs +46 -0
  38. package/dist/lib/preset-audit-contracts.mjs +40 -0
  39. package/dist/lib/preset-engine.mjs +516 -0
  40. package/dist/lib/preset-registry.mjs +831 -0
  41. package/dist/lib/preset-resource-contracts.mjs +83 -0
  42. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  43. package/dist/lib/status-builder.mjs +87 -0
  44. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +48 -47
  45. package/dist/lib/structure-migration.mjs +404 -0
  46. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  47. package/dist/lib/task-audit-metadata.mjs +376 -0
  48. package/dist/lib/task-audit-migration.mjs +355 -0
  49. package/dist/lib/task-completion-consistency.mjs +29 -0
  50. package/dist/lib/task-index.mjs +133 -0
  51. package/dist/lib/task-lesson-candidates.mjs +239 -0
  52. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  53. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  54. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  55. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  56. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  57. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  58. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  59. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  60. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  61. package/dist/lib/task-lifecycle.mjs +611 -0
  62. package/dist/lib/task-metadata.mjs +116 -0
  63. package/dist/lib/task-review-model.mjs +474 -0
  64. package/dist/lib/task-scanner.mjs +439 -0
  65. package/dist/lib/task-tombstone-commands.mjs +125 -0
  66. package/dist/postinstall.mjs +14 -0
  67. package/dist/run-built-tests.mjs +84 -0
  68. package/docs-release/README.md +1 -0
  69. package/docs-release/architecture/overview.md +13 -13
  70. package/docs-release/architecture/overview.zh-CN.md +13 -13
  71. package/docs-release/architecture/system-explainer/01-system-overview.md +218 -0
  72. package/docs-release/architecture/system-explainer/02-module-dependency.md +257 -0
  73. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +304 -0
  74. package/docs-release/architecture/system-explainer/04-check-and-governance.md +241 -0
  75. package/docs-release/architecture/system-explainer/05-data-flow.md +276 -0
  76. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +300 -0
  77. package/docs-release/architecture/system-explainer/README.md +67 -0
  78. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +227 -0
  79. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +263 -0
  80. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +319 -0
  81. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +252 -0
  82. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +290 -0
  83. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +320 -0
  84. package/docs-release/architecture/system-explainer/en-US/README.md +70 -0
  85. package/docs-release/guides/agent-installation.en-US.md +22 -15
  86. package/docs-release/guides/agent-installation.md +23 -15
  87. package/docs-release/guides/contributing.md +3 -3
  88. package/docs-release/guides/contributing.zh-CN.md +3 -3
  89. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  90. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  91. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  92. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  93. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  94. package/docs-release/guides/migration-playbook.md +59 -1
  95. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  96. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  97. package/docs-release/guides/preset-development.md +28 -4
  98. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  99. package/docs-release/guides/repository-operating-models.md +21 -21
  100. package/docs-release/guides/task-state-machine.en-US.md +35 -18
  101. package/docs-release/guides/task-state-machine.md +35 -18
  102. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  103. package/examples/minimal-project/AGENTS.md +2 -2
  104. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  105. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/INDEX.md +60 -0
  106. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  107. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  108. package/package.json +22 -13
  109. package/presets/legacy-migration/preset.yaml +5 -5
  110. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  111. package/presets/lesson-sedimentation/preset.yaml +3 -3
  112. package/presets/module/preset.yaml +2 -2
  113. package/presets/module/templates/execution_strategy.append.md +1 -1
  114. package/presets/module/templates/task_plan.append.md +3 -3
  115. package/presets/standard-task/preset.yaml +2 -2
  116. package/references/adversarial-review-standard.md +2 -2
  117. package/references/agents-md-pattern.md +14 -14
  118. package/references/cadence-ledger.md +1 -1
  119. package/references/ci-cd-standard.md +1 -1
  120. package/references/delivery-operating-model-standard.md +4 -4
  121. package/references/docs-directory-standard.md +65 -159
  122. package/references/external-source-intake-standard.md +10 -10
  123. package/references/harness-ledger.md +6 -6
  124. package/references/legacy-12-phase-bootstrap.md +2 -2
  125. package/references/lessons-governance.md +15 -15
  126. package/references/long-running-task-standard.md +6 -6
  127. package/references/module-parallel-standard.md +34 -34
  128. package/references/planning-loop.md +6 -6
  129. package/references/project-onboarding-audit.md +4 -4
  130. package/references/regression-system.md +2 -2
  131. package/references/repo-governance-standard.md +4 -4
  132. package/references/review-routing-standard.md +1 -1
  133. package/references/ssot-governance.md +19 -19
  134. package/references/taskr-gap-analysis.md +5 -5
  135. package/references/walkthrough-closeout.md +14 -14
  136. package/references/worktree-parallel.md +3 -3
  137. package/skills/preset-creator/references/complex-task-skeleton/brief.md +11 -0
  138. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  139. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  140. package/templates/AGENTS.md.template +31 -29
  141. package/templates/architecture/README.md +4 -4
  142. package/templates/architecture/service-catalog.md +2 -2
  143. package/templates/architecture/services/service-template.md +1 -1
  144. package/templates/dashboard/assets/app-src/00-state.js +12 -0
  145. package/templates/dashboard/assets/app-src/10-router.js +3 -0
  146. package/templates/dashboard/assets/app-src/20-overview.js +13 -3
  147. package/templates/dashboard/assets/app-src/35-task-detail.js +46 -6
  148. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  149. package/templates/dashboard/assets/app-src/55-presets.js +375 -0
  150. package/templates/dashboard/assets/app-src/60-shared.js +3 -1
  151. package/templates/dashboard/assets/app-src/90-bindings.js +131 -0
  152. package/templates/dashboard/assets/app.css +583 -0
  153. package/templates/dashboard/assets/app.css.manifest.json +1 -0
  154. package/templates/dashboard/assets/app.js +585 -11
  155. package/templates/dashboard/assets/app.manifest.json +1 -0
  156. package/templates/dashboard/assets/css-src/00-foundation.css +4 -0
  157. package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +62 -0
  158. package/templates/dashboard/assets/css-src/45-presets.css +516 -0
  159. package/templates/dashboard/assets/i18n.js +144 -4
  160. package/templates/development/README.md +10 -10
  161. package/templates/development/cross-repo-debugging.md +3 -3
  162. package/templates/development/external-context/service-template.md +2 -2
  163. package/templates/development/external-source-packs/README.md +4 -4
  164. package/templates/integrations/README.md +4 -4
  165. package/templates/integrations/api-contract.md +2 -2
  166. package/templates/integrations/event-contract.md +2 -2
  167. package/templates/integrations/third-party/vendor-template.md +2 -2
  168. package/templates/integrations/webhook-contract.md +2 -2
  169. package/templates/ledger/Harness-Ledger.md +1 -1
  170. package/templates/planning/INDEX.md +88 -0
  171. package/templates/planning/brief.md +1 -1
  172. package/templates/planning/module_session_prompt.md +2 -1
  173. package/templates/planning/review.md +0 -18
  174. package/templates/planning/task_plan.md +5 -44
  175. package/templates/planning/visual_map.md +13 -9
  176. package/templates/planning/visual_map.simple.md +52 -0
  177. package/templates/planning/walkthrough.md +47 -0
  178. package/templates/reference/docs-library-standard.md +8 -8
  179. package/templates/reference/execution-workflow-standard.md +29 -2
  180. package/templates/reference/external-source-intake-standard.md +15 -15
  181. package/templates/reference/repo-governance-standard.md +1 -1
  182. package/templates/ssot/Module-Registry.md +1 -1
  183. package/templates/walkthrough/walkthrough-template.md +2 -2
  184. package/templates-zh-CN/AGENTS.md.template +31 -29
  185. package/templates-zh-CN/CLAUDE.md.template +1 -1
  186. package/templates-zh-CN/architecture/README.md +4 -4
  187. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  188. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  189. package/templates-zh-CN/development/README.md +10 -10
  190. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  191. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  192. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  193. package/templates-zh-CN/integrations/README.md +4 -4
  194. package/templates-zh-CN/integrations/api-contract.md +2 -2
  195. package/templates-zh-CN/integrations/event-contract.md +2 -2
  196. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  197. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  198. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  199. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  200. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  201. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  202. package/templates-zh-CN/planning/INDEX.md +87 -0
  203. package/templates-zh-CN/planning/brief.md +1 -1
  204. package/templates-zh-CN/planning/module_session_prompt.md +12 -11
  205. package/templates-zh-CN/planning/review.md +0 -18
  206. package/templates-zh-CN/planning/task_plan.md +3 -63
  207. package/templates-zh-CN/planning/visual_map.md +14 -7
  208. package/templates-zh-CN/planning/visual_map.simple.md +48 -0
  209. package/templates-zh-CN/planning/walkthrough.md +47 -0
  210. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  211. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  212. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  213. package/templates-zh-CN/reference/execution-workflow-standard.md +32 -7
  214. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  215. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  216. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  217. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  218. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  219. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  220. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  221. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  222. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  223. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  224. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  225. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  226. package/tsconfig.dist.json +16 -0
  227. package/tsconfig.json +25 -0
  228. package/tsconfig.runtime.json +24 -0
  229. package/examples/minimal-project/.harness-capabilities.json +0 -8
  230. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  231. package/scripts/check-harness.mjs +0 -508
  232. package/scripts/commands/dashboard-command.mjs +0 -67
  233. package/scripts/commands/migration-command.mjs +0 -96
  234. package/scripts/commands/preset-command.mjs +0 -73
  235. package/scripts/commands/task-command.mjs +0 -327
  236. package/scripts/harness.mjs +0 -287
  237. package/scripts/lib/capability-registry.mjs +0 -591
  238. package/scripts/lib/check-module-parallel.mjs +0 -237
  239. package/scripts/lib/check-profiles.mjs +0 -418
  240. package/scripts/lib/check-task-contracts.mjs +0 -47
  241. package/scripts/lib/core-shared.mjs +0 -196
  242. package/scripts/lib/dashboard-data.mjs +0 -412
  243. package/scripts/lib/dashboard-workbench.mjs +0 -257
  244. package/scripts/lib/dashboard-writer.mjs +0 -198
  245. package/scripts/lib/git-status-summary.mjs +0 -46
  246. package/scripts/lib/governance-index-generator.mjs +0 -174
  247. package/scripts/lib/governance-sync.mjs +0 -514
  248. package/scripts/lib/governance-table-boundary.mjs +0 -175
  249. package/scripts/lib/lesson-maintenance.mjs +0 -152
  250. package/scripts/lib/markdown-utils.mjs +0 -158
  251. package/scripts/lib/migration-planner.mjs +0 -478
  252. package/scripts/lib/migration-support.mjs +0 -312
  253. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  254. package/scripts/lib/preset-engine.mjs +0 -497
  255. package/scripts/lib/preset-registry.mjs +0 -627
  256. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  257. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  258. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  259. package/scripts/lib/task-completion-consistency.mjs +0 -16
  260. package/scripts/lib/task-index.mjs +0 -93
  261. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  262. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  263. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -101
  264. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -70
  265. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  266. package/scripts/lib/task-lifecycle.mjs +0 -649
  267. package/scripts/lib/task-review-model.mjs +0 -469
  268. package/scripts/lib/task-scanner.mjs +0 -576
  269. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  270. package/scripts/postinstall.mjs +0 -14
  271. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  272. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  273. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  274. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  275. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  276. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  277. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  278. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  279. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
@@ -0,0 +1,511 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
+ const sourceRoots = ["scripts", "tests"];
8
+ const sourceExtensionPattern = /\.(mjs|mts|ts)$/;
9
+ export function buildImportGraph({ repoRoot = defaultRepoRoot } = {}) {
10
+ const files = collectSourceFiles(repoRoot);
11
+ const fileSet = new Set(files);
12
+ const nodesByPath = new Map();
13
+ const unresolvedEdges = [];
14
+ const runtimeMjsToTsEdges = [];
15
+ const typesValueImports = [];
16
+ for (const file of files) {
17
+ const content = fs.readFileSync(path.join(repoRoot, file), "utf8");
18
+ const imports = [];
19
+ for (const imported of parseImports(content)) {
20
+ if (!isLocalSpecifier(imported.specifier))
21
+ continue;
22
+ const resolved = resolveLocalSpecifier(repoRoot, file, imported.specifier);
23
+ const target = resolved && fileSet.has(resolved) ? resolved : undefined;
24
+ const edge = {
25
+ specifier: imported.specifier,
26
+ kind: imported.kind,
27
+ importType: imported.importType,
28
+ target,
29
+ };
30
+ imports.push(edge);
31
+ if (!target) {
32
+ unresolvedEdges.push({
33
+ file,
34
+ specifier: imported.specifier,
35
+ resolved: resolved || null,
36
+ message: `${file} imports unresolved local specifier ${imported.specifier}`,
37
+ });
38
+ }
39
+ if (file.endsWith(".mjs") && (hasTypeScriptSourceExtension(imported.specifier) || hasTypeScriptSourceExtension(target))) {
40
+ runtimeMjsToTsEdges.push({
41
+ file,
42
+ specifier: imported.specifier,
43
+ target: target || resolved || null,
44
+ message: `${file} imports TypeScript from runtime .mjs: ${imported.specifier}`,
45
+ });
46
+ }
47
+ if (target && isSharedTypesPath(target) && imported.importType !== "type") {
48
+ typesValueImports.push({
49
+ file,
50
+ specifier: imported.specifier,
51
+ target,
52
+ message: `${file} value-imports shared type island: ${imported.specifier}`,
53
+ });
54
+ }
55
+ }
56
+ nodesByPath.set(file, {
57
+ path: file,
58
+ kind: path.extname(file).slice(1),
59
+ imports,
60
+ importType: [...new Set(imports.map((imported) => imported.importType))],
61
+ reachableFromHarnessCore: false,
62
+ reachableFromBin: false,
63
+ barrelReachable: false,
64
+ layer: null,
65
+ });
66
+ }
67
+ for (const entrypoint of ["scripts/harness.mts", "scripts/harness.mjs"]) {
68
+ markReachable(nodesByPath, entrypoint, "reachableFromBin");
69
+ }
70
+ for (const core of ["scripts/lib/harness-core.mts", "scripts/lib/harness-core.mjs"]) {
71
+ markReachable(nodesByPath, core, "reachableFromHarnessCore");
72
+ markBarrelReachable(nodesByPath, core);
73
+ }
74
+ const cycles = findCycles(nodesByPath);
75
+ const cycleNodeSet = new Set(cycles.flat());
76
+ assignLayers(nodesByPath, cycleNodeSet);
77
+ const nodes = [...nodesByPath.values()].sort((left, right) => left.path.localeCompare(right.path));
78
+ const localEdgeCount = nodes.reduce((count, node) => count + node.imports.filter((imported) => imported.target).length, 0);
79
+ const barrelTargets = [
80
+ ...(nodesByPath.get("scripts/lib/harness-core.mts")?.imports.filter((imported) => imported.kind === "export" && imported.target) || []),
81
+ ...(nodesByPath.get("scripts/lib/harness-core.mjs")?.imports.filter((imported) => imported.kind === "export" && imported.target) || []),
82
+ ];
83
+ return {
84
+ schemaVersion: 1,
85
+ sourceRoots,
86
+ summary: {
87
+ fileCount: nodes.length,
88
+ mjsCount: nodes.filter((node) => node.path.endsWith(".mjs")).length,
89
+ localEdgeCount,
90
+ unresolvedLocalEdges: unresolvedEdges.length,
91
+ cycleNodes: cycleNodeSet.size,
92
+ runtimeMjsToTsEdges: runtimeMjsToTsEdges.length,
93
+ typesValueImports: typesValueImports.length,
94
+ binReachableFiles: nodes.filter((node) => node.reachableFromBin).length,
95
+ harnessCoreBarrelTargets: barrelTargets.length,
96
+ },
97
+ nodes,
98
+ unresolvedEdges,
99
+ cycles,
100
+ runtimeMjsToTsEdges,
101
+ typesValueImports,
102
+ };
103
+ }
104
+ export function checkImportGraph({ repoRoot = defaultRepoRoot, expectNodes, expectEdges } = {}) {
105
+ const graph = buildImportGraph({ repoRoot });
106
+ const violations = [];
107
+ for (const edge of graph.unresolvedEdges) {
108
+ violations.push({ code: "unresolved-local-edge", ...edge });
109
+ }
110
+ for (const cycle of graph.cycles) {
111
+ violations.push({
112
+ code: "cycle",
113
+ cycle,
114
+ message: `import cycle detected: ${cycle.join(" -> ")}`,
115
+ });
116
+ }
117
+ for (const edge of graph.runtimeMjsToTsEdges) {
118
+ violations.push({ code: "mjs-imports-ts", ...edge });
119
+ }
120
+ for (const edge of graph.typesValueImports) {
121
+ violations.push({ code: "types-value-import", ...edge });
122
+ }
123
+ const barrels = graph.nodes.filter((node) => node.path === "scripts/lib/harness-core.mts" || node.path === "scripts/lib/harness-core.mjs");
124
+ for (const edge of barrels.flatMap((barrel) => barrel.imports || [])) {
125
+ if (edge.kind !== "export" || !edge.target)
126
+ continue;
127
+ const target = graph.nodes.find((node) => node.path === edge.target);
128
+ if (!target?.barrelReachable) {
129
+ violations.push({
130
+ code: "barrel-target-not-reachable",
131
+ file: barrel.path,
132
+ target: edge.target,
133
+ message: `${edge.target} is exported by harness-core but is not marked barrel reachable`,
134
+ });
135
+ }
136
+ }
137
+ if (expectNodes !== undefined && graph.summary.fileCount !== expectNodes) {
138
+ violations.push({
139
+ code: "node-count-drift",
140
+ expected: expectNodes,
141
+ actual: graph.summary.fileCount,
142
+ message: `expected ${expectNodes} graph files, got ${graph.summary.fileCount}`,
143
+ });
144
+ }
145
+ if (expectEdges !== undefined && graph.summary.localEdgeCount !== expectEdges) {
146
+ violations.push({
147
+ code: "edge-count-drift",
148
+ expected: expectEdges,
149
+ actual: graph.summary.localEdgeCount,
150
+ message: `expected ${expectEdges} local graph edges, got ${graph.summary.localEdgeCount}`,
151
+ });
152
+ }
153
+ return { ok: violations.length === 0, graph, violations };
154
+ }
155
+ function collectSourceFiles(repoRoot) {
156
+ const files = [];
157
+ for (const root of sourceRoots) {
158
+ const absoluteRoot = path.join(repoRoot, root);
159
+ if (!fs.existsSync(absoluteRoot))
160
+ continue;
161
+ walk(absoluteRoot, files, repoRoot);
162
+ }
163
+ return files.sort();
164
+ }
165
+ function walk(current, files, repoRoot) {
166
+ const stat = fs.lstatSync(current);
167
+ if (stat.isSymbolicLink())
168
+ return;
169
+ if (stat.isDirectory()) {
170
+ const name = path.basename(current);
171
+ if (name === "node_modules" || name === ".worktrees" || name === "tmp" || name === "dist" || name === "coverage")
172
+ return;
173
+ for (const entry of fs.readdirSync(current))
174
+ walk(path.join(current, entry), files, repoRoot);
175
+ return;
176
+ }
177
+ if (stat.isFile() && sourceExtensionPattern.test(current)) {
178
+ files.push(path.relative(repoRoot, current).split(path.sep).join("/"));
179
+ }
180
+ }
181
+ function parseImports(content) {
182
+ const imports = [];
183
+ let index = 0;
184
+ while (index < content.length) {
185
+ const skipped = skipNonCode(content, index);
186
+ if (skipped !== index) {
187
+ index = skipped;
188
+ continue;
189
+ }
190
+ if (isKeywordAt(content, index, "import")) {
191
+ const afterKeyword = skipWhitespace(content, index + "import".length);
192
+ if (content[afterKeyword] === ".") {
193
+ index = afterKeyword + 1;
194
+ continue;
195
+ }
196
+ if (content[afterKeyword] === "(") {
197
+ const specifier = readFirstStringArgument(content, afterKeyword + 1);
198
+ if (specifier)
199
+ imports.push({ kind: "import", importType: "dynamic", specifier });
200
+ index = afterKeyword + 1;
201
+ continue;
202
+ }
203
+ const statement = content.slice(index, findStatementEnd(content, index));
204
+ const sideEffect = statement.match(/\bimport\s+["']([^"']+)["']/s);
205
+ const fromImport = statement.match(/\bfrom\s*["']([^"']+)["']/s);
206
+ const specifier = fromImport?.[1] || sideEffect?.[1];
207
+ if (specifier) {
208
+ imports.push({
209
+ kind: "import",
210
+ importType: /^\s*import\s+type\b/s.test(statement) ? "type" : "value",
211
+ specifier,
212
+ });
213
+ }
214
+ }
215
+ else if (isKeywordAt(content, index, "export")) {
216
+ const statement = content.slice(index, findStatementEnd(content, index));
217
+ const fromExport = statement.match(/\bfrom\s*["']([^"']+)["']/s);
218
+ if (fromExport) {
219
+ imports.push({
220
+ kind: "export",
221
+ importType: /^\s*export\s+type\b/s.test(statement) ? "type" : "re-export",
222
+ specifier: fromExport[1],
223
+ });
224
+ }
225
+ }
226
+ index += 1;
227
+ }
228
+ return imports;
229
+ }
230
+ function skipNonCode(content, index) {
231
+ const char = content[index];
232
+ const next = content[index + 1];
233
+ if (char === "/" && next === "/") {
234
+ const lineEnd = content.indexOf("\n", index + 2);
235
+ return lineEnd === -1 ? content.length : lineEnd + 1;
236
+ }
237
+ if (char === "/" && next === "*") {
238
+ const commentEnd = content.indexOf("*/", index + 2);
239
+ return commentEnd === -1 ? content.length : commentEnd + 2;
240
+ }
241
+ if (char === "'" || char === '"' || char === "`") {
242
+ return skipString(content, index, char);
243
+ }
244
+ return index;
245
+ }
246
+ function skipString(content, index, quote) {
247
+ let cursor = index + 1;
248
+ while (cursor < content.length) {
249
+ if (content[cursor] === "\\") {
250
+ cursor += 2;
251
+ continue;
252
+ }
253
+ if (content[cursor] === quote)
254
+ return cursor + 1;
255
+ cursor += 1;
256
+ }
257
+ return content.length;
258
+ }
259
+ function findStatementEnd(content, index) {
260
+ let cursor = index;
261
+ while (cursor < content.length) {
262
+ const skipped = skipNonCode(content, cursor);
263
+ if (skipped !== cursor) {
264
+ cursor = skipped;
265
+ continue;
266
+ }
267
+ if (content[cursor] === ";")
268
+ return cursor + 1;
269
+ cursor += 1;
270
+ }
271
+ return content.length;
272
+ }
273
+ function readFirstStringArgument(content, index) {
274
+ let cursor = skipWhitespace(content, index);
275
+ const quote = content[cursor];
276
+ if (quote !== "'" && quote !== '"')
277
+ return undefined;
278
+ cursor += 1;
279
+ let value = "";
280
+ while (cursor < content.length) {
281
+ if (content[cursor] === "\\") {
282
+ value += content[cursor + 1] || "";
283
+ cursor += 2;
284
+ continue;
285
+ }
286
+ if (content[cursor] === quote)
287
+ return value;
288
+ value += content[cursor];
289
+ cursor += 1;
290
+ }
291
+ return undefined;
292
+ }
293
+ function skipWhitespace(content, index) {
294
+ let cursor = index;
295
+ while (/\s/.test(content[cursor] || ""))
296
+ cursor += 1;
297
+ return cursor;
298
+ }
299
+ function isKeywordAt(content, index, keyword) {
300
+ if (content.slice(index, index + keyword.length) !== keyword)
301
+ return false;
302
+ const before = content[index - 1] || "";
303
+ const after = content[index + keyword.length] || "";
304
+ return !isIdentifierChar(before) && !isIdentifierChar(after);
305
+ }
306
+ function isIdentifierChar(char) {
307
+ return /[A-Za-z0-9_$]/.test(char);
308
+ }
309
+ function isLocalSpecifier(specifier) {
310
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
311
+ }
312
+ function resolveLocalSpecifier(repoRoot, importer, specifier) {
313
+ const importerDir = path.dirname(path.join(repoRoot, importer));
314
+ const basePath = specifier.startsWith("/") ? path.join(repoRoot, specifier) : path.resolve(importerDir, specifier);
315
+ for (const candidate of candidatePaths(basePath)) {
316
+ if (fs.existsSync(candidate))
317
+ return path.relative(repoRoot, candidate).split(path.sep).join("/");
318
+ }
319
+ const relative = path.relative(repoRoot, basePath).split(path.sep).join("/");
320
+ return relative.startsWith("..") ? undefined : relative;
321
+ }
322
+ function candidatePaths(basePath) {
323
+ const extension = path.extname(basePath);
324
+ if (extension) {
325
+ const paths = [basePath];
326
+ if (extension === ".js")
327
+ paths.push(basePath.slice(0, -3) + ".ts", basePath.slice(0, -3) + ".mts", basePath.slice(0, -3) + ".mjs");
328
+ if (extension === ".mjs")
329
+ paths.push(basePath.slice(0, -4) + ".mts");
330
+ return paths;
331
+ }
332
+ return [
333
+ basePath,
334
+ `${basePath}.mjs`,
335
+ `${basePath}.mts`,
336
+ `${basePath}.ts`,
337
+ `${basePath}.js`,
338
+ path.join(basePath, "index.mjs"),
339
+ path.join(basePath, "index.ts"),
340
+ ];
341
+ }
342
+ function markReachable(nodesByPath, startPath, field) {
343
+ const stack = nodesByPath.has(startPath) ? [startPath] : [];
344
+ const seen = new Set();
345
+ while (stack.length > 0) {
346
+ const current = stack.pop();
347
+ if (seen.has(current))
348
+ continue;
349
+ seen.add(current);
350
+ const node = nodesByPath.get(current);
351
+ if (!node)
352
+ continue;
353
+ node[field] = true;
354
+ for (const imported of node.imports) {
355
+ if (imported.target)
356
+ stack.push(imported.target);
357
+ }
358
+ }
359
+ }
360
+ function markBarrelReachable(nodesByPath, barrelPath) {
361
+ const barrel = nodesByPath.get(barrelPath);
362
+ if (!barrel)
363
+ return;
364
+ for (const imported of barrel.imports) {
365
+ if (imported.kind !== "export" || !imported.target)
366
+ continue;
367
+ const target = nodesByPath.get(imported.target);
368
+ if (target)
369
+ target.barrelReachable = true;
370
+ }
371
+ }
372
+ function findCycles(nodesByPath) {
373
+ const indexByPath = new Map();
374
+ const lowlinkByPath = new Map();
375
+ const stack = [];
376
+ const onStack = new Set();
377
+ const cycles = [];
378
+ let index = 0;
379
+ function strongConnect(file) {
380
+ indexByPath.set(file, index);
381
+ lowlinkByPath.set(file, index);
382
+ index += 1;
383
+ stack.push(file);
384
+ onStack.add(file);
385
+ for (const target of adjacency(nodesByPath, file)) {
386
+ if (!indexByPath.has(target)) {
387
+ strongConnect(target);
388
+ lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file), lowlinkByPath.get(target)));
389
+ }
390
+ else if (onStack.has(target)) {
391
+ lowlinkByPath.set(file, Math.min(lowlinkByPath.get(file), indexByPath.get(target)));
392
+ }
393
+ }
394
+ if (lowlinkByPath.get(file) === indexByPath.get(file)) {
395
+ const component = [];
396
+ let current;
397
+ do {
398
+ current = stack.pop();
399
+ onStack.delete(current);
400
+ component.push(current);
401
+ } while (current !== file);
402
+ if (component.length > 1 || hasSelfLoop(nodesByPath, component[0]))
403
+ cycles.push(component.sort());
404
+ }
405
+ }
406
+ for (const file of nodesByPath.keys()) {
407
+ if (!indexByPath.has(file))
408
+ strongConnect(file);
409
+ }
410
+ return cycles.sort((left, right) => left[0].localeCompare(right[0]));
411
+ }
412
+ function assignLayers(nodesByPath, cycleNodeSet) {
413
+ const memo = new Map();
414
+ function layerFor(file, visiting = new Set()) {
415
+ if (memo.has(file))
416
+ return memo.get(file);
417
+ if (cycleNodeSet.has(file) || visiting.has(file)) {
418
+ memo.set(file, null);
419
+ return null;
420
+ }
421
+ visiting.add(file);
422
+ let maxDependencyLayer = -1;
423
+ for (const target of adjacency(nodesByPath, file)) {
424
+ const dependencyLayer = layerFor(target, visiting);
425
+ if (dependencyLayer !== null)
426
+ maxDependencyLayer = Math.max(maxDependencyLayer, dependencyLayer);
427
+ }
428
+ visiting.delete(file);
429
+ const layer = maxDependencyLayer + 1;
430
+ memo.set(file, layer);
431
+ return layer;
432
+ }
433
+ for (const node of nodesByPath.values()) {
434
+ node.layer = layerFor(node.path);
435
+ }
436
+ }
437
+ function adjacency(nodesByPath, file) {
438
+ return (nodesByPath.get(file)?.imports || []).map((imported) => imported.target).filter((target) => target && nodesByPath.has(target));
439
+ }
440
+ function hasSelfLoop(nodesByPath, file) {
441
+ return adjacency(nodesByPath, file).includes(file);
442
+ }
443
+ function isSharedTypesPath(relativePath) {
444
+ return relativePath === "scripts/lib/types" || relativePath.startsWith("scripts/lib/types/");
445
+ }
446
+ function hasTypeScriptSourceExtension(filePath) {
447
+ return typeof filePath === "string" && /\.(mts|ts)$/.test(filePath);
448
+ }
449
+ function parseCliArgs(argv) {
450
+ const args = {
451
+ check: false,
452
+ json: false,
453
+ out: undefined,
454
+ expectNodes: undefined,
455
+ expectEdges: undefined,
456
+ };
457
+ for (let index = 0; index < argv.length; index += 1) {
458
+ const arg = argv[index];
459
+ if (arg === "--check")
460
+ args.check = true;
461
+ else if (arg === "--json")
462
+ args.json = true;
463
+ else if (arg === "--out")
464
+ args.out = argv[++index];
465
+ else if (arg === "--expect-nodes")
466
+ args.expectNodes = Number(argv[++index]);
467
+ else if (arg === "--expect-edges")
468
+ args.expectEdges = Number(argv[++index]);
469
+ else
470
+ throw new Error(`Unknown argument: ${arg}`);
471
+ }
472
+ return args;
473
+ }
474
+ function writeOutput({ graph, args }) {
475
+ if (args.out) {
476
+ fs.mkdirSync(path.dirname(path.resolve(args.out)), { recursive: true });
477
+ fs.writeFileSync(args.out, `${JSON.stringify(graph, null, 2)}\n`);
478
+ }
479
+ if (args.json) {
480
+ console.log(JSON.stringify(graph, null, 2));
481
+ return;
482
+ }
483
+ console.log([
484
+ `Import graph: ${graph.summary.fileCount} files, ${graph.summary.localEdgeCount} local edges`,
485
+ `unresolved=${graph.summary.unresolvedLocalEdges}`,
486
+ `cycles=${graph.summary.cycleNodes}`,
487
+ `mjsToTs=${graph.summary.runtimeMjsToTsEdges}`,
488
+ `typesValueImports=${graph.summary.typesValueImports}`,
489
+ ].join(", "));
490
+ }
491
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
492
+ try {
493
+ const args = parseCliArgs(process.argv.slice(2));
494
+ const result = checkImportGraph({
495
+ expectNodes: args.expectNodes,
496
+ expectEdges: args.expectEdges,
497
+ });
498
+ writeOutput({ graph: result.graph, args });
499
+ if (args.check || args.expectNodes !== undefined || args.expectEdges !== undefined) {
500
+ if (!result.ok) {
501
+ console.error(result.violations.map((violation) => violation.message).join("\n"));
502
+ process.exit(1);
503
+ }
504
+ console.log("Import graph gate passed");
505
+ }
506
+ }
507
+ catch (error) {
508
+ console.error(error instanceof Error ? error.message : String(error));
509
+ process.exit(1);
510
+ }
511
+ }