coding-agent-harness 1.0.5 → 1.0.7

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 (260) hide show
  1. package/CONTRIBUTING.md +2 -2
  2. package/README.md +63 -3
  3. package/README.zh-CN.md +52 -3
  4. package/SKILL.md +43 -43
  5. package/dist/build-dist.mjs +189 -0
  6. package/dist/check-dist-observation.mjs +428 -0
  7. package/dist/check-harness.mjs +489 -0
  8. package/dist/check-import-graph.mjs +511 -0
  9. package/dist/check-runtime-emit.mjs +304 -0
  10. package/dist/check-type-boundaries.mjs +139 -0
  11. package/dist/commands/dashboard-command.mjs +80 -0
  12. package/dist/commands/migration-command.mjs +152 -0
  13. package/dist/commands/preset-command.mjs +91 -0
  14. package/dist/commands/task-command.mjs +324 -0
  15. package/dist/harness.mjs +304 -0
  16. package/dist/lib/capability-registry.mjs +643 -0
  17. package/dist/lib/check-module-parallel.mjs +227 -0
  18. package/dist/lib/check-profiles.mjs +414 -0
  19. package/dist/lib/check-task-contracts.mjs +54 -0
  20. package/dist/lib/core-shared.mjs +254 -0
  21. package/dist/lib/dashboard-data.mjs +608 -0
  22. package/dist/lib/dashboard-workbench.mjs +334 -0
  23. package/dist/lib/dashboard-writer.mjs +200 -0
  24. package/dist/lib/git-status-summary.mjs +45 -0
  25. package/dist/lib/governance-index-generator.mjs +236 -0
  26. package/dist/lib/governance-sync.mjs +617 -0
  27. package/dist/lib/governance-table-boundary.mjs +161 -0
  28. package/{scripts → dist}/lib/harness-core.mjs +2 -0
  29. package/dist/lib/harness-paths.mjs +338 -0
  30. package/dist/lib/lesson-maintenance.mjs +139 -0
  31. package/dist/lib/markdown-utils.mjs +193 -0
  32. package/dist/lib/migration-planner.mjs +439 -0
  33. package/dist/lib/migration-support.mjs +317 -0
  34. package/dist/lib/phase-kind.mjs +46 -0
  35. package/dist/lib/preset-audit-contracts.mjs +40 -0
  36. package/dist/lib/preset-engine.mjs +516 -0
  37. package/dist/lib/preset-registry.mjs +831 -0
  38. package/dist/lib/preset-resource-contracts.mjs +83 -0
  39. package/dist/lib/review-confirm-git-gate.mjs +244 -0
  40. package/dist/lib/status-builder.mjs +87 -0
  41. package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
  42. package/dist/lib/structure-migration.mjs +404 -0
  43. package/dist/lib/subagent-authorization-audit.mjs +198 -0
  44. package/dist/lib/task-audit-metadata.mjs +376 -0
  45. package/dist/lib/task-audit-migration.mjs +355 -0
  46. package/dist/lib/task-completion-consistency.mjs +29 -0
  47. package/dist/lib/task-index.mjs +133 -0
  48. package/dist/lib/task-lesson-candidates.mjs +239 -0
  49. package/dist/lib/task-lesson-sedimentation.mjs +300 -0
  50. package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
  51. package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
  52. package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
  53. package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
  54. package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
  55. package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
  56. package/dist/lib/task-lifecycle/template-files.mjs +52 -0
  57. package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
  58. package/dist/lib/task-lifecycle.mjs +611 -0
  59. package/dist/lib/task-metadata.mjs +116 -0
  60. package/dist/lib/task-review-model.mjs +474 -0
  61. package/dist/lib/task-scanner.mjs +439 -0
  62. package/dist/lib/task-tombstone-commands.mjs +125 -0
  63. package/dist/postinstall.mjs +14 -0
  64. package/dist/run-built-tests.mjs +84 -0
  65. package/docs-release/README.md +1 -0
  66. package/docs-release/architecture/overview.md +12 -12
  67. package/docs-release/architecture/overview.zh-CN.md +12 -12
  68. package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
  69. package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
  70. package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
  71. package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
  72. package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
  73. package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
  74. package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
  75. package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
  76. package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
  77. package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
  78. package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
  79. package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +1 -4
  80. package/docs-release/guides/agent-installation.en-US.md +14 -8
  81. package/docs-release/guides/agent-installation.md +14 -8
  82. package/docs-release/guides/contributing.md +3 -3
  83. package/docs-release/guides/contributing.zh-CN.md +3 -3
  84. package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
  85. package/docs-release/guides/document-audience-and-surfaces.md +10 -10
  86. package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
  87. package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
  88. package/docs-release/guides/migration-playbook.en-US.md +63 -1
  89. package/docs-release/guides/migration-playbook.md +59 -1
  90. package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
  91. package/docs-release/guides/parent-control-repository-pattern.md +25 -25
  92. package/docs-release/guides/preset-development.md +2 -2
  93. package/docs-release/guides/repository-operating-models.en-US.md +21 -21
  94. package/docs-release/guides/repository-operating-models.md +21 -21
  95. package/docs-release/guides/task-state-machine.en-US.md +5 -5
  96. package/docs-release/guides/task-state-machine.md +5 -5
  97. package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
  98. package/examples/minimal-project/AGENTS.md +2 -2
  99. package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
  100. package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
  101. package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
  102. package/package.json +20 -12
  103. package/presets/legacy-migration/preset.yaml +5 -5
  104. package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
  105. package/presets/lesson-sedimentation/preset.yaml +3 -3
  106. package/presets/module/preset.yaml +2 -2
  107. package/presets/module/templates/execution_strategy.append.md +1 -1
  108. package/presets/module/templates/task_plan.append.md +3 -3
  109. package/presets/standard-task/preset.yaml +2 -2
  110. package/references/adversarial-review-standard.md +2 -2
  111. package/references/agents-md-pattern.md +14 -14
  112. package/references/cadence-ledger.md +1 -1
  113. package/references/ci-cd-standard.md +1 -1
  114. package/references/delivery-operating-model-standard.md +4 -4
  115. package/references/docs-directory-standard.md +65 -159
  116. package/references/external-source-intake-standard.md +10 -10
  117. package/references/harness-ledger.md +5 -5
  118. package/references/legacy-12-phase-bootstrap.md +2 -2
  119. package/references/lessons-governance.md +15 -15
  120. package/references/long-running-task-standard.md +6 -6
  121. package/references/module-parallel-standard.md +34 -34
  122. package/references/planning-loop.md +6 -6
  123. package/references/project-onboarding-audit.md +4 -4
  124. package/references/regression-system.md +2 -2
  125. package/references/repo-governance-standard.md +4 -4
  126. package/references/review-routing-standard.md +1 -1
  127. package/references/ssot-governance.md +19 -19
  128. package/references/taskr-gap-analysis.md +5 -5
  129. package/references/walkthrough-closeout.md +14 -14
  130. package/references/worktree-parallel.md +3 -3
  131. package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
  132. package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
  133. package/templates/AGENTS.md.template +26 -26
  134. package/templates/architecture/README.md +4 -4
  135. package/templates/architecture/service-catalog.md +2 -2
  136. package/templates/architecture/services/service-template.md +1 -1
  137. package/templates/dashboard/assets/app-src/20-overview.js +11 -5
  138. package/templates/dashboard/assets/app-src/40-modules.js +1 -1
  139. package/templates/dashboard/assets/app.js +12 -6
  140. package/templates/dashboard/assets/i18n.js +4 -2
  141. package/templates/development/README.md +10 -10
  142. package/templates/development/cross-repo-debugging.md +3 -3
  143. package/templates/development/external-context/service-template.md +2 -2
  144. package/templates/development/external-source-packs/README.md +4 -4
  145. package/templates/integrations/README.md +4 -4
  146. package/templates/integrations/api-contract.md +2 -2
  147. package/templates/integrations/event-contract.md +2 -2
  148. package/templates/integrations/third-party/vendor-template.md +2 -2
  149. package/templates/integrations/webhook-contract.md +2 -2
  150. package/templates/ledger/Harness-Ledger.md +1 -1
  151. package/templates/planning/INDEX.md +1 -0
  152. package/templates/planning/module_session_prompt.md +1 -1
  153. package/templates/planning/task_plan.md +1 -1
  154. package/templates/planning/walkthrough.md +47 -0
  155. package/templates/reference/docs-library-standard.md +8 -8
  156. package/templates/reference/external-source-intake-standard.md +15 -15
  157. package/templates/reference/repo-governance-standard.md +1 -1
  158. package/templates/ssot/Module-Registry.md +1 -1
  159. package/templates/walkthrough/walkthrough-template.md +2 -2
  160. package/templates-zh-CN/AGENTS.md.template +26 -26
  161. package/templates-zh-CN/CLAUDE.md.template +1 -1
  162. package/templates-zh-CN/architecture/README.md +4 -4
  163. package/templates-zh-CN/architecture/service-catalog.md +2 -2
  164. package/templates-zh-CN/architecture/services/service-template.md +1 -1
  165. package/templates-zh-CN/development/README.md +10 -10
  166. package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
  167. package/templates-zh-CN/development/external-context/service-template.md +2 -2
  168. package/templates-zh-CN/development/external-source-packs/README.md +4 -4
  169. package/templates-zh-CN/integrations/README.md +4 -4
  170. package/templates-zh-CN/integrations/api-contract.md +2 -2
  171. package/templates-zh-CN/integrations/event-contract.md +2 -2
  172. package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
  173. package/templates-zh-CN/integrations/webhook-contract.md +2 -2
  174. package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
  175. package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
  176. package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
  177. package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
  178. package/templates-zh-CN/planning/module_session_prompt.md +11 -11
  179. package/templates-zh-CN/planning/walkthrough.md +47 -0
  180. package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
  181. package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
  182. package/templates-zh-CN/reference/docs-library-standard.md +28 -28
  183. package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
  184. package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
  185. package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
  186. package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
  187. package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
  188. package/templates-zh-CN/reference/review-routing-standard.md +1 -1
  189. package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
  190. package/templates-zh-CN/reference/worktree-standard.md +1 -1
  191. package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
  192. package/templates-zh-CN/ssot/Delivery-SSoT.md +3 -3
  193. package/templates-zh-CN/ssot/Module-Registry.md +3 -3
  194. package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
  195. package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
  196. package/tsconfig.dist.json +16 -0
  197. package/tsconfig.json +25 -0
  198. package/tsconfig.runtime.json +24 -0
  199. package/examples/minimal-project/.harness-capabilities.json +0 -8
  200. package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
  201. package/scripts/check-harness.mjs +0 -508
  202. package/scripts/commands/dashboard-command.mjs +0 -67
  203. package/scripts/commands/migration-command.mjs +0 -126
  204. package/scripts/commands/preset-command.mjs +0 -73
  205. package/scripts/commands/task-command.mjs +0 -328
  206. package/scripts/harness.mjs +0 -291
  207. package/scripts/lib/capability-registry.mjs +0 -587
  208. package/scripts/lib/check-module-parallel.mjs +0 -230
  209. package/scripts/lib/check-profiles.mjs +0 -372
  210. package/scripts/lib/check-task-contracts.mjs +0 -55
  211. package/scripts/lib/core-shared.mjs +0 -249
  212. package/scripts/lib/dashboard-data.mjs +0 -520
  213. package/scripts/lib/dashboard-workbench.mjs +0 -336
  214. package/scripts/lib/dashboard-writer.mjs +0 -202
  215. package/scripts/lib/git-status-summary.mjs +0 -46
  216. package/scripts/lib/governance-index-generator.mjs +0 -174
  217. package/scripts/lib/governance-sync.mjs +0 -611
  218. package/scripts/lib/governance-table-boundary.mjs +0 -175
  219. package/scripts/lib/lesson-maintenance.mjs +0 -152
  220. package/scripts/lib/markdown-utils.mjs +0 -191
  221. package/scripts/lib/migration-planner.mjs +0 -476
  222. package/scripts/lib/migration-support.mjs +0 -312
  223. package/scripts/lib/phase-kind.mjs +0 -50
  224. package/scripts/lib/preset-audit-contracts.mjs +0 -37
  225. package/scripts/lib/preset-engine.mjs +0 -494
  226. package/scripts/lib/preset-registry.mjs +0 -776
  227. package/scripts/lib/preset-resource-contracts.mjs +0 -83
  228. package/scripts/lib/review-confirm-git-gate.mjs +0 -248
  229. package/scripts/lib/status-builder.mjs +0 -88
  230. package/scripts/lib/subagent-authorization-audit.mjs +0 -196
  231. package/scripts/lib/task-audit-metadata.mjs +0 -385
  232. package/scripts/lib/task-audit-migration.mjs +0 -350
  233. package/scripts/lib/task-completion-consistency.mjs +0 -26
  234. package/scripts/lib/task-index.mjs +0 -93
  235. package/scripts/lib/task-lesson-candidates.mjs +0 -242
  236. package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
  237. package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
  238. package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
  239. package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
  240. package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
  241. package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
  242. package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
  243. package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
  244. package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
  245. package/scripts/lib/task-lifecycle.mjs +0 -616
  246. package/scripts/lib/task-metadata.mjs +0 -118
  247. package/scripts/lib/task-review-model.mjs +0 -455
  248. package/scripts/lib/task-scanner.mjs +0 -503
  249. package/scripts/lib/task-tombstone-commands.mjs +0 -140
  250. package/scripts/postinstall.mjs +0 -14
  251. package/templates/walkthrough/Closeout-SSoT.md +0 -43
  252. package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
  253. /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
  254. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
  255. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
  256. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
  257. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
  258. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
  259. /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
  260. /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
+ }