@topogram/cli 0.3.63 → 0.3.65

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 (344) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +703 -0
  3. package/src/adoption/plan.d.ts +6 -0
  4. package/src/adoption/plan.js +12 -703
  5. package/src/adoption/reporting.d.ts +10 -0
  6. package/src/adoption/review-groups.d.ts +6 -0
  7. package/src/agent-brief.d.ts +3 -0
  8. package/src/agent-brief.js +495 -0
  9. package/src/agent-ops/query-builders/auth.js +375 -0
  10. package/src/agent-ops/query-builders/change-risk/change-plan.js +123 -0
  11. package/src/agent-ops/query-builders/change-risk/import-plan.js +49 -0
  12. package/src/agent-ops/query-builders/change-risk/maintained.js +286 -0
  13. package/src/agent-ops/query-builders/change-risk/review-packets.js +123 -0
  14. package/src/agent-ops/query-builders/change-risk/risk.js +189 -0
  15. package/src/agent-ops/query-builders/change-risk.js +25 -0
  16. package/src/agent-ops/query-builders/common.js +149 -0
  17. package/src/agent-ops/query-builders/maintained-risk.js +539 -0
  18. package/src/agent-ops/query-builders/maintained-shared.js +120 -0
  19. package/src/agent-ops/query-builders/multi-agent.js +547 -0
  20. package/src/agent-ops/query-builders/projection-impacts.js +514 -0
  21. package/src/agent-ops/query-builders/work-packets.js +417 -0
  22. package/src/agent-ops/query-builders/workflow-context-shared.js +300 -0
  23. package/src/agent-ops/query-builders/workflow-context.js +398 -0
  24. package/src/agent-ops/query-builders/workflow-presets-core.js +676 -0
  25. package/src/agent-ops/query-builders/workflow-presets.js +341 -0
  26. package/src/agent-ops/query-builders.d.ts +26 -0
  27. package/src/agent-ops/query-builders.js +42 -5021
  28. package/src/archive/archive.d.ts +2 -0
  29. package/src/archive/compact.d.ts +1 -0
  30. package/src/archive/unarchive.d.ts +1 -0
  31. package/src/catalog/constants.js +10 -0
  32. package/src/catalog/copy.js +60 -0
  33. package/src/catalog/diagnostics.js +15 -0
  34. package/src/catalog/entries.js +42 -0
  35. package/src/catalog/files.js +67 -0
  36. package/src/catalog/provenance.js +122 -0
  37. package/src/catalog/source.js +150 -0
  38. package/src/catalog/validation.js +252 -0
  39. package/src/catalog.d.ts +12 -0
  40. package/src/catalog.js +18 -750
  41. package/src/cli/catalog-alias.d.ts +1 -0
  42. package/src/cli/command-parser.js +38 -0
  43. package/src/cli/command-parsers/core.js +102 -0
  44. package/src/cli/command-parsers/generator.js +39 -0
  45. package/src/cli/command-parsers/import.js +44 -0
  46. package/src/cli/command-parsers/legacy-workflow.js +21 -0
  47. package/src/cli/command-parsers/project.js +47 -0
  48. package/src/cli/command-parsers/sdlc.js +47 -0
  49. package/src/cli/command-parsers/shared.js +51 -0
  50. package/src/cli/command-parsers/template.js +48 -0
  51. package/src/cli/commands/agent.js +47 -0
  52. package/src/cli/commands/catalog/check.js +31 -0
  53. package/src/cli/commands/catalog/copy.js +59 -0
  54. package/src/cli/commands/catalog/doctor.js +248 -0
  55. package/src/cli/commands/catalog/help.js +21 -0
  56. package/src/cli/commands/catalog/list.js +52 -0
  57. package/src/cli/commands/catalog/runner.js +92 -0
  58. package/src/cli/commands/catalog/shared.js +17 -0
  59. package/src/cli/commands/catalog/show.js +134 -0
  60. package/src/cli/commands/catalog.js +32 -0
  61. package/src/cli/commands/check.js +268 -0
  62. package/src/cli/commands/doctor.js +268 -0
  63. package/src/cli/commands/emit.js +149 -0
  64. package/src/cli/commands/generate.js +96 -0
  65. package/src/cli/commands/generator-policy/package-info.js +162 -0
  66. package/src/cli/commands/generator-policy/payloads.js +372 -0
  67. package/src/cli/commands/generator-policy/printers.js +159 -0
  68. package/src/cli/commands/generator-policy/runner.js +81 -0
  69. package/src/cli/commands/generator-policy/shared.js +39 -0
  70. package/src/cli/commands/generator-policy.js +17 -0
  71. package/src/cli/commands/generator.js +443 -0
  72. package/src/cli/commands/import/adopt.js +170 -0
  73. package/src/cli/commands/import/check.js +91 -0
  74. package/src/cli/commands/import/diff.js +84 -0
  75. package/src/cli/commands/import/help.js +47 -0
  76. package/src/cli/commands/import/paths.js +277 -0
  77. package/src/cli/commands/import/plan.js +284 -0
  78. package/src/cli/commands/import/refresh.js +470 -0
  79. package/src/cli/commands/import/status-history.js +196 -0
  80. package/src/cli/commands/import/workspace.js +230 -0
  81. package/src/cli/commands/import-runner.js +157 -0
  82. package/src/cli/commands/import.js +35 -0
  83. package/src/cli/commands/inspect.js +55 -0
  84. package/src/cli/commands/new.js +94 -0
  85. package/src/cli/commands/package/constants.js +17 -0
  86. package/src/cli/commands/package/doctor.js +240 -0
  87. package/src/cli/commands/package/help.js +27 -0
  88. package/src/cli/commands/package/lockfile.js +135 -0
  89. package/src/cli/commands/package/npm.js +97 -0
  90. package/src/cli/commands/package/reporting.js +35 -0
  91. package/src/cli/commands/package/runner.js +33 -0
  92. package/src/cli/commands/package/shared.js +9 -0
  93. package/src/cli/commands/package/update-cli.js +252 -0
  94. package/src/cli/commands/package/versions.js +35 -0
  95. package/src/cli/commands/package.js +31 -0
  96. package/src/cli/commands/query/change-plan.js +68 -0
  97. package/src/cli/commands/query/definitions.js +202 -0
  98. package/src/cli/commands/query/import-adopt.js +121 -0
  99. package/src/cli/commands/query/runner/artifacts.js +102 -0
  100. package/src/cli/commands/query/runner/boundaries.js +211 -0
  101. package/src/cli/commands/query/runner/change.js +182 -0
  102. package/src/cli/commands/query/runner/import-adopt.js +111 -0
  103. package/src/cli/commands/query/runner/index.js +31 -0
  104. package/src/cli/commands/query/runner/output.js +12 -0
  105. package/src/cli/commands/query/runner/workflow.js +241 -0
  106. package/src/cli/commands/query/runner.js +3 -0
  107. package/src/cli/commands/query/workflow-context.js +5 -0
  108. package/src/cli/commands/query/workspace.js +274 -0
  109. package/src/cli/commands/query.js +11 -0
  110. package/src/cli/commands/release-rollout.js +257 -0
  111. package/src/cli/commands/release-shared.js +528 -0
  112. package/src/cli/commands/release-status.js +429 -0
  113. package/src/cli/commands/release.js +107 -0
  114. package/src/cli/commands/sdlc.js +168 -0
  115. package/src/cli/commands/setup.js +76 -0
  116. package/src/cli/commands/source.js +291 -0
  117. package/src/cli/commands/template/baseline.js +100 -0
  118. package/src/cli/commands/template/check.js +466 -0
  119. package/src/cli/commands/template/constants.js +8 -0
  120. package/src/cli/commands/template/diagnostics.js +26 -0
  121. package/src/cli/commands/template/help.js +28 -0
  122. package/src/cli/commands/template/lifecycle.js +404 -0
  123. package/src/cli/commands/template/list-show.js +287 -0
  124. package/src/cli/commands/template/policy.js +422 -0
  125. package/src/cli/commands/template/shared.js +127 -0
  126. package/src/cli/commands/template/updates.js +352 -0
  127. package/src/cli/commands/template-runner.js +198 -0
  128. package/src/cli/commands/template.js +43 -0
  129. package/src/cli/commands/trust.js +219 -0
  130. package/src/cli/commands/version.js +40 -0
  131. package/src/cli/commands/widget.js +168 -0
  132. package/src/cli/commands/workflow.js +63 -0
  133. package/src/cli/dispatcher.js +392 -0
  134. package/src/cli/help-dispatch.js +188 -0
  135. package/src/cli/help.js +296 -0
  136. package/src/cli/migration-guidance.js +59 -0
  137. package/src/cli/options.js +96 -0
  138. package/src/cli/output-safety.js +107 -0
  139. package/src/cli/path-normalization.js +29 -0
  140. package/src/cli.js +47 -11711
  141. package/src/example-implementation.d.ts +2 -0
  142. package/src/format.d.ts +1 -0
  143. package/src/generator/api/contracts.js +497 -0
  144. package/src/generator/api/metadata.js +221 -0
  145. package/src/generator/api/openapi.js +559 -0
  146. package/src/generator/api/schema.js +124 -0
  147. package/src/generator/api/types.d.ts +98 -0
  148. package/src/generator/api.js +3 -1195
  149. package/src/generator/check.d.ts +1 -0
  150. package/src/generator/context/bundle.d.ts +1 -0
  151. package/src/generator/context/shared/domain-sdlc.js +282 -0
  152. package/src/generator/context/shared/maintained-boundary.js +665 -0
  153. package/src/generator/context/shared/metrics.js +85 -0
  154. package/src/generator/context/shared/primitives.js +64 -0
  155. package/src/generator/context/shared/relationships.js +453 -0
  156. package/src/generator/context/shared/summaries.js +263 -0
  157. package/src/generator/context/shared/types.d.ts +207 -0
  158. package/src/generator/context/shared.d.ts +44 -0
  159. package/src/generator/context/shared.js +80 -1390
  160. package/src/generator/context/slice/core.js +397 -0
  161. package/src/generator/context/slice/sdlc.js +417 -0
  162. package/src/generator/context/slice/ui-packets.js +183 -0
  163. package/src/generator/context/slice.js +2 -859
  164. package/src/generator/native/parity-bundle.js +2 -1
  165. package/src/generator/registry/index.js +507 -0
  166. package/src/generator/registry.js +18 -504
  167. package/src/generator/runtime/environment/index.js +666 -0
  168. package/src/generator/runtime/environment.js +4 -666
  169. package/src/generator/runtime/runtime-check/index.js +554 -0
  170. package/src/generator/runtime/runtime-check.js +4 -554
  171. package/src/generator/runtime/shared/index.js +572 -0
  172. package/src/generator/runtime/shared.js +19 -570
  173. package/src/generator/shared.d.ts +2 -0
  174. package/src/generator/surfaces/shared.d.ts +3 -0
  175. package/src/generator/surfaces/web/html-escape.js +22 -0
  176. package/src/generator/surfaces/web/react.js +10 -8
  177. package/src/generator/surfaces/web/sveltekit.js +7 -5
  178. package/src/generator/surfaces/web/vanilla.js +8 -4
  179. package/src/generator/widget-conformance/behavior-report.js +258 -0
  180. package/src/generator/widget-conformance/checks.js +371 -0
  181. package/src/generator/widget-conformance/projection-context.js +200 -0
  182. package/src/generator/widget-conformance/report.js +166 -0
  183. package/src/generator/widget-conformance/types.d.ts +121 -0
  184. package/src/generator/widget-conformance.js +3 -824
  185. package/src/generator.d.ts +2 -0
  186. package/src/github-client.js +520 -0
  187. package/src/import/core/context.d.ts +3 -0
  188. package/src/import/core/contracts.d.ts +1 -0
  189. package/src/import/core/registry.d.ts +4 -0
  190. package/src/import/core/runner/candidates.js +217 -0
  191. package/src/import/core/runner/options.js +22 -0
  192. package/src/import/core/runner/reports.js +50 -0
  193. package/src/import/core/runner/run.js +79 -0
  194. package/src/import/core/runner/tracks.js +150 -0
  195. package/src/import/core/runner/ui-drafts.js +337 -0
  196. package/src/import/core/runner.js +3 -698
  197. package/src/import/core/shared/api-routes.js +221 -0
  198. package/src/import/core/shared/candidates.js +97 -0
  199. package/src/import/core/shared/files.js +177 -0
  200. package/src/import/core/shared/next-app.js +389 -0
  201. package/src/import/core/shared/types.d.ts +51 -0
  202. package/src/import/core/shared/ui-routes.js +230 -0
  203. package/src/import/core/shared.js +67 -910
  204. package/src/import/extractors/api/flutter-dio.js +4 -8
  205. package/src/import/extractors/api/react-native-repository.js +4 -8
  206. package/src/import/index.d.ts +4 -0
  207. package/src/import/provenance.d.ts +4 -0
  208. package/src/new-project/constants.js +128 -0
  209. package/src/new-project/create.js +83 -0
  210. package/src/new-project/json.js +28 -0
  211. package/src/new-project/metadata.js +96 -0
  212. package/src/new-project/package-spec.js +161 -0
  213. package/src/new-project/project-files.js +348 -0
  214. package/src/new-project/template-policy.js +269 -0
  215. package/src/new-project/template-resolution.js +368 -0
  216. package/src/new-project/template-snapshots.js +430 -0
  217. package/src/new-project/template-updates.js +512 -0
  218. package/src/new-project/types.d.ts +83 -0
  219. package/src/new-project.js +6 -2188
  220. package/src/npm-safety.js +79 -0
  221. package/src/parser.d.ts +87 -0
  222. package/src/parser.js +118 -0
  223. package/src/path-helpers.d.ts +1 -0
  224. package/src/path-helpers.js +20 -0
  225. package/src/policy/review-boundaries.d.ts +15 -0
  226. package/src/project-config/index.js +564 -0
  227. package/src/project-config.js +19 -560
  228. package/src/reconcile/docs.d.ts +8 -0
  229. package/src/reconcile/journeys.d.ts +1 -0
  230. package/src/resolver/enrich/acceptance-criterion.js +2 -0
  231. package/src/resolver/enrich/bug.js +2 -0
  232. package/src/resolver/enrich/pitch.js +2 -0
  233. package/src/resolver/enrich/requirement.js +2 -0
  234. package/src/resolver/enrich/task.js +2 -0
  235. package/src/resolver/index.js +19 -2089
  236. package/src/resolver/normalize.js +384 -1
  237. package/src/resolver/plans.js +168 -0
  238. package/src/resolver/projections-api.js +494 -0
  239. package/src/resolver/projections-db.js +133 -0
  240. package/src/resolver/projections-ui.js +317 -0
  241. package/src/resolver/shapes.js +251 -0
  242. package/src/resolver/shared.js +278 -0
  243. package/src/resolver/widgets.js +132 -0
  244. package/src/resolver.d.ts +1 -0
  245. package/src/runtime-support.js +29 -0
  246. package/src/sdlc/adopt.d.ts +1 -0
  247. package/src/sdlc/check.d.ts +1 -0
  248. package/src/sdlc/explain.d.ts +1 -0
  249. package/src/sdlc/release.d.ts +1 -0
  250. package/src/sdlc/scaffold.d.ts +1 -0
  251. package/src/sdlc/transition.d.ts +1 -0
  252. package/src/template-trust/constants.js +62 -0
  253. package/src/template-trust/content.js +258 -0
  254. package/src/template-trust/diff.js +92 -0
  255. package/src/template-trust/policy.js +61 -0
  256. package/src/template-trust/record.js +90 -0
  257. package/src/template-trust/status.js +182 -0
  258. package/src/template-trust.js +24 -687
  259. package/src/text-helpers.d.ts +7 -0
  260. package/src/text-helpers.js +245 -0
  261. package/src/topogram-config.js +306 -0
  262. package/src/topogram-types.d.ts +69 -0
  263. package/src/validator/common.js +488 -0
  264. package/src/validator/data-model.js +237 -0
  265. package/src/validator/docs.js +167 -0
  266. package/src/validator/expressions.js +146 -1
  267. package/src/validator/index.d.ts +23 -0
  268. package/src/validator/index.js +32 -3585
  269. package/src/validator/kinds.d.ts +41 -0
  270. package/src/validator/kinds.js +2 -0
  271. package/src/validator/model-helpers.js +46 -0
  272. package/src/validator/per-kind/acceptance-criterion.js +5 -0
  273. package/src/validator/per-kind/bug.js +6 -0
  274. package/src/validator/per-kind/domain.js +15 -2
  275. package/src/validator/per-kind/pitch.js +7 -0
  276. package/src/validator/per-kind/requirement.js +5 -0
  277. package/src/validator/per-kind/task.js +7 -0
  278. package/src/validator/per-kind/widget.js +14 -0
  279. package/src/validator/projections/api-http-async.js +410 -0
  280. package/src/validator/projections/api-http-authz.js +88 -0
  281. package/src/validator/projections/api-http-core.js +205 -0
  282. package/src/validator/projections/api-http-policies.js +339 -0
  283. package/src/validator/projections/api-http-responses.js +233 -0
  284. package/src/validator/projections/api-http.js +44 -0
  285. package/src/validator/projections/db.js +353 -0
  286. package/src/validator/projections/generator-defaults.js +45 -0
  287. package/src/validator/projections/helpers.js +87 -0
  288. package/src/validator/projections/ui-helpers.js +214 -0
  289. package/src/validator/projections/ui-navigation.js +344 -0
  290. package/src/validator/projections/ui-structure.js +364 -0
  291. package/src/validator/projections/ui-widgets.js +493 -0
  292. package/src/validator/projections/ui.js +46 -0
  293. package/src/validator/registry.js +48 -1
  294. package/src/validator/utils.d.ts +20 -0
  295. package/src/validator/utils.js +115 -12
  296. package/src/validator.d.ts +2 -0
  297. package/src/widget-behavior.d.ts +1 -0
  298. package/src/workflows/adoption/index.js +26 -0
  299. package/src/workflows/docs-generate.js +262 -0
  300. package/src/workflows/docs-scan.js +703 -0
  301. package/src/workflows/docs.js +15 -0
  302. package/src/workflows/import-app/api/collect.js +221 -0
  303. package/src/workflows/import-app/api/openapi.js +257 -0
  304. package/src/workflows/import-app/api/routes.js +327 -0
  305. package/src/workflows/import-app/api/sources.js +22 -0
  306. package/src/workflows/import-app/api.js +4 -0
  307. package/src/workflows/import-app/db.js +538 -0
  308. package/src/workflows/import-app/index.js +30 -0
  309. package/src/workflows/import-app/shared.js +218 -0
  310. package/src/workflows/import-app/ui.js +443 -0
  311. package/src/workflows/import-app/workflow.js +159 -0
  312. package/src/workflows/reconcile/adoption-plan/build.js +208 -0
  313. package/src/workflows/reconcile/adoption-plan/dependencies.js +75 -0
  314. package/src/workflows/reconcile/adoption-plan/outputs.js +143 -0
  315. package/src/workflows/reconcile/adoption-plan/paths.js +58 -0
  316. package/src/workflows/reconcile/adoption-plan/projection-patches.js +177 -0
  317. package/src/workflows/reconcile/adoption-plan/reasons.js +107 -0
  318. package/src/workflows/reconcile/adoption-plan.js +32 -0
  319. package/src/workflows/reconcile/auth/closures.js +115 -0
  320. package/src/workflows/reconcile/auth/formatters.js +142 -0
  321. package/src/workflows/reconcile/auth/inference.js +330 -0
  322. package/src/workflows/reconcile/auth/roles.js +122 -0
  323. package/src/workflows/reconcile/auth.js +37 -0
  324. package/src/workflows/reconcile/bundle-core/index.js +600 -0
  325. package/src/workflows/reconcile/bundle-core.js +14 -0
  326. package/src/workflows/reconcile/bundle-shared.js +75 -0
  327. package/src/workflows/reconcile/candidate-model.js +477 -0
  328. package/src/workflows/reconcile/canonical-surface.js +264 -0
  329. package/src/workflows/reconcile/gap-report.js +333 -0
  330. package/src/workflows/reconcile/ids.js +6 -0
  331. package/src/workflows/reconcile/impacts/adoption-plan.js +192 -0
  332. package/src/workflows/reconcile/impacts/indexes.js +101 -0
  333. package/src/workflows/reconcile/impacts/patches.js +252 -0
  334. package/src/workflows/reconcile/impacts/reports.js +80 -0
  335. package/src/workflows/reconcile/impacts.js +16 -0
  336. package/src/workflows/reconcile/index.js +7 -0
  337. package/src/workflows/reconcile/renderers.js +461 -0
  338. package/src/workflows/reconcile/summary.js +90 -0
  339. package/src/workflows/reconcile/workflow.js +309 -0
  340. package/src/workflows/shared.js +189 -0
  341. package/src/workflows/types.d.ts +93 -0
  342. package/src/workflows.d.ts +1 -0
  343. package/src/workflows.js +10 -7652
  344. package/src/workspace-docs.d.ts +29 -0
@@ -3,6 +3,7 @@ import {
3
3
  dedupeCandidateRecords,
4
4
  findImportFiles,
5
5
  makeCandidateRecord,
6
+ pluralizeCandidateTerm,
6
7
  relativeTo,
7
8
  titleCase
8
9
  } from "../../core/shared.js";
@@ -11,16 +12,11 @@ function featureStemFromPath(filePath) {
11
12
  return canonicalCandidateTerm(filePath.match(/\/features\/([^/]+)\//)?.[1] || "item");
12
13
  }
13
14
 
14
- function pluralizeStem(stem) {
15
- if (stem.endsWith("s")) return stem;
16
- return `${stem}s`;
17
- }
18
-
19
15
  function capabilityIdFor(featureStem, methodName, httpMethod) {
20
16
  const stem = canonicalCandidateTerm(featureStem);
21
17
  const normalizedMethod = String(methodName || "").toLowerCase();
22
18
  if (httpMethod === "GET") {
23
- return `cap_list_${pluralizeStem(stem)}`;
19
+ return `cap_list_${pluralizeCandidateTerm(stem)}`;
24
20
  }
25
21
  if (httpMethod === "POST") {
26
22
  return `cap_create_${stem}`;
@@ -59,12 +55,12 @@ function parseDatasourceFile(text, provenance, filePath, apiConfigPaths) {
59
55
  const httpMethod = dioCall[1].toUpperCase();
60
56
  const callArgs = dioCall[2];
61
57
  const apiRef = callArgs.match(/ApiConfig\.([A-Za-z_][A-Za-z0-9_]*)/);
62
- const basePath = apiConfigPaths.get(apiRef?.[1] || "") || `/${pluralizeStem(featureStem)}`;
58
+ const basePath = apiConfigPaths.get(apiRef?.[1] || "") || `/${pluralizeCandidateTerm(featureStem)}`;
63
59
  const dynamicPath = callArgs.match(/"\$\{ApiConfig\.[A-Za-z_][A-Za-z0-9_]*\}\/\$\{[^}]+\}"/);
64
60
  const path = dynamicPath ? `${basePath}/{id}` : basePath;
65
61
  const queryParams = [...body.matchAll(/'([^']+)'\s*:\s*[^,}]+/g)]
66
62
  .map((entry) => ({ name: entry[1], required: false, type: null }));
67
- const outputFields = /^GET$/.test(httpMethod) ? [pluralizeStem(featureStem)] : [];
63
+ const outputFields = /^GET$/.test(httpMethod) ? [pluralizeCandidateTerm(featureStem)] : [];
68
64
  const inputFields = [...new Set([...callArgs.matchAll(/data:\s*([A-Za-z_][A-Za-z0-9_]*)/g)].map((entry) => entry[1]))];
69
65
  capabilities.push(makeCandidateRecord({
70
66
  kind: "capability",
@@ -3,15 +3,11 @@ import {
3
3
  dedupeCandidateRecords,
4
4
  findImportFiles,
5
5
  makeCandidateRecord,
6
+ pluralizeCandidateTerm,
6
7
  relativeTo,
7
8
  titleCase
8
9
  } from "../../core/shared.js";
9
10
 
10
- function pluralizeStem(stem) {
11
- if (stem.endsWith("s")) return stem;
12
- return `${stem}s`;
13
- }
14
-
15
11
  function stemFromRepoPath(filePath) {
16
12
  return canonicalCandidateTerm(filePath.match(/\/src\/([^/]+)\//)?.[1] || "item");
17
13
  }
@@ -22,7 +18,7 @@ function capabilityIdFor(stem, methodName, httpMethod) {
22
18
  return `cap_get_${stem}`;
23
19
  }
24
20
  if (httpMethod === "GET") {
25
- return `cap_list_${pluralizeStem(stem)}`;
21
+ return `cap_list_${pluralizeCandidateTerm(stem)}`;
26
22
  }
27
23
  if (httpMethod === "POST") {
28
24
  return `cap_create_${stem}`;
@@ -39,7 +35,7 @@ function capabilityIdFor(stem, methodName, httpMethod) {
39
35
  function parseRepositoryFile(text, provenance, filePath) {
40
36
  const stem = stemFromRepoPath(filePath);
41
37
  const baseUrlMatch = String(text || "").match(/baseUrl\s*=\s*["'`]([^"'`]+)["'`]/);
42
- const basePath = baseUrlMatch?.[1] || `/${pluralizeStem(stem)}`;
38
+ const basePath = baseUrlMatch?.[1] || `/${pluralizeCandidateTerm(stem)}`;
43
39
  const capabilities = [];
44
40
  const methods = [...String(text || "").matchAll(/public\s+async\s+([A-Za-z_][A-Za-z0-9_]*)\(([\s\S]*?)\)\s*(?::\s*[\s\S]*?)?\s*\{/g)];
45
41
  for (let index = 0; index < methods.length; index += 1) {
@@ -59,7 +55,7 @@ function parseRepositoryFile(text, provenance, filePath) {
59
55
  ...[...String(body || "").matchAll(/payload\.([A-Za-z_][A-Za-z0-9_]*)/g)].map((entry) => entry[1])
60
56
  ])].filter((field) => field !== "id");
61
57
  const outputFields = httpMethod === "GET"
62
- ? (/count/.test(body) ? [pluralizeStem(stem), "count"] : (/map\(/.test(body) ? [pluralizeStem(stem)] : ["id", "title", "body"]))
58
+ ? (/count/.test(body) ? [pluralizeCandidateTerm(stem), "count"] : (/map\(/.test(body) ? [pluralizeCandidateTerm(stem)] : ["id", "title", "body"]))
63
59
  : [];
64
60
  capabilities.push(makeCandidateRecord({
65
61
  kind: "capability",
@@ -0,0 +1,4 @@
1
+ export function createImportContext(...args: any[]): any;
2
+ export function normalizeWorkspacePaths(...args: any[]): any;
3
+ export function parseImportTracks(...args: any[]): any;
4
+ export function runImportAppWorkflow(...args: any[]): any;
@@ -0,0 +1,4 @@
1
+ export const TOPOGRAM_IMPORT_FILE: string;
2
+ export function buildTopogramImportStatus(projectRoot: string): any;
3
+ export function collectImportSourceFileRecords(sourceRoot: string, options?: any): any[];
4
+ export function writeTopogramImportRecord(projectRoot: string, input: any): any;
@@ -0,0 +1,128 @@
1
+ // @ts-check
2
+
3
+ export const CLI_PACKAGE_NAME = "@topogram/cli";
4
+ export const DEFAULT_TEMPLATE_NAME = "hello-web";
5
+ export const TEMPLATE_MANIFEST = "topogram-template.json";
6
+ export const TEMPLATE_FILES_MANIFEST = ".topogram-template-files.json";
7
+ export const TEMPLATE_POLICY_FILE = "topogram.template-policy.json";
8
+ export const MAX_TEXT_DIFF_BYTES = 256 * 1024;
9
+
10
+ export const GENERATOR_LABELS = new Map([
11
+ ["topogram/express", "Express"],
12
+ ["topogram/hono", "Hono"],
13
+ ["topogram/postgres", "Postgres"],
14
+ ["topogram/react", "React"],
15
+ ["topogram/sqlite", "SQLite"],
16
+ ["topogram/sveltekit", "SvelteKit"],
17
+ ["topogram/vanilla-web", "Vanilla HTML/CSS/JS"]
18
+ ]);
19
+
20
+ export const SURFACE_ORDER = new Map([
21
+ ["web_surface", 10],
22
+ ["api_service", 20],
23
+ ["database", 30],
24
+ ["ios_surface", 40],
25
+ ["android_surface", 50]
26
+ ]);
27
+
28
+ /**
29
+ * @param {string} templateId
30
+ * @param {string} relativePath
31
+ * @returns {string}
32
+ */
33
+ export function unsupportedTemplateSymlinkMessage(templateId, relativePath) {
34
+ return `Template '${templateId}' contains unsupported symlink '${relativePath}'. Template packs must copy real files because Topogram records hashes for copied topogram/ and implementation/ content; symlinks can point outside the trusted template root. Replace the symlink with a real file or directory before running topogram new or topogram template check.`;
35
+ }
36
+
37
+ /**
38
+ * @typedef {Object} CreateNewProjectOptions
39
+ * @property {string} targetPath
40
+ * @property {string} [templateName]
41
+ * @property {string} engineRoot
42
+ * @property {string} templatesRoot
43
+ * @property {CatalogTemplateProvenance|null} [templateProvenance]
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} TemplateUpdatePlanOptions
48
+ * @property {string} projectRoot
49
+ * @property {Record<string, any>} projectConfig
50
+ * @property {string|null} [templateName]
51
+ * @property {string} templatesRoot
52
+ */
53
+
54
+ /**
55
+ * @typedef {TemplateUpdatePlanOptions & { filePath: string, action: "accept-current"|"accept-candidate"|"delete-current" }} TemplateUpdateFileActionOptions
56
+ */
57
+
58
+ /**
59
+ * @typedef {Object} TemplateOwnedFileRecord
60
+ * @property {string} path
61
+ * @property {string} sha256
62
+ * @property {number} size
63
+ */
64
+
65
+ /**
66
+ * @typedef {Object} TemplateManifest
67
+ * @property {string} id
68
+ * @property {string} version
69
+ * @property {string} kind
70
+ * @property {string} topogramVersion
71
+ * @property {boolean} [includesExecutableImplementation]
72
+ * @property {string} [description]
73
+ * @property {Record<string, string>} [starterScripts]
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} TemplateTopologySummary
78
+ * @property {string[]} surfaces
79
+ * @property {string[]} generators
80
+ * @property {string} stack
81
+ */
82
+
83
+ /**
84
+ * @typedef {Object} TemplatePolicy
85
+ * @property {string} version
86
+ * @property {Array<"local"|"package">} allowedSources
87
+ * @property {string[]} allowedTemplateIds
88
+ * @property {string[]} [allowedPackageScopes]
89
+ * @property {"allow"|"warn"|"deny"} executableImplementation
90
+ * @property {Record<string, string>} [pinnedVersions]
91
+ */
92
+
93
+ /**
94
+ * @typedef {Object} TemplatePolicyInfo
95
+ * @property {string} path
96
+ * @property {TemplatePolicy|null} policy
97
+ * @property {boolean} exists
98
+ * @property {TemplateUpdateDiagnostic[]} diagnostics
99
+ */
100
+
101
+ /**
102
+ * @typedef {Object} TemplateUpdateDiagnostic
103
+ * @property {string} code
104
+ * @property {"error"|"warning"} severity
105
+ * @property {string} message
106
+ * @property {string|null} path
107
+ * @property {string|null} suggestedFix
108
+ * @property {string|null} step
109
+ */
110
+
111
+ /**
112
+ * @typedef {Object} ResolvedTemplate
113
+ * @property {string} requested
114
+ * @property {string} root
115
+ * @property {TemplateManifest} manifest
116
+ * @property {"local"|"package"} source
117
+ * @property {string|null} packageSpec
118
+ */
119
+
120
+ /**
121
+ * @typedef {Object} CatalogTemplateProvenance
122
+ * @property {string} id
123
+ * @property {string} source
124
+ * @property {string} package
125
+ * @property {string} version
126
+ * @property {string} packageSpec
127
+ * @property {boolean} [includesExecutableImplementation]
128
+ */
@@ -0,0 +1,83 @@
1
+ // @ts-check
2
+
3
+ import path from "node:path";
4
+
5
+ import { defaultGeneratorPolicy, writeGeneratorPolicy } from "../generator-policy.js";
6
+ import { writeTemplateTrustRecord } from "../template-trust.js";
7
+ import { DEFAULT_TEMPLATE_NAME } from "./constants.js";
8
+ import { writeProjectTemplateMetadata } from "./metadata.js";
9
+ import { assertProjectOutsideEngine, copyTopogramWorkspace, ensureCreatableProjectRoot, writeAgentsGuide, writeExplainScript, writeProjectPackage, writeProjectReadme } from "./project-files.js";
10
+ import { resolveTemplate } from "./template-resolution.js";
11
+ import { defaultTemplatePolicyForTemplate, writeTemplatePolicy } from "./template-policy.js";
12
+ import { writeTemplateFilesManifest } from "./template-snapshots.js";
13
+
14
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
15
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
16
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
17
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
18
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
19
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
20
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
21
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
22
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
23
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
24
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
25
+
26
+ /**
27
+ * @param {CreateNewProjectOptions} options
28
+ * @returns {{ projectRoot: string, templateName: string, template: Record<string, any>, topogramPath: string, appPath: string, warnings: string[] }}
29
+ */
30
+ export function createNewProject({
31
+ targetPath,
32
+ templateName = DEFAULT_TEMPLATE_NAME,
33
+ engineRoot,
34
+ templatesRoot,
35
+ templateProvenance = null
36
+ }) {
37
+ if (!targetPath) {
38
+ throw new Error("topogram new requires <path>.");
39
+ }
40
+ const projectRoot = path.resolve(targetPath);
41
+ assertProjectOutsideEngine(projectRoot, engineRoot);
42
+ const template = resolveTemplate(templateName, templatesRoot);
43
+ if (
44
+ templateProvenance &&
45
+ typeof templateProvenance.includesExecutableImplementation === "boolean" &&
46
+ templateProvenance.includesExecutableImplementation !== Boolean(template.manifest.includesExecutableImplementation)
47
+ ) {
48
+ throw new Error(
49
+ `Catalog entry '${templateProvenance.id}' declares includesExecutableImplementation: ${templateProvenance.includesExecutableImplementation}, ` +
50
+ `but template package '${template.packageSpec || template.requested}' declares includesExecutableImplementation: ${Boolean(template.manifest.includesExecutableImplementation)}.`
51
+ );
52
+ }
53
+
54
+ ensureCreatableProjectRoot(projectRoot);
55
+ copyTopogramWorkspace(template.root, projectRoot);
56
+ const projectConfig = writeProjectTemplateMetadata(projectRoot, template, templateProvenance);
57
+ writeProjectPackage(projectRoot, engineRoot, template);
58
+ writeExplainScript(projectRoot);
59
+ writeProjectReadme(projectRoot, projectConfig);
60
+ writeAgentsGuide(projectRoot, projectConfig);
61
+ writeTemplateFilesManifest(projectRoot, projectConfig);
62
+ writeTemplatePolicy(projectRoot, defaultTemplatePolicyForTemplate(template));
63
+ writeGeneratorPolicy(projectRoot, defaultGeneratorPolicy());
64
+
65
+ const warnings = [];
66
+ if (template.manifest.includesExecutableImplementation) {
67
+ writeTemplateTrustRecord(projectRoot, projectConfig);
68
+ warnings.push(
69
+ `Template '${template.manifest.id}' copied implementation/ code into this project. ` +
70
+ "topogram new did not execute it, but topogram generate may load it later. " +
71
+ "Recorded local trust in .topogram-template-trust.json."
72
+ );
73
+ }
74
+
75
+ return {
76
+ projectRoot,
77
+ templateName: template.manifest.id,
78
+ template: projectConfig.template,
79
+ topogramPath: path.join(projectRoot, "topogram"),
80
+ appPath: path.join(projectRoot, "app"),
81
+ warnings
82
+ };
83
+ }
@@ -0,0 +1,28 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {any} value
5
+ * @returns {any}
6
+ */
7
+ function sortJsonValue(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map(sortJsonValue);
10
+ }
11
+ if (value && typeof value === "object") {
12
+ /** @type {Record<string, any>} */
13
+ const sorted = {};
14
+ for (const key of Object.keys(value).sort((left, right) => left.localeCompare(right))) {
15
+ sorted[key] = sortJsonValue(value[key]);
16
+ }
17
+ return sorted;
18
+ }
19
+ return value;
20
+ }
21
+
22
+ /**
23
+ * @param {any} value
24
+ * @returns {string}
25
+ */
26
+ export function stableJsonStringify(value) {
27
+ return JSON.stringify(sortJsonValue(value), null, 2);
28
+ }
@@ -0,0 +1,96 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { stableJsonStringify } from "./json.js";
7
+
8
+ /** @typedef {import("./types.js").CreateNewProjectOptions} CreateNewProjectOptions */
9
+ /** @typedef {import("./types.js").TemplateUpdatePlanOptions} TemplateUpdatePlanOptions */
10
+ /** @typedef {import("./types.js").TemplateUpdateFileActionOptions} TemplateUpdateFileActionOptions */
11
+ /** @typedef {import("./types.js").TemplateOwnedFileRecord} TemplateOwnedFileRecord */
12
+ /** @typedef {import("./types.js").TemplateManifest} TemplateManifest */
13
+ /** @typedef {import("./types.js").TemplateTopologySummary} TemplateTopologySummary */
14
+ /** @typedef {import("./types.js").TemplatePolicy} TemplatePolicy */
15
+ /** @typedef {import("./types.js").TemplatePolicyInfo} TemplatePolicyInfo */
16
+ /** @typedef {import("./types.js").TemplateUpdateDiagnostic} TemplateUpdateDiagnostic */
17
+ /** @typedef {import("./types.js").ResolvedTemplate} ResolvedTemplate */
18
+ /** @typedef {import("./types.js").CatalogTemplateProvenance} CatalogTemplateProvenance */
19
+
20
+ /**
21
+ * @param {string} projectRoot
22
+ * @param {ResolvedTemplate} template
23
+ * @param {CatalogTemplateProvenance|null} [templateProvenance]
24
+ * @returns {Record<string, any>}
25
+ */
26
+ export function writeProjectTemplateMetadata(projectRoot, template, templateProvenance = null) {
27
+ const projectConfigPath = path.join(projectRoot, "topogram.project.json");
28
+ const projectConfig = JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
29
+ projectConfig.template = projectTemplateMetadata(template, templateProvenance);
30
+ fs.writeFileSync(projectConfigPath, `${stableJsonStringify(projectConfig)}\n`, "utf8");
31
+ return projectConfig;
32
+ }
33
+
34
+ /**
35
+ * @param {ResolvedTemplate} template
36
+ * @param {CatalogTemplateProvenance|null} [templateProvenance]
37
+ * @returns {{ id: string, version: string, source: string, requested: string, sourceSpec: string, sourceRoot: string|null, includesExecutableImplementation: boolean, catalog?: CatalogTemplateProvenance }}
38
+ */
39
+ export function projectTemplateMetadata(template, templateProvenance = null) {
40
+ /** @type {{ id: string, version: string, source: string, requested: string, sourceSpec: string, sourceRoot: string|null, includesExecutableImplementation: boolean, catalog?: CatalogTemplateProvenance }} */
41
+ const metadata = {
42
+ id: template.manifest.id,
43
+ version: template.manifest.version,
44
+ source: template.source,
45
+ requested: templateProvenance?.id || template.requested,
46
+ sourceSpec: template.packageSpec || template.requested,
47
+ sourceRoot: template.source === "local" ? template.root : null,
48
+ includesExecutableImplementation: Boolean(template.manifest.includesExecutableImplementation)
49
+ };
50
+ if (templateProvenance) {
51
+ metadata.catalog = templateProvenance;
52
+ }
53
+ return metadata;
54
+ }
55
+
56
+ /**
57
+ * @param {Record<string, any>} projectConfig
58
+ * @returns {{ id: string|null, version: string|null, source: string|null, sourceSpec: string|null, requested: string|null }}
59
+ */
60
+ export function currentTemplateMetadata(projectConfig) {
61
+ const currentTemplate = projectConfig.template || {};
62
+ return {
63
+ id: typeof currentTemplate.id === "string" ? currentTemplate.id : null,
64
+ version: typeof currentTemplate.version === "string" ? currentTemplate.version : null,
65
+ source: typeof currentTemplate.source === "string" ? currentTemplate.source : null,
66
+ sourceSpec: typeof currentTemplate.sourceSpec === "string" ? currentTemplate.sourceSpec : null,
67
+ requested: typeof currentTemplate.requested === "string" ? currentTemplate.requested : null
68
+ };
69
+ }
70
+
71
+ /**
72
+ * @param {ResolvedTemplate} template
73
+ * @param {Record<string, any>|null} currentProjectConfig
74
+ * @returns {ReturnType<typeof projectTemplateMetadata>}
75
+ */
76
+ export function candidateProjectTemplateMetadata(template, currentProjectConfig) {
77
+ const metadata = projectTemplateMetadata(template);
78
+ const currentTemplate = currentProjectConfig?.template || null;
79
+ if (!currentTemplate || currentTemplate.id !== metadata.id) {
80
+ return metadata;
81
+ }
82
+ if (typeof currentTemplate.requested === "string" && currentTemplate.requested) {
83
+ metadata.requested = currentTemplate.requested;
84
+ }
85
+ if (currentTemplate.catalog && typeof currentTemplate.catalog === "object") {
86
+ metadata.catalog = {
87
+ ...currentTemplate.catalog,
88
+ package: typeof currentTemplate.catalog.package === "string"
89
+ ? currentTemplate.catalog.package
90
+ : metadata.id,
91
+ version: metadata.version,
92
+ packageSpec: metadata.sourceSpec
93
+ };
94
+ }
95
+ return metadata;
96
+ }
@@ -0,0 +1,161 @@
1
+ // @ts-check
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { CLI_PACKAGE_NAME } from "./constants.js";
7
+
8
+ /**
9
+ * @param {string} projectRoot
10
+ * @returns {string}
11
+ */
12
+ export function packageNameFromPath(projectRoot) {
13
+ const baseName = path.basename(path.resolve(projectRoot)).toLowerCase();
14
+ const normalized = baseName
15
+ .replace(/[^a-z0-9._-]+/g, "-")
16
+ .replace(/^[._-]+/, "")
17
+ .replace(/[._-]+$/, "");
18
+ return normalized || "topogram-app";
19
+ }
20
+
21
+ /**
22
+ * @param {string} projectRoot
23
+ * @param {string} engineRoot
24
+ * @returns {string}
25
+ */
26
+ function fileDependencyForEngine(projectRoot, engineRoot) {
27
+ const relative = path.relative(projectRoot, engineRoot).replace(/\\/g, "/");
28
+ if (!relative || relative.startsWith("..")) {
29
+ return `file:${engineRoot}`;
30
+ }
31
+ return `file:./${relative}`;
32
+ }
33
+
34
+ /**
35
+ * @param {string} engineRoot
36
+ * @returns {{ name: string, version: string }}
37
+ */
38
+ function readCliPackageMetadata(engineRoot) {
39
+ const packagePath = path.join(engineRoot, "package.json");
40
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
41
+ return {
42
+ name: typeof pkg.name === "string" ? pkg.name : CLI_PACKAGE_NAME,
43
+ version: typeof pkg.version === "string" ? pkg.version : "0.0.0"
44
+ };
45
+ }
46
+
47
+ /**
48
+ * @param {string} engineRoot
49
+ * @returns {boolean}
50
+ */
51
+ function isSourceCheckoutEngine(engineRoot) {
52
+ return fs.existsSync(path.join(engineRoot, "tests", "active"));
53
+ }
54
+
55
+ /**
56
+ * @param {string} projectRoot
57
+ * @param {string} engineRoot
58
+ * @returns {{ name: string, spec: string }}
59
+ */
60
+ export function cliDependencyForProject(projectRoot, engineRoot) {
61
+ const metadata = readCliPackageMetadata(engineRoot);
62
+ const overrideSpec = process.env.TOPOGRAM_CLI_PACKAGE_SPEC || "";
63
+ if (overrideSpec) {
64
+ return { name: metadata.name, spec: overrideSpec };
65
+ }
66
+ if (isSourceCheckoutEngine(engineRoot)) {
67
+ return { name: metadata.name, spec: fileDependencyForEngine(projectRoot, engineRoot) };
68
+ }
69
+ return { name: metadata.name, spec: metadata.version };
70
+ }
71
+
72
+ /**
73
+ * @param {string} projectRoot
74
+ * @param {{ name: string, spec: string }} cliDependency
75
+ * @returns {void}
76
+ */
77
+ export function writeProjectNpmConfig(projectRoot, cliDependency) {
78
+ void projectRoot;
79
+ void cliDependency;
80
+ }
81
+
82
+ /**
83
+ * @param {string} templateRoot
84
+ * @returns {Record<string, string>}
85
+ */
86
+ export function generatorDependenciesForTemplate(templateRoot) {
87
+ const packagePath = path.join(templateRoot, "package.json");
88
+ if (!fs.existsSync(packagePath)) {
89
+ return {};
90
+ }
91
+ const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
92
+ const explicit = pkg.topogramGeneratorDependencies &&
93
+ typeof pkg.topogramGeneratorDependencies === "object" &&
94
+ !Array.isArray(pkg.topogramGeneratorDependencies)
95
+ ? pkg.topogramGeneratorDependencies
96
+ : {};
97
+ const dependencies = {
98
+ ...(pkg.dependencies || {}),
99
+ ...(pkg.devDependencies || {}),
100
+ ...explicit
101
+ };
102
+ return Object.fromEntries(Object.entries(dependencies).filter(([name, spec]) =>
103
+ typeof name === "string" &&
104
+ (name.includes("topogram-generator") || name.startsWith("@topogram/generator-")) &&
105
+ typeof spec === "string" &&
106
+ spec.length > 0
107
+ ));
108
+ }
109
+
110
+ /**
111
+ * @param {string} parent
112
+ * @param {string} child
113
+ * @returns {boolean}
114
+ */
115
+ export function isSameOrInside(parent, child) {
116
+ const relative = path.relative(parent, child);
117
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
118
+ }
119
+
120
+ /**
121
+ * @param {string} value
122
+ * @returns {boolean}
123
+ */
124
+ export function isLocalTemplateSpec(value) {
125
+ return value === "." ||
126
+ value.startsWith("./") ||
127
+ value.startsWith("../") ||
128
+ path.isAbsolute(value);
129
+ }
130
+
131
+ /**
132
+ * @param {string} spec
133
+ * @returns {string}
134
+ */
135
+ export function packageNameFromSpec(spec) {
136
+ if (spec.startsWith("@")) {
137
+ const segments = spec.split("/");
138
+ if (segments.length < 2) {
139
+ throw new Error(`Invalid scoped template package spec '${spec}'.`);
140
+ }
141
+ const scope = segments[0];
142
+ const nameAndVersion = segments[1];
143
+ const versionIndex = nameAndVersion.indexOf("@");
144
+ const name = versionIndex >= 0 ? nameAndVersion.slice(0, versionIndex) : nameAndVersion;
145
+ return `${scope}/${name}`;
146
+ }
147
+ const versionIndex = spec.indexOf("@");
148
+ return versionIndex >= 0 ? spec.slice(0, versionIndex) : spec;
149
+ }
150
+
151
+ /**
152
+ * @param {string|null|undefined} spec
153
+ * @returns {string|null}
154
+ */
155
+ export function packageScopeFromSpec(spec) {
156
+ if (!spec) {
157
+ return null;
158
+ }
159
+ const packageName = packageNameFromSpec(spec);
160
+ return packageName.startsWith("@") ? packageName.split("/")[0] : null;
161
+ }