@warpgogol/forge 5.2.3 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (584) hide show
  1. package/AGENTS.md +34 -2
  2. package/README.md +1 -1
  3. package/README.uk.md +8 -8
  4. package/bin/cli.ts +1 -0
  5. package/dist/bin/cli.js +1 -0
  6. package/dist/bin/cli.js.map +1 -1
  7. package/dist/os/adr/adr.module.d.ts.map +1 -1
  8. package/dist/os/adr/adr.module.js +8 -3
  9. package/dist/os/adr/adr.module.js.map +1 -1
  10. package/dist/os/adr/frontmatter-io.js +5 -5
  11. package/dist/os/adr/frontmatter-io.js.map +1 -1
  12. package/dist/os/adr/handlers/archive.d.ts.map +1 -1
  13. package/dist/os/adr/handlers/archive.js +10 -4
  14. package/dist/os/adr/handlers/archive.js.map +1 -1
  15. package/dist/os/adr/handlers/implement-stamp.d.ts.map +1 -1
  16. package/dist/os/adr/handlers/implement-stamp.js +10 -9
  17. package/dist/os/adr/handlers/implement-stamp.js.map +1 -1
  18. package/dist/os/adr/handlers/list-create.js +5 -5
  19. package/dist/os/adr/handlers/list-create.js.map +1 -1
  20. package/dist/os/adr/handlers/validate.js +1 -1
  21. package/dist/os/adr/handlers/validate.js.map +1 -1
  22. package/dist/os/audit/audit.module.js +3 -3
  23. package/dist/os/audit/audit.module.js.map +1 -1
  24. package/dist/os/audit/frontmatter-io.js +5 -5
  25. package/dist/os/audit/frontmatter-io.js.map +1 -1
  26. package/dist/os/audit/handlers/archive.d.ts.map +1 -1
  27. package/dist/os/audit/handlers/archive.js +10 -4
  28. package/dist/os/audit/handlers/archive.js.map +1 -1
  29. package/dist/os/compass/compass.module.d.ts.map +1 -1
  30. package/dist/os/compass/compass.module.js +17 -1
  31. package/dist/os/compass/compass.module.js.map +1 -1
  32. package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
  33. package/dist/os/compass/handlers/compass-audit-handler.js +12 -5
  34. package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
  35. package/dist/os/compass/handlers/compass-change-summary-handler.d.ts.map +1 -1
  36. package/dist/os/compass/handlers/compass-change-summary-handler.js +3 -2
  37. package/dist/os/compass/handlers/compass-change-summary-handler.js.map +1 -1
  38. package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
  39. package/dist/os/compass/handlers/compass-inventory-handler.js +6 -4
  40. package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
  41. package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
  42. package/dist/os/compass/handlers/compass-inventory.js +10 -8
  43. package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
  44. package/dist/os/compass/handlers/compass-migrate-handler.js +1 -1
  45. package/dist/os/compass/handlers/compass-migrate-handler.js.map +1 -1
  46. package/dist/os/compass/handlers/compass-migrate.d.ts.map +1 -1
  47. package/dist/os/compass/handlers/compass-migrate.js +6 -3
  48. package/dist/os/compass/handlers/compass-migrate.js.map +1 -1
  49. package/dist/os/compass/handlers/git-revision.d.ts.map +1 -1
  50. package/dist/os/compass/handlers/git-revision.js +9 -6
  51. package/dist/os/compass/handlers/git-revision.js.map +1 -1
  52. package/dist/os/compass/handlers/resolve-scan-root.js +1 -1
  53. package/dist/os/compass/handlers/resolve-scan-root.js.map +1 -1
  54. package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
  55. package/dist/os/compass/handlers/summary-record.js +8 -4
  56. package/dist/os/compass/handlers/summary-record.js.map +1 -1
  57. package/dist/os/core/core.module.d.ts.map +1 -1
  58. package/dist/os/core/core.module.js +57 -4
  59. package/dist/os/core/core.module.js.map +1 -1
  60. package/dist/os/core/handlers/assets-helpers.d.ts.map +1 -1
  61. package/dist/os/core/handlers/assets-helpers.js +6 -3
  62. package/dist/os/core/handlers/assets-helpers.js.map +1 -1
  63. package/dist/os/core/handlers/build.js +1 -1
  64. package/dist/os/core/handlers/build.js.map +1 -1
  65. package/dist/os/core/handlers/determinism-check.d.ts.map +1 -1
  66. package/dist/os/core/handlers/determinism-check.js +10 -6
  67. package/dist/os/core/handlers/determinism-check.js.map +1 -1
  68. package/dist/os/core/handlers/dev.js +1 -1
  69. package/dist/os/core/handlers/dev.js.map +1 -1
  70. package/dist/os/core/handlers/file-size-lint.d.ts.map +1 -1
  71. package/dist/os/core/handlers/file-size-lint.js +11 -7
  72. package/dist/os/core/handlers/file-size-lint.js.map +1 -1
  73. package/dist/os/core/handlers/forge-autonomy-validate.d.ts.map +1 -1
  74. package/dist/os/core/handlers/forge-autonomy-validate.js +9 -5
  75. package/dist/os/core/handlers/forge-autonomy-validate.js.map +1 -1
  76. package/dist/os/core/handlers/knowledge-compact.js +1 -1
  77. package/dist/os/core/handlers/knowledge-compact.js.map +1 -1
  78. package/dist/os/core/handlers/package-health.js +1 -1
  79. package/dist/os/core/handlers/package-health.js.map +1 -1
  80. package/dist/os/core/handlers/pinned-check.js +2 -2
  81. package/dist/os/core/handlers/pinned-check.js.map +1 -1
  82. package/dist/os/core/handlers/pinned-init.d.ts.map +1 -1
  83. package/dist/os/core/handlers/pinned-init.js +9 -8
  84. package/dist/os/core/handlers/pinned-init.js.map +1 -1
  85. package/dist/os/core/handlers/pinned-validate.js +3 -3
  86. package/dist/os/core/handlers/pinned-validate.js.map +1 -1
  87. package/dist/os/core/handlers/profile-resolve.js +1 -1
  88. package/dist/os/core/handlers/profile-resolve.js.map +1 -1
  89. package/dist/os/core/handlers/public-surface.js +1 -1
  90. package/dist/os/core/handlers/public-surface.js.map +1 -1
  91. package/dist/os/core/handlers/release-prepare.d.ts.map +1 -1
  92. package/dist/os/core/handlers/release-prepare.js +10 -6
  93. package/dist/os/core/handlers/release-prepare.js.map +1 -1
  94. package/dist/os/core/handlers/release-publish.js +6 -6
  95. package/dist/os/core/handlers/release-publish.js.map +1 -1
  96. package/dist/os/core/handlers/validate.js +1 -1
  97. package/dist/os/core/handlers/validate.js.map +1 -1
  98. package/dist/os/exploration/exploration.module.d.ts.map +1 -1
  99. package/dist/os/exploration/exploration.module.js +1 -0
  100. package/dist/os/exploration/exploration.module.js.map +1 -1
  101. package/dist/os/exploration/frontmatter-io.js +4 -4
  102. package/dist/os/exploration/frontmatter-io.js.map +1 -1
  103. package/dist/os/exploration/handlers/archive.js +3 -3
  104. package/dist/os/exploration/handlers/archive.js.map +1 -1
  105. package/dist/os/mission/handlers/archive.d.ts.map +1 -1
  106. package/dist/os/mission/handlers/archive.js +22 -19
  107. package/dist/os/mission/handlers/archive.js.map +1 -1
  108. package/dist/os/mission/mission.module.js +3 -3
  109. package/dist/os/mission/mission.module.js.map +1 -1
  110. package/dist/os/naming/naming-convention.d.ts.map +1 -1
  111. package/dist/os/naming/naming-convention.js +7 -3
  112. package/dist/os/naming/naming-convention.js.map +1 -1
  113. package/dist/os/naming/naming.module.d.ts.map +1 -1
  114. package/dist/os/naming/naming.module.js +4 -3
  115. package/dist/os/naming/naming.module.js.map +1 -1
  116. package/dist/os/notes/notes.module.d.ts.map +1 -1
  117. package/dist/os/notes/notes.module.js +6 -3
  118. package/dist/os/notes/notes.module.js.map +1 -1
  119. package/dist/os/plan/frontmatter-io.js +5 -5
  120. package/dist/os/plan/frontmatter-io.js.map +1 -1
  121. package/dist/os/plan/handlers/archive.d.ts.map +1 -1
  122. package/dist/os/plan/handlers/archive.js +10 -4
  123. package/dist/os/plan/handlers/archive.js.map +1 -1
  124. package/dist/os/plan/plan.module.js +3 -3
  125. package/dist/os/plan/plan.module.js.map +1 -1
  126. package/dist/os/plugin/plugin.module.d.ts.map +1 -1
  127. package/dist/os/plugin/plugin.module.js +6 -3
  128. package/dist/os/plugin/plugin.module.js.map +1 -1
  129. package/dist/os/program/discovery.js +2 -2
  130. package/dist/os/program/discovery.js.map +1 -1
  131. package/dist/os/program/handlers/complete.js +1 -1
  132. package/dist/os/program/handlers/complete.js.map +1 -1
  133. package/dist/os/program/handlers/lease.js +1 -1
  134. package/dist/os/program/handlers/lease.js.map +1 -1
  135. package/dist/os/program/handlers/seal.js +1 -1
  136. package/dist/os/program/handlers/seal.js.map +1 -1
  137. package/dist/os/program/lease.js +1 -1
  138. package/dist/os/program/lease.js.map +1 -1
  139. package/dist/os/program/program.module.d.ts.map +1 -1
  140. package/dist/os/program/program.module.js +9 -3
  141. package/dist/os/program/program.module.js.map +1 -1
  142. package/dist/os/queue/index.js +1 -1
  143. package/dist/os/queue/manifest.d.ts.map +1 -1
  144. package/dist/os/queue/manifest.js +4 -8
  145. package/dist/os/queue/manifest.js.map +1 -1
  146. package/dist/os/rfc/acceptance.d.ts.map +1 -1
  147. package/dist/os/rfc/acceptance.js +10 -6
  148. package/dist/os/rfc/acceptance.js.map +1 -1
  149. package/dist/os/rfc/decision-log.d.ts.map +1 -1
  150. package/dist/os/rfc/decision-log.js +4 -3
  151. package/dist/os/rfc/decision-log.js.map +1 -1
  152. package/dist/os/rfc/dna-trace.d.ts.map +1 -1
  153. package/dist/os/rfc/dna-trace.js +7 -2
  154. package/dist/os/rfc/dna-trace.js.map +1 -1
  155. package/dist/os/rfc/frontmatter-io.js +5 -5
  156. package/dist/os/rfc/frontmatter-io.js.map +1 -1
  157. package/dist/os/rfc/handlers/archive.d.ts.map +1 -1
  158. package/dist/os/rfc/handlers/archive.js +11 -5
  159. package/dist/os/rfc/handlers/archive.js.map +1 -1
  160. package/dist/os/rfc/handlers/check.d.ts.map +1 -1
  161. package/dist/os/rfc/handlers/check.js +6 -3
  162. package/dist/os/rfc/handlers/check.js.map +1 -1
  163. package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
  164. package/dist/os/rfc/handlers/implement-stamp.js +11 -10
  165. package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
  166. package/dist/os/rfc/handlers/index-graph.js +3 -3
  167. package/dist/os/rfc/handlers/index-graph.js.map +1 -1
  168. package/dist/os/rfc/handlers/lifecycle.d.ts.map +1 -1
  169. package/dist/os/rfc/handlers/lifecycle.js +6 -2
  170. package/dist/os/rfc/handlers/lifecycle.js.map +1 -1
  171. package/dist/os/rfc/handlers/list-create.d.ts.map +1 -1
  172. package/dist/os/rfc/handlers/list-create.js +7 -6
  173. package/dist/os/rfc/handlers/list-create.js.map +1 -1
  174. package/dist/os/rfc/handlers/shared.js +3 -3
  175. package/dist/os/rfc/handlers/shared.js.map +1 -1
  176. package/dist/os/rfc/handlers/supersede-propose.d.ts.map +1 -1
  177. package/dist/os/rfc/handlers/supersede-propose.js +7 -3
  178. package/dist/os/rfc/handlers/supersede-propose.js.map +1 -1
  179. package/dist/os/rfc/handlers/validate-rules.d.ts.map +1 -1
  180. package/dist/os/rfc/handlers/validate-rules.js +8 -5
  181. package/dist/os/rfc/handlers/validate-rules.js.map +1 -1
  182. package/dist/os/rfc/rfc.module.d.ts.map +1 -1
  183. package/dist/os/rfc/rfc.module.js +44 -4
  184. package/dist/os/rfc/rfc.module.js.map +1 -1
  185. package/dist/os/rfc/verification-evidence.d.ts.map +1 -1
  186. package/dist/os/rfc/verification-evidence.js +9 -5
  187. package/dist/os/rfc/verification-evidence.js.map +1 -1
  188. package/dist/os/rfc/verification-refresh.d.ts.map +1 -1
  189. package/dist/os/rfc/verification-refresh.js +4 -3
  190. package/dist/os/rfc/verification-refresh.js.map +1 -1
  191. package/dist/os/session/frontmatter-io.js +12 -12
  192. package/dist/os/session/frontmatter-io.js.map +1 -1
  193. package/dist/os/session/handlers/archive.d.ts.map +1 -1
  194. package/dist/os/session/handlers/archive.js +10 -4
  195. package/dist/os/session/handlers/archive.js.map +1 -1
  196. package/dist/os/session/handlers/metrics-aggregate.d.ts.map +1 -1
  197. package/dist/os/session/handlers/metrics-aggregate.js +9 -5
  198. package/dist/os/session/handlers/metrics-aggregate.js.map +1 -1
  199. package/dist/os/session/handlers/metrics-rfc.d.ts.map +1 -1
  200. package/dist/os/session/handlers/metrics-rfc.js +11 -8
  201. package/dist/os/session/handlers/metrics-rfc.js.map +1 -1
  202. package/dist/os/session/handlers/metrics-session.d.ts.map +1 -1
  203. package/dist/os/session/handlers/metrics-session.js +6 -3
  204. package/dist/os/session/handlers/metrics-session.js.map +1 -1
  205. package/dist/os/session/handlers/save.d.ts.map +1 -1
  206. package/dist/os/session/handlers/save.js +14 -8
  207. package/dist/os/session/handlers/save.js.map +1 -1
  208. package/dist/os/session/session.module.d.ts.map +1 -1
  209. package/dist/os/session/session.module.js +6 -0
  210. package/dist/os/session/session.module.js.map +1 -1
  211. package/dist/os/spec/live-spec-list.js +4 -4
  212. package/dist/os/spec/live-spec-list.js.map +1 -1
  213. package/dist/os/spec/live-spec-merge.js +5 -5
  214. package/dist/os/spec/live-spec-merge.js.map +1 -1
  215. package/dist/os/spec/live-spec-show.js +3 -3
  216. package/dist/os/spec/live-spec-show.js.map +1 -1
  217. package/dist/os/spec/live-spec-validate.js +4 -4
  218. package/dist/os/spec/live-spec-validate.js.map +1 -1
  219. package/dist/os/spec/spec-materialize.js +9 -9
  220. package/dist/os/spec/spec-materialize.js.map +1 -1
  221. package/dist/os/spec/spec-status.js +9 -9
  222. package/dist/os/spec/spec-status.js.map +1 -1
  223. package/dist/os/spec/spec-validate.js +7 -7
  224. package/dist/os/spec/spec-validate.js.map +1 -1
  225. package/dist/os/spec/spec.module.d.ts.map +1 -1
  226. package/dist/os/spec/spec.module.js +8 -0
  227. package/dist/os/spec/spec.module.js.map +1 -1
  228. package/dist/os/werkstatt/handlers/lock.js +16 -16
  229. package/dist/os/werkstatt/handlers/lock.js.map +1 -1
  230. package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js +3 -3
  231. package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js.map +1 -1
  232. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.d.ts.map +1 -1
  233. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js +3 -2
  234. package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js.map +1 -1
  235. package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
  236. package/dist/os/werkstatt/werkstatt.module.js +6 -2
  237. package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
  238. package/dist/os/workflow/handlers.d.ts.map +1 -1
  239. package/dist/os/workflow/handlers.js +8 -5
  240. package/dist/os/workflow/handlers.js.map +1 -1
  241. package/dist/os/workflow/workflow.module.d.ts.map +1 -1
  242. package/dist/os/workflow/workflow.module.js +6 -3
  243. package/dist/os/workflow/workflow.module.js.map +1 -1
  244. package/dist/src/compass/contract-registry.js +1 -1
  245. package/dist/src/compass/contract-registry.js.map +1 -1
  246. package/dist/src/config/forge-config.d.ts +10 -1
  247. package/dist/src/config/forge-config.d.ts.map +1 -1
  248. package/dist/src/config/forge-config.js +8 -1
  249. package/dist/src/config/forge-config.js.map +1 -1
  250. package/dist/src/knowledge/budgets.js +1 -1
  251. package/dist/src/knowledge/budgets.js.map +1 -1
  252. package/dist/src/knowledge/compact.js +1 -1
  253. package/dist/src/knowledge/compact.js.map +1 -1
  254. package/dist/src/knowledge/parse.js +1 -1
  255. package/dist/src/knowledge/parse.js.map +1 -1
  256. package/dist/src/knowledge/sync.d.ts.map +1 -1
  257. package/dist/src/knowledge/sync.js +3 -5
  258. package/dist/src/knowledge/sync.js.map +1 -1
  259. package/dist/src/migration-adapters/git-utils.js +2 -2
  260. package/dist/src/migration-adapters/git-utils.js.map +1 -1
  261. package/dist/src/migration-adapters/ignored-files.js +2 -2
  262. package/dist/src/migration-adapters/ignored-files.js.map +1 -1
  263. package/dist/src/migration-adapters/node-typescript-pnpm/index.js +1 -1
  264. package/dist/src/migration-adapters/node-typescript-pnpm/index.js.map +1 -1
  265. package/dist/src/migration-adapters/phaser-pnpm/index.js +1 -1
  266. package/dist/src/migration-adapters/phaser-pnpm/index.js.map +1 -1
  267. package/dist/src/onboarding/agents-generate.d.ts +2 -0
  268. package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
  269. package/dist/src/onboarding/agents-generate.js +73 -48
  270. package/dist/src/onboarding/agents-generate.js.map +1 -1
  271. package/dist/src/onboarding/create.d.ts.map +1 -1
  272. package/dist/src/onboarding/create.js +10 -10
  273. package/dist/src/onboarding/create.js.map +1 -1
  274. package/dist/src/onboarding/doctor.d.ts +6 -0
  275. package/dist/src/onboarding/doctor.d.ts.map +1 -1
  276. package/dist/src/onboarding/doctor.js +179 -61
  277. package/dist/src/onboarding/doctor.js.map +1 -1
  278. package/dist/src/onboarding/init.d.ts.map +1 -1
  279. package/dist/src/onboarding/init.js +30 -1
  280. package/dist/src/onboarding/init.js.map +1 -1
  281. package/dist/src/onboarding/invariant-engine.js +1 -1
  282. package/dist/src/onboarding/invariant-engine.js.map +1 -1
  283. package/dist/src/onboarding/memory-compact.d.ts +22 -0
  284. package/dist/src/onboarding/memory-compact.d.ts.map +1 -0
  285. package/dist/src/onboarding/memory-compact.js +148 -0
  286. package/dist/src/onboarding/memory-compact.js.map +1 -0
  287. package/dist/src/onboarding/memory-scaffold.d.ts +1 -0
  288. package/dist/src/onboarding/memory-scaffold.d.ts.map +1 -1
  289. package/dist/src/onboarding/memory-scaffold.js +2 -2
  290. package/dist/src/onboarding/memory-scaffold.js.map +1 -1
  291. package/dist/src/onboarding/nested-agents-generate.d.ts +4 -1
  292. package/dist/src/onboarding/nested-agents-generate.d.ts.map +1 -1
  293. package/dist/src/onboarding/nested-agents-generate.js +35 -6
  294. package/dist/src/onboarding/nested-agents-generate.js.map +1 -1
  295. package/dist/src/onboarding/nested-agents-templates.d.ts.map +1 -1
  296. package/dist/src/onboarding/nested-agents-templates.js +8 -12
  297. package/dist/src/onboarding/nested-agents-templates.js.map +1 -1
  298. package/dist/src/onboarding/npm-token-check.js +2 -2
  299. package/dist/src/onboarding/npm-token-check.js.map +1 -1
  300. package/dist/src/onboarding/prettierignore.d.ts +29 -0
  301. package/dist/src/onboarding/prettierignore.d.ts.map +1 -0
  302. package/dist/src/onboarding/prettierignore.js +231 -0
  303. package/dist/src/onboarding/prettierignore.js.map +1 -0
  304. package/dist/src/onboarding/profile-validate.d.ts.map +1 -1
  305. package/dist/src/onboarding/profile-validate.js +5 -4
  306. package/dist/src/onboarding/profile-validate.js.map +1 -1
  307. package/dist/src/onboarding/scaffold-project.d.ts.map +1 -1
  308. package/dist/src/onboarding/scaffold-project.js +35 -27
  309. package/dist/src/onboarding/scaffold-project.js.map +1 -1
  310. package/dist/src/onboarding/scaffold.js +1 -1
  311. package/dist/src/onboarding/scaffold.js.map +1 -1
  312. package/dist/src/onboarding/skill-markers.d.ts +27 -0
  313. package/dist/src/onboarding/skill-markers.d.ts.map +1 -0
  314. package/dist/src/onboarding/skill-markers.js +131 -0
  315. package/dist/src/onboarding/skill-markers.js.map +1 -0
  316. package/dist/src/onboarding/upgrade.d.ts +10 -0
  317. package/dist/src/onboarding/upgrade.d.ts.map +1 -1
  318. package/dist/src/onboarding/upgrade.js +215 -74
  319. package/dist/src/onboarding/upgrade.js.map +1 -1
  320. package/dist/src/onboarding/workspace-discovery.d.ts +1 -1
  321. package/dist/src/onboarding/workspace-discovery.d.ts.map +1 -1
  322. package/dist/src/onboarding/workspace-discovery.js +15 -3
  323. package/dist/src/onboarding/workspace-discovery.js.map +1 -1
  324. package/dist/src/pipeline-status.d.ts +6 -5
  325. package/dist/src/pipeline-status.d.ts.map +1 -1
  326. package/dist/src/pipeline-status.js +24 -19
  327. package/dist/src/pipeline-status.js.map +1 -1
  328. package/dist/src/profiles/stack-profile.js +1 -1
  329. package/dist/src/profiles/stack-profile.js.map +1 -1
  330. package/dist/src/registry.d.ts +1 -1
  331. package/dist/src/registry.d.ts.map +1 -1
  332. package/dist/src/registry.js +3 -3
  333. package/dist/src/registry.js.map +1 -1
  334. package/dist/src/skill-schema.d.ts +1 -1
  335. package/dist/src/skill-schema.d.ts.map +1 -1
  336. package/dist/src/skill-schema.js +4 -1
  337. package/dist/src/skill-schema.js.map +1 -1
  338. package/dist/src/types.d.ts +116 -0
  339. package/dist/src/types.d.ts.map +1 -1
  340. package/dist/src/utils/editable-region.d.ts +46 -0
  341. package/dist/src/utils/editable-region.d.ts.map +1 -0
  342. package/dist/src/utils/editable-region.js +125 -0
  343. package/dist/src/utils/editable-region.js.map +1 -0
  344. package/dist/src/utils/fs-idempotent.d.ts +2 -1
  345. package/dist/src/utils/fs-idempotent.d.ts.map +1 -1
  346. package/dist/src/utils/fs-idempotent.js +11 -7
  347. package/dist/src/utils/fs-idempotent.js.map +1 -1
  348. package/dist/src/utils/generated-marker.d.ts +17 -0
  349. package/dist/src/utils/generated-marker.d.ts.map +1 -1
  350. package/dist/src/utils/generated-marker.js +29 -1
  351. package/dist/src/utils/generated-marker.js.map +1 -1
  352. package/dist/src/utils/index.d.ts +3 -1
  353. package/dist/src/utils/index.d.ts.map +1 -1
  354. package/dist/src/utils/index.js +3 -1
  355. package/dist/src/utils/index.js.map +1 -1
  356. package/dist/src/utils/io.d.ts +9 -0
  357. package/dist/src/utils/io.d.ts.map +1 -0
  358. package/dist/src/utils/io.js +130 -0
  359. package/dist/src/utils/io.js.map +1 -0
  360. package/dist/src/utils/markdown-table.d.ts +21 -0
  361. package/dist/src/utils/markdown-table.d.ts.map +1 -0
  362. package/dist/src/utils/markdown-table.js +116 -0
  363. package/dist/src/utils/markdown-table.js.map +1 -0
  364. package/dist/src/utils/sync-fs.d.ts +5 -0
  365. package/dist/src/utils/sync-fs.d.ts.map +1 -0
  366. package/dist/src/utils/sync-fs.js +26 -0
  367. package/dist/src/utils/sync-fs.js.map +1 -0
  368. package/dist/src/validators/note-frontmatter-validate.js +1 -1
  369. package/dist/src/validators/note-frontmatter-validate.js.map +1 -1
  370. package/dist/src/validators/note-link-validate.js +1 -1
  371. package/dist/src/validators/note-link-validate.js.map +1 -1
  372. package/dist/src/validators/note-orphan-detect.js +1 -1
  373. package/dist/src/validators/note-orphan-detect.js.map +1 -1
  374. package/dist/src/validators/port-validate.js +1 -1
  375. package/dist/src/validators/port-validate.js.map +1 -1
  376. package/dist/src/validators/skill-validate.d.ts.map +1 -1
  377. package/dist/src/validators/skill-validate.js +34 -13
  378. package/dist/src/validators/skill-validate.js.map +1 -1
  379. package/docs/reference/forge-yaml.md +12 -0
  380. package/os/adr/adr.module.ts +136 -132
  381. package/os/adr/frontmatter-io.ts +5 -5
  382. package/os/adr/handlers/archive.ts +8 -4
  383. package/os/adr/handlers/implement-stamp.ts +14 -9
  384. package/os/adr/handlers/list-create.ts +5 -5
  385. package/os/adr/handlers/validate.ts +1 -1
  386. package/os/audit/audit.module.ts +33 -34
  387. package/os/audit/frontmatter-io.ts +5 -5
  388. package/os/audit/handlers/archive.ts +8 -4
  389. package/os/compass/compass.module.ts +17 -1
  390. package/os/compass/handlers/compass-audit-handler.ts +13 -5
  391. package/os/compass/handlers/compass-change-summary-handler.ts +4 -2
  392. package/os/compass/handlers/compass-inventory-handler.ts +7 -4
  393. package/os/compass/handlers/compass-inventory.ts +11 -8
  394. package/os/compass/handlers/compass-migrate-handler.ts +1 -1
  395. package/os/compass/handlers/compass-migrate.ts +7 -3
  396. package/os/compass/handlers/git-revision.ts +11 -6
  397. package/os/compass/handlers/resolve-scan-root.ts +1 -1
  398. package/os/compass/handlers/summary-record.ts +10 -4
  399. package/os/compass/handlers/tests/compass-audit-plan.test.ts +2 -1
  400. package/os/core/core.module.ts +58 -4
  401. package/os/core/handlers/assets-helpers.ts +7 -3
  402. package/os/core/handlers/build.ts +1 -1
  403. package/os/core/handlers/determinism-check.ts +12 -6
  404. package/os/core/handlers/dev.ts +1 -1
  405. package/os/core/handlers/file-size-lint.ts +16 -9
  406. package/os/core/handlers/forge-autonomy-validate.ts +10 -5
  407. package/os/core/handlers/knowledge-compact.ts +1 -1
  408. package/os/core/handlers/package-health.ts +1 -1
  409. package/os/core/handlers/pinned-check.ts +2 -2
  410. package/os/core/handlers/pinned-init.ts +9 -8
  411. package/os/core/handlers/pinned-validate.ts +3 -3
  412. package/os/core/handlers/profile-resolve.ts +1 -1
  413. package/os/core/handlers/public-surface.ts +1 -1
  414. package/os/core/handlers/release-prepare.ts +11 -6
  415. package/os/core/handlers/release-publish.ts +6 -6
  416. package/os/core/handlers/validate.ts +1 -1
  417. package/os/exploration/exploration.module.ts +1 -0
  418. package/os/exploration/frontmatter-io.ts +4 -4
  419. package/os/exploration/handlers/archive.ts +3 -3
  420. package/os/mission/handlers/archive.ts +27 -19
  421. package/os/mission/mission.module.ts +38 -39
  422. package/os/naming/naming-convention.ts +9 -3
  423. package/os/naming/naming.module.ts +25 -25
  424. package/os/notes/notes.module.ts +87 -85
  425. package/os/plan/frontmatter-io.ts +5 -5
  426. package/os/plan/handlers/archive.ts +8 -4
  427. package/os/plan/plan.module.ts +33 -34
  428. package/os/plugin/plugin.module.ts +8 -6
  429. package/os/program/discovery.ts +2 -2
  430. package/os/program/handlers/complete.ts +1 -1
  431. package/os/program/handlers/lease.ts +1 -1
  432. package/os/program/handlers/seal.ts +1 -1
  433. package/os/program/lease.ts +1 -1
  434. package/os/program/program.module.ts +198 -193
  435. package/os/queue/index.ts +1 -1
  436. package/os/queue/manifest.ts +10 -9
  437. package/os/queue/queue-validate.test.ts +6 -15
  438. package/os/rfc/acceptance.ts +12 -6
  439. package/os/rfc/decision-log.ts +4 -3
  440. package/os/rfc/dna-trace.ts +8 -2
  441. package/os/rfc/frontmatter-io.ts +5 -5
  442. package/os/rfc/handlers/archive.ts +9 -5
  443. package/os/rfc/handlers/check.ts +5 -3
  444. package/os/rfc/handlers/implement-stamp.ts +15 -10
  445. package/os/rfc/handlers/index-graph.ts +3 -3
  446. package/os/rfc/handlers/lifecycle.ts +7 -2
  447. package/os/rfc/handlers/list-create.ts +7 -6
  448. package/os/rfc/handlers/shared.ts +3 -3
  449. package/os/rfc/handlers/supersede-propose.ts +8 -3
  450. package/os/rfc/handlers/validate-rules-rfc1006.test.ts +18 -6
  451. package/os/rfc/handlers/validate-rules.ts +10 -5
  452. package/os/rfc/rfc-create-concurrent.test.ts +11 -3
  453. package/os/rfc/rfc.module.ts +413 -374
  454. package/os/rfc/verification-evidence.ts +11 -5
  455. package/os/rfc/verification-refresh.ts +4 -3
  456. package/os/session/frontmatter-io.ts +14 -14
  457. package/os/session/handlers/archive.ts +8 -4
  458. package/os/session/handlers/metrics-aggregate.ts +34 -13
  459. package/os/session/handlers/metrics-rfc.ts +13 -8
  460. package/os/session/handlers/metrics-session.ts +7 -3
  461. package/os/session/handlers/save.ts +12 -8
  462. package/os/session/session.module.ts +6 -0
  463. package/os/spec/live-spec-list.ts +4 -4
  464. package/os/spec/live-spec-merge.ts +5 -5
  465. package/os/spec/live-spec-show.ts +3 -3
  466. package/os/spec/live-spec-validate.ts +4 -4
  467. package/os/spec/spec-materialize.ts +9 -9
  468. package/os/spec/spec-status.ts +9 -9
  469. package/os/spec/spec-validate.ts +7 -7
  470. package/os/spec/spec.module.ts +8 -0
  471. package/os/werkstatt/handlers/lock.ts +16 -16
  472. package/os/werkstatt/handlers/werkstatt-lock-recover.ts +3 -3
  473. package/os/werkstatt/handlers/werkstatt-operation-validate.ts +4 -2
  474. package/os/werkstatt/werkstatt.module.ts +8 -5
  475. package/os/workflow/handlers.ts +7 -5
  476. package/os/workflow/workflow.module.ts +45 -44
  477. package/package.json +1 -1
  478. package/profiles/forge-shell.yaml +3 -0
  479. package/profiles/godot-game.yaml +3 -0
  480. package/profiles/phaser-game.yaml +3 -0
  481. package/profiles/site-workshop.yaml +567 -564
  482. package/skills/fo/fo-add-tests/SKILL.md +2 -2
  483. package/skills/fo/fo-architecture/SKILL.md +2 -2
  484. package/skills/fo/fo-compass-annotate/SKILL.md +2 -2
  485. package/skills/fo/fo-design-summit/SKILL.md +2 -2
  486. package/skills/fo/fo-doc-audit/SKILL.md +2 -2
  487. package/skills/fo/fo-explore/SKILL.md +2 -2
  488. package/skills/fo/fo-extract-dna/SKILL.md +2 -2
  489. package/skills/fo/fo-fix/SKILL.md +2 -2
  490. package/skills/fo/fo-handoff/SKILL.md +2 -2
  491. package/skills/fo/fo-harvest/SKILL.md +2 -2
  492. package/skills/fo/fo-idea/SKILL.md +2 -2
  493. package/skills/fo/fo-idea-audit/SKILL.md +2 -2
  494. package/skills/fo/fo-idea-create-adr/SKILL.md +3 -3
  495. package/skills/fo/fo-idea-create-rfc/SKILL.md +3 -3
  496. package/skills/fo/fo-idea-enhance/SKILL.md +2 -2
  497. package/skills/fo/fo-idea-i-just-want-to-see-the-plan/SKILL.md +2 -2
  498. package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +1 -1
  499. package/skills/fo/fo-idea-implement/SKILL.md +2 -2
  500. package/skills/fo/fo-idea-plan/SKILL.md +2 -2
  501. package/skills/fo/fo-idea-status/SKILL.md +2 -2
  502. package/skills/fo/fo-knowledge-distill/SKILL.md +2 -2
  503. package/skills/fo/fo-memory-sync/SKILL.md +2 -2
  504. package/skills/fo/fo-qa/SKILL.md +2 -2
  505. package/skills/fo/fo-review/SKILL.md +2 -2
  506. package/skills/fo/fo-session-retro/SKILL.md +2 -2
  507. package/skills/fo/fo-session-save/SKILL.md +2 -2
  508. package/skills/fo/fo-spec-ingest/SKILL.md +2 -2
  509. package/skills/fo/fo-step-commit/SKILL.md +1 -1
  510. package/skills/fo/fo-triage/SKILL.md +2 -2
  511. package/skills/meta/forge-bootstrap/SKILL.md +1 -1
  512. package/skills/meta/port-to-forge/SKILL.md +1 -1
  513. package/skills/meta/skill-create/SKILL.md +1 -1
  514. package/skills/shared/grilling/SKILL.md +1 -1
  515. package/skills/shared/grilling/learned-principles.archive.md +17 -0
  516. package/skills/shared/grilling/learned-principles.md +0 -1
  517. package/skills/shared/knowledge/learned-principles.md +6 -0
  518. package/skills/shared/my-preferences/SKILL.md +1 -1
  519. package/skills/shared/windows-ai-tooling/SKILL.md +1 -1
  520. package/skills/shared/writing-great-skills/SKILL.md +1 -1
  521. package/src/compass/contract-registry.ts +1 -1
  522. package/src/config/forge-config.ts +12 -1
  523. package/src/knowledge/__tests__/sync.test.ts +2 -7
  524. package/src/knowledge/budgets.ts +1 -1
  525. package/src/knowledge/compact.ts +1 -1
  526. package/src/knowledge/parse.ts +1 -1
  527. package/src/knowledge/sync.ts +3 -5
  528. package/src/migration-adapters/git-utils.ts +2 -2
  529. package/src/migration-adapters/ignored-files.ts +2 -2
  530. package/src/migration-adapters/node-typescript-pnpm/index.ts +1 -1
  531. package/src/migration-adapters/phaser-pnpm/index.ts +1 -1
  532. package/src/onboarding/agents-generate.ts +91 -50
  533. package/src/onboarding/create.ts +10 -10
  534. package/src/onboarding/doctor.ts +199 -59
  535. package/src/onboarding/init.ts +38 -1
  536. package/src/onboarding/invariant-engine.ts +1 -1
  537. package/src/onboarding/memory-compact.ts +189 -0
  538. package/src/onboarding/memory-scaffold.ts +2 -2
  539. package/src/onboarding/nested-agents-generate.ts +50 -5
  540. package/src/onboarding/nested-agents-templates.ts +19 -13
  541. package/src/onboarding/npm-token-check.ts +2 -2
  542. package/src/onboarding/prettierignore.ts +263 -0
  543. package/src/onboarding/profile-validate.ts +5 -4
  544. package/src/onboarding/scaffold-project.ts +38 -27
  545. package/src/onboarding/scaffold.ts +1 -1
  546. package/src/onboarding/skill-markers.ts +162 -0
  547. package/src/onboarding/templates/behavioral-layer-core.md +2 -4
  548. package/src/onboarding/upgrade.ts +285 -89
  549. package/src/onboarding/workspace-discovery.ts +16 -2
  550. package/src/pipeline-status.test.ts +1 -4
  551. package/src/pipeline-status.ts +30 -13
  552. package/src/profiles/stack-profile.ts +1 -1
  553. package/src/registry.ts +4 -4
  554. package/src/skill-schema.ts +4 -1
  555. package/src/tests/acceptance-probe-kinds.test.ts +5 -15
  556. package/src/tests/agents-generate.test.ts +32 -7
  557. package/src/tests/contract-registry.test.ts +12 -6
  558. package/src/tests/doctor-dist-freshness.test.ts +110 -0
  559. package/src/tests/doctor-fix.test.ts +142 -0
  560. package/src/tests/doctor-nested-agents.test.ts +160 -0
  561. package/src/tests/editable-generated-region.test.ts +231 -0
  562. package/src/tests/file-size-lint.test.ts +8 -12
  563. package/src/tests/fixtures/agents-generate-business-before.txt +54 -54
  564. package/src/tests/markdown-table.test.ts +107 -0
  565. package/src/tests/memory-compact.test.ts +143 -0
  566. package/src/tests/prettierignore-manage.test.ts +134 -0
  567. package/src/tests/skill-agents-sync.test.ts +168 -0
  568. package/src/tests/skill-sync-marker.test.ts +134 -0
  569. package/src/tests/skill-validate.test.ts +12 -5
  570. package/src/tests/upgrade.test.ts +87 -41
  571. package/src/tests/workspace-discovery.test.ts +28 -0
  572. package/src/types.ts +123 -0
  573. package/src/utils/editable-region.ts +156 -0
  574. package/src/utils/fs-idempotent.ts +12 -6
  575. package/src/utils/generated-marker.ts +36 -2
  576. package/src/utils/index.ts +17 -0
  577. package/src/utils/io.ts +143 -0
  578. package/src/utils/markdown-table.ts +121 -0
  579. package/src/utils/sync-fs.ts +43 -0
  580. package/src/validators/note-frontmatter-validate.ts +1 -1
  581. package/src/validators/note-link-validate.ts +1 -1
  582. package/src/validators/note-orphan-detect.ts +1 -1
  583. package/src/validators/port-validate.ts +1 -1
  584. package/src/validators/skill-validate.ts +38 -13
