groundwork-method 0.0.1 → 0.10.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 (629) hide show
  1. package/CHANGELOG.md +781 -0
  2. package/LICENSE +21 -0
  3. package/README.md +44 -29
  4. package/bin/groundwork.js +1654 -0
  5. package/dist/src/generators/add-capability/generator.d.ts +8 -0
  6. package/dist/src/generators/add-capability/generator.js +60 -0
  7. package/dist/src/generators/add-capability/generator.js.map +1 -0
  8. package/dist/src/generators/cli-app/generator.d.ts +9 -0
  9. package/dist/src/generators/cli-app/generator.js +140 -0
  10. package/dist/src/generators/cli-app/generator.js.map +1 -0
  11. package/dist/src/generators/docs-site/generator.d.ts +5 -0
  12. package/dist/src/generators/docs-site/generator.js +441 -0
  13. package/dist/src/generators/docs-site/generator.js.map +1 -0
  14. package/dist/src/generators/electron-app/generator.d.ts +6 -0
  15. package/dist/src/generators/electron-app/generator.js +261 -0
  16. package/dist/src/generators/electron-app/generator.js.map +1 -0
  17. package/dist/src/generators/flutter-app/generator.d.ts +6 -0
  18. package/dist/src/generators/flutter-app/generator.js +314 -0
  19. package/dist/src/generators/flutter-app/generator.js.map +1 -0
  20. package/dist/src/generators/go-microservice/generator.d.ts +8 -0
  21. package/dist/src/generators/go-microservice/generator.js +232 -0
  22. package/dist/src/generators/go-microservice/generator.js.map +1 -0
  23. package/dist/src/generators/nextjs-app/generator.d.ts +8 -0
  24. package/dist/src/generators/nextjs-app/generator.js +294 -0
  25. package/dist/src/generators/nextjs-app/generator.js.map +1 -0
  26. package/dist/src/generators/python-microservice/generator.d.ts +13 -0
  27. package/dist/src/generators/python-microservice/generator.js +265 -0
  28. package/dist/src/generators/python-microservice/generator.js.map +1 -0
  29. package/dist/src/generators/shared/brand-tokens.d.ts +89 -0
  30. package/dist/src/generators/shared/brand-tokens.js +308 -0
  31. package/dist/src/generators/shared/brand-tokens.js.map +1 -0
  32. package/dist/src/generators/shared/capabilities.d.ts +101 -0
  33. package/dist/src/generators/shared/capabilities.js +279 -0
  34. package/dist/src/generators/shared/capabilities.js.map +1 -0
  35. package/dist/src/generators/shared/provenance.d.ts +2 -0
  36. package/dist/src/generators/shared/provenance.js +85 -0
  37. package/dist/src/generators/shared/provenance.js.map +1 -0
  38. package/dist/src/generators/shared/scaffold-helpers.d.ts +72 -0
  39. package/dist/src/generators/shared/scaffold-helpers.js +309 -0
  40. package/dist/src/generators/shared/scaffold-helpers.js.map +1 -0
  41. package/dist/src/generators/system-test-runner/generator.d.ts +23 -0
  42. package/dist/src/generators/system-test-runner/generator.js +125 -0
  43. package/dist/src/generators/system-test-runner/generator.js.map +1 -0
  44. package/dist/src/generators/workspace-dev-cli/generator.d.ts +7 -0
  45. package/dist/src/generators/workspace-dev-cli/generator.js +138 -0
  46. package/dist/src/generators/workspace-dev-cli/generator.js.map +1 -0
  47. package/generators.json +57 -0
  48. package/lib/repo-map/grammars/tree-sitter-c.wasm +0 -0
  49. package/lib/repo-map/grammars/tree-sitter-cpp.wasm +0 -0
  50. package/lib/repo-map/grammars/tree-sitter-csharp.wasm +0 -0
  51. package/lib/repo-map/grammars/tree-sitter-dart.wasm +0 -0
  52. package/lib/repo-map/grammars/tree-sitter-go.wasm +0 -0
  53. package/lib/repo-map/grammars/tree-sitter-java.wasm +0 -0
  54. package/lib/repo-map/grammars/tree-sitter-javascript.wasm +0 -0
  55. package/lib/repo-map/grammars/tree-sitter-kotlin.wasm +0 -0
  56. package/lib/repo-map/grammars/tree-sitter-lua.wasm +0 -0
  57. package/lib/repo-map/grammars/tree-sitter-php.wasm +0 -0
  58. package/lib/repo-map/grammars/tree-sitter-python.wasm +0 -0
  59. package/lib/repo-map/grammars/tree-sitter-ruby.wasm +0 -0
  60. package/lib/repo-map/grammars/tree-sitter-rust.wasm +0 -0
  61. package/lib/repo-map/grammars/tree-sitter-scala.wasm +0 -0
  62. package/lib/repo-map/grammars/tree-sitter-swift.wasm +0 -0
  63. package/lib/repo-map/grammars/tree-sitter-tsx.wasm +0 -0
  64. package/lib/repo-map/grammars/tree-sitter-typescript.wasm +0 -0
  65. package/lib/repo-map/index.js +386 -0
  66. package/lib/repo-map/languages.js +514 -0
  67. package/lib/repo-map/pagerank.js +59 -0
  68. package/migrations/README.md +60 -0
  69. package/migrations/_template/cli-migration.js +27 -0
  70. package/migrations/gw-bet-prose-redesign.js +105 -0
  71. package/migrations/gw-drop-test-manifest.js +37 -0
  72. package/migrations/gw-register-serena-mcp.js +42 -0
  73. package/migrations/gw-relocate-hidden-skills.js +40 -0
  74. package/migrations/gw-seed-config-toml.js +24 -0
  75. package/migrations/index.json +40 -0
  76. package/package.json +70 -6
  77. package/src/AGENTS.md +36 -0
  78. package/src/config/config.toml +30 -0
  79. package/src/config/groundwork-state.json +5 -0
  80. package/src/docs/llms.txt +72 -0
  81. package/src/docs/principles/ai-native/agent-native-systems.md +90 -0
  82. package/src/docs/principles/ai-native/agentic-systems.md +78 -0
  83. package/src/docs/principles/ai-native/ai-engineering.md +100 -0
  84. package/src/docs/principles/ai-native/ai-native-product.md +76 -0
  85. package/src/docs/principles/delivery/cost-engineering.md +89 -0
  86. package/src/docs/principles/delivery/day-2-operational-baseline.md +57 -0
  87. package/src/docs/principles/delivery/devex.md +88 -0
  88. package/src/docs/principles/delivery/platform.md +101 -0
  89. package/src/docs/principles/delivery/progressive-delivery.md +92 -0
  90. package/src/docs/principles/design/ai-native-design.md +73 -0
  91. package/src/docs/principles/design/design-foundations.md +80 -0
  92. package/src/docs/principles/design/design-systems-and-tokens.md +72 -0
  93. package/src/docs/principles/design/interaction-and-motion.md +69 -0
  94. package/src/docs/principles/design/layout-and-space.md +72 -0
  95. package/src/docs/principles/design/usability-and-ux.md +68 -0
  96. package/src/docs/principles/design/visual-design.md +84 -0
  97. package/src/docs/principles/foundations/code-craft.md +86 -0
  98. package/src/docs/principles/foundations/continuous-discovery.md +75 -0
  99. package/src/docs/principles/foundations/documentation.md +102 -0
  100. package/src/docs/principles/foundations/prioritization-and-appetite.md +78 -0
  101. package/src/docs/principles/foundations/product-engineering.md +90 -0
  102. package/src/docs/principles/foundations/product-risks.md +89 -0
  103. package/src/docs/principles/foundations/requirements-and-specs.md +80 -0
  104. package/src/docs/principles/foundations/success-metrics.md +66 -0
  105. package/src/docs/principles/foundations/testing.md +82 -0
  106. package/src/docs/principles/index.md +23 -0
  107. package/src/docs/principles/quality/accessibility.md +88 -0
  108. package/src/docs/principles/quality/observability.md +84 -0
  109. package/src/docs/principles/quality/performance.md +84 -0
  110. package/src/docs/principles/quality/privacy.md +92 -0
  111. package/src/docs/principles/quality/reliability.md +89 -0
  112. package/src/docs/principles/quality/security.md +78 -0
  113. package/src/docs/principles/stack/postgres.md +100 -0
  114. package/src/docs/principles/system-design/api-design.md +86 -0
  115. package/src/docs/principles/system-design/architecture-decisions.md +81 -0
  116. package/src/docs/principles/system-design/code-structure.md +104 -0
  117. package/src/docs/principles/system-design/data-engineering.md +87 -0
  118. package/src/docs/principles/system-design/durable-execution.md +89 -0
  119. package/src/docs/principles/system-design/evolutionary-architecture.md +81 -0
  120. package/src/docs/principles/system-design/identity-and-access.md +76 -0
  121. package/src/docs/principles/system-design/integration-patterns.md +84 -0
  122. package/src/docs/principles/system-design/real-time.md +83 -0
  123. package/src/docs/principles/system-design/surface-architecture.md +74 -0
  124. package/src/docs/ways-of-working/documentation.md +69 -0
  125. package/src/docs/ways-of-working/how-we-work.md +76 -0
  126. package/src/docs/ways-of-working/units-of-work.md +40 -0
  127. package/src/engineer-skills/groundwork-electron-engineer/SKILL.md +118 -0
  128. package/src/engineer-skills/groundwork-electron-engineer/references/ipc-contracts.md +138 -0
  129. package/src/engineer-skills/groundwork-electron-engineer/references/packaging-and-updates.md +82 -0
  130. package/src/engineer-skills/groundwork-electron-engineer/references/process-model.md +94 -0
  131. package/src/engineer-skills/groundwork-electron-engineer/references/security.md +107 -0
  132. package/src/engineer-skills/groundwork-electron-engineer/references/testing-and-smoke.md +107 -0
  133. package/src/engineer-skills/groundwork-electron-engineer/references/theming-and-tokens.md +74 -0
  134. package/src/engineer-skills/groundwork-electron-engineer/sync-anchor.md +14 -0
  135. package/src/engineer-skills/groundwork-flutter-engineer/SKILL.md +108 -0
  136. package/src/engineer-skills/groundwork-flutter-engineer/references/accessibility.md +92 -0
  137. package/src/engineer-skills/groundwork-flutter-engineer/references/architecture.md +189 -0
  138. package/src/engineer-skills/groundwork-flutter-engineer/references/data-and-contracts.md +136 -0
  139. package/src/engineer-skills/groundwork-flutter-engineer/references/navigation.md +122 -0
  140. package/src/engineer-skills/groundwork-flutter-engineer/references/platform-channels.md +93 -0
  141. package/src/engineer-skills/groundwork-flutter-engineer/references/releases-and-distribution.md +84 -0
  142. package/src/engineer-skills/groundwork-flutter-engineer/references/state-management.md +166 -0
  143. package/src/engineer-skills/groundwork-flutter-engineer/references/testing.md +135 -0
  144. package/src/engineer-skills/groundwork-flutter-engineer/references/theming-and-design-tokens.md +109 -0
  145. package/src/engineer-skills/groundwork-flutter-engineer/references/widgets-and-composition.md +123 -0
  146. package/src/engineer-skills/groundwork-flutter-engineer/sync-anchor.md +15 -0
  147. package/src/engineer-skills/groundwork-go-engineer/SKILL.md +171 -0
  148. package/src/engineer-skills/groundwork-go-engineer/references/api-design.md +82 -0
  149. package/src/engineer-skills/groundwork-go-engineer/references/architecture.md +42 -0
  150. package/src/engineer-skills/groundwork-go-engineer/references/capability-ports.md +50 -0
  151. package/src/engineer-skills/groundwork-go-engineer/references/code-craft-security.md +34 -0
  152. package/src/engineer-skills/groundwork-go-engineer/references/concurrency.md +108 -0
  153. package/src/engineer-skills/groundwork-go-engineer/references/go-services.md +77 -0
  154. package/src/engineer-skills/groundwork-go-engineer/references/http-handlers.md +172 -0
  155. package/src/engineer-skills/groundwork-go-engineer/references/implementation-patterns.md +156 -0
  156. package/src/engineer-skills/groundwork-go-engineer/references/integration-realtime-data.md +57 -0
  157. package/src/engineer-skills/groundwork-go-engineer/references/observability.md +49 -0
  158. package/src/engineer-skills/groundwork-go-engineer/references/postgres.md +41 -0
  159. package/src/engineer-skills/groundwork-go-engineer/references/reliability-performance.md +105 -0
  160. package/src/engineer-skills/groundwork-go-engineer/references/testing.md +139 -0
  161. package/src/engineer-skills/groundwork-go-engineer/sync-anchor.md +11 -0
  162. package/src/engineer-skills/groundwork-nextjs-engineer/SKILL.md +107 -0
  163. package/src/engineer-skills/groundwork-nextjs-engineer/references/architecture.md +323 -0
  164. package/src/engineer-skills/groundwork-nextjs-engineer/references/data-fetching.md +458 -0
  165. package/src/engineer-skills/groundwork-nextjs-engineer/references/documentation.md +324 -0
  166. package/src/engineer-skills/groundwork-nextjs-engineer/references/error-boundaries.md +383 -0
  167. package/src/engineer-skills/groundwork-nextjs-engineer/references/mutations-and-forms.md +396 -0
  168. package/src/engineer-skills/groundwork-nextjs-engineer/references/performance-and-deployment.md +947 -0
  169. package/src/engineer-skills/groundwork-nextjs-engineer/references/routing-and-navigation.md +405 -0
  170. package/src/engineer-skills/groundwork-nextjs-engineer/references/server-components.md +394 -0
  171. package/src/engineer-skills/groundwork-nextjs-engineer/references/tailwind-and-styling.md +134 -0
  172. package/src/engineer-skills/groundwork-nextjs-engineer/references/testing.md +433 -0
  173. package/src/engineer-skills/groundwork-nextjs-engineer/references/type-system.md +368 -0
  174. package/src/engineer-skills/groundwork-nextjs-engineer/references/ux-principles.md +278 -0
  175. package/src/engineer-skills/groundwork-nextjs-engineer/references/visual-language.md +69 -0
  176. package/src/engineer-skills/groundwork-nextjs-engineer/sync-anchor.md +9 -0
  177. package/src/engineer-skills/groundwork-python-engineer/SKILL.md +196 -0
  178. package/src/engineer-skills/groundwork-python-engineer/references/api-standards.md +88 -0
  179. package/src/engineer-skills/groundwork-python-engineer/references/architecture.md +57 -0
  180. package/src/engineer-skills/groundwork-python-engineer/references/async-patterns.md +103 -0
  181. package/src/engineer-skills/groundwork-python-engineer/references/capability-ports.md +44 -0
  182. package/src/engineer-skills/groundwork-python-engineer/references/database.md +88 -0
  183. package/src/engineer-skills/groundwork-python-engineer/references/documentation-mcp.md +167 -0
  184. package/src/engineer-skills/groundwork-python-engineer/references/implementation-patterns.md +166 -0
  185. package/src/engineer-skills/groundwork-python-engineer/references/ml-pipelines.md +119 -0
  186. package/src/engineer-skills/groundwork-python-engineer/references/ml-systems-ai-engineering.md +74 -0
  187. package/src/engineer-skills/groundwork-python-engineer/references/observability.md +57 -0
  188. package/src/engineer-skills/groundwork-python-engineer/references/resilience.md +126 -0
  189. package/src/engineer-skills/groundwork-python-engineer/references/testing.md +177 -0
  190. package/src/engineer-skills/groundwork-python-engineer/sync-anchor.md +13 -0
  191. package/src/generators/add-capability/generator.ts +70 -0
  192. package/src/generators/add-capability/schema.json +30 -0
  193. package/src/generators/capabilities/llm/capability.json +28 -0
  194. package/src/generators/capabilities/llm/providers/anthropic/footprint.json +13 -0
  195. package/src/generators/capabilities/llm/providers/anthropic/stacks/go/internal/llm/llm.go.template +102 -0
  196. package/src/generators/capabilities/llm/providers/anthropic/stacks/python/src/__packageName__/adapters/llm.py.template +61 -0
  197. package/src/generators/capabilities/llm/providers/local/footprint.json +13 -0
  198. package/src/generators/capabilities/llm/providers/local/stacks/go/internal/llm/llm.go.template +102 -0
  199. package/src/generators/capabilities/llm/providers/local/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
  200. package/src/generators/capabilities/llm/providers/localai/footprint.json +29 -0
  201. package/src/generators/capabilities/llm/providers/localai/stacks/go/internal/llm/llm.go.template +102 -0
  202. package/src/generators/capabilities/llm/providers/localai/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
  203. package/src/generators/capabilities/llm/providers/none/footprint.json +9 -0
  204. package/src/generators/capabilities/llm/providers/none/stacks/go/internal/llm/llm.go.template +35 -0
  205. package/src/generators/capabilities/llm/providers/none/stacks/python/src/__packageName__/adapters/llm.py.template +25 -0
  206. package/src/generators/capabilities/llm/providers/ollama/footprint.json +20 -0
  207. package/src/generators/capabilities/llm/providers/ollama/stacks/go/internal/llm/llm.go.template +102 -0
  208. package/src/generators/capabilities/llm/providers/ollama/stacks/python/src/__packageName__/adapters/llm.py.template +53 -0
  209. package/src/generators/capabilities/llm/providers/openai/footprint.json +13 -0
  210. package/src/generators/capabilities/llm/providers/openai/stacks/go/internal/llm/llm.go.template +98 -0
  211. package/src/generators/capabilities/llm/providers/openai/stacks/python/src/__packageName__/adapters/llm.py.template +60 -0
  212. package/src/generators/capabilities/llm/stacks/go/internal/core/service/llm.go.template +12 -0
  213. package/src/generators/capabilities/llm/stacks/go/internal/llm/llm_test.go.template +33 -0
  214. package/src/generators/capabilities/llm/stacks/python/src/__packageName__/core/llm.py.template +15 -0
  215. package/src/generators/capabilities/llm/stacks/python/tests/contracts/test_llm.py.template +37 -0
  216. package/src/generators/cli-app/files/README.md.template +76 -0
  217. package/src/generators/cli-app/files/build.mjs.template +15 -0
  218. package/src/generators/cli-app/files/package.json.template +21 -0
  219. package/src/generators/cli-app/files/src/cli.ts.template +67 -0
  220. package/src/generators/cli-app/files/src/commands/hello.ts.template +17 -0
  221. package/src/generators/cli-app/files/src/commands/status.ts.template +23 -0
  222. package/src/generators/cli-app/files/src/core/client.test.ts.template +80 -0
  223. package/src/generators/cli-app/files/src/core/client.ts.template +64 -0
  224. package/src/generators/cli-app/files/src/registry.test.ts.template +35 -0
  225. package/src/generators/cli-app/files/src/registry.ts.template +31 -0
  226. package/src/generators/cli-app/files/tsconfig.json.template +16 -0
  227. package/src/generators/cli-app/files/tsconfig.test.json.template +11 -0
  228. package/src/generators/cli-app/generator.ts +138 -0
  229. package/src/generators/cli-app/schema.json +24 -0
  230. package/src/generators/docs-site/files/.gitignore.ejs +40 -0
  231. package/src/generators/docs-site/files/app/docs/__slug__/page.tsx +101 -0
  232. package/src/generators/docs-site/files/app/docs/layout.tsx +14 -0
  233. package/src/generators/docs-site/files/app/docs.css +43 -0
  234. package/src/generators/docs-site/files/app/layout.tsx +24 -0
  235. package/src/generators/docs-site/files/app/page.tsx +135 -0
  236. package/src/generators/docs-site/files/app/source.ts +8 -0
  237. package/src/generators/docs-site/files/components/mermaid.tsx +67 -0
  238. package/src/generators/docs-site/files/next.config.mjs +10 -0
  239. package/src/generators/docs-site/files/package.json +32 -0
  240. package/src/generators/docs-site/files/pnpm-workspace.yaml +7 -0
  241. package/src/generators/docs-site/files/postcss.config.mjs +6 -0
  242. package/src/generators/docs-site/files/source.config.ts +77 -0
  243. package/src/generators/docs-site/files/tailwind.config.js +10 -0
  244. package/src/generators/docs-site/files/tsconfig.json +27 -0
  245. package/src/generators/docs-site/generator.ts +476 -0
  246. package/src/generators/docs-site/schema.json +17 -0
  247. package/src/generators/electron-app/docs/principles/stack/electron/index.md +47 -0
  248. package/src/generators/electron-app/docs/principles/stack/electron/ipc-contracts.md +71 -0
  249. package/src/generators/electron-app/docs/principles/stack/electron/packaging-and-updates.md +59 -0
  250. package/src/generators/electron-app/docs/principles/stack/electron/process-model.md +53 -0
  251. package/src/generators/electron-app/docs/principles/stack/electron/security.md +70 -0
  252. package/src/generators/electron-app/docs/principles/stack/typescript/frontend.md +65 -0
  253. package/src/generators/electron-app/files/.gitignore.template +20 -0
  254. package/src/generators/electron-app/files/README.md.template +125 -0
  255. package/src/generators/electron-app/files/electron.vite.config.ts +31 -0
  256. package/src/generators/electron-app/files/eslint.config.mjs +92 -0
  257. package/src/generators/electron-app/files/forge.config.ts.template +44 -0
  258. package/src/generators/electron-app/files/package.json.template +54 -0
  259. package/src/generators/electron-app/files/playwright.config.ts +18 -0
  260. package/src/generators/electron-app/files/project.json.template +65 -0
  261. package/src/generators/electron-app/files/src/main/core-client.test.ts +81 -0
  262. package/src/generators/electron-app/files/src/main/core-client.ts +55 -0
  263. package/src/generators/electron-app/files/src/main/index.ts +157 -0
  264. package/src/generators/electron-app/files/src/main/ipc.ts +52 -0
  265. package/src/generators/electron-app/files/src/main/policy.test.ts +71 -0
  266. package/src/generators/electron-app/files/src/main/policy.ts +73 -0
  267. package/src/generators/electron-app/files/src/preload/index.ts +23 -0
  268. package/src/generators/electron-app/files/src/renderer/index.html.template +20 -0
  269. package/src/generators/electron-app/files/src/renderer/src/App.test.tsx +61 -0
  270. package/src/generators/electron-app/files/src/renderer/src/App.tsx.template +43 -0
  271. package/src/generators/electron-app/files/src/renderer/src/assets/main.css +40 -0
  272. package/src/generators/electron-app/files/src/renderer/src/env.d.ts +14 -0
  273. package/src/generators/electron-app/files/src/renderer/src/main.tsx +25 -0
  274. package/src/generators/electron-app/files/src/shared/ipc.ts +54 -0
  275. package/src/generators/electron-app/files/tests/smoke/app.spec.ts.template +68 -0
  276. package/src/generators/electron-app/files/tool/electron_exec.sh.template +83 -0
  277. package/src/generators/electron-app/files/tsconfig.json +7 -0
  278. package/src/generators/electron-app/files/tsconfig.node.json +27 -0
  279. package/src/generators/electron-app/files/tsconfig.web.json +22 -0
  280. package/src/generators/electron-app/files/vitest.config.ts +32 -0
  281. package/src/generators/electron-app/files/vitest.setup.ts +1 -0
  282. package/src/generators/electron-app/generator.ts +288 -0
  283. package/src/generators/electron-app/schema.json +23 -0
  284. package/src/generators/flutter-app/docs/principles/stack/flutter/architecture.md +78 -0
  285. package/src/generators/flutter-app/docs/principles/stack/flutter/index.md +38 -0
  286. package/src/generators/flutter-app/docs/principles/stack/flutter/platform-channels.md +51 -0
  287. package/src/generators/flutter-app/docs/principles/stack/flutter/releases-and-distribution.md +59 -0
  288. package/src/generators/flutter-app/docs/principles/stack/flutter/state-management.md +85 -0
  289. package/src/generators/flutter-app/docs/principles/stack/flutter/testing.md +74 -0
  290. package/src/generators/flutter-app/docs/principles/stack/flutter/widgets-and-composition.md +69 -0
  291. package/src/generators/flutter-app/files/.gitignore.template +30 -0
  292. package/src/generators/flutter-app/files/README.md.template +100 -0
  293. package/src/generators/flutter-app/files/analysis_options.yaml.template +18 -0
  294. package/src/generators/flutter-app/files/integration_test/app_test.dart.template +30 -0
  295. package/src/generators/flutter-app/files/lib/app.dart.template +24 -0
  296. package/src/generators/flutter-app/files/lib/config/app_config.dart +15 -0
  297. package/src/generators/flutter-app/files/lib/data/repositories/status_repository.dart +36 -0
  298. package/src/generators/flutter-app/files/lib/data/services/api_client.dart +71 -0
  299. package/src/generators/flutter-app/files/lib/domain/models/health_status.dart +23 -0
  300. package/src/generators/flutter-app/files/lib/main.dart +11 -0
  301. package/src/generators/flutter-app/files/lib/router.dart +23 -0
  302. package/src/generators/flutter-app/files/lib/ui/core/theme/app_theme.dart +110 -0
  303. package/src/generators/flutter-app/files/lib/ui/home/home_view.dart +89 -0
  304. package/src/generators/flutter-app/files/lib/ui/home/home_view_model.dart.template +38 -0
  305. package/src/generators/flutter-app/files/project.json.template +51 -0
  306. package/src/generators/flutter-app/files/pubspec.yaml.template +47 -0
  307. package/src/generators/flutter-app/files/test/api_client_test.dart.template +63 -0
  308. package/src/generators/flutter-app/files/test/fakes/fake_status_repository.dart.template +19 -0
  309. package/src/generators/flutter-app/files/test/home_view_test.dart.template +58 -0
  310. package/src/generators/flutter-app/files/tool/flutter_exec.sh.template +60 -0
  311. package/src/generators/flutter-app/generator.ts +362 -0
  312. package/src/generators/flutter-app/schema.json +23 -0
  313. package/src/generators/go-microservice/docs/principles/stack/go/concurrency.md +123 -0
  314. package/src/generators/go-microservice/docs/principles/stack/go/index.md +70 -0
  315. package/src/generators/go-microservice/docs/principles/stack/go/testing.md +152 -0
  316. package/src/generators/go-microservice/files/.air.toml.template +38 -0
  317. package/src/generators/go-microservice/files/.env.template +4 -0
  318. package/src/generators/go-microservice/files/.golangci.yml.template +82 -0
  319. package/src/generators/go-microservice/files/Dockerfile.dev.template +12 -0
  320. package/src/generators/go-microservice/files/asyncapi-pubsub.yaml.template +33 -0
  321. package/src/generators/go-microservice/files/asyncapi-ws.yaml.template +34 -0
  322. package/src/generators/go-microservice/files/cmd/api/main.go.template +149 -0
  323. package/src/generators/go-microservice/files/cmd/api/main_test.go.template +99 -0
  324. package/src/generators/go-microservice/files/cmd/worker/cleanup/main.go.template +39 -0
  325. package/src/generators/go-microservice/files/db/schema.sql.template +24 -0
  326. package/src/generators/go-microservice/files/go.mod.template +39 -0
  327. package/src/generators/go-microservice/files/internal/config/config.go.template +52 -0
  328. package/src/generators/go-microservice/files/internal/config/otel.go.template +93 -0
  329. package/src/generators/go-microservice/files/internal/core/domain/errors.go.template +16 -0
  330. package/src/generators/go-microservice/files/internal/core/domain/model.go.template +28 -0
  331. package/src/generators/go-microservice/files/internal/core/domain/user.go.template +13 -0
  332. package/src/generators/go-microservice/files/internal/core/pagination.go.template +16 -0
  333. package/src/generators/go-microservice/files/internal/core/service/app_service.go.template +79 -0
  334. package/src/generators/go-microservice/files/internal/core/service/event_hub.go.template +9 -0
  335. package/src/generators/go-microservice/files/internal/core/service/message_queue.go.template +10 -0
  336. package/src/generators/go-microservice/files/internal/core/service/outbox_repository.go.template +31 -0
  337. package/src/generators/go-microservice/files/internal/core/service/repository.go.template +23 -0
  338. package/src/generators/go-microservice/files/internal/core/service/user_repository.go.template +15 -0
  339. package/src/generators/go-microservice/files/internal/core/service/user_service.go.template +43 -0
  340. package/src/generators/go-microservice/files/internal/entrypoints/api/app_handler.go.template +108 -0
  341. package/src/generators/go-microservice/files/internal/entrypoints/api/auth_middleware_test.go.template +52 -0
  342. package/src/generators/go-microservice/files/internal/entrypoints/api/clerk_webhook.go.template +202 -0
  343. package/src/generators/go-microservice/files/internal/entrypoints/api/clerk_webhook_test.go.template +82 -0
  344. package/src/generators/go-microservice/files/internal/entrypoints/api/health_handler.go.template +80 -0
  345. package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/middleware.go.template +87 -0
  346. package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/middleware_test.go.template +76 -0
  347. package/src/generators/go-microservice/files/internal/entrypoints/api/idempotency/repository.go.template +37 -0
  348. package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_auth.go.template +40 -0
  349. package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_loadshed.go.template +38 -0
  350. package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_logging.go.template +40 -0
  351. package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_ratelimit.go.template +48 -0
  352. package/src/generators/go-microservice/files/internal/entrypoints/api/middleware_test.go.template +81 -0
  353. package/src/generators/go-microservice/files/internal/entrypoints/api/router.go.template +105 -0
  354. package/src/generators/go-microservice/files/internal/entrypoints/api/types.go.template +70 -0
  355. package/src/generators/go-microservice/files/internal/entrypoints/api/websocket_handler.go.template +39 -0
  356. package/src/generators/go-microservice/files/internal/httpclient/http_client.go.template +87 -0
  357. package/src/generators/go-microservice/files/internal/kafka/kafka.go.template +34 -0
  358. package/src/generators/go-microservice/files/internal/postgres/postgres.go.template +195 -0
  359. package/src/generators/go-microservice/files/internal/postgres/postgres_test.go.template +156 -0
  360. package/src/generators/go-microservice/files/internal/postgres/user_repository.go.template +56 -0
  361. package/src/generators/go-microservice/files/internal/pubsub/gcp_pubsub.go.template +35 -0
  362. package/src/generators/go-microservice/files/internal/websocket/client.go.template +151 -0
  363. package/src/generators/go-microservice/files/internal/websocket/hub.go.template +261 -0
  364. package/src/generators/go-microservice/files/scripts/apply-schema.sh.template +21 -0
  365. package/src/generators/go-microservice/files/tools/tools.go.template +10 -0
  366. package/src/generators/go-microservice/generator.ts +240 -0
  367. package/src/generators/go-microservice/schema.json +63 -0
  368. package/src/generators/nextjs-app/docs/principles/stack/typescript/frontend.md +65 -0
  369. package/src/generators/nextjs-app/files/.dockerignore.template +7 -0
  370. package/src/generators/nextjs-app/files/.env.example.template +24 -0
  371. package/src/generators/nextjs-app/files/.gitignore.template +5 -0
  372. package/src/generators/nextjs-app/files/Dockerfile +53 -0
  373. package/src/generators/nextjs-app/files/app/(auth)/sign-in/__sign-in__/page.tsx.template +9 -0
  374. package/src/generators/nextjs-app/files/app/(auth)/sign-up/__sign-up__/page.tsx.template +9 -0
  375. package/src/generators/nextjs-app/files/app/api/config/route.ts.template +39 -0
  376. package/src/generators/nextjs-app/files/app/api/healthz/route.test.ts +15 -0
  377. package/src/generators/nextjs-app/files/app/api/healthz/route.ts +5 -0
  378. package/src/generators/nextjs-app/files/app/api/proxy/__path__/route.test.ts.template +55 -0
  379. package/src/generators/nextjs-app/files/app/api/proxy/__path__/route.ts.template +126 -0
  380. package/src/generators/nextjs-app/files/app/error.tsx +39 -0
  381. package/src/generators/nextjs-app/files/app/global-error.tsx +68 -0
  382. package/src/generators/nextjs-app/files/app/globals.css +105 -0
  383. package/src/generators/nextjs-app/files/app/layout.tsx +59 -0
  384. package/src/generators/nextjs-app/files/app/loading.tsx +13 -0
  385. package/src/generators/nextjs-app/files/app/not-found.tsx +30 -0
  386. package/src/generators/nextjs-app/files/app/page.tsx +20 -0
  387. package/src/generators/nextjs-app/files/components/providers/default.tsx +19 -0
  388. package/src/generators/nextjs-app/files/components/providers/production.tsx +32 -0
  389. package/src/generators/nextjs-app/files/components/providers/telemetry.tsx +76 -0
  390. package/src/generators/nextjs-app/files/components/render-smoke.test.tsx +29 -0
  391. package/src/generators/nextjs-app/files/components/theme-provider.tsx +11 -0
  392. package/src/generators/nextjs-app/files/components.json +21 -0
  393. package/src/generators/nextjs-app/files/eslint.config.mjs +120 -0
  394. package/src/generators/nextjs-app/files/hooks/use-toast.ts +7 -0
  395. package/src/generators/nextjs-app/files/instrumentation.ts +90 -0
  396. package/src/generators/nextjs-app/files/lib/api/fetcher.ts.template +130 -0
  397. package/src/generators/nextjs-app/files/lib/config.ts +21 -0
  398. package/src/generators/nextjs-app/files/lib/logger.ts +29 -0
  399. package/src/generators/nextjs-app/files/lib/schemas/index.ts +19 -0
  400. package/src/generators/nextjs-app/files/lib/utils.ts +6 -0
  401. package/src/generators/nextjs-app/files/next.config.mjs +9 -0
  402. package/src/generators/nextjs-app/files/package.json +70 -0
  403. package/src/generators/nextjs-app/files/postcss.config.mjs +8 -0
  404. package/src/generators/nextjs-app/files/proxy.test.ts.template +30 -0
  405. package/src/generators/nextjs-app/files/proxy.ts +31 -0
  406. package/src/generators/nextjs-app/files/public/.gitkeep +1 -0
  407. package/src/generators/nextjs-app/files/tsconfig.json +42 -0
  408. package/src/generators/nextjs-app/files/vitest.config.mts +15 -0
  409. package/src/generators/nextjs-app/files/vitest.setup.ts +7 -0
  410. package/src/generators/nextjs-app/generator.ts +307 -0
  411. package/src/generators/nextjs-app/schema.json +44 -0
  412. package/src/generators/python-microservice/docs/principles/stack/python/async.md +168 -0
  413. package/src/generators/python-microservice/docs/principles/stack/python/documentation.md +240 -0
  414. package/src/generators/python-microservice/docs/principles/stack/python/mcp.md +147 -0
  415. package/src/generators/python-microservice/docs/principles/stack/python/resilience.md +193 -0
  416. package/src/generators/python-microservice/docs/principles/stack/python/testing.md +281 -0
  417. package/src/generators/python-microservice/files/.env.example.template +30 -0
  418. package/src/generators/python-microservice/files/Dockerfile.template +36 -0
  419. package/src/generators/python-microservice/files/db/schema.sql.template +19 -0
  420. package/src/generators/python-microservice/files/pyproject.toml.template +76 -0
  421. package/src/generators/python-microservice/files/scripts/apply-schema.sh.template +25 -0
  422. package/src/generators/python-microservice/files/src/__packageName__/adapters/comfyui.py.template +87 -0
  423. package/src/generators/python-microservice/files/src/__packageName__/adapters/config.py.template +48 -0
  424. package/src/generators/python-microservice/files/src/__packageName__/adapters/database.py.template +21 -0
  425. package/src/generators/python-microservice/files/src/__packageName__/adapters/message_queue.py.template +29 -0
  426. package/src/generators/python-microservice/files/src/__packageName__/adapters/repository.py.template +130 -0
  427. package/src/generators/python-microservice/files/src/__packageName__/adapters/telemetry.py.template +68 -0
  428. package/src/generators/python-microservice/files/src/__packageName__/adapters/websocket_hub.py.template +36 -0
  429. package/src/generators/python-microservice/files/src/__packageName__/core/domain/entities.py.template +22 -0
  430. package/src/generators/python-microservice/files/src/__packageName__/core/domain/exceptions.py.template +43 -0
  431. package/src/generators/python-microservice/files/src/__packageName__/core/ports.py.template +42 -0
  432. package/src/generators/python-microservice/files/src/__packageName__/core/service/example_service.py.template +68 -0
  433. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/dependencies.py.template +50 -0
  434. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/middleware.py.template +131 -0
  435. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/router.py.template +37 -0
  436. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/api/websocket_handler.py.template +20 -0
  437. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/worker/cleanup.py.template +35 -0
  438. package/src/generators/python-microservice/files/src/__packageName__/entrypoints/worker/worker.py.template +28 -0
  439. package/src/generators/python-microservice/files/src/__packageName__/main.py.template +108 -0
  440. package/src/generators/python-microservice/files/tests/test_main.py.template +74 -0
  441. package/src/generators/python-microservice/files/tests/test_middleware.py.template +109 -0
  442. package/src/generators/python-microservice/files/tests/test_worker.py.template +16 -0
  443. package/src/generators/python-microservice/generator.ts +286 -0
  444. package/src/generators/python-microservice/schema.json +86 -0
  445. package/src/generators/shared/brand-tokens.ts +301 -0
  446. package/src/generators/shared/capabilities.ts +349 -0
  447. package/src/generators/shared/provenance.ts +61 -0
  448. package/src/generators/shared/scaffold-helpers.ts +309 -0
  449. package/src/generators/system-test-runner/files/tests/bets/.gitkeep +0 -0
  450. package/src/generators/system-test-runner/files/tests/bets/_archive/.gitkeep +0 -0
  451. package/src/generators/system-test-runner/files/tests/conftest.py.template +503 -0
  452. package/src/generators/system-test-runner/files/tests/pyproject.toml.template +20 -0
  453. package/src/generators/system-test-runner/files/tests/system/pages/__init__.py.template +9 -0
  454. package/src/generators/system-test-runner/files/tests/system/pages/base_page.py.template +36 -0
  455. package/src/generators/system-test-runner/files/tests/system/test_a11y_smoke.py.template +132 -0
  456. package/src/generators/system-test-runner/files/tests/system/test_contract_conformance.py.template +140 -0
  457. package/src/generators/system-test-runner/files/tests/system/test_layout_geometry.py.template +109 -0
  458. package/src/generators/system-test-runner/files/tests/system/test_render_smoke.py.template +227 -0
  459. package/src/generators/system-test-runner/files/tests/system/test_system.py.template +158 -0
  460. package/src/generators/system-test-runner/files/tests/system/test_token_conformance.py.template +206 -0
  461. package/src/generators/system-test-runner/files/tests/system/test_visual_regression.py.template +104 -0
  462. package/src/generators/system-test-runner/generator.ts +142 -0
  463. package/src/generators/system-test-runner/schema.json +24 -0
  464. package/src/generators/workspace-dev-cli/cli-src/build.mjs +42 -0
  465. package/src/generators/workspace-dev-cli/cli-src/dist/dev-bundle.js +2168 -0
  466. package/src/generators/workspace-dev-cli/cli-src/src/commands/bet.ts +442 -0
  467. package/src/generators/workspace-dev-cli/cli-src/src/commands/completion.ts +87 -0
  468. package/src/generators/workspace-dev-cli/cli-src/src/commands/doctor.ts +139 -0
  469. package/src/generators/workspace-dev-cli/cli-src/src/commands/lifecycle.ts +548 -0
  470. package/src/generators/workspace-dev-cli/cli-src/src/commands/quality.ts +127 -0
  471. package/src/generators/workspace-dev-cli/cli-src/src/commands/surface.ts +214 -0
  472. package/src/generators/workspace-dev-cli/cli-src/src/index.ts +127 -0
  473. package/src/generators/workspace-dev-cli/cli-src/src/registry.ts +194 -0
  474. package/src/generators/workspace-dev-cli/cli-src/src/theme/color.ts +130 -0
  475. package/src/generators/workspace-dev-cli/cli-src/src/theme/render.ts +158 -0
  476. package/src/generators/workspace-dev-cli/cli-src/src/theme/tokens.ts +122 -0
  477. package/src/generators/workspace-dev-cli/cli-src/src/util/context.ts +43 -0
  478. package/src/generators/workspace-dev-cli/cli-src/src/util/extensions.ts +99 -0
  479. package/src/generators/workspace-dev-cli/cli-src/src/util/paths.ts +46 -0
  480. package/src/generators/workspace-dev-cli/cli-src/src/util/proc.ts +106 -0
  481. package/src/generators/workspace-dev-cli/cli-src/src/util/prompt.ts +108 -0
  482. package/src/generators/workspace-dev-cli/cli-src/src/util/runners.ts +70 -0
  483. package/src/generators/workspace-dev-cli/cli-src/src/util/services.ts +221 -0
  484. package/src/generators/workspace-dev-cli/cli-src/src/util/version.ts +21 -0
  485. package/src/generators/workspace-dev-cli/cli-src/tsconfig.json +16 -0
  486. package/src/generators/workspace-dev-cli/files/.agents/skills/workspace-cli/SKILL.md.template +74 -0
  487. package/src/generators/workspace-dev-cli/files/dev.template +16 -0
  488. package/src/generators/workspace-dev-cli/files/docker-compose.yml.template +20 -0
  489. package/src/generators/workspace-dev-cli/files/scripts/cli/templates/milestone-test.pytmpl.template +46 -0
  490. package/src/generators/workspace-dev-cli/files/scripts/cli/templates/slice-test.pytmpl.template +38 -0
  491. package/src/generators/workspace-dev-cli/generator.ts +136 -0
  492. package/src/generators/workspace-dev-cli/schema.json +22 -0
  493. package/src/hidden-skills/code-intelligence.md +129 -0
  494. package/src/hidden-skills/groundwork-architect/SKILL.md +114 -0
  495. package/src/hidden-skills/groundwork-architect/references/agentic-systems.md +44 -0
  496. package/src/hidden-skills/groundwork-architect/references/ai-native-architecture.md +37 -0
  497. package/src/hidden-skills/groundwork-architect/references/api-and-contracts.md +45 -0
  498. package/src/hidden-skills/groundwork-architect/references/core-and-boundaries.md +45 -0
  499. package/src/hidden-skills/groundwork-architect/references/data-architecture.md +33 -0
  500. package/src/hidden-skills/groundwork-architect/references/decision-records.md +34 -0
  501. package/src/hidden-skills/groundwork-architect/references/durable-execution.md +45 -0
  502. package/src/hidden-skills/groundwork-architect/references/evolutionary-architecture.md +37 -0
  503. package/src/hidden-skills/groundwork-architect/references/identity-and-access.md +41 -0
  504. package/src/hidden-skills/groundwork-architect/references/integration-patterns.md +39 -0
  505. package/src/hidden-skills/groundwork-architect/references/observability.md +36 -0
  506. package/src/hidden-skills/groundwork-architect/references/performance-and-scale.md +41 -0
  507. package/src/hidden-skills/groundwork-architect/references/platform-and-delivery.md +47 -0
  508. package/src/hidden-skills/groundwork-architect/references/realtime-and-async.md +28 -0
  509. package/src/hidden-skills/groundwork-architect/references/reliability.md +31 -0
  510. package/src/hidden-skills/groundwork-architect/references/security-and-trust.md +47 -0
  511. package/src/hidden-skills/groundwork-architect/references/surface-architecture.md +40 -0
  512. package/src/hidden-skills/groundwork-architect/sync-anchor.md +34 -0
  513. package/src/hidden-skills/groundwork-architecture/architecture-template.md +50 -0
  514. package/src/hidden-skills/groundwork-architecture/instructions.md +139 -0
  515. package/src/hidden-skills/groundwork-architecture/phases/01-context-ingestion.md +18 -0
  516. package/src/hidden-skills/groundwork-architecture/phases/02-technical-constraints.md +27 -0
  517. package/src/hidden-skills/groundwork-architecture/phases/03-service-design.md +19 -0
  518. package/src/hidden-skills/groundwork-architecture/phases/04-data-flow-communication.md +23 -0
  519. package/src/hidden-skills/groundwork-architecture/phases/05-component-boundaries-contracts.md +17 -0
  520. package/src/hidden-skills/groundwork-architecture/phases/06-draft-review-present.md +38 -0
  521. package/src/hidden-skills/groundwork-architecture/phases/07-commit.md +33 -0
  522. package/src/hidden-skills/groundwork-architecture/templates/architecture-cache.md +43 -0
  523. package/src/hidden-skills/groundwork-architecture-extract/instructions.md +163 -0
  524. package/src/hidden-skills/groundwork-architecture-extract/templates/architecture-extract-cache.md +21 -0
  525. package/src/hidden-skills/groundwork-bet/briefs/slice-worker.md +191 -0
  526. package/src/hidden-skills/groundwork-bet/instructions.md +88 -0
  527. package/src/hidden-skills/groundwork-bet/templates/bet-progress-test.md +126 -0
  528. package/src/hidden-skills/groundwork-bet/templates/change-proposal.md +38 -0
  529. package/src/hidden-skills/groundwork-bet/templates/decomposition/meta.json +4 -0
  530. package/src/hidden-skills/groundwork-bet/templates/decomposition/milestone-index.md +35 -0
  531. package/src/hidden-skills/groundwork-bet/templates/decomposition/slice.md +35 -0
  532. package/src/hidden-skills/groundwork-bet/templates/pitch.md +45 -0
  533. package/src/hidden-skills/groundwork-bet/templates/technical-design/01-ui-design.md +51 -0
  534. package/src/hidden-skills/groundwork-bet/templates/technical-design/02-data-flows.md +36 -0
  535. package/src/hidden-skills/groundwork-bet/templates/technical-design/03-api-design.md +90 -0
  536. package/src/hidden-skills/groundwork-bet/templates/technical-design/04-data-design.md +29 -0
  537. package/src/hidden-skills/groundwork-bet/workflows/01-discovery.md +198 -0
  538. package/src/hidden-skills/groundwork-bet/workflows/02-design.md +168 -0
  539. package/src/hidden-skills/groundwork-bet/workflows/03-decomposition.md +246 -0
  540. package/src/hidden-skills/groundwork-bet/workflows/04-delivery.md +193 -0
  541. package/src/hidden-skills/groundwork-bet/workflows/05-validation.md +199 -0
  542. package/src/hidden-skills/groundwork-design-system/instructions.md +125 -0
  543. package/src/hidden-skills/groundwork-design-system/templates/brand-tokens.md +182 -0
  544. package/src/hidden-skills/groundwork-design-system/templates/design-system-cache.md +64 -0
  545. package/src/hidden-skills/groundwork-design-system/tracks/_foundation.md +136 -0
  546. package/src/hidden-skills/groundwork-design-system/tracks/agentic-protocol.md +269 -0
  547. package/src/hidden-skills/groundwork-design-system/tracks/cli.md +355 -0
  548. package/src/hidden-skills/groundwork-design-system/tracks/graphical-ui.md +330 -0
  549. package/src/hidden-skills/groundwork-design-system-extract/instructions.md +124 -0
  550. package/src/hidden-skills/groundwork-design-system-extract/templates/design-system-extract-cache.md +19 -0
  551. package/src/hidden-skills/groundwork-designer/SKILL.md +108 -0
  552. package/src/hidden-skills/groundwork-designer/references/accessibility.md +33 -0
  553. package/src/hidden-skills/groundwork-designer/references/ai-native-design.md +37 -0
  554. package/src/hidden-skills/groundwork-designer/references/design-review.md +29 -0
  555. package/src/hidden-skills/groundwork-designer/references/design-systems-and-tokens.md +33 -0
  556. package/src/hidden-skills/groundwork-designer/references/interaction-and-motion.md +37 -0
  557. package/src/hidden-skills/groundwork-designer/references/layout-and-space.md +33 -0
  558. package/src/hidden-skills/groundwork-designer/references/usability-and-ux.md +33 -0
  559. package/src/hidden-skills/groundwork-designer/references/visual-craft.md +49 -0
  560. package/src/hidden-skills/groundwork-designer/sync-anchor.md +20 -0
  561. package/src/hidden-skills/groundwork-doc-sync/instructions.md +100 -0
  562. package/src/hidden-skills/groundwork-elicit/instructions.md +66 -0
  563. package/src/hidden-skills/groundwork-elicit/methods.md +65 -0
  564. package/src/hidden-skills/groundwork-infra-adopt/instructions.md +168 -0
  565. package/src/hidden-skills/groundwork-infra-adopt/templates/infra-adopt-cache.md +21 -0
  566. package/src/hidden-skills/groundwork-mvp/instructions.md +223 -0
  567. package/src/hidden-skills/groundwork-mvp/templates/mvp-cache.md +9 -0
  568. package/src/hidden-skills/groundwork-patch/instructions.md +40 -0
  569. package/src/hidden-skills/groundwork-persona/instructions.md +54 -0
  570. package/src/hidden-skills/groundwork-product/SKILL.md +102 -0
  571. package/src/hidden-skills/groundwork-product/references/ai-native-product.md +45 -0
  572. package/src/hidden-skills/groundwork-product/references/discovery-and-opportunity.md +38 -0
  573. package/src/hidden-skills/groundwork-product/references/product-risks.md +52 -0
  574. package/src/hidden-skills/groundwork-product/references/requirements-and-specs.md +39 -0
  575. package/src/hidden-skills/groundwork-product/references/scope-and-sequencing.md +35 -0
  576. package/src/hidden-skills/groundwork-product/references/shaping-and-appetite.md +48 -0
  577. package/src/hidden-skills/groundwork-product/references/success-metrics-and-signals.md +37 -0
  578. package/src/hidden-skills/groundwork-product/sync-anchor.md +19 -0
  579. package/src/hidden-skills/groundwork-product-brief/instructions.md +231 -0
  580. package/src/hidden-skills/groundwork-product-brief-extract/instructions.md +139 -0
  581. package/src/hidden-skills/groundwork-product-brief-extract/templates/product-brief-extract-cache.md +17 -0
  582. package/src/hidden-skills/groundwork-review/checklists/architecture.md +93 -0
  583. package/src/hidden-skills/groundwork-review/checklists/bet-pitch.md +94 -0
  584. package/src/hidden-skills/groundwork-review/checklists/decomposition.md +135 -0
  585. package/src/hidden-skills/groundwork-review/checklists/design-system.md +85 -0
  586. package/src/hidden-skills/groundwork-review/checklists/domain-entity.md +66 -0
  587. package/src/hidden-skills/groundwork-review/checklists/implementation-readiness.md +46 -0
  588. package/src/hidden-skills/groundwork-review/checklists/infrastructure.md +68 -0
  589. package/src/hidden-skills/groundwork-review/checklists/maturity.md +71 -0
  590. package/src/hidden-skills/groundwork-review/checklists/product-brief.md +69 -0
  591. package/src/hidden-skills/groundwork-review/checklists/technical-design.md +112 -0
  592. package/src/hidden-skills/groundwork-review/instructions.md +181 -0
  593. package/src/hidden-skills/groundwork-scaffold/instructions.md +254 -0
  594. package/src/hidden-skills/groundwork-scaffold/phases/01-ingestion-service-mapping.md +87 -0
  595. package/src/hidden-skills/groundwork-scaffold/phases/02-scaffolding-execution.md +15 -0
  596. package/src/hidden-skills/groundwork-scaffold/phases/03-service-documentation-api-stubs.md +100 -0
  597. package/src/hidden-skills/groundwork-scaffold/phases/04-infrastructure-verification.md +17 -0
  598. package/src/hidden-skills/groundwork-scaffold/phases/05-draft-review.md +19 -0
  599. package/src/hidden-skills/groundwork-scaffold/phases/06-commit.md +19 -0
  600. package/src/hidden-skills/groundwork-scaffold/templates/scaffold-cache.md +23 -0
  601. package/src/hidden-skills/groundwork-scan/instructions.md +164 -0
  602. package/src/hidden-skills/groundwork-scan/references/digest-schema.md +66 -0
  603. package/src/hidden-skills/groundwork-scan/references/exclusions.md +44 -0
  604. package/src/hidden-skills/groundwork-scan/templates/architecture-findings.md +42 -0
  605. package/src/hidden-skills/groundwork-scan/templates/design-findings.md +23 -0
  606. package/src/hidden-skills/groundwork-scan/templates/overview.md +26 -0
  607. package/src/hidden-skills/groundwork-scan/templates/product-findings.md +23 -0
  608. package/src/hidden-skills/groundwork-scan/templates/scan-state.json +19 -0
  609. package/src/hidden-skills/groundwork-stack-forge/instructions.md +150 -0
  610. package/src/hidden-skills/groundwork-stack-forge/references/authoring-engineer-skills.md +107 -0
  611. package/src/hidden-skills/groundwork-surface-activation/instructions.md +138 -0
  612. package/src/hidden-skills/groundwork-update/briefs/reconcile-worker.md +196 -0
  613. package/src/hidden-skills/groundwork-update/instructions.md +200 -0
  614. package/src/hidden-skills/groundwork-writer/SKILL.md +278 -0
  615. package/src/hidden-skills/maturity-model.md +125 -0
  616. package/src/hidden-skills/operating-contract.md +400 -0
  617. package/src/hidden-skills/repo-map-schema.md +90 -0
  618. package/src/hidden-skills/templates/adr.md +57 -0
  619. package/src/hidden-skills/templates/capability-ports.md +71 -0
  620. package/src/hidden-skills/templates/discovery-notes.md +33 -0
  621. package/src/hidden-skills/templates/domain-entity.md +80 -0
  622. package/src/hidden-skills/templates/gap-ledger.md +21 -0
  623. package/src/hidden-skills/templates/handoff.md +37 -0
  624. package/src/hidden-skills/templates/maturity.md +39 -0
  625. package/src/hidden-skills/templates/surfaces.md +207 -0
  626. package/src/skills/groundwork-check/SKILL.md +56 -0
  627. package/src/skills/groundwork-check/instructions.md +70 -0
  628. package/src/skills/groundwork-orchestrator/SKILL.md +176 -0
  629. package/src/skills/groundwork-orchestrator/workflow-index.md +50 -0
@@ -0,0 +1,1654 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const readline = require('readline');
7
+ const { execSync, execFileSync } = require('child_process');
8
+
9
+ const command = process.argv[2];
10
+ const PKG = require(path.join(__dirname, '..', 'package.json'));
11
+
12
+ // ─── Output helpers ─────────────────────────────────────────────────────────
13
+
14
+ const c = {
15
+ ok: (msg) => console.log(`\x1b[32m✔\x1b[0m ${msg}`),
16
+ warn: (msg) => console.warn(`\x1b[33m[warn]\x1b[0m ${msg}`),
17
+ err: (msg) => console.error(`\x1b[31m✖\x1b[0m ${msg}`),
18
+ info: (msg) => console.log(`\x1b[34m[info]\x1b[0m ${msg}`),
19
+ dim: (msg) => console.log(`\x1b[2m${msg}\x1b[0m`),
20
+ };
21
+
22
+ function banner() {
23
+ console.log(`\n\x1b[1m\x1b[36m▲ GroundWork\x1b[0m\n`);
24
+ }
25
+
26
+ function printHelp() {
27
+ banner();
28
+ console.log(`\x1b[1mCommands:\x1b[0m
29
+ \x1b[36minit\x1b[0m Install GroundWork skills, config, and the Serena code-intelligence MCP server into the current project
30
+ \x1b[36mupdate\x1b[0m Bring the install up to this package version: skills, seeded docs, the ./dev bundle,
31
+ and scripted migrations. Judgment-lane work lands in an upgrade brief for your agent.
32
+ \x1b[2m--dry-run prints the full plan without writing anything; --full shows complete changelog entries.\x1b[0m
33
+ \x1b[36mcheck\x1b[0m Report framework staleness (version gap, pending migrations) and documentation drift
34
+ \x1b[36mrepo-map\x1b[0m Build the deterministic code map (.groundwork/cache/repo-map.json): tree-sitter
35
+ import edges + PageRank centrality (Go, Python, TS/JS, Java) plus a symbol index
36
+ for many more languages; extensible per-project. Incremental — only changed files
37
+ reparse. \x1b[2m--check reports staleness without rebuilding.\x1b[0m
38
+ \x1b[36mhelp\x1b[0m Show this message
39
+
40
+ \x1b[1minit flags:\x1b[0m
41
+ \x1b[36m--agent <name>\x1b[0m Wire a specific agent (repeatable, comma-friendly); skips the prompt
42
+ \x1b[36m--yes, -y\x1b[0m Non-interactive: wire the auto-detected agents (or Claude Code)
43
+ \x1b[2mSupported agents: claude-code, cursor, codex, opencode, cline\x1b[0m
44
+
45
+ \x1b[1mExamples:\x1b[0m
46
+ npx groundwork-method init
47
+ npx groundwork-method init --agent claude-code --agent cursor
48
+ npx groundwork-method init --yes
49
+ npx groundwork-method update
50
+ npx groundwork-method check
51
+
52
+ \x1b[1mExit codes (check):\x1b[0m
53
+ 0 documentation is current with the code it describes
54
+ 1 drift detected (stale docs), or check could not run (no git repo, no docs/)
55
+ 2 internal error — git history could not be read for a tracked doc
56
+
57
+ After init, ask your AI agent to run the \x1b[36mgroundwork-orchestrator\x1b[0m skill — it reads project
58
+ state and routes to the next lifecycle step (greenfield discovery, brownfield scan, or the bet loop).
59
+ `);
60
+
61
+ // Print the generated workflow index so `npx groundwork-method help` shows the same
62
+ // lifecycle map the orchestrator's help intent presents.
63
+ const indexPath = path.join(__dirname, '..', 'src', 'skills', 'groundwork-orchestrator', 'workflow-index.md');
64
+ if (fs.existsSync(indexPath)) {
65
+ const body = fs.readFileSync(indexPath, 'utf8').replace(/^<!--[\s\S]*?-->\s*/, '');
66
+ console.log(`\x1b[1mThe lifecycle map:\x1b[0m\n`);
67
+ console.log(body.split('\n').map((l) => ` ${l}`).join('\n'));
68
+ }
69
+ }
70
+
71
+ // ─── Shared install/copy machinery ──────────────────────────────────────────
72
+
73
+ function getPaths() {
74
+ const targetDir = process.cwd();
75
+ return {
76
+ targetDir,
77
+ targetSkillsDir: path.join(targetDir, '.agents', 'skills'),
78
+ targetHiddenSkillsDir: path.join(targetDir, '.groundwork', 'skills'),
79
+ targetConfigDir: path.join(targetDir, '.groundwork', 'config'),
80
+ targetCacheDir: path.join(targetDir, '.groundwork', 'cache'),
81
+ sourceSkillsDir: path.join(__dirname, '..', 'src', 'skills'),
82
+ sourceHiddenSkillsDir: path.join(__dirname, '..', 'src', 'hidden-skills'),
83
+ sourceConfigDir: path.join(__dirname, '..', 'src', 'config'),
84
+ sourceDocsDir: path.join(__dirname, '..', 'src', 'docs'),
85
+ sourceAgentsMd: path.join(__dirname, '..', 'src', 'AGENTS.md'),
86
+ sourceDevBundle: path.join(__dirname, '..', 'src', 'generators', 'workspace-dev-cli', 'cli-src', 'dist', 'dev-bundle.js'),
87
+ sourceDevLauncher: path.join(__dirname, '..', 'src', 'generators', 'workspace-dev-cli', 'files', 'dev.template'),
88
+ };
89
+ }
90
+
91
+ // True when the command runs inside the GroundWork package/source tree itself —
92
+ // installing there would rm-and-replace the repo's own skill sources.
93
+ function isSelfCopy(p) {
94
+ return path.resolve(p.targetDir) === path.resolve(__dirname, '..');
95
+ }
96
+
97
+ // ─── Version stamp (decision D4) ────────────────────────────────────────────
98
+ // state.json's top-level `version` is the state-schema version; the framework
99
+ // version that wrote the install lives under `groundwork.version`.
100
+
101
+ function readStampedVersion(p) {
102
+ const statePath = path.join(p.targetConfigDir, 'state.json');
103
+ try {
104
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
105
+ return (state.groundwork && state.groundwork.version) || null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ function stampVersion(p) {
112
+ const statePath = path.join(p.targetConfigDir, 'state.json');
113
+ try {
114
+ let state = {};
115
+ if (fs.existsSync(statePath)) {
116
+ state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
117
+ }
118
+ state.groundwork = { ...(state.groundwork || {}), version: PKG.version };
119
+ fs.mkdirSync(p.targetConfigDir, { recursive: true });
120
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
121
+ } catch (err) {
122
+ c.warn(`Could not stamp framework version into state.json: ${err.message}`);
123
+ }
124
+ }
125
+
126
+ // ─── Changelog (migration notes on update) ──────────────────────────────────
127
+
128
+ function semverCompare(a, b) {
129
+ const pa = a.split('.').map(Number);
130
+ const pb = b.split('.').map(Number);
131
+ for (let i = 0; i < 3; i++) {
132
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
133
+ }
134
+ return 0;
135
+ }
136
+
137
+ function parseChangelog() {
138
+ const clPath = path.join(__dirname, '..', 'CHANGELOG.md');
139
+ if (!fs.existsSync(clPath)) return [];
140
+ const entries = [];
141
+ let current = null;
142
+ for (const line of fs.readFileSync(clPath, 'utf8').split('\n')) {
143
+ const m = line.match(/^## \[(\d+\.\d+\.\d+)\] - (.+)$/);
144
+ if (m) {
145
+ current = { version: m[1], date: m[2], lines: [] };
146
+ entries.push(current);
147
+ } else if (current) {
148
+ current.lines.push(line);
149
+ }
150
+ }
151
+ return entries; // newest first, matching file order
152
+ }
153
+
154
+ // Reduces a `### Category (headline, date)` section header to a scannable label.
155
+ // Older bare `### Category` headers (and the `[no-migration]` backtick tag some
156
+ // headers carry) degrade to just the category word.
157
+ function changelogHeadline(header) {
158
+ const body = header
159
+ .slice(4)
160
+ .replace(/\s*`\[(?:no-)?migration\]`\s*$/i, '')
161
+ .trim();
162
+ const m = body.match(/^(\w+)\s*\(([\s\S]*)\)\s*$/);
163
+ if (!m) return { category: body.split(/\s+/)[0] || body, text: null };
164
+ const text = m[2].replace(/,\s*\d{4}-\d{2}-\d{2}\s*$/, '').trim();
165
+ return { category: m[1], text };
166
+ }
167
+
168
+ // Collects the genuine `- [migration]` bullets from a slice's lines. Matches only
169
+ // bullet-leading tokens, not prose that mentions `[migration]` in passing (e.g. the
170
+ // upgrade-path note that the token references registry ids) — that false match used
171
+ // to leak a stray, prefix-stripped line into the "Migration required" list.
172
+ function collectMigrations(entries) {
173
+ const out = [];
174
+ for (const e of entries) {
175
+ for (const line of e.lines) {
176
+ if (/^\s*-\s*\[migration\]/i.test(line)) {
177
+ out.push(line.trim().replace(/^-\s*\[migration\]\s*/i, ''));
178
+ }
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+
184
+ // Prints the changelog entries in (fromVersion, toVersion]. By default it renders a
185
+ // scannable one-line-per-change summary; `full` dumps the entry bodies verbatim.
186
+ // Genuine [migration] bullets are always surfaced separately.
187
+ function printChangelogSlice(fromVersion, toVersion, full = false) {
188
+ const entries = parseChangelog().filter(
189
+ (e) =>
190
+ semverCompare(e.version, toVersion) <= 0 &&
191
+ (fromVersion === null || semverCompare(e.version, fromVersion) > 0)
192
+ );
193
+ if (entries.length === 0) return;
194
+
195
+ console.log(`\x1b[1mWhat changed:\x1b[0m`);
196
+ if (full) {
197
+ for (const e of entries) {
198
+ console.log(`\n \x1b[1m${e.version}\x1b[0m \x1b[2m(${e.date})\x1b[0m`);
199
+ for (const line of e.lines) {
200
+ if (!line.trim()) continue;
201
+ if (line.startsWith('### ')) console.log(` \x1b[1m${line.slice(4)}\x1b[0m`);
202
+ else console.log(` ${line}`);
203
+ }
204
+ }
205
+ } else {
206
+ for (const e of entries) {
207
+ const headlines = e.lines.filter((l) => l.startsWith('### ')).map(changelogHeadline);
208
+ const n = headlines.length;
209
+ console.log(
210
+ `\n \x1b[1m${e.version}\x1b[0m \x1b[2m(${e.date}` +
211
+ (n ? ` · ${n} change${n === 1 ? '' : 's'}` : '') +
212
+ `)\x1b[0m`
213
+ );
214
+ for (const { category, text } of headlines) {
215
+ const label = text ? `\x1b[1m${category}\x1b[0m ${text}` : `\x1b[1m${category}\x1b[0m`;
216
+ console.log(` \x1b[2m•\x1b[0m ${label}`);
217
+ }
218
+ }
219
+ console.log(
220
+ `\n \x1b[2mFull detail in CHANGELOG.md — or re-run with \x1b[0m\x1b[36m--full\x1b[0m\x1b[2m for the complete entries.\x1b[0m`
221
+ );
222
+ }
223
+
224
+ const migrations = collectMigrations(entries);
225
+ if (migrations.length > 0) {
226
+ console.log(`\n\x1b[33m\x1b[1m⚠ Migration required:\x1b[0m`);
227
+ for (const m of migrations) console.log(` \x1b[33m- ${m}\x1b[0m`);
228
+ }
229
+ console.log('');
230
+ }
231
+
232
+ function walkFiles(dir, base) {
233
+ // Returns relative file paths under dir, sorted for stable diff output.
234
+ const out = [];
235
+ if (!fs.existsSync(dir)) return out;
236
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
237
+ const rel = base ? path.join(base, entry.name) : entry.name;
238
+ if (entry.isDirectory()) {
239
+ out.push(...walkFiles(path.join(dir, entry.name), rel));
240
+ } else {
241
+ out.push(rel);
242
+ }
243
+ }
244
+ return out.sort();
245
+ }
246
+
247
+ function diffDirs(srcDir, destDir) {
248
+ const srcFiles = walkFiles(srcDir, '');
249
+ const destFiles = walkFiles(destDir, '');
250
+ const destSet = new Set(destFiles);
251
+ const srcSet = new Set(srcFiles);
252
+ const added = srcFiles.filter((f) => !destSet.has(f));
253
+ const removed = destFiles.filter((f) => !srcSet.has(f));
254
+ const changed = srcFiles.filter((f) => {
255
+ if (!destSet.has(f)) return false;
256
+ return !fs.readFileSync(path.join(srcDir, f)).equals(fs.readFileSync(path.join(destDir, f)));
257
+ });
258
+ return { added, changed, removed };
259
+ }
260
+
261
+ // ─── Install manifest (upgrade-path plan WS-A) ─────────────────────────────
262
+ // The manifest records what was deployed, from which package version, and the
263
+ // content hash at deploy — the base every later update classifies against.
264
+ // It lives apart from state.json: state is lifecycle, the manifest is a ledger.
265
+
266
+ const MANIFEST_VERSION = 1;
267
+
268
+ function sha256(buf) {
269
+ return crypto.createHash('sha256').update(buf).digest('hex');
270
+ }
271
+
272
+ function hashFile(file) {
273
+ try {
274
+ return sha256(fs.readFileSync(file));
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+
280
+ function manifestPath(p) {
281
+ return path.join(p.targetConfigDir, 'manifest.json');
282
+ }
283
+
284
+ function readManifest(p) {
285
+ try {
286
+ const m = JSON.parse(fs.readFileSync(manifestPath(p), 'utf8'));
287
+ return m && typeof m === 'object' ? m : null;
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+
293
+ function writeManifestFile(p, manifest) {
294
+ fs.mkdirSync(p.targetConfigDir, { recursive: true });
295
+ fs.writeFileSync(manifestPath(p), JSON.stringify(manifest, null, 2) + '\n');
296
+ }
297
+
298
+ function emptyManifest() {
299
+ return {
300
+ manifest_version: MANIFEST_VERSION,
301
+ _schema:
302
+ 'files: { <project-relative path>: { tier, source, version, hash, provenance, base? } }. ' +
303
+ 'tier 1 = framework-owned (clean-replaced on update); tier 2 = framework-seeded, user-editable ' +
304
+ '(hash-classified: pristine refreshes, edited queues for the groundwork-update skill). ' +
305
+ 'provenance "deployed" = this CLI wrote it; "adopted" = found on disk with unknown ancestry, treated as user-edited. ' +
306
+ 'base (tier 2 only) = hash of the PACKAGE content this file was last reconciled against — a merge queues only ' +
307
+ 'when the package moves past it; null means unknown ancestry. ' +
308
+ 'generated: { <generator[:name]>: { generator, version, options, files } } — written by the Nx generators.',
309
+ files: {},
310
+ generated: {},
311
+ };
312
+ }
313
+
314
+ // The deployable tier-1/tier-2 surface, as { rel, tier, source, sourcePath } specs.
315
+ // generators.json and the dev bundle/launcher are handled separately: the former's
316
+ // content is machine-resolved, the latter pair exists only in scaffolded projects.
317
+ function deployableSpecs(p) {
318
+ const pkgRoot = path.resolve(__dirname, '..');
319
+ const specs = [];
320
+ const pushTree = (srcDir, destPrefix, tier) => {
321
+ for (const f of walkFiles(srcDir, '')) {
322
+ const sourcePath = path.join(srcDir, f);
323
+ specs.push({ rel: path.join(destPrefix, f), tier, source: path.relative(pkgRoot, sourcePath), sourcePath });
324
+ }
325
+ };
326
+ pushTree(p.sourceSkillsDir, path.join('.agents', 'skills'), 1);
327
+ pushTree(p.sourceHiddenSkillsDir, path.join('.groundwork', 'skills'), 1);
328
+ if (fs.existsSync(p.sourceDocsDir)) {
329
+ for (const f of walkFiles(p.sourceDocsDir, '')) {
330
+ if (f === 'llms.txt') continue; // deployed to the project root, not docs/
331
+ const sourcePath = path.join(p.sourceDocsDir, f);
332
+ specs.push({ rel: path.join('docs', f), tier: 2, source: path.relative(pkgRoot, sourcePath), sourcePath });
333
+ }
334
+ const llms = path.join(p.sourceDocsDir, 'llms.txt');
335
+ if (fs.existsSync(llms)) specs.push({ rel: 'llms.txt', tier: 2, source: path.relative(pkgRoot, llms), sourcePath: llms });
336
+ }
337
+ if (fs.existsSync(p.sourceAgentsMd)) {
338
+ specs.push({ rel: 'AGENTS.md', tier: 2, source: path.relative(pkgRoot, p.sourceAgentsMd), sourcePath: p.sourceAgentsMd });
339
+ }
340
+ return specs;
341
+ }
342
+
343
+ // A3 — backfill a manifest for an install that predates manifests. Files matching
344
+ // the current package's bytes are recorded pristine; anything else on disk is
345
+ // `adopted` (unknown ancestry — treated as user-edited until proven otherwise).
346
+ function bootstrapManifest(p, stampedVersion) {
347
+ const manifest = emptyManifest();
348
+ const version = stampedVersion || 'unknown';
349
+ for (const spec of deployableSpecs(p)) {
350
+ const diskHash = hashFile(path.join(p.targetDir, spec.rel));
351
+ if (diskHash === null) continue; // absent — nothing to record yet
352
+ const pkgHash = hashFile(spec.sourcePath);
353
+ const pristine = diskHash === pkgHash;
354
+ manifest.files[spec.rel] = {
355
+ tier: spec.tier,
356
+ source: spec.source,
357
+ version: pristine ? PKG.version : version,
358
+ hash: diskHash,
359
+ provenance: pristine ? 'deployed' : 'adopted',
360
+ ...(spec.tier === 2 ? { base: pristine ? pkgHash : null } : {}),
361
+ };
362
+ }
363
+ const genJson = path.join(p.targetConfigDir, 'generators.json');
364
+ const genHash = hashFile(genJson);
365
+ if (genHash !== null) {
366
+ manifest.files[path.join('.groundwork', 'config', 'generators.json')] = {
367
+ tier: 1, source: 'generators.json', version, hash: genHash, provenance: 'deployed',
368
+ };
369
+ }
370
+ // Generator output without provenance: detect the dev CLI heuristically so the
371
+ // bundle lands in tier 1 and the rest of its output gets a reconcile candidate.
372
+ const bundleHash = hashFile(path.join(p.targetDir, '.dev', 'dev-bundle.js'));
373
+ if (bundleHash !== null) {
374
+ manifest.files[path.join('.dev', 'dev-bundle.js')] = {
375
+ tier: 1, source: 'src/generators/workspace-dev-cli/cli-src/dist/dev-bundle.js',
376
+ version, hash: bundleHash, provenance: 'deployed',
377
+ };
378
+ const launcherHash = hashFile(path.join(p.targetDir, 'dev'));
379
+ if (launcherHash !== null) {
380
+ manifest.files['dev'] = {
381
+ tier: 1, source: 'src/generators/workspace-dev-cli/files/dev.template',
382
+ version, hash: launcherHash, provenance: 'deployed',
383
+ };
384
+ }
385
+ manifest.generated['workspace-dev-cli'] = {
386
+ generator: 'workspace-dev-cli', version, options: null, files: null, provenance: 'adopted',
387
+ };
388
+ }
389
+ return manifest;
390
+ }
391
+
392
+ // ─── Tier-2 refresh (upgrade-path plan WS-C) ────────────────────────────────
393
+ // Seeded docs stop fossilizing: pristine (disk hash == deploy hash) files refresh
394
+ // to the current package content; edited files are left alone and queued for the
395
+ // groundwork-update skill; absent files are copied as before.
396
+
397
+ function classifyTier2(p, manifest) {
398
+ const out = { copy: [], refresh: [], current: [], edited: [] };
399
+ for (const spec of deployableSpecs(p)) {
400
+ if (spec.tier !== 2) continue;
401
+ const destPath = path.join(p.targetDir, spec.rel);
402
+ const diskHash = hashFile(destPath);
403
+ const pkgHash = hashFile(spec.sourcePath);
404
+ if (diskHash === null) {
405
+ out.copy.push(spec);
406
+ } else if (diskHash === pkgHash) {
407
+ out.current.push(spec);
408
+ } else {
409
+ const entry = manifest.files[spec.rel];
410
+ const pristine = entry && entry.provenance === 'deployed' && entry.hash === diskHash;
411
+ if (pristine) {
412
+ out.refresh.push(spec);
413
+ } else {
414
+ // User-edited (or unknown ancestry). A merge is only owed when the package
415
+ // has moved past the base this copy was last reconciled against — an edit
416
+ // to a file the framework never changed is simply the user's file.
417
+ const base = entry ? (entry.base !== undefined ? entry.base : entry.hash) : null;
418
+ if (base === null || pkgHash !== base) out.edited.push({ ...spec, baseHash: base });
419
+ }
420
+ }
421
+ }
422
+ return out;
423
+ }
424
+
425
+ function applyTier2(p, tier2) {
426
+ for (const spec of [...tier2.copy, ...tier2.refresh]) {
427
+ const dest = path.join(p.targetDir, spec.rel);
428
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
429
+ fs.copyFileSync(spec.sourcePath, dest);
430
+ }
431
+ }
432
+
433
+ // Rebuild the manifest's `files` section after an update has deployed everything.
434
+ // Edited/adopted tier-2 entries keep their original record — the deploy-time hash
435
+ // is the merge base the update skill reasons against (decision O5).
436
+ function rebuildManifest(p, previous, tier2, generatorsConfig, devCli) {
437
+ const manifest = emptyManifest();
438
+ manifest.generated = (previous && previous.generated) || {};
439
+ const editedSet = new Set((tier2 ? tier2.edited : []).map((s) => s.rel));
440
+ for (const spec of deployableSpecs(p)) {
441
+ if (spec.tier === 2 && editedSet.has(spec.rel)) {
442
+ const prior = previous && previous.files[spec.rel];
443
+ manifest.files[spec.rel] = prior || {
444
+ tier: 2, source: spec.source, version: 'unknown',
445
+ hash: hashFile(path.join(p.targetDir, spec.rel)), provenance: 'adopted', base: null,
446
+ };
447
+ continue;
448
+ }
449
+ const hash = hashFile(path.join(p.targetDir, spec.rel));
450
+ if (hash === null) continue;
451
+ manifest.files[spec.rel] = {
452
+ tier: spec.tier, source: spec.source, version: PKG.version, hash, provenance: 'deployed',
453
+ ...(spec.tier === 2 ? { base: hash } : {}),
454
+ };
455
+ }
456
+ if (generatorsConfig) {
457
+ manifest.files[path.join('.groundwork', 'config', 'generators.json')] = {
458
+ tier: 1, source: 'generators.json', version: PKG.version, hash: sha256(generatorsConfig), provenance: 'deployed',
459
+ };
460
+ }
461
+ if (devCli) {
462
+ for (const [rel, source] of [
463
+ [path.join('.dev', 'dev-bundle.js'), 'src/generators/workspace-dev-cli/cli-src/dist/dev-bundle.js'],
464
+ ['dev', 'src/generators/workspace-dev-cli/files/dev.template'],
465
+ ]) {
466
+ const hash = hashFile(path.join(p.targetDir, rel));
467
+ if (hash === null) continue;
468
+ const prior = previous && previous.files[rel];
469
+ const refreshed = (devCli.replaceBundle && rel !== 'dev') || (devCli.replaceLauncher && rel === 'dev');
470
+ manifest.files[rel] = refreshed || !prior
471
+ ? { tier: 1, source, version: PKG.version, hash, provenance: 'deployed' }
472
+ : prior;
473
+ }
474
+ }
475
+ return manifest;
476
+ }
477
+
478
+ // ─── Dev CLI refresh (upgrade-path plan WS-D, D1) ───────────────────────────
479
+ // The ./dev bundle + launcher are framework-owned (decision S7): built from
480
+ // cli-src, copied verbatim, never meant to be user-edited. A scaffolded project
481
+ // gets them clean-replaced on update; a customized launcher (hash matches neither
482
+ // the manifest nor the shipped template) is queued for judgment instead.
483
+
484
+ function classifyDevCli(p, manifest) {
485
+ const out = { present: false, replaceBundle: false, replaceLauncher: false, customLauncher: false };
486
+ const bundleDisk = hashFile(path.join(p.targetDir, '.dev', 'dev-bundle.js'));
487
+ if (bundleDisk === null) return out; // not a scaffolded project — nothing to track
488
+ out.present = true;
489
+ const bundlePkg = hashFile(p.sourceDevBundle);
490
+ if (bundlePkg !== null && bundleDisk !== bundlePkg) out.replaceBundle = true;
491
+ const launcherDisk = hashFile(path.join(p.targetDir, 'dev'));
492
+ const launcherPkg = hashFile(p.sourceDevLauncher);
493
+ if (launcherDisk !== null && launcherPkg !== null && launcherDisk !== launcherPkg) {
494
+ const entry = manifest.files['dev'];
495
+ if (entry && entry.hash === launcherDisk) out.replaceLauncher = true;
496
+ else out.customLauncher = true;
497
+ }
498
+ return out;
499
+ }
500
+
501
+ function applyDevCli(p, devCli) {
502
+ if (devCli.replaceBundle) {
503
+ fs.copyFileSync(p.sourceDevBundle, path.join(p.targetDir, '.dev', 'dev-bundle.js'));
504
+ }
505
+ if (devCli.replaceLauncher) {
506
+ const dest = path.join(p.targetDir, 'dev');
507
+ fs.copyFileSync(p.sourceDevLauncher, dest);
508
+ fs.chmodSync(dest, 0o755);
509
+ }
510
+ }
511
+
512
+ // ─── Migration registry (upgrade-path plan WS-B) ────────────────────────────
513
+ // migrations/index.json ships in the package. Every registry entry is a scripted
514
+ // `cli` migration — mechanical, forward-only, idempotent, detect-first file ops run
515
+ // inside update. Structural/judgment advancement is no longer a per-change migration:
516
+ // the groundwork-update skill reconciles each artifact family to the current canonical.
517
+ // GROUNDWORK_MIGRATIONS_DIR overrides the registry location (test seam).
518
+
519
+ function migrationsDir() {
520
+ return process.env.GROUNDWORK_MIGRATIONS_DIR || path.join(__dirname, '..', 'migrations');
521
+ }
522
+
523
+ function loadMigrationRegistry() {
524
+ const dir = migrationsDir();
525
+ const indexPath = path.join(dir, 'index.json');
526
+ if (!fs.existsSync(indexPath)) return { dir, entries: [] };
527
+ try {
528
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
529
+ return { dir, entries: Array.isArray(index.migrations) ? index.migrations : [] };
530
+ } catch (err) {
531
+ c.warn(`Could not read migration registry: ${err.message}`);
532
+ return { dir, entries: [] };
533
+ }
534
+ }
535
+
536
+ function readCompletedMigrations(p) {
537
+ const statePath = path.join(p.targetConfigDir, 'state.json');
538
+ try {
539
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
540
+ return (state.groundwork && Array.isArray(state.groundwork.migrations)) ? state.groundwork.migrations : [];
541
+ } catch {
542
+ return [];
543
+ }
544
+ }
545
+
546
+ function recordMigrations(p, ids) {
547
+ if (ids.length === 0) return;
548
+ const statePath = path.join(p.targetConfigDir, 'state.json');
549
+ try {
550
+ let state = {};
551
+ if (fs.existsSync(statePath)) state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
552
+ state.groundwork = state.groundwork || {};
553
+ const done = new Set(Array.isArray(state.groundwork.migrations) ? state.groundwork.migrations : []);
554
+ for (const id of ids) done.add(id);
555
+ state.groundwork.migrations = [...done];
556
+ fs.mkdirSync(p.targetConfigDir, { recursive: true });
557
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
558
+ } catch (err) {
559
+ c.warn(`Could not record migrations in state.json: ${err.message}`);
560
+ }
561
+ }
562
+
563
+ // Partition unrecorded registry entries into work. The registry is cli-only now:
564
+ // each entry is asked via its detect(). Structural advancement moved out of the
565
+ // registry to the groundwork-update skill's reconcile pass.
566
+ function pendingMigrations(p, registry) {
567
+ const done = new Set(readCompletedMigrations(p));
568
+ const ctx = { targetDir: p.targetDir, packageRoot: path.resolve(__dirname, '..') };
569
+ const cli = [];
570
+ const settled = []; // detect said done/n-a — record so detect isn't re-asked forever
571
+ for (const entry of registry.entries) {
572
+ if (done.has(entry.id)) continue;
573
+ if (entry.kind !== 'cli') continue; // registry is cli-only; ignore stray kinds
574
+ let mod;
575
+ try {
576
+ mod = require(path.join(registry.dir, `${entry.id}.js`));
577
+ } catch (err) {
578
+ c.warn(`Migration ${entry.id} could not be loaded: ${err.message}`);
579
+ continue;
580
+ }
581
+ let verdict;
582
+ try {
583
+ verdict = mod.detect(ctx);
584
+ } catch (err) {
585
+ c.warn(`Migration ${entry.id} detect() failed: ${err.message}`);
586
+ continue;
587
+ }
588
+ if (verdict === 'pending') cli.push({ entry, mod, ctx });
589
+ else settled.push(entry.id);
590
+ }
591
+ return { cli, settled };
592
+ }
593
+
594
+ // B2 — run pending cli migrations in registry order. A failure stops the run with
595
+ // the migration named and the version stamp is NOT advanced past it.
596
+ function runCliMigrations(p, pending) {
597
+ const completed = [];
598
+ for (const { entry, mod, ctx } of pending) {
599
+ try {
600
+ mod.run(ctx);
601
+ completed.push(entry.id);
602
+ c.ok(`Migration ${entry.id} — ${entry.title}`);
603
+ } catch (err) {
604
+ recordMigrations(p, completed);
605
+ c.err(`Migration ${entry.id} failed: ${err.message}`);
606
+ console.error(` The version stamp was not advanced. Fix the issue and re-run \x1b[36mnpx groundwork-method update\x1b[0m —`);
607
+ console.error(` migrations are idempotent, so completed ones are skipped and this one retries.\n`);
608
+ return { completed, failed: entry };
609
+ }
610
+ }
611
+ recordMigrations(p, completed);
612
+ return { completed, failed: null };
613
+ }
614
+
615
+ // ─── The upgrade brief (upgrade-path plan WS-E input) ───────────────────────
616
+ // The file-level judgment work — edited tier-2 merges, a customized launcher,
617
+ // generator-output reconciliation — is compiled into a brief the groundwork-update
618
+ // skill works through conversationally (its Phase A). Incoming content is copied
619
+ // into .groundwork/cache so the skill needs nothing from the npx package cache.
620
+ // Structural advancement is the skill's Phase B reconcile, not a brief item.
621
+
622
+ function upgradeBriefPath(p) {
623
+ return path.join(p.targetDir, '.groundwork', 'cache', 'upgrade-brief.json');
624
+ }
625
+
626
+ function buildBriefItems(p, tier2, devCli, manifest) {
627
+ const items = [];
628
+ for (const spec of tier2.edited) {
629
+ items.push({
630
+ type: 'tier2-merge',
631
+ id: `tier2:${spec.rel}`,
632
+ path: spec.rel,
633
+ incoming: path.join('.groundwork', 'cache', 'upgrade', 'tier2', spec.rel),
634
+ base_hash: spec.baseHash,
635
+ summary: `Framework improvements to ${spec.rel} need merging into your edited copy.`,
636
+ status: 'pending',
637
+ });
638
+ }
639
+ if (devCli.customLauncher) {
640
+ items.push({
641
+ type: 'tier1-custom',
642
+ id: 'tier1:dev',
643
+ path: 'dev',
644
+ incoming: path.join('.groundwork', 'cache', 'upgrade', 'tier1', 'dev'),
645
+ summary: 'The ./dev launcher was customized; the framework ships a newer one. Reconcile by hand.',
646
+ status: 'pending',
647
+ });
648
+ }
649
+ for (const [key, gen] of Object.entries(manifest.generated || {})) {
650
+ if (gen.version === PKG.version) continue;
651
+ items.push({
652
+ type: 'regenerate',
653
+ id: `regen:${key}`,
654
+ artifact: key,
655
+ generator: gen.generator,
656
+ options: gen.options,
657
+ recorded_version: gen.version,
658
+ summary: `Generator output recorded at ${gen.version}; regenerate with recorded options and reconcile the diff.`,
659
+ status: 'pending',
660
+ });
661
+ }
662
+ return items;
663
+ }
664
+
665
+ // Item types the groundwork-update skill's Phase A still executes. A brief written
666
+ // before the agent-migration retirement can carry orphaned `agent-migration` items
667
+ // (their work is now the skill's Phase B reconcile); they are pruned on the next
668
+ // update so the skill is never handed a type it cannot run.
669
+ const SUPPORTED_BRIEF_ITEM_TYPES = new Set(['tier2-merge', 'tier1-custom', 'regenerate']);
670
+
671
+ // Normalize a pre-cutover brief on disk: clear the orphaned agent-migration payload
672
+ // cache, drop items whose type the current skill can't run, and delete the brief
673
+ // when nothing supported survives. Idempotent and safe when no brief exists. Called
674
+ // on both update paths so a stale brief is cleaned even when nothing else changed.
675
+ function pruneStaleBrief(p) {
676
+ // The briefs/ cache staged agent-migration payloads only; the current brief never
677
+ // writes there, so a lingering dir is orphaned by the retirement.
678
+ const orphanedBriefs = path.join(p.targetDir, '.groundwork', 'cache', 'upgrade', 'briefs');
679
+ if (fs.existsSync(orphanedBriefs)) fs.rmSync(orphanedBriefs, { recursive: true, force: true });
680
+
681
+ const briefPath = upgradeBriefPath(p);
682
+ if (!fs.existsSync(briefPath)) return;
683
+ let brief;
684
+ try { brief = JSON.parse(fs.readFileSync(briefPath, 'utf8')); }
685
+ catch { return; } // corrupt brief — leave for writeUpgradeBrief to rebuild
686
+ if (!brief || !Array.isArray(brief.items)) return;
687
+
688
+ const kept = brief.items.filter((i) => SUPPORTED_BRIEF_ITEM_TYPES.has(i.type));
689
+ if (kept.length === brief.items.length) return; // nothing to prune
690
+ if (kept.length === 0) {
691
+ // Pruning emptied the brief — remove it so `check` and the skill don't see a
692
+ // phantom work list.
693
+ fs.rmSync(briefPath, { force: true });
694
+ return;
695
+ }
696
+ brief.items = kept;
697
+ fs.writeFileSync(briefPath, JSON.stringify(brief, null, 2) + '\n');
698
+ }
699
+
700
+ // Write the brief (merging an existing one by item id — completed work survives)
701
+ // and stage every referenced payload into the cache.
702
+ function writeUpgradeBrief(p, items, stamped) {
703
+ pruneStaleBrief(p); // normalize any pre-cutover brief before merging into it
704
+ const briefPath = upgradeBriefPath(p);
705
+ let brief = { brief_version: 1, from: stamped, to: PKG.version, items: [] };
706
+ try {
707
+ if (fs.existsSync(briefPath)) {
708
+ const existing = JSON.parse(fs.readFileSync(briefPath, 'utf8'));
709
+ if (existing && Array.isArray(existing.items)) {
710
+ brief.items = existing.items;
711
+ // The brief spans the whole catch-up: keep the original `from` so a
712
+ // re-run after stamping doesn't rewrite history (and stays a no-op).
713
+ if (existing.from !== undefined) brief.from = existing.from;
714
+ }
715
+ }
716
+ } catch { /* corrupt brief — rebuild from scratch */ }
717
+ const have = new Set(brief.items.map((i) => i.id));
718
+ for (const item of items) {
719
+ if (!have.has(item.id)) brief.items.push(item);
720
+ }
721
+ if (brief.items.length === 0) return 0;
722
+
723
+ for (const item of brief.items) {
724
+ if (item.status !== 'pending') continue;
725
+ if (item.type === 'tier2-merge') {
726
+ const spec = deployableSpecs(p).find((s) => s.rel === item.path);
727
+ if (spec) {
728
+ const dest = path.join(p.targetDir, item.incoming);
729
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
730
+ fs.copyFileSync(spec.sourcePath, dest);
731
+ }
732
+ } else if (item.type === 'tier1-custom' && item.path === 'dev') {
733
+ const dest = path.join(p.targetDir, item.incoming);
734
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
735
+ fs.copyFileSync(p.sourceDevLauncher, dest);
736
+ }
737
+ }
738
+ fs.mkdirSync(path.dirname(briefPath), { recursive: true });
739
+ fs.writeFileSync(briefPath, JSON.stringify(brief, null, 2) + '\n');
740
+ return brief.items.filter((i) => i.status === 'pending').length;
741
+ }
742
+
743
+ // Clean-copy the two skill trees. Removing first prevents deprecated skills from lingering.
744
+ // Throws on copy failure — callers abort rather than report success over a partial install.
745
+ function installSkillTrees(p) {
746
+ for (const [src, dest, label] of [
747
+ [p.sourceSkillsDir, p.targetSkillsDir, 'Registered skills'],
748
+ [p.sourceHiddenSkillsDir, p.targetHiddenSkillsDir, 'Hidden methodology skills'],
749
+ ]) {
750
+ if (fs.existsSync(dest)) {
751
+ try {
752
+ fs.rmSync(dest, { recursive: true, force: true });
753
+ } catch (err) {
754
+ c.warn(`Failed to clean ${label.toLowerCase()} dir: ${err.message}`);
755
+ }
756
+ }
757
+ fs.mkdirSync(dest, { recursive: true });
758
+ try {
759
+ execSync(`cp -R "${src}/"* "${dest}/"`);
760
+ } catch (err) {
761
+ throw new Error(`Failed to install ${label.toLowerCase()}: ${err.message}`);
762
+ }
763
+ }
764
+ }
765
+
766
+ // generators.json ships with repo-relative factory/schema paths; resolve them against the
767
+ // installed package location so the scaffold skill can invoke generators from any project.
768
+ function buildGeneratorsConfig() {
769
+ const sourceGeneratorsJson = path.join(__dirname, '..', 'generators.json');
770
+ if (!fs.existsSync(sourceGeneratorsJson)) return null;
771
+ const pkgRoot = path.resolve(__dirname, '..');
772
+ const generatorsJson = JSON.parse(fs.readFileSync(sourceGeneratorsJson, 'utf8'));
773
+ for (const gen of Object.values(generatorsJson.generators)) {
774
+ gen.factory = path.resolve(pkgRoot, gen.factory.replace(/^\.\//, ''));
775
+ gen.schema = path.resolve(pkgRoot, gen.schema.replace(/^\.\//, ''));
776
+ }
777
+ return JSON.stringify(generatorsJson, null, 2);
778
+ }
779
+
780
+ // ─── Agent wiring ─────────────────────────────────────────────────────────────
781
+ // GroundWork keeps one canonical source of truth — AGENTS.md (instructions) + .agents/
782
+ // (skills) — and wires each agent tool to it with symlinks rather than copies, so there is
783
+ // never a second copy to drift. AGENTS.md is ALWAYS the real file; agent-specific files
784
+ // (CLAUDE.md, …) are symlinks pointing at it, so adding or switching agents never moves or
785
+ // orphans the canonical.
786
+ //
787
+ // `native: true` agents (Cursor, Codex, OpenCode, Cline) read AGENTS.md and .agents/skills/
788
+ // directly — generating the canonical files is the entire wiring; they need no symlink.
789
+ const AGENT_ADAPTERS = {
790
+ 'claude-code': {
791
+ label: 'Claude Code',
792
+ detect: ['.claude', 'CLAUDE.md'],
793
+ dirLink: { link: '.claude', target: '.agents' },
794
+ fileLink: { link: 'CLAUDE.md', target: 'AGENTS.md' },
795
+ },
796
+ 'cursor': { label: 'Cursor', detect: ['.cursor', '.cursorrules'], native: true },
797
+ 'codex': { label: 'Codex', detect: ['.codex'], native: true },
798
+ 'opencode': { label: 'OpenCode', detect: ['.opencode', 'opencode.json'], native: true },
799
+ 'cline': { label: 'Cline', detect: ['.clinerules', '.cline'], native: true },
800
+ };
801
+ const AGENT_KEYS = Object.keys(AGENT_ADAPTERS);
802
+
803
+ // Agents whose marker files/dirs already exist in the target — used to pre-select the prompt
804
+ // and as the non-interactive default.
805
+ function detectAgents(targetDir) {
806
+ return AGENT_KEYS.filter((key) =>
807
+ AGENT_ADAPTERS[key].detect.some((marker) => fs.existsSync(path.join(targetDir, marker)))
808
+ );
809
+ }
810
+
811
+ // Deploy the canonical AGENTS.md router to the project root (idempotent — never overwrites a
812
+ // user-authored AGENTS.md). Returns true only when it actually created the file.
813
+ function ensureAgentsMd(p) {
814
+ if (!fs.existsSync(p.sourceAgentsMd)) return false;
815
+ const target = path.join(p.targetDir, 'AGENTS.md');
816
+ if (fs.existsSync(target)) return false;
817
+ try {
818
+ fs.copyFileSync(p.sourceAgentsMd, target);
819
+ return true;
820
+ } catch (err) {
821
+ c.err(`Failed to install AGENTS.md: ${err.message}`);
822
+ return false;
823
+ }
824
+ }
825
+
826
+ // Create one symlink (link → target) relative to targetDir, gracefully handling an existing
827
+ // real file/dir and Windows symlink restrictions. `type` is 'junction' for directory links.
828
+ function linkOne(targetDir, link, target, type) {
829
+ const linkPath = path.join(targetDir, link);
830
+ const isDir = type === 'junction';
831
+ try {
832
+ const stat = fs.existsSync(linkPath) ? fs.lstatSync(linkPath) : null;
833
+ if (!stat) {
834
+ fs.symlinkSync(target, linkPath, type);
835
+ c.ok(`Linked ${link} → ${target}`);
836
+ } else if (stat.isSymbolicLink()) {
837
+ // already a symlink — no-op regardless of where it points
838
+ } else {
839
+ c.warn(`${link} already exists as a real ${isDir ? 'directory' : 'file'}. To enable the link:`);
840
+ console.warn(` move its contents into ${target}${isDir ? '/' : ''}, delete ${link}${isDir ? '/' : ''}, then run: ln -s ${target} ${link}`);
841
+ }
842
+ } catch (err) {
843
+ c.warn(`Could not create ${link} symlink: ${err.message}`);
844
+ console.warn(` On Windows, enable Developer Mode or run as Administrator and retry,`);
845
+ console.warn(` or create it manually: ln -s ${target} ${link}`);
846
+ }
847
+ }
848
+
849
+ // Wire the selected agent tools to the canonical AGENTS.md + .agents/ source of truth.
850
+ // Idempotent: re-running never duplicates or clobbers, so init and update can both call it.
851
+ function wireAgents(targetDir, selectedKeys) {
852
+ const keys = selectedKeys.filter((k) => AGENT_ADAPTERS[k]);
853
+ if (keys.length === 0) return;
854
+
855
+ const native = [];
856
+ for (const key of keys) {
857
+ const a = AGENT_ADAPTERS[key];
858
+ if (a.native) {
859
+ native.push(a.label);
860
+ continue;
861
+ }
862
+ if (a.dirLink) linkOne(targetDir, a.dirLink.link, a.dirLink.target, 'junction');
863
+ // The file symlink only fires once the canonical AGENTS.md exists (init generates it first).
864
+ if (a.fileLink && fs.existsSync(path.join(targetDir, a.fileLink.target))) {
865
+ linkOne(targetDir, a.fileLink.link, a.fileLink.target);
866
+ }
867
+ }
868
+ if (native.length) {
869
+ c.ok(`${native.join(', ')} read AGENTS.md + .agents/skills/ natively — no link needed`);
870
+ }
871
+ }
872
+
873
+ // Record the wired agents so `update` self-heals the same links and re-init stays idempotent.
874
+ function persistAgents(p, keys) {
875
+ const statePath = path.join(p.targetConfigDir, 'state.json');
876
+ try {
877
+ let state = {};
878
+ if (fs.existsSync(statePath)) state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
879
+ state.groundwork = { ...(state.groundwork || {}), agents: keys };
880
+ fs.mkdirSync(p.targetConfigDir, { recursive: true });
881
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
882
+ } catch (err) {
883
+ c.warn(`Could not record wired agents in state.json: ${err.message}`);
884
+ }
885
+ }
886
+
887
+ function readPersistedAgents(p) {
888
+ const statePath = path.join(p.targetConfigDir, 'state.json');
889
+ try {
890
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
891
+ return state.groundwork && Array.isArray(state.groundwork.agents) ? state.groundwork.agents : null;
892
+ } catch {
893
+ return null;
894
+ }
895
+ }
896
+
897
+ // The native tools (Cursor, Codex, OpenCode, Cline) read AGENTS.md + .agents/skills/ directly, so
898
+ // they need no setup. The ONLY decision worth a prompt is whether to wire Claude Code, which looks
899
+ // for CLAUDE.md / .claude/ instead and gets symlinks to the canonical. So the interactive prompt
900
+ // is a single yes/no, not a five-box picker where four boxes are no-ops.
901
+ // No TTY (piped npx, CI): wire any detected wired tool, else default to Claude Code, so unattended
902
+ // installs still wire the verified host.
903
+ function promptAgents(detected) {
904
+ const wiredKeys = AGENT_KEYS.filter((k) => !AGENT_ADAPTERS[k].native);
905
+ const nativeLabels = AGENT_KEYS.filter((k) => AGENT_ADAPTERS[k].native).map((k) => AGENT_ADAPTERS[k].label);
906
+
907
+ return new Promise((resolve) => {
908
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
909
+ const det = wiredKeys.filter((k) => detected.includes(k));
910
+ resolve(det.length ? det : ['claude-code']);
911
+ return;
912
+ }
913
+
914
+ // Today there is exactly one wired (non-native) tool: Claude Code. If a second is ever added,
915
+ // restore a checkbox picker over `wiredKeys` here — this single yes/no only covers one.
916
+ const only = wiredKeys[0];
917
+ const a = AGENT_ADAPTERS[only];
918
+ const claudeDetected = detected.includes(only);
919
+ const nativeDetected = AGENT_KEYS.some((k) => AGENT_ADAPTERS[k].native && detected.includes(k));
920
+ // Default yes unless a native tool is the only thing we detected (then they're already set up).
921
+ const defaultYes = claudeDetected || !nativeDetected;
922
+
923
+ // Lead with the consequence: the native tools already work, so the question is only about
924
+ // Claude Code — and a "yes" is the one answer that writes files.
925
+ const nativeList = nativeLabels.length > 1
926
+ ? `${nativeLabels.slice(0, -1).join(', ')}, and ${nativeLabels[nativeLabels.length - 1]}`
927
+ : nativeLabels[0] || '';
928
+ console.log(`\n\x1b[1mGroundWork keeps all your project guidance in ${a.fileLink.target} and the ${a.dirLink.target}/ folder.\x1b[0m`);
929
+ if (nativeList) {
930
+ console.log(` \x1b[32m✓\x1b[0m \x1b[2m${nativeList} read them automatically — nothing to set up.\x1b[0m`);
931
+ }
932
+ console.log(` \x1b[2m${a.label} looks for ${a.fileLink.link} / ${a.dirLink.link}/ instead, so it needs links to them.\x1b[0m`);
933
+
934
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
935
+ const choices = defaultYes ? '\x1b[2m[Y/n]\x1b[0m' : '\x1b[2m[y/N]\x1b[0m';
936
+ let settled = false;
937
+ const settle = (yes) => {
938
+ if (settled) return;
939
+ settled = true;
940
+ rl.close();
941
+ resolve(yes ? [only] : []);
942
+ };
943
+ // Enter answers; an empty line or EOF (Ctrl-D / a closed pipe) takes the shown default so the
944
+ // prompt can never hang an install.
945
+ rl.on('close', () => settle(defaultYes));
946
+ rl.question(`\nAre you using ${a.label} in this project? ${choices} `, (ans) => {
947
+ const t = ans.trim().toLowerCase();
948
+ settle(t === '' ? defaultYes : t[0] === 'y');
949
+ });
950
+ });
951
+ }
952
+
953
+ // Parse `--agent <key>` / `--agent=<key>` (repeatable, comma-friendly) and `--yes`/`-y`.
954
+ function parseInitFlags(argv) {
955
+ const requested = [];
956
+ let yes = false;
957
+ for (let i = 0; i < argv.length; i++) {
958
+ const arg = argv[i];
959
+ if (arg === '--yes' || arg === '-y') {
960
+ yes = true;
961
+ } else if (arg === '--agent' || arg === '--agents') {
962
+ const val = argv[i + 1];
963
+ if (val && !val.startsWith('-')) { requested.push(...val.split(',')); i++; }
964
+ } else if (arg.startsWith('--agent=') || arg.startsWith('--agents=')) {
965
+ requested.push(...arg.slice(arg.indexOf('=') + 1).split(','));
966
+ }
967
+ }
968
+ const normalized = requested.map((s) => s.trim()).filter(Boolean);
969
+ return {
970
+ agents: normalized.filter((a) => AGENT_ADAPTERS[a]),
971
+ invalid: normalized.filter((a) => !AGENT_ADAPTERS[a]),
972
+ yes,
973
+ };
974
+ }
975
+
976
+ // The "switch implications" guidance: make the single-source-of-truth model explicit.
977
+ function printWiringGuidance(selectedKeys) {
978
+ const labels = selectedKeys.map((k) => AGENT_ADAPTERS[k] && AGENT_ADAPTERS[k].label).filter(Boolean).join(', ');
979
+ // No wired tool (e.g. a Cursor/Codex user) is not "nothing set up" — their tool reads AGENTS.md
980
+ // directly, so frame the empty case around the canonical, not around a missing link.
981
+ const reads = labels
982
+ ? `${labels} ${selectedKeys.length === 1 ? 'reads' : 'read'} them.`
983
+ : `Your AI tool reads them directly — no links needed.`;
984
+ console.log(`\n\x1b[1mAgent wiring\x1b[0m`);
985
+ console.log(` \x1b[2mAGENTS.md and .agents/ are your single source of truth.\x1b[0m ${reads}`);
986
+ console.log(` \x1b[2mAdd one later:\x1b[0m npx groundwork-method init --agent <name> \x1b[2m(non-destructive)\x1b[0m`);
987
+ console.log(` \x1b[2mEdit AGENTS.md, never a symlinked copy — switching agents never moves it.\x1b[0m`);
988
+ }
989
+
990
+ // Register Serena (github.com/oraios/serena) as a project-scoped MCP server. Serena is an
991
+ // LSP-based code-intelligence tool (40+ languages) that GroundWork treats as a first-class
992
+ // code map: the brownfield scan, the architecture extractor, and groundwork-check reason over
993
+ // its symbol/reference queries (and build repo-map.json from them), and the engineer skills
994
+ // use its symbolic editing. The registration is idempotent and additive — it never clobbers
995
+ // other servers — and it removes any prior depwire entry so a re-init/upgrade swaps cleanly.
996
+ // Every consumer degrades gracefully when Serena is absent (it needs `uv`), so this is a
997
+ // force-multiplier, never a hard dependency.
998
+ const SERENA_MCP_SERVER = {
999
+ command: 'uvx',
1000
+ args: ['--from', 'serena-agent==1.5.3', 'serena', 'start-mcp-server', '--context', 'ide-assistant', '--project', '.'],
1001
+ };
1002
+ function registerSerenaMcp(targetDir) {
1003
+ const mcpPath = path.join(targetDir, '.mcp.json');
1004
+ try {
1005
+ let config = { mcpServers: {} };
1006
+ if (fs.existsSync(mcpPath)) {
1007
+ config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
1008
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
1009
+ config.mcpServers = {};
1010
+ }
1011
+ }
1012
+ const hadDepwire = Boolean(config.mcpServers.depwire);
1013
+ if (hadDepwire) delete config.mcpServers.depwire; // pull out the retired server
1014
+ if (config.mcpServers.serena && !hadDepwire) {
1015
+ return; // already registered and nothing to remove — preserve the user's configuration
1016
+ }
1017
+ config.mcpServers.serena = SERENA_MCP_SERVER;
1018
+ fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2));
1019
+ c.ok(`Registered Serena code-intelligence MCP server (.mcp.json)`);
1020
+ } catch (err) {
1021
+ c.warn(`Could not register Serena MCP server: ${err.message}`);
1022
+ console.warn(` GroundWork still works without it — the code map falls back to LLM inference.`);
1023
+ }
1024
+ }
1025
+
1026
+ // ─── init ───────────────────────────────────────────────────────────────────
1027
+
1028
+ async function initGroundWork(options = {}) {
1029
+ const p = getPaths();
1030
+
1031
+ banner();
1032
+ c.info(`Initializing in \x1b[2m${p.targetDir}\x1b[0m\n`);
1033
+
1034
+ if (isSelfCopy(p)) {
1035
+ c.warn(`You are running this command inside the GroundWork source repository itself.`);
1036
+ console.warn(` Skipping skill installation to prevent recursive copying.\n`);
1037
+ return;
1038
+ }
1039
+
1040
+ for (const dir of [p.targetSkillsDir, p.targetHiddenSkillsDir, p.targetConfigDir, p.targetCacheDir]) {
1041
+ fs.mkdirSync(dir, { recursive: true });
1042
+ }
1043
+
1044
+ try {
1045
+ installSkillTrees(p);
1046
+ } catch (err) {
1047
+ c.err(err.message);
1048
+ c.err(`Install aborted — resolve the issue and re-run npx groundwork-method init.`);
1049
+ process.exitCode = 1;
1050
+ return;
1051
+ }
1052
+ c.ok(`Installed orchestrator, registered skills, and hidden methodology skills`);
1053
+
1054
+ const generatorsConfig = buildGeneratorsConfig();
1055
+ if (generatorsConfig) {
1056
+ try {
1057
+ fs.writeFileSync(path.join(p.targetConfigDir, 'generators.json'), generatorsConfig);
1058
+ c.ok(`Installed generators config`);
1059
+ } catch (err) {
1060
+ c.err(`Failed to install generators config: ${err.message}`);
1061
+ }
1062
+ }
1063
+
1064
+ // Create state file only if it doesn't exist — preserves completed phase history across updates
1065
+ const sourceState = path.join(p.sourceConfigDir, 'groundwork-state.json');
1066
+ const targetState = path.join(p.targetConfigDir, 'state.json');
1067
+ if (fs.existsSync(sourceState) && !fs.existsSync(targetState)) {
1068
+ try {
1069
+ fs.copyFileSync(sourceState, targetState);
1070
+ c.ok(`Initialized project state`);
1071
+ } catch (err) {
1072
+ c.err(`Failed to initialize state: ${err.message}`);
1073
+ }
1074
+ }
1075
+
1076
+ // Seed the user config once; it is user-owned and never overwritten afterwards.
1077
+ const sourceConfigToml = path.join(p.sourceConfigDir, 'config.toml');
1078
+ const targetConfigToml = path.join(p.targetConfigDir, 'config.toml');
1079
+ if (fs.existsSync(sourceConfigToml) && !fs.existsSync(targetConfigToml)) {
1080
+ try {
1081
+ fs.copyFileSync(sourceConfigToml, targetConfigToml);
1082
+ c.ok(`Seeded user config (.groundwork/config/config.toml)`);
1083
+ } catch (err) {
1084
+ c.err(`Failed to seed user config: ${err.message}`);
1085
+ }
1086
+ }
1087
+
1088
+ // Deploy documentation foundations + llms.txt + AGENTS.md (tier 2: copy when
1089
+ // absent, never overwrite what exists — classification handles the rest on update).
1090
+ // AGENTS.md must precede wireAgents so the CLAUDE.md → AGENTS.md link resolves.
1091
+ try {
1092
+ const tier2 = classifyTier2(p, emptyManifest());
1093
+ applyTier2(p, tier2);
1094
+ c.ok(`Installed documentation foundations`);
1095
+ if (tier2.copy.some((s) => s.rel === 'AGENTS.md')) c.ok(`Installed canonical AGENTS.md`);
1096
+ } catch (err) {
1097
+ c.err(`Failed to install documentation foundations: ${err.message}`);
1098
+ }
1099
+
1100
+ stampVersion(p);
1101
+
1102
+ // A fresh install needs no migrations: record the whole registry as settled so
1103
+ // update never queues catch-up work this init already delivered (detect-honesty).
1104
+ const registry = loadMigrationRegistry();
1105
+ recordMigrations(p, registry.entries.map((e) => e.id));
1106
+
1107
+ // Write the install manifest — the provenance ledger every future update
1108
+ // classifies against (what was deployed, from which version, with which hash).
1109
+ try {
1110
+ writeManifestFile(p, rebuildManifest(p, readManifest(p), classifyTier2(p, emptyManifest()), generatorsConfig, classifyDevCli(p, emptyManifest())));
1111
+ c.ok(`Wrote install manifest (.groundwork/config/manifest.json)`);
1112
+ } catch (err) {
1113
+ c.warn(`Could not write install manifest: ${err.message}`);
1114
+ }
1115
+
1116
+ // Decide which agent tools to wire to the canonical source: explicit --agent flags win,
1117
+ // then --yes uses the detected set, otherwise prompt (detected agents pre-selected).
1118
+ const detected = detectAgents(p.targetDir);
1119
+ let selected;
1120
+ if (options.agents && options.agents.length) {
1121
+ selected = options.agents;
1122
+ } else if (options.yes) {
1123
+ selected = detected.length ? detected : ['claude-code'];
1124
+ } else {
1125
+ selected = await promptAgents(detected);
1126
+ }
1127
+ wireAgents(p.targetDir, selected);
1128
+ persistAgents(p, selected);
1129
+
1130
+ registerSerenaMcp(p.targetDir);
1131
+
1132
+ printWiringGuidance(selected);
1133
+
1134
+ console.log(`\n\x1b[32m[success]\x1b[0m GroundWork ${PKG.version} initialization complete!`);
1135
+ console.log(` Ask your AI to run the \x1b[36mgroundwork-orchestrator\x1b[0m skill to find out what to do next.\n`);
1136
+ }
1137
+
1138
+ // ─── update ─────────────────────────────────────────────────────────────────
1139
+
1140
+ function reportDiff(label, diff) {
1141
+ if (diff.added.length + diff.changed.length + diff.removed.length === 0) return;
1142
+ console.log(`\x1b[1m${label}\x1b[0m`);
1143
+ for (const f of diff.added) console.log(` \x1b[32m+ ${f}\x1b[0m`);
1144
+ for (const f of diff.changed) console.log(` \x1b[33m~ ${f}\x1b[0m`);
1145
+ for (const f of diff.removed) console.log(` \x1b[31m- ${f}\x1b[0m`);
1146
+ }
1147
+
1148
+ function reportTier2(tier2, devCli) {
1149
+ if (tier2.copy.length + tier2.refresh.length + tier2.edited.length === 0 && !devCli.replaceBundle && !devCli.replaceLauncher && !devCli.customLauncher) return;
1150
+ console.log(`\x1b[1mSeeded docs & framework files\x1b[0m`);
1151
+ for (const s of tier2.copy) console.log(` \x1b[32m+ ${s.rel}\x1b[0m \x1b[2m(missing — copied)\x1b[0m`);
1152
+ for (const s of tier2.refresh) console.log(` \x1b[33m~ ${s.rel}\x1b[0m \x1b[2m(pristine — refreshed)\x1b[0m`);
1153
+ for (const s of tier2.edited) console.log(` \x1b[36m⊜ ${s.rel}\x1b[0m \x1b[2m(edited — queued for the update skill, your copy untouched)\x1b[0m`);
1154
+ if (devCli.replaceBundle) console.log(` \x1b[33m~ .dev/dev-bundle.js\x1b[0m \x1b[2m(framework-owned — replaced with the current bundle)\x1b[0m`);
1155
+ if (devCli.replaceLauncher) console.log(` \x1b[33m~ dev\x1b[0m \x1b[2m(framework-owned — replaced with the current launcher)\x1b[0m`);
1156
+ if (devCli.customLauncher) console.log(` \x1b[36m⊜ dev\x1b[0m \x1b[2m(customized — queued for the update skill, your copy untouched)\x1b[0m`);
1157
+ }
1158
+
1159
+ function updateGroundWork(flags = {}) {
1160
+ const p = getPaths();
1161
+ const dryRun = !!flags.dryRun;
1162
+
1163
+ banner();
1164
+
1165
+ if (isSelfCopy(p)) {
1166
+ c.warn(`You are running this command inside the GroundWork source repository itself.`);
1167
+ console.warn(` Skipping update to prevent recursive copying.\n`);
1168
+ return;
1169
+ }
1170
+
1171
+ if (!fs.existsSync(p.targetSkillsDir) && !fs.existsSync(p.targetHiddenSkillsDir)) {
1172
+ c.err(`No GroundWork installation found in ${p.targetDir}`);
1173
+ console.error(` Run \x1b[36mnpx groundwork-method init\x1b[0m first.\n`);
1174
+ process.exitCode = 1;
1175
+ return;
1176
+ }
1177
+
1178
+ const stamped = readStampedVersion(p);
1179
+
1180
+ // A3 — a pre-manifest install gets its manifest backfilled before anything
1181
+ // reads it: tier-2 classification needs a base to compare against.
1182
+ let manifest = readManifest(p);
1183
+ const bootstrapped = manifest === null;
1184
+ if (bootstrapped) {
1185
+ manifest = bootstrapManifest(p, stamped);
1186
+ if (!dryRun) {
1187
+ writeManifestFile(p, manifest);
1188
+ c.ok(`Bootstrapped install manifest (.groundwork/config/manifest.json)`);
1189
+ }
1190
+ }
1191
+
1192
+ // Classify everything before touching anything, so the summary (and --dry-run)
1193
+ // reflects exactly what a real run performs.
1194
+ const skillsDiff = diffDirs(p.sourceSkillsDir, p.targetSkillsDir);
1195
+ const hiddenDiff = diffDirs(p.sourceHiddenSkillsDir, p.targetHiddenSkillsDir);
1196
+
1197
+ const generatorsConfig = buildGeneratorsConfig();
1198
+ const targetGeneratorsJson = path.join(p.targetConfigDir, 'generators.json');
1199
+ const generatorsChanged =
1200
+ generatorsConfig !== null &&
1201
+ (!fs.existsSync(targetGeneratorsJson) ||
1202
+ fs.readFileSync(targetGeneratorsJson, 'utf8') !== generatorsConfig);
1203
+
1204
+ const tier2 = classifyTier2(p, manifest);
1205
+ const devCli = classifyDevCli(p, manifest);
1206
+ const registry = loadMigrationRegistry();
1207
+ const pending = pendingMigrations(p, registry);
1208
+ const briefItems = buildBriefItems(p, tier2, devCli, manifest);
1209
+
1210
+ const total =
1211
+ skillsDiff.added.length + skillsDiff.changed.length + skillsDiff.removed.length +
1212
+ hiddenDiff.added.length + hiddenDiff.changed.length + hiddenDiff.removed.length +
1213
+ (generatorsChanged ? 1 : 0) +
1214
+ tier2.copy.length + tier2.refresh.length +
1215
+ (devCli.replaceBundle ? 1 : 0) + (devCli.replaceLauncher ? 1 : 0) +
1216
+ pending.cli.length + briefItems.length;
1217
+
1218
+ if (total === 0) {
1219
+ if (!dryRun) {
1220
+ pruneStaleBrief(p); // a pre-cutover brief is the one thing `total` can't see — clean it
1221
+ recordMigrations(p, pending.settled);
1222
+ if (stamped !== PKG.version) stampVersion(p); // files identical, stamp drifted — repair silently
1223
+ if (bootstrapped) writeManifestFile(p, rebuildManifest(p, manifest, tier2, generatorsConfig, devCli));
1224
+ }
1225
+ c.ok(`Already up to date — installed skills match groundwork ${PKG.version}.`);
1226
+ console.log(` \x1b[2m.groundwork/config and docs/ were not touched.\x1b[0m\n`);
1227
+ return;
1228
+ }
1229
+
1230
+ if (stamped && stamped !== PKG.version) {
1231
+ c.info(`Updating ${stamped} → ${PKG.version}\n`);
1232
+ } else if (!stamped) {
1233
+ c.info(`No version stamp found (pre-0.9 install) — updating to ${PKG.version}\n`);
1234
+ }
1235
+
1236
+ // B4 — dry run: print the full plan, mutate nothing.
1237
+ if (dryRun) {
1238
+ console.log(`\x1b[1mDry run — nothing will be written.\x1b[0m\n`);
1239
+ reportDiff('.agents/skills/', skillsDiff);
1240
+ reportDiff('.groundwork/skills/', hiddenDiff);
1241
+ if (generatorsChanged) console.log(`\x1b[1m.groundwork/config/\x1b[0m\n \x1b[33m~ generators.json\x1b[0m`);
1242
+ reportTier2(tier2, devCli);
1243
+ if (pending.cli.length) {
1244
+ console.log(`\x1b[1mScripted migrations to run:\x1b[0m`);
1245
+ for (const { entry } of pending.cli) console.log(` \x1b[33m▸ ${entry.id}\x1b[0m — ${entry.title}`);
1246
+ }
1247
+ if (briefItems.length) {
1248
+ console.log(`\x1b[1mUpgrade brief (judgment lane — handled by the groundwork-update skill):\x1b[0m`);
1249
+ for (const item of briefItems) console.log(` \x1b[36m▸ ${item.id}\x1b[0m — ${item.summary || item.title}`);
1250
+ }
1251
+ console.log('');
1252
+ return;
1253
+ }
1254
+
1255
+ // ── Mechanical lane ──
1256
+ // Any I/O failure here aborts before the stamp and manifest advance: a partial
1257
+ // apply must read as "update failed, re-run" — never as a clean update whose
1258
+ // half-copied files classify as user edits on the next run.
1259
+ try {
1260
+ installSkillTrees(p);
1261
+ if (generatorsChanged) {
1262
+ fs.mkdirSync(p.targetConfigDir, { recursive: true });
1263
+ fs.writeFileSync(targetGeneratorsJson, generatorsConfig);
1264
+ }
1265
+ applyTier2(p, tier2);
1266
+ applyDevCli(p, devCli);
1267
+ } catch (err) {
1268
+ c.err(`Update failed while copying files: ${err.message}`);
1269
+ c.err(`Aborted — version stamp and manifest were not advanced. Re-run npx groundwork-method update after resolving.`);
1270
+ process.exitCode = 1;
1271
+ return;
1272
+ }
1273
+
1274
+ c.ok(`Updated GroundWork skills\n`);
1275
+ reportDiff('.agents/skills/', skillsDiff);
1276
+ reportDiff('.groundwork/skills/', hiddenDiff);
1277
+ if (generatorsChanged) console.log(`\x1b[1m.groundwork/config/\x1b[0m\n \x1b[33m~ generators.json\x1b[0m`);
1278
+ reportTier2(tier2, devCli);
1279
+
1280
+ console.log(`\n \x1b[2mPreserved: .groundwork/config (state, settings), .groundwork/cache, and every doc you edited.\x1b[0m\n`);
1281
+
1282
+ // B2 — run pending scripted migrations in registry order; record completions.
1283
+ let migrationResult = { completed: [], failed: null };
1284
+ if (pending.cli.length) {
1285
+ console.log(`\x1b[1mRunning migrations:\x1b[0m`);
1286
+ migrationResult = runCliMigrations(p, pending.cli);
1287
+ console.log('');
1288
+ }
1289
+ recordMigrations(p, pending.settled);
1290
+
1291
+ // Migration notes: surface the changelog slice between the stamped and current versions.
1292
+ if (stamped === null || semverCompare(stamped, PKG.version) < 0) {
1293
+ printChangelogSlice(stamped, PKG.version, !!flags.full);
1294
+ }
1295
+
1296
+ // Self-heal agent wiring: ensure the canonical AGENTS.md exists and the recorded agents'
1297
+ // symlinks are intact (idempotent). Pre-0.9 installs have no record — fall back to detection.
1298
+ if (ensureAgentsMd(p)) c.ok(`Installed canonical AGENTS.md`);
1299
+ const agents = readPersistedAgents(p) || detectAgents(p.targetDir);
1300
+ if (agents.length) {
1301
+ wireAgents(p.targetDir, agents);
1302
+ if (!readPersistedAgents(p)) persistAgents(p, agents);
1303
+ }
1304
+
1305
+ // E4 — compile the judgment lane's work list. Written even when a migration
1306
+ // failed: the brief is how the rest of the catch-up happens.
1307
+ const briefCount = writeUpgradeBrief(p, briefItems, stamped);
1308
+
1309
+ // A failed migration stops the stamp from advancing past it (decision S4:
1310
+ // idempotent + detect-first makes the re-run safe).
1311
+ if (migrationResult.failed) {
1312
+ process.exitCode = 1;
1313
+ return;
1314
+ }
1315
+
1316
+ writeManifestFile(p, rebuildManifest(p, manifest, tier2, generatorsConfig, devCli));
1317
+ stampVersion(p);
1318
+
1319
+ if (briefCount > 0) {
1320
+ console.log(`\n\x1b[33m\x1b[1m⚠ ${briefCount} item(s) need a working session:\x1b[0m open your agent and say \x1b[36m"update groundwork"\x1b[0m.`);
1321
+ console.log(` \x1b[2mThe work list is at .groundwork/cache/upgrade-brief.json — the groundwork-update skill consumes it, then reconciles structure.\x1b[0m\n`);
1322
+ }
1323
+ }
1324
+
1325
+ // ─── check ──────────────────────────────────────────────────────────────────
1326
+
1327
+ function parseFrontmatter(content) {
1328
+ // Minimal YAML frontmatter reader: flat `key: value` pairs between --- fences.
1329
+ if (!content.startsWith('---')) return null;
1330
+ const end = content.indexOf('\n---', 3);
1331
+ if (end === -1) return null;
1332
+ const fm = {};
1333
+ for (const line of content.slice(3, end).split('\n')) {
1334
+ const m = line.match(/^([A-Za-z_][\w-]*):\s*(.*)$/);
1335
+ if (m) fm[m[1]] = m[2].trim().replace(/^["']|["']$/g, '');
1336
+ }
1337
+ return fm;
1338
+ }
1339
+
1340
+ // F1 — the framework section of `check`: is this install behind the framework?
1341
+ // No network: the package running the check IS the newest version the user
1342
+ // fetched (decision O2). Returns true when the install is stale.
1343
+ function reportFrameworkStatus(p) {
1344
+ const installed = fs.existsSync(p.targetSkillsDir) || fs.existsSync(p.targetHiddenSkillsDir);
1345
+ if (!installed) return false;
1346
+
1347
+ let stale = false;
1348
+ const stamped = readStampedVersion(p);
1349
+ console.log(`\x1b[1mFramework:\x1b[0m installed ${stamped || 'unstamped (pre-0.9)'} · package ${PKG.version}`);
1350
+
1351
+ if (!stamped || stamped !== PKG.version) {
1352
+ stale = true;
1353
+ c.warn(`This install trails the framework — run \x1b[36mnpx groundwork-method update\x1b[0m.`);
1354
+ } else {
1355
+ // Same version: any divergence from the package is a user edit to framework-owned
1356
+ // files, which clean-replace will revert — name it instead of surprising them.
1357
+ const mismatched = [];
1358
+ for (const [src, dest, prefix] of [
1359
+ [p.sourceSkillsDir, p.targetSkillsDir, '.agents/skills'],
1360
+ [p.sourceHiddenSkillsDir, p.targetHiddenSkillsDir, '.groundwork/skills'],
1361
+ ]) {
1362
+ const d = diffDirs(src, dest);
1363
+ for (const f of [...d.changed, ...d.removed]) mismatched.push(path.join(prefix, f));
1364
+ }
1365
+ if (mismatched.length) {
1366
+ stale = true;
1367
+ c.warn(`${mismatched.length} framework-owned file(s) differ from the package (edits here are lost on update):`);
1368
+ for (const f of mismatched.slice(0, 10)) console.log(` ${f}`);
1369
+ if (mismatched.length > 10) console.log(` … and ${mismatched.length - 10} more`);
1370
+ }
1371
+ }
1372
+
1373
+ const registry = loadMigrationRegistry();
1374
+ const pending = pendingMigrations(p, registry);
1375
+ const pendingCount = pending.cli.length;
1376
+ if (pendingCount > 0) {
1377
+ stale = true;
1378
+ c.warn(`${pendingCount} pending migration(s): ${pending.cli.map((m) => m.entry.id).join(', ')}`);
1379
+ console.log(` Run \x1b[36mnpx groundwork-method update\x1b[0m — these scripted migrations run there.`);
1380
+ }
1381
+
1382
+ try {
1383
+ const brief = JSON.parse(fs.readFileSync(upgradeBriefPath(p), 'utf8'));
1384
+ const open = (brief.items || []).filter((i) => i.status === 'pending').length;
1385
+ if (open > 0) {
1386
+ stale = true;
1387
+ c.warn(`Unconsumed upgrade brief: ${open} item(s) await a working session — say \x1b[36m"upgrade groundwork"\x1b[0m to your agent.`);
1388
+ }
1389
+ } catch { /* no brief — nothing pending */ }
1390
+
1391
+ if (!stale) c.ok(`Install is current with the framework.`);
1392
+ console.log('');
1393
+ return stale;
1394
+ }
1395
+
1396
+ function checkGroundWork() {
1397
+ const p = getPaths();
1398
+ const docsDir = path.join(p.targetDir, 'docs');
1399
+
1400
+ banner();
1401
+
1402
+ // Framework staleness first — it needs no git history and tells the project it
1403
+ // has been left behind even when doc drift cannot run.
1404
+ const frameworkStale = reportFrameworkStatus(p);
1405
+ if (frameworkStale) process.exitCode = 1;
1406
+
1407
+ // Drift detection compares last_reviewed against git history — without a repo,
1408
+ // every per-doc `git log` would fail with a cryptic error.
1409
+ try {
1410
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd: p.targetDir, stdio: 'ignore' });
1411
+ } catch {
1412
+ c.err(`groundwork check requires a git repository (drift detection reads git history).`);
1413
+ console.error(` Run it from your project root, or \x1b[36mgit init\x1b[0m first.\n`);
1414
+ process.exitCode = 1;
1415
+ return;
1416
+ }
1417
+
1418
+ // Code-map freshness — advisory, never fails the build (Serena/LLM fallbacks exist).
1419
+ reportRepoMapStatus(p);
1420
+
1421
+ if (!fs.existsSync(docsDir)) {
1422
+ c.err(`No docs/ directory found in ${p.targetDir} — nothing to check.`);
1423
+ process.exitCode = 1;
1424
+ return;
1425
+ }
1426
+
1427
+ // The drift-tracked set: code-coupled docs that carry source_of_truth frontmatter.
1428
+ const candidates = [];
1429
+ for (const sub of ['services', 'api', 'domain']) {
1430
+ const dir = path.join(docsDir, sub);
1431
+ if (!fs.existsSync(dir)) continue;
1432
+ for (const f of fs.readdirSync(dir)) {
1433
+ if (f.endsWith('.md')) candidates.push(path.join(dir, f));
1434
+ }
1435
+ }
1436
+ const archDoc = path.join(docsDir, 'architecture.md');
1437
+ if (fs.existsSync(archDoc)) candidates.push(archDoc);
1438
+
1439
+ if (candidates.length === 0) {
1440
+ c.warn(`No drift-tracked docs found (docs/architecture/services/, docs/architecture/api/, docs/architecture/domain/, docs/architecture/index.md).`);
1441
+ console.log(` Nothing to check yet — docs gain drift tracking once scaffold or brownfield adoption stamps them.\n`);
1442
+ return;
1443
+ }
1444
+
1445
+ const stale = [];
1446
+ const current = [];
1447
+ const unassessed = [];
1448
+
1449
+ for (const docPath of candidates) {
1450
+ const rel = path.relative(p.targetDir, docPath);
1451
+ const fm = parseFrontmatter(fs.readFileSync(docPath, 'utf8'));
1452
+ if (!fm || !fm.last_reviewed || !fm.source_of_truth) {
1453
+ unassessed.push({ rel, reason: !fm ? 'no frontmatter' : 'missing last_reviewed or source_of_truth' });
1454
+ continue;
1455
+ }
1456
+ const sources = fm.source_of_truth.split(/[;,]/).map((s) => s.trim()).filter(Boolean);
1457
+ let commits;
1458
+ try {
1459
+ commits = execFileSync(
1460
+ 'git',
1461
+ ['log', `--since=${fm.last_reviewed}`, '--oneline', '--', ...sources],
1462
+ { cwd: p.targetDir, encoding: 'utf8' }
1463
+ ).trim();
1464
+ } catch (err) {
1465
+ c.err(`git log failed for ${rel}: ${err.message}`);
1466
+ process.exitCode = 2;
1467
+ return;
1468
+ }
1469
+ if (commits) {
1470
+ stale.push({ rel, fm, count: commits.split('\n').length });
1471
+ } else {
1472
+ current.push(rel);
1473
+ }
1474
+ }
1475
+
1476
+ console.log(`Checked ${candidates.length} drift-tracked doc(s): \x1b[32m${current.length} current\x1b[0m, \x1b[31m${stale.length} stale\x1b[0m, \x1b[33m${unassessed.length} unassessed\x1b[0m\n`);
1477
+
1478
+ if (stale.length > 0) {
1479
+ console.log(`\x1b[1mStale — code changed after last_reviewed:\x1b[0m`);
1480
+ const recovery = {
1481
+ generated: 're-run the generator that produced it',
1482
+ extracted: 'run the groundwork-update skill',
1483
+ authored: 'manual review required',
1484
+ };
1485
+ for (const s of stale) {
1486
+ const mode = s.fm.generation_mode || 'authored';
1487
+ console.log(` \x1b[31m✖\x1b[0m ${s.rel} — ${s.count} commit(s) since ${s.fm.last_reviewed} → ${recovery[mode] || recovery.authored}`);
1488
+ }
1489
+ console.log('');
1490
+ }
1491
+
1492
+ if (unassessed.length > 0) {
1493
+ console.log(`\x1b[1mUnassessed — cannot be drift-checked:\x1b[0m`);
1494
+ for (const u of unassessed) {
1495
+ console.log(` \x1b[33m?\x1b[0m ${u.rel} (${u.reason})`);
1496
+ }
1497
+ console.log('');
1498
+ }
1499
+
1500
+ if (stale.length > 0) {
1501
+ console.log(`Repair: ask your AI agent to run the \x1b[36mgroundwork-doc-sync\x1b[0m skill — it maps the`);
1502
+ console.log(`commits behind this report to surgical doc edits and gates them through review.`);
1503
+ console.log(`For dependency-graph-aware detection beyond file paths, run the \x1b[36mgroundwork-check\x1b[0m skill.\n`);
1504
+ process.exitCode = 1;
1505
+ } else {
1506
+ c.ok(`Documentation is current with the code it describes.\n`);
1507
+ }
1508
+ }
1509
+
1510
+ // ─── repo-map ─────────────────────────────────────────────────────────────
1511
+ // The deterministic code map: tree-sitter import edges + PageRank centrality,
1512
+ // cached to .groundwork/cache/repo-map.json. Complements Serena — Serena answers
1513
+ // precise per-symbol questions live; this is the whole-repo aggregate it cannot
1514
+ // export. Engine lives in lib/repo-map so the CLI stays require-only.
1515
+
1516
+ function loadRepoMapEngine() {
1517
+ try {
1518
+ return require(path.join(__dirname, '..', 'lib', 'repo-map'));
1519
+ } catch (err) {
1520
+ c.err(`repo-map engine unavailable: ${err.message}`);
1521
+ console.error(` The tree-sitter dependencies failed to load. Reinstall, or rely on`);
1522
+ console.error(` the LLM-inference fallback path in the groundwork-scan skill.\n`);
1523
+ return null;
1524
+ }
1525
+ }
1526
+
1527
+ // Advisory used by `check`: warn (never fail) when the cached map trails HEAD.
1528
+ function reportRepoMapStatus(p) {
1529
+ const engine = loadRepoMapEngine();
1530
+ if (!engine) return;
1531
+ const s = engine.staleness({ cwd: p.targetDir, cacheDir: p.targetCacheDir });
1532
+ if (s.state === 'absent') return; // no map yet — not every project builds one
1533
+ if (s.state === 'stale') {
1534
+ c.warn(`Code map is stale — ${s.changedSource.length} source file(s) changed since it was generated.`);
1535
+ console.warn(` Refresh with \x1b[36mnpx groundwork-method repo-map\x1b[0m (incremental — only changed files reparse).\n`);
1536
+ } else if (s.state === 'fresh') {
1537
+ c.ok(`Code map (repo-map.json) is current with HEAD.`);
1538
+ }
1539
+ }
1540
+
1541
+ async function repoMapCommand(argv) {
1542
+ const p = getPaths();
1543
+ banner();
1544
+ const engine = loadRepoMapEngine();
1545
+ if (!engine) { process.exitCode = 1; return; }
1546
+
1547
+ if (argv.includes('--check')) {
1548
+ const s = engine.staleness({ cwd: p.targetDir, cacheDir: p.targetCacheDir });
1549
+ if (s.state === 'absent') {
1550
+ c.warn(`No code map yet (.groundwork/cache/repo-map.json). Run \x1b[36mnpx groundwork-method repo-map\x1b[0m to build one.\n`);
1551
+ } else if (s.state === 'stale') {
1552
+ c.warn(`Code map is stale — ${s.changedSource.length} source file(s) changed since ${s.sinceCommit.slice(0, 8)}.`);
1553
+ console.warn(` Refresh: \x1b[36mnpx groundwork-method repo-map\x1b[0m\n`);
1554
+ } else if (s.state === 'unknown') {
1555
+ c.info(`Code map freshness indeterminate (${s.reason}).\n`);
1556
+ } else {
1557
+ c.ok(`Code map is current with HEAD.\n`);
1558
+ }
1559
+ return; // advisory: never fails the build
1560
+ }
1561
+
1562
+ const { map, cache, stats, diagnostics } = await engine.generate({ cwd: p.targetDir, cacheDir: p.targetCacheDir });
1563
+
1564
+ // A malformed project-local language config is the user's to fix — surface it.
1565
+ for (const e of diagnostics.projectErrors || []) c.warn(`repo-map.languages.js: ${e}`);
1566
+
1567
+ if (map.stats.files === 0) {
1568
+ c.warn(`No mappable source files found. Nothing to map.`);
1569
+ reportUnmapped(diagnostics.unmapped);
1570
+ return;
1571
+ }
1572
+ engine.write({ cacheDir: p.targetCacheDir, map, cache });
1573
+
1574
+ const langList = Object.entries(map.stats.languages).map(([l, n]) => `${l}:${n}`).join(' ');
1575
+ c.ok(`Wrote .groundwork/cache/repo-map.json — ${map.stats.files} files, ${map.stats.edges} edges (${langList})`);
1576
+ c.dim(` ${stats.parsed} parsed, ${stats.cached} reused from cache`);
1577
+ if (diagnostics.projectLanguages && diagnostics.projectLanguages.length) {
1578
+ c.dim(` project languages: ${diagnostics.projectLanguages.join(', ')}`);
1579
+ }
1580
+ if (map.centrality.length) {
1581
+ console.log(`\n\x1b[1mMost-referenced files (centrality):\x1b[0m`);
1582
+ for (const hub of map.centrality.slice(0, 5)) {
1583
+ console.log(` ${hub.file} \x1b[2m(rank ${hub.rank}, ${hub.in} incoming)\x1b[0m`);
1584
+ }
1585
+ }
1586
+ reportUnmapped(diagnostics.unmapped);
1587
+ console.log('');
1588
+ }
1589
+
1590
+ // Nudge: name the languages present in the repo but not mapped, and point at the
1591
+ // one place that explains how to enable them. This is the "lay the groundwork"
1592
+ // path — repo-map says what it cannot see rather than implying full coverage.
1593
+ function reportUnmapped(unmapped) {
1594
+ if (!unmapped || !unmapped.length) return;
1595
+ console.log('');
1596
+ c.warn(`Some languages in this repo are not mapped:`);
1597
+ for (const u of unmapped) {
1598
+ console.warn(` • ${u.language} — ${u.files} file(s); ${u.reason}`);
1599
+ }
1600
+ console.warn(
1601
+ ` Enable them by adding \x1b[36m.groundwork/config/repo-map.languages.js\x1b[0m —`
1602
+ );
1603
+ console.warn(` see \x1b[36m.groundwork/skills/code-intelligence.md\x1b[0m (“Enable repo-map for your language”).`);
1604
+ }
1605
+
1606
+ // ─── Dispatch ───────────────────────────────────────────────────────────────
1607
+
1608
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
1609
+ printHelp();
1610
+ process.exit(0);
1611
+ }
1612
+
1613
+ switch (command) {
1614
+ case 'init': {
1615
+ const flags = parseInitFlags(process.argv.slice(3));
1616
+ if (flags.invalid.length) {
1617
+ c.warn(`Unknown agent(s) ignored: ${flags.invalid.join(', ')}`);
1618
+ console.warn(` Supported: ${AGENT_KEYS.join(', ')}`);
1619
+ }
1620
+ initGroundWork(flags).catch((err) => {
1621
+ c.err(`init failed: ${err.message}`);
1622
+ process.exit(1);
1623
+ });
1624
+ break;
1625
+ }
1626
+ case 'update':
1627
+ updateGroundWork({
1628
+ dryRun: process.argv.includes('--dry-run'),
1629
+ full: process.argv.includes('--full'),
1630
+ });
1631
+ break;
1632
+ case 'check':
1633
+ // `check --help` documents behavior (incl. exit codes) instead of running.
1634
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
1635
+ printHelp();
1636
+ process.exit(0);
1637
+ }
1638
+ checkGroundWork();
1639
+ break;
1640
+ case 'repo-map':
1641
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
1642
+ printHelp();
1643
+ process.exit(0);
1644
+ }
1645
+ repoMapCommand(process.argv.slice(3)).catch((err) => {
1646
+ c.err(`repo-map failed: ${err.message}`);
1647
+ process.exit(1);
1648
+ });
1649
+ break;
1650
+ default:
1651
+ console.log(`Unknown command: ${command}`);
1652
+ printHelp();
1653
+ process.exit(1);
1654
+ }