@@ -78,14 +78,14 @@ describe("RFC-0539: Pack skill validation (SKILL-14, SKILL-15)", () => {
78
78
  });
79
79
  });
80
80
 
81
- describe("RFC-0548: SKILL-16 triggers field validation", () => {
82
- test("SKILL-16: real workspace fo-skills have valid triggers (no violations)", () => {
81
+ describe("RFC-0548: SKILL-16 triggerPhrases field validation", () => {
82
+ test("SKILL-16: real workspace fo-skills have valid triggerPhrases (no violations)", () => {
83
83
  const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
84
84
  const skill16Violations = result.violations.filter((v) => v.rule === "SKILL-16");
85
85
  expect(skill16Violations).toEqual([]);
86
86
  });
87
87
 
88
- test("SKILL-16: triggers are only on fo-category skills", () => {
88
+ test("SKILL-16: triggerPhrases are only on fo-category skills", () => {
89
89
  const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
90
90
  const skill16Violations = result.violations.filter(
91
91
  (v) => v.rule === "SKILL-16" && v.message.includes("only allowed on fo-category"),
@@ -93,13 +93,20 @@ describe("RFC-0548: SKILL-16 triggers field validation", () => {
93
93
  expect(skill16Violations).toEqual([]);
94
94
  });
95
95
 
96
- test("SKILL-16: no pack skills declare triggers", () => {
96
+ test("SKILL-16: no pack skills declare triggerPhrases", () => {
97
97
  const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
98
98
  const packTriggerViolations = result.violations.filter(
99
- (v) => v.rule === "SKILL-16" && v.message.includes("pack skills may not declare triggers"),
99
+ (v) =>
100
+ v.rule === "SKILL-16" && v.message.includes("pack skills may not declare triggerPhrases"),
100
101
  );
101
102
  expect(packTriggerViolations).toEqual([]);
102
103
  });
104
+
105
+ test("SKILL-22: no skill declares the reserved `triggers` key", () => {
106
+ const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
107
+ const skill22Violations = result.violations.filter((v) => v.rule === "SKILL-22");
108
+ expect(skill22Violations).toEqual([]);
109
+ });
103
110
  });
104
111
 
105
112
  describe("RFC-0553: SKILL-17 platform reference prohibition", () => {
@@ -9,11 +9,15 @@
9
9
  */
10
10
 
11
11
  import { test, expect, beforeEach, afterEach, vi } from "vitest";
12
- import { mkdtemp, mkdir, writeFile, rm, readFile } from "node:fs/promises";
12
+ import { mkdtemp, mkdir, writeFile, rm, readFile, appendFile } from "node:fs/promises";
13
13
  import { existsSync } from "node:fs";
14
14
  import { tmpdir } from "node:os";
15
15
  import { join } from "node:path";
16
16
  import { runUpgrade } from "../onboarding/upgrade.ts";
17
+ import { generateNestedAgentsMd } from "../onboarding/nested-agents-generate.ts";
18
+ import { ambientIo } from "../utils/io.ts";
19
+ import { buildGeneratedHeader } from "../utils/index.ts";
20
+ import type { WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
17
21
  import { runInit } from "../onboarding/init.ts";
18
22
  import {
19
23
  loadForgeConfig,
@@ -163,7 +167,9 @@ test("forge.upgrade with syncedVersion matching installed version returns noop",
163
167
  expect(result.data?.status).toBe("noop");
164
168
  expect(result.data?.fromVersion).toBe("0.2.0");
165
169
  expect(result.data?.toVersion).toBe("0.2.0");
166
- expect(result.data?.skillsUpdated).toEqual([]);
170
+ // RFC-1154: the reconcile pass still runs on a same-version noop — skills it
171
+ // re-synced are reported in skillsUpdated.
172
+ expect(result.data?.skillsUpdated).toContain("fo-idea");
167
173
  expect(result.exitCode).toBe(0);
168
174
  });
169
175
 
@@ -358,66 +364,40 @@ test("forge.upgrade --update-npm attempts npm install for npm consumer", async (
358
364
  await setupNpmConsumerForge(tempDir, "0.2.0");
359
365
  await setupConsumerForgeYaml(tempDir, null);
360
366
 
361
- // Mock execSync to avoid real npm install
362
- const execSyncMock = vi.fn(() => "");
363
- vi.resetModules();
364
- vi.doMock("node:child_process", () => ({
365
- execSync: execSyncMock,
366
- exec: vi.fn(),
367
- execFile: vi.fn(),
368
- spawn: vi.fn(),
369
- fork: vi.fn(),
370
- }));
371
-
372
- // Re-import with mock
373
- const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
374
-
375
- const result = await runUpgradeMocked(
367
+ // RFC-1152: inject a fake WorkspaceIO — exec is the port seam; real fs stays.
368
+ const execMock = vi.fn(async () => ({ exitCode: 0, stdout: "", stderr: "" }));
369
+ const io = { ...ambientIo, exec: execMock } as WorkspaceIO;
370
+
371
+ const result = await runUpgrade(
376
372
  { argv: [], flags: { "update-npm": true } },
377
- makeContext(tempDir, false),
373
+ { ...makeContext(tempDir, false), io },
378
374
  );
379
375
 
380
376
  expect(result.data?.status).toBe("pass");
381
- expect(execSyncMock).toHaveBeenCalled();
377
+ expect(execMock).toHaveBeenCalled();
382
378
  expect(result.data?.npmUpdated).toBe(true);
383
379
  expect(result.data?.npmUpdateSkipped).toBe(null);
384
-
385
- vi.doUnmock("node:child_process");
386
- vi.resetModules();
387
380
  });
388
381
 
389
382
  test("forge.upgrade --update-npm reports failure when npm install throws", async () => {
390
383
  await setupNpmConsumerForge(tempDir, "0.2.0");
391
384
  await setupConsumerForgeYaml(tempDir, null);
392
385
 
393
- // Mock execSync to throw (simulates network error / registry unavailable)
394
- const execSyncMock = vi.fn(() => {
395
- throw new Error("network error");
396
- });
397
- vi.resetModules();
398
- vi.doMock("node:child_process", () => ({
399
- execSync: execSyncMock,
400
- exec: vi.fn(),
401
- execFile: vi.fn(),
402
- spawn: vi.fn(),
403
- fork: vi.fn(),
404
- }));
405
-
406
- const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
386
+ // RFC-1152: fake io.exec fails — simulates network error / registry unavailable
387
+ const execMock = vi.fn(async () => ({ exitCode: 1, stdout: "", stderr: "network error" }));
388
+ const io = { ...ambientIo, exec: execMock } as WorkspaceIO;
407
389
 
408
- const result = await runUpgradeMocked(
390
+ const result = await runUpgrade(
409
391
  { argv: [], flags: { "update-npm": true } },
410
- makeContext(tempDir, false),
392
+ { ...makeContext(tempDir, false), io },
411
393
  );
412
394
 
413
395
  expect(result.data?.status).toBe("pass");
414
- expect(execSyncMock).toHaveBeenCalled();
396
+ expect(execMock).toHaveBeenCalled();
415
397
  expect(result.data?.npmUpdated).toBe(false);
416
398
  expect(result.data?.npmUpdateSkipped).toBe(
417
399
  "install failed (network error or registry unavailable)",
418
400
  );
419
-
420
- vi.doUnmock("node:child_process");
421
401
  vi.resetModules();
422
402
  });
423
403
 
@@ -432,6 +412,72 @@ test("forge.upgrade without --update-npm does not attempt npm install", async ()
432
412
  expect(result.data?.npmUpdateSkipped).toBe(null);
433
413
  });
434
414
 
415
+ // RFC-1153: dry-run preview of the nested-AGENTS.md merge --------------------
416
+
417
+ test("AC-7: forge.upgrade --dry-run reports nestedAgentsPlanned and nestedAgentsSkipped", async () => {
418
+ await setupForgeSource(tempDir, "0.2.0");
419
+ await setupConsumerForgeYaml(tempDir, null);
420
+
421
+ // Workspace with no AGENTS.md — merged output would be written → planned
422
+ await mkdir(join(tempDir, "packages", "newpkg"), { recursive: true });
423
+ await writeFile(join(tempDir, "packages", "newpkg", "package.json"), '{"name":"newpkg"}', "utf8");
424
+
425
+ // Marker-bearing file that diverges with no resolvable boundary → skipped
426
+ await mkdir(join(tempDir, "packages", "unmapped"), { recursive: true });
427
+ await writeFile(
428
+ join(tempDir, "packages", "unmapped", "package.json"),
429
+ '{"name":"unmapped"}',
430
+ "utf8",
431
+ );
432
+ await writeFile(
433
+ join(tempDir, "packages", "unmapped", "AGENTS.md"),
434
+ `${buildGeneratedHeader({ filePath: "AGENTS.md", ownerCommand: "forge.agents.generate", editable: true })}\n# Hand-maintained\n\ndivergent body\n`,
435
+ "utf8",
436
+ );
437
+
438
+ const result = await runUpgrade(
439
+ { argv: [], flags: { "dry-run": true } },
440
+ makeContext(tempDir, true),
441
+ );
442
+
443
+ expect(result.data?.status).toBe("pass");
444
+ const planned = result.data?.nestedAgentsPlanned ?? [];
445
+ const skipped = result.data?.nestedAgentsSkipped ?? [];
446
+
447
+ expect(
448
+ planned,
449
+ "planned must contain exactly the guides whose merged output differs from disk",
450
+ ).toContain(join("packages", "newpkg", "AGENTS.md"));
451
+ expect(skipped.some((s) => s.includes("unmapped") && s.includes("unmapped-customization"))).toBe(
452
+ true,
453
+ );
454
+ // No files written under dry-run
455
+ expect(existsSync(join(tempDir, "packages", "newpkg", "AGENTS.md"))).toBe(false);
456
+ });
457
+
458
+ test("forge.upgrade executing path reports nestedAgentsPreserved for carried tails", async () => {
459
+ await setupForgeSource(tempDir, "0.2.0");
460
+ await setupConsumerForgeYaml(tempDir, null);
461
+
462
+ // First run materializes the nested guide with the boundary marker
463
+ await mkdir(join(tempDir, "packages", "pkg"), { recursive: true });
464
+ await writeFile(join(tempDir, "packages", "pkg", "package.json"), '{"name":"pkg"}', "utf8");
465
+ const config = loadForgeConfig(tempDir);
466
+ await generateNestedAgentsMd(tempDir, config, false);
467
+ await appendFile(
468
+ join(tempDir, "packages", "pkg", "AGENTS.md"),
469
+ "\n## Custom rules\n\n- keep me\n",
470
+ "utf8",
471
+ );
472
+
473
+ const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
474
+
475
+ expect(result.data?.status).toBe("pass");
476
+ expect(result.data?.nestedAgentsPreserved).toContain(join("packages", "pkg", "AGENTS.md"));
477
+ const merged = await readFile(join(tempDir, "packages", "pkg", "AGENTS.md"), "utf8");
478
+ expect(merged).toContain("- keep me");
479
+ });
480
+
435
481
  function qaLog(entries: { id: string; title: string; body: string }[]): string {
436
482
  const blocks = entries.map(
437
483
  (e) =>
@@ -104,6 +104,34 @@ test("discoverWorkspaces skips node_modules, .git, dist, .turbo, .cache, .agents
104
104
  expect(workspaces).toHaveLength(0);
105
105
  });
106
106
 
107
+ test("discoverWorkspaces skips missions, test-fixtures, .stryker-tmp", async () => {
108
+ // Pipeline-managed workpieces/archives, fixture workshops, and mutation-testing
109
+ // sandboxes are not project workspaces — their AGENTS.md is owned elsewhere.
110
+ for (const skip of ["missions", "test-fixtures", ".stryker-tmp"]) {
111
+ await mkdir(join(tempDir, skip, "sub"), { recursive: true });
112
+ await writeFile(join(tempDir, skip, "sub", "package.json"), "{}");
113
+ }
114
+
115
+ const workspaces = discoverWorkspaces(tempDir);
116
+ expect(workspaces).toHaveLength(0);
117
+ });
118
+
119
+ // RFC-1150: consumer-declared exclusions (bindings.workspaces.skipDirs)
120
+
121
+ test("discoverWorkspaces honors extraSkipDirs for consumer artifact dirs", async () => {
122
+ await mkdir(join(tempDir, "builds", "out"), { recursive: true });
123
+ await writeFile(join(tempDir, "builds", "out", "package.json"), "{}");
124
+ await mkdir(join(tempDir, "packages", "real-pkg"), { recursive: true });
125
+ await writeFile(join(tempDir, "packages", "real-pkg", "package.json"), "{}");
126
+
127
+ const without = discoverWorkspaces(tempDir);
128
+ expect(without.map((w) => w.path)).toContain(join("builds", "out"));
129
+
130
+ const withSkip = discoverWorkspaces(tempDir, undefined, ["builds"]);
131
+ expect(withSkip.map((w) => w.path)).not.toContain(join("builds", "out"));
132
+ expect(withSkip.map((w) => w.path)).toContain(join("packages", "real-pkg"));
133
+ });
134
+
107
135
  test("discoverWorkspaces detects generated AGENTS.md", async () => {
108
136
  await mkdir(join(tempDir, "packages", "my-pkg"), { recursive: true });
109
137
  await writeFile(join(tempDir, "packages", "my-pkg", "package.json"), "{}");
package/src/types.ts CHANGED
@@ -20,6 +20,101 @@ Sweep batch 4: 73 Compass headers on headerless engine files (certification, com
20
20
  </CHANGE_SUMMARY>
21
21
  */
22
22
 
23
+ // RFC-1152: WorkspaceIO port types — declared locally so the standalone npm
24
+ // build needs no @warpgogol/* package (ADR-0019). Structurally identical to
25
+ // @warpgogol/werkstatt-shared/kernel/workspace-io — kernel callers passing a
26
+ // real WorkspaceIO (context.io) are assignable both ways.
27
+
28
+ export interface ExecOptions {
29
+ cwd?: string;
30
+ timeoutMs?: number;
31
+ /** Extra environment variables merged over `process.env` for the child. */
32
+ env?: Record<string, string>;
33
+ }
34
+
35
+ export interface ExecResult {
36
+ exitCode: number | null;
37
+ stdout: string;
38
+ stderr: string;
39
+ }
40
+
41
+ /**
42
+ * A directory entry in a port-neutral shape — no `node:fs` `Dirent` leaks
43
+ * through the port, so command modules never need to import `node:fs` just to
44
+ * type a listing. `isFile`/`isDirectory` are plain booleans (not methods).
45
+ */
46
+ export interface DirEntry {
47
+ name: string;
48
+ isFile: boolean;
49
+ isDirectory: boolean;
50
+ }
51
+
52
+ /** Port-neutral `stat` result — no `node:fs` `Stats` leaks through the port. */
53
+ export interface FileStat {
54
+ isFile: boolean;
55
+ isDirectory: boolean;
56
+ mtimeMs: number;
57
+ size: number;
58
+ }
59
+
60
+ export interface WorkspaceIO {
61
+ readFile(path: string): Promise<string>;
62
+ readFileBytes(path: string): Promise<Uint8Array>;
63
+ exists(path: string): Promise<boolean>;
64
+ glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;
65
+ /** List a directory's immediate entries. Read-only; throws if the path is not a directory. */
66
+ readdir(path: string): Promise<DirEntry[]>;
67
+ /** File metadata in a port-neutral shape. Throws when the path does not exist. */
68
+ stat(path: string): Promise<FileStat>;
69
+ /** Resolve symlinks to the canonical absolute path. Read-only. */
70
+ realpath(path: string): Promise<string>;
71
+ /** Atomic by contract (rfc-0258). */
72
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
73
+ /** Append content to a file, creating it (and parent dirs) when absent. */
74
+ appendFile(path: string, content: string | Uint8Array): Promise<void>;
75
+ mkdir(path: string): Promise<void>;
76
+ rm(path: string, opts?: { recursive?: boolean }): Promise<void>;
77
+ rename(src: string, dst: string): Promise<void>;
78
+ copyFile(src: string, dst: string): Promise<void>;
79
+ /** Create a unique temporary directory under `prefix`; returns its path. */
80
+ mkdtemp(prefix: string): Promise<string>;
81
+ exec(command: string, args: string[], opts?: ExecOptions): Promise<ExecResult>;
82
+ /**
83
+ * RFC-1176: `true` only on the read-only adapter (`mutatesState: false`
84
+ * commands). Best-effort bookkeeping writes must be skipped with
85
+ * `if (!io.readonly)` instead of attempting a mutation that throws
86
+ * KERNEL-META-01 — the throw stays as the enforcement floor for
87
+ * undeclared mutations. Undefined on real and recording adapters.
88
+ */
89
+ readonly readonly?: boolean;
90
+ }
91
+
92
+ /**
93
+ * RFC-0960: Declared generated artifact specification on a kernel command.
94
+ * Lives on `ForgeCommandMetadata.generates[]` and is the source of truth
95
+ * for the derived generator ownership map.
96
+ */
97
+ export interface GeneratedArtifactSpec {
98
+ /** Workpiece-relative path or glob, e.g. "src/content/system-health.generated.yaml". */
99
+ path: string;
100
+ /** Skip absence checks (RFC-0636 semantics) — e.g. produced only in build.post. */
101
+ conditional?: boolean;
102
+ /** Pipeline phase that produces it: informs which validator phase may assert existence. */
103
+ phase: "build.prepare" | "build.post" | "on-demand";
104
+ /**
105
+ * Override the default markerPolicy derivation. Default: public/** → "registry-only",
106
+ * everything else → "embedded". Set explicitly only for edge cases (e.g. .cache/pdf/**).
107
+ */
108
+ markerPolicy?: "embedded" | "registry-only";
109
+ /**
110
+ * Committed bytes contain non-deterministic fields (Ed25519 proofs, real-time
111
+ * created/generatedAt timestamps, build durations) — byte-compare against a
112
+ * dryRun render can never pass. generated.drift.validate skips volatile
113
+ * artifacts; existence/staleness/signature checks still apply elsewhere.
114
+ */
115
+ volatile?: boolean;
116
+ }
117
+
23
118
  // ---------------------------------------------------------------------------
24
119
  // Command input / output
25
120
  // ---------------------------------------------------------------------------
@@ -71,6 +166,24 @@ export interface ForgeCommandMetadata {
71
166
  description: string;
72
167
  scope: ForgeCommandScope;
73
168
  mutatesState?: boolean;
169
+ /**
170
+ * RFC-1176: permits `io.exec` on the read-only adapter — the command
171
+ * declares `mutatesState: false` but legitimately shells out for
172
+ * read-only probes. Structurally mirrors KernelCommandMetadata.
173
+ */
174
+ execOnReadOnly?: boolean;
175
+ /**
176
+ * RFC-1176: flag names whose truthy presence upgrades this invocation to
177
+ * a real (or `--dry-run` recording) adapter. Structurally mirrors
178
+ * KernelCommandMetadata.
179
+ */
180
+ mutatingFlags?: string[];
181
+ /**
182
+ * RFC-0960/0810: repo-relative path to the implementing module file —
183
+ * propagated into generator ownership entries (OWN-XCHECK-03 requires it
184
+ * on every command that declares generates[]).
185
+ */
186
+ modulePath?: string;
74
187
  requiresNetwork?: boolean;
75
188
  supportsAllSites?: boolean;
76
189
  /** RFC-0963: validation contract this command belongs to (e.g. "content", "seo"). */
@@ -118,6 +231,8 @@ export interface ForgeRegisteredCommandInfo extends ForgeCommandMetadata {
118
231
  flags?: Record<string, ForgeFlagSpec>;
119
232
  reads?: string[];
120
233
  writes?: string[];
234
+ /** RFC-0960 generated-artifact declarations (on-demand generators). */
235
+ generates?: GeneratedArtifactSpec[];
121
236
  hidden?: boolean;
122
237
  }
123
238
 
@@ -126,6 +241,8 @@ export interface ForgeCommandDefinition<TData = unknown> extends ForgeCommandMet
126
241
  flags?: Record<string, ForgeFlagSpec>;
127
242
  reads?: string[];
128
243
  writes?: string[];
244
+ /** RFC-0960 generated-artifact declarations (on-demand generators). */
245
+ generates?: GeneratedArtifactSpec[];
129
246
  execute(
130
247
  input: ForgeCommandInput,
131
248
  context: ForgeRuntimeContext,
@@ -227,4 +344,10 @@ export interface ForgeRuntimeContext {
227
344
  * use this instead of calling resolveForgeRoot(workspaceRoot).
228
345
  */
229
346
  forgeRoot?: string;
347
+ /**
348
+ * RFC-1152: optional WorkspaceIO port — populated when a kernel invokes the
349
+ * command (writes flow through the recording adapter); absent under the
350
+ * standalone `forge` CLI, where helpers fall back to the ambient adapter.
351
+ */
352
+ io?: WorkspaceIO;
230
353
  }
@@ -0,0 +1,156 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-1153: preservation-boundary split/merge for editable generated
4
+ files. Pure, no I/O — callers read the existing file and write the merged
5
+ result through WorkspaceIO. The `forge:custom` marker line bounds the
6
+ generator-owned head; everything below it is carried over verbatim.</purpose>
7
+ <non-goals>
8
+ <item>Do not perform file I/O — operates on in-memory content strings only.</item>
9
+ <item>Do not apply to non-editable generated files — GENERATED_MARKER files
10
+ keep the RFC-0081 binary semantics (marker present = overwrite, absent = skip).</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <KEY_DECISIONS>
14
+ <item>forge:custom marker bounds the generated head; canonical footer is the fallback boundary when absent.</item>
15
+ </KEY_DECISIONS>
16
+ <CHANGE_SUMMARY>
17
+ <item>RFC-1153: initial — splitEditableGenerated + mergeEditableGenerated with
18
+ forge:custom marker boundary and canonical-footer (last non-empty render line) fallback.</item>
19
+ </CHANGE_SUMMARY>
20
+ */
21
+
22
+ import {
23
+ CUSTOM_BOUNDARY_HINTS,
24
+ CUSTOM_BOUNDARY_MARKERS,
25
+ CUSTOM_BOUNDARY_TOKEN,
26
+ commentStyleForPath,
27
+ } from "./generated-marker.ts";
28
+
29
+ export interface EditableRegionSplit {
30
+ /** Content up to and including the boundary line (marker or footer). */
31
+ generatedHead: string;
32
+ /** Content after the boundary line, verbatim. null when empty. */
33
+ customTail: string | null;
34
+ boundary: "marker" | "footer" | "none";
35
+ }
36
+
37
+ /** DNA-58 rule: boundary and footer matching normalize CRLF to LF. */
38
+ function normalizeNewlines(text: string): string {
39
+ return text.replace(/\r\n/g, "\n");
40
+ }
41
+
42
+ /**
43
+ * Canonical footer of a render — its last non-empty line. Callers that need
44
+ * the boundary value separately (doctor staleness, preserved-tail detection)
45
+ * derive it from the same render they pass to the merge.
46
+ */
47
+ export function canonicalFooterOf(rendered: string): string | null {
48
+ const lines = normalizeNewlines(rendered).split("\n");
49
+ for (let i = lines.length - 1; i >= 0; i--) {
50
+ const line = lines[i];
51
+ if (line !== undefined && line.trim() !== "") return line;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * Split an existing editable generated file at its preservation boundary.
58
+ *
59
+ * Priority:
60
+ * 1. First line containing the `forge:custom` token (comment-style agnostic —
61
+ * a token in prose is a documented, deliberate-looking boundary hazard).
62
+ * The marker line and an immediately following canonical hint line belong
63
+ * to the generated head.
64
+ * 2. `footerLine` (canonical template footer, derived by callers from the
65
+ * render as its last non-empty line) — first occurrence is the boundary.
66
+ * 3. `"none"` — no resolvable boundary.
67
+ */
68
+ export function splitEditableGenerated(
69
+ content: string,
70
+ filePath: string,
71
+ footerLine?: string,
72
+ ): EditableRegionSplit {
73
+ const text = normalizeNewlines(content);
74
+ const style = commentStyleForPath(filePath);
75
+ const hint = CUSTOM_BOUNDARY_HINTS[style];
76
+ const lines = text.split("\n");
77
+
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const line = lines[i];
80
+ if (line !== undefined && line.includes(CUSTOM_BOUNDARY_TOKEN)) {
81
+ let headEnd = i + 1;
82
+ if (lines[headEnd] === hint) headEnd += 1;
83
+ const tail = lines.slice(headEnd).join("\n");
84
+ return {
85
+ generatedHead: lines.slice(0, headEnd).join("\n"),
86
+ customTail: tail.trim() === "" ? null : tail,
87
+ boundary: "marker",
88
+ };
89
+ }
90
+ }
91
+
92
+ if (footerLine !== undefined) {
93
+ // lastIndexOf: the canonical footer is the template's last line — the
94
+ // trailingmost occurrence is the correct boundary even when an operator
95
+ // copied the line into a mid-file section.
96
+ const idx = lines.lastIndexOf(footerLine);
97
+ if (idx >= 0) {
98
+ const tail = lines.slice(idx + 1).join("\n");
99
+ return {
100
+ generatedHead: lines.slice(0, idx + 1).join("\n"),
101
+ customTail: tail.trim() === "" ? null : tail,
102
+ boundary: "footer",
103
+ };
104
+ }
105
+ }
106
+
107
+ return { generatedHead: text, customTail: null, boundary: "none" };
108
+ }
109
+
110
+ /**
111
+ * Merge a fresh render with an existing editable generated file.
112
+ *
113
+ * The canonical footer is derived internally as the last non-empty line of
114
+ * `rendered` — no per-file-kind footer registry exists; when a template tail
115
+ * changes, the boundary follows the new render automatically.
116
+ *
117
+ * Merge contract:
118
+ * - `existing === null` (new file): rendered + boundary marker + hint.
119
+ * - marker boundary: head = fresh render + marker + hint; tail verbatim.
120
+ * - footer boundary (legacy marker-less file): head = fresh render + marker +
121
+ * hint (marker introduced now); tail = content after the legacy footer line.
122
+ * - none + identical content: clean regeneration, marker + hint appended.
123
+ * - none + divergent content: returns null — caller must skip and warn
124
+ * (`unmapped-customization`), never overwrite.
125
+ *
126
+ * The result is LF-normalized; callers write through `writeFileIfChanged`.
127
+ */
128
+ export function mergeEditableGenerated(
129
+ rendered: string,
130
+ existing: string | null,
131
+ filePath: string,
132
+ ): string | null {
133
+ const render = normalizeNewlines(rendered);
134
+ const style = commentStyleForPath(filePath);
135
+ const marker = CUSTOM_BOUNDARY_MARKERS[style];
136
+ const hint = CUSTOM_BOUNDARY_HINTS[style];
137
+ const boundaryBlock = `${marker}\n${hint}\n`;
138
+
139
+ if (existing === null) {
140
+ return `${render}${boundaryBlock}`;
141
+ }
142
+
143
+ const text = normalizeNewlines(existing);
144
+ const footer = canonicalFooterOf(render);
145
+ const split = splitEditableGenerated(text, filePath, footer ?? undefined);
146
+
147
+ if (split.boundary === "marker" || split.boundary === "footer") {
148
+ return `${render}${boundaryBlock}${split.customTail ?? ""}`;
149
+ }
150
+
151
+ if (text === render || text === `${render}${boundaryBlock}`) {
152
+ return `${render}${boundaryBlock}`;
153
+ }
154
+
155
+ return null;
156
+ }
@@ -27,28 +27,34 @@ Sweep batch 4: 73 Compass headers on headerless engine files (certification, com
27
27
  </CHANGE_SUMMARY>
28
28
  */
29
29
 
30
- import { readFile } from "node:fs/promises";
31
- import { writeFileAtomic } from "./fs-atomic.ts";
30
+ import type { WorkspaceIO } from "../types.ts";
31
+ import { resolveIo } from "./io.ts";
32
32
 
33
+ // Forge-autonomy boundary (ADR-0019 / FORGE-AUTONOMY-01): `io` is optional and
34
+ // TYPE-ONLY — forge's standalone CLI has no kernel context, so `resolveIo`
35
+ // falls back to the ambient adapter. Kernel callers pass context.io so the
36
+ // read-compare-write goes through the recording adapter.
33
37
  export async function writeFileIfChanged(
34
38
  filePath: string,
35
39
  content: string | Uint8Array,
40
+ io?: WorkspaceIO,
36
41
  ): Promise<"written" | "unchanged"> {
42
+ const fio = resolveIo(io);
37
43
  try {
38
44
  if (typeof content === "string") {
39
- const existing = await readFile(filePath, "utf8");
45
+ const existing = await fio.readFile(filePath);
40
46
  if (existing === content) {
41
47
  return "unchanged";
42
48
  }
43
49
  } else {
44
- const existing = await readFile(filePath);
45
- if (Buffer.compare(existing, Buffer.from(content)) === 0) {
50
+ const existing = await fio.readFileBytes(filePath);
51
+ if (Buffer.compare(Buffer.from(existing), Buffer.from(content)) === 0) {
46
52
  return "unchanged";
47
53
  }
48
54
  }
49
55
  } catch {
50
56
  // File does not exist — proceed to write.
51
57
  }
52
- await writeFileAtomic(filePath, content);
58
+ await fio.writeFile(filePath, content);
53
59
  return "written";
54
60
  }
@@ -20,6 +20,31 @@ export const GENERATED_MARKER =
20
20
  export const EDITABLE_GENERATED_MARKER =
21
21
  "GENERATED. Safe to edit — project-specific changes are expected and encouraged.";
22
22
 
23
+ /**
24
+ * RFC-1153: preservation boundary marker for editable generated files.
25
+ * Everything below the marker line is carried over verbatim on regeneration;
26
+ * everything above (including the marker line itself) is generator-owned.
27
+ * Emitted in the file's comment style — see CUSTOM_BOUNDARY_MARKERS.
28
+ */
29
+ export const CUSTOM_BOUNDARY_TOKEN = "forge:custom";
30
+
31
+ export const CUSTOM_BOUNDARY_MARKERS: Record<GeneratedHeaderCommentStyle, string> = {
32
+ "line-slash": "// forge:custom",
33
+ "line-hash": "# forge:custom",
34
+ "block-html": "<!-- forge:custom -->",
35
+ "block-css": "/* forge:custom */",
36
+ };
37
+
38
+ const CUSTOM_BOUNDARY_HINT_TEXT =
39
+ "Add workspace-specific guidance below this line. Everything below is preserved on regeneration.";
40
+
41
+ export const CUSTOM_BOUNDARY_HINTS: Record<GeneratedHeaderCommentStyle, string> = {
42
+ "line-slash": `// ${CUSTOM_BOUNDARY_HINT_TEXT}`,
43
+ "line-hash": `# ${CUSTOM_BOUNDARY_HINT_TEXT}`,
44
+ "block-html": `<!-- ${CUSTOM_BOUNDARY_HINT_TEXT} -->`,
45
+ "block-css": `/* ${CUSTOM_BOUNDARY_HINT_TEXT} */`,
46
+ };
47
+
23
48
  const DO_NOT_EDIT_LINE = "DO NOT EDIT THIS FILE. Changes are overwritten on the next build.";
24
49
  const SAFE_TO_EDIT_LINE =
25
50
  "This file is generated but editable — your changes are preserved on regeneration.";
@@ -28,6 +53,15 @@ export function hasGeneratedMarker(content: string): boolean {
28
53
  return content.includes(GENERATED_MARKER) || content.includes(EDITABLE_GENERATED_MARKER);
29
54
  }
30
55
 
56
+ /**
57
+ * RFC-1153: true only for files carrying the editable marker. A file bearing
58
+ * just GENERATED_MARKER is fully generator-owned (RFC-0081) — the merge
59
+ * upgrades it to the editable boundary contract instead of skipping it.
60
+ */
61
+ export function hasEditableGeneratedMarker(content: string): boolean {
62
+ return content.includes(EDITABLE_GENERATED_MARKER);
63
+ }
64
+
31
65
  export interface StripGeneratedMarkerResult {
32
66
  changed: boolean;
33
67
  content: string;
@@ -112,9 +146,9 @@ export interface GeneratedHeaderInput {
112
146
  editable?: boolean;
113
147
  }
114
148
 
115
- type GeneratedHeaderCommentStyle = "line-slash" | "line-hash" | "block-html" | "block-css";
149
+ export type GeneratedHeaderCommentStyle = "line-slash" | "line-hash" | "block-html" | "block-css";
116
150
 
117
- function commentStyleForPath(filePath: string): GeneratedHeaderCommentStyle {
151
+ export function commentStyleForPath(filePath: string): GeneratedHeaderCommentStyle {
118
152
  const lower = filePath.toLowerCase().replace(/\\/g, "/");
119
153
  const base = lower.slice(lower.lastIndexOf("/") + 1);
120
154
  if (