create-workframe 0.1.0 → 0.1.2

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 (198) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +38 -40
  5. package/bin/workframe.js +329 -329
  6. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
  7. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
  8. package/package.json +3 -6
  9. package/profiles/architect/AGENTS.md +29 -29
  10. package/profiles/architect/SOUL.md +2 -2
  11. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  12. package/profiles/designer/AGENTS.md +26 -26
  13. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  14. package/profiles/dev/AGENTS.md +28 -28
  15. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  16. package/profiles/docs/AGENTS.md +27 -27
  17. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  18. package/profiles/research/AGENTS.md +26 -26
  19. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/visionary/AGENTS.md +25 -25
  21. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  22. package/profiles/workframe-agent/AGENTS.md +37 -37
  23. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  24. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  25. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  26. package/rules/workspace-README.md +5 -5
  27. package/scripts/bundle-workframe-ui.mjs +3 -3
  28. package/scripts/ensure-compose-host-paths.mjs +51 -51
  29. package/scripts/lib/install-identity.mjs +212 -212
  30. package/scripts/set-compose-public-url.mjs +92 -92
  31. package/scripts/sync-canonical-to-package.mjs +27 -9
  32. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  33. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  34. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  35. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  36. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  37. package/workframe-api/README.md +26 -28
  38. package/workframe-api/action_proxy.py +131 -131
  39. package/workframe-api/auth_rate_limit.py +49 -49
  40. package/workframe-api/credential_vault.py +445 -445
  41. package/workframe-api/data/avatar-catalog.json +41 -41
  42. package/workframe-api/email_sender.py +220 -220
  43. package/workframe-api/google_auth.py +90 -90
  44. package/workframe-api/install_api.py +359 -359
  45. package/workframe-api/internal_proxy_auth.py +150 -150
  46. package/workframe-api/llm_proxy.py +277 -277
  47. package/workframe-api/oidc_jwt.py +108 -108
  48. package/workframe-api/package.json +12 -13
  49. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  50. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  51. package/workframe-api/requirements.txt +2 -2
  52. package/workframe-api/site_meta.py +271 -271
  53. package/workframe-api/stack_config.py +427 -427
  54. package/workframe-api/time-bind-chat.py +99 -99
  55. package/workframe-api/turn_credentials.py +226 -226
  56. package/workframe-api/updates.py +417 -417
  57. package/workframe-api/vault_kek.py +159 -159
  58. package/workframe-api/zk_auth.py +633 -633
  59. package/workframe-supervisor/Dockerfile +11 -11
  60. package/workframe-supervisor/server.py +787 -787
  61. package/workframe-ui/docker/nginx.conf +85 -85
  62. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  63. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  64. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  65. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  66. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  67. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  68. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  69. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  70. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  71. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  72. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  73. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  74. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  75. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  76. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  81. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  84. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  85. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  86. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  87. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  88. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  89. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  90. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  91. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  92. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  93. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  94. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  95. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  96. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  97. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  98. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  99. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  100. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  101. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  102. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  103. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  104. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  105. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  106. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  107. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  108. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  109. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  110. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  111. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  112. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  113. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  114. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  115. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  116. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  117. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  118. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  119. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  120. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  121. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  122. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  123. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  124. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  125. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  126. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  127. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  128. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  129. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  130. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  131. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  132. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  133. package/workframe-ui/public/favicon.svg +7 -7
  134. package/workframe-ui/public/index.html +50 -50
  135. package/workframe-ui/public/workframe-config.json +3 -3
  136. package/scripts/security_audit.py +0 -156
  137. package/scripts/test-scaffold.mjs +0 -390
  138. package/workframe-api/tests/__init__.py +0 -0
  139. package/workframe-api/tests/db_setup.py +0 -13
  140. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  141. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  142. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  143. package/workframe-api/tests/test_auth_email.py +0 -222
  144. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  145. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  146. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  147. package/workframe-api/tests/test_child_soul_template.py +0 -71
  148. package/workframe-api/tests/test_credential_canary.py +0 -135
  149. package/workframe-api/tests/test_credential_isolation.py +0 -448
  150. package/workframe-api/tests/test_credential_resolution.py +0 -206
  151. package/workframe-api/tests/test_device_oauth.py +0 -108
  152. package/workframe-api/tests/test_doctor_repair.py +0 -103
  153. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  154. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  155. package/workframe-api/tests/test_install_secure_host.py +0 -39
  156. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  157. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  158. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  159. package/workframe-api/tests/test_llm_proxy.py +0 -155
  160. package/workframe-api/tests/test_login_access_policy.py +0 -183
  161. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  162. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  163. package/workframe-api/tests/test_platform_auth.py +0 -47
  164. package/workframe-api/tests/test_profile_config_path.py +0 -56
  165. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  166. package/workframe-api/tests/test_profile_create.py +0 -72
  167. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  168. package/workframe-api/tests/test_profile_install_health.py +0 -45
  169. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  170. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  171. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  172. package/workframe-api/tests/test_provider_connect.py +0 -54
  173. package/workframe-api/tests/test_room_crud.py +0 -192
  174. package/workframe-api/tests/test_room_tenancy.py +0 -701
  175. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  176. package/workframe-api/tests/test_site_meta.py +0 -81
  177. package/workframe-api/tests/test_soul_stub.py +0 -42
  178. package/workframe-api/tests/test_space_member_sync.py +0 -99
  179. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  180. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  181. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  182. package/workframe-api/tests/test_updates.py +0 -176
  183. package/workframe-api/tests/test_user_cohort.py +0 -113
  184. package/workframe-api/tests/test_vault_envelope.py +0 -110
  185. package/workframe-api/tests/test_workspace_members.py +0 -183
  186. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  187. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  188. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  189. package/workframe-supervisor/tests/test_server_import.py +0 -21
  190. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  191. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  192. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  193. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  194. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  195. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  196. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  197. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  198. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
package/bin/workframe.js CHANGED
@@ -1,329 +1,329 @@
1
- #!/usr/bin/env node
2
- /**
3
- * workframe — lifecycle CLI for generated Workframe projects.
4
- *
5
- * Commands:
6
- * workframe doctor [--repair] Diagnose stack; --repair provisions missing agent DM runtimes
7
- * workframe setup Open Hermes setup (credentials)
8
- * workframe stop Stop all stack containers
9
- * workframe start Start the full stack (docker compose up -d)
10
- * workframe restart Restart the full stack
11
- * workframe status Show running containers
12
- * workframe logs Tail gateway logs
13
- * workframe ui Open Workframe UI in browser
14
- */
15
- import fs from 'node:fs';
16
- import path from 'node:path';
17
- import { spawn, spawnSync } from 'node:child_process';
18
- import { fileURLToPath } from 'node:url';
19
-
20
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
-
22
- function findProjectRoot() {
23
- let dir = process.cwd();
24
- while (true) {
25
- if (fs.existsSync(path.join(dir, 'workframe-manifest.json'))) return dir;
26
- if (fs.existsSync(path.join(dir, 'docker-compose.yml')) && fs.existsSync(path.join(dir, '.env'))) return dir;
27
- const parent = path.dirname(dir);
28
- if (parent === dir) return null;
29
- dir = parent;
30
- }
31
- }
32
-
33
- function readManifest(root) {
34
- const p = path.join(root, 'workframe-manifest.json');
35
- if (!fs.existsSync(p)) return null;
36
- return JSON.parse(fs.readFileSync(p, 'utf8'));
37
- }
38
-
39
- function readEnvPort(root, key) {
40
- const envFile = path.join(root, '.env');
41
- if (!fs.existsSync(envFile)) return null;
42
- const line = fs.readFileSync(envFile, 'utf8').split('\n').find((l) => l.startsWith(`${key}=`));
43
- return line ? line.split('=', 2)[1].trim() : null;
44
- }
45
-
46
- function dockerCompose(cwd, args, { stdio = 'inherit' } = {}) {
47
- return spawnSync('docker', ['compose', ...args], { cwd, stdio, encoding: 'utf8' });
48
- }
49
-
50
- function doctorAgentDmRuntimes(root, { repair = false } = {}) {
51
- const fn = repair ? 'doctor_repair_agent_dm_runtimes(repair=True)' : 'doctor_audit_agent_dm_runtimes()';
52
- const py = `import json, server; print(json.dumps(server.${fn}))`;
53
- return dockerCompose(root, ['exec', '-T', 'workframe-api', 'python3', '-c', py], { stdio: 'pipe' });
54
- }
55
-
56
- function cmdDoctor(root, extraArgs = []) {
57
- const repair = extraArgs.includes('--repair');
58
- const manifest = readManifest(root);
59
- const issues = [];
60
-
61
- console.log(repair ? 'workframe doctor --repair' : 'workframe doctor');
62
- console.log('================\n');
63
-
64
- // Check Docker
65
- const dockerCheck = spawnSync('docker', ['info'], { encoding: 'utf8' });
66
- if (dockerCheck.status !== 0) {
67
- issues.push('Docker is not running or not installed.');
68
- console.log('[FAIL] Docker: not running');
69
- } else {
70
- console.log('[ OK] Docker: running');
71
- }
72
-
73
- // Check manifest
74
- if (!manifest) {
75
- issues.push('No workframe-manifest.json found. Are you in a Workframe project?');
76
- console.log('[FAIL] Manifest: not found');
77
- } else {
78
- console.log(`[ OK] Manifest: ${manifest.project_name} (${manifest.docker?.stack})`);
79
- }
80
-
81
- // Check .env
82
- const envFile = path.join(root, '.env');
83
- if (!fs.existsSync(envFile)) {
84
- issues.push('.env file missing.');
85
- console.log('[FAIL] .env: missing');
86
- } else {
87
- console.log('[ OK] .env: present');
88
- }
89
-
90
- // Check docker-compose.yml
91
- const composeFile = path.join(root, 'docker-compose.yml');
92
- if (!fs.existsSync(composeFile)) {
93
- issues.push('docker-compose.yml missing.');
94
- console.log('[FAIL] docker-compose.yml: missing');
95
- } else {
96
- console.log('[ OK] docker-compose.yml: present');
97
- }
98
-
99
- // Check required directories
100
- for (const dir of ['Agents', 'Files']) {
101
- if (!fs.existsSync(path.join(root, dir))) {
102
- issues.push(`${dir}/ directory missing.`);
103
- console.log(`[FAIL] ${dir}/: missing`);
104
- } else {
105
- console.log(`[ OK] ${dir}/: present`);
106
- }
107
- }
108
-
109
- // Check containers
110
- if (manifest && dockerCheck.status === 0) {
111
- const ps = dockerCompose(root, ['ps', '--format', 'json']);
112
- if (ps.status === 0 && ps.stdout) {
113
- try {
114
- const containers = JSON.parse(ps.stdout);
115
- const expected = ['gateway', 'dashboard', 'workframe-api', 'workframe'];
116
- for (const name of expected) {
117
- const container = containers.find((c) => c.Name?.includes(name) || c.Service === name);
118
- if (!container) {
119
- issues.push(`${name} container not found.`);
120
- console.log(`[FAIL] ${name}: not found`);
121
- } else if (container.State !== 'running') {
122
- issues.push(`${name} container is ${container.State}, not running.`);
123
- console.log(`[FAIL] ${name}: ${container.State}`);
124
- } else {
125
- console.log(`[ OK] ${name}: running`);
126
- }
127
- }
128
- } catch {
129
- // Fallback: plain text parse
130
- const psPlain = dockerCompose(root, ['ps']);
131
- console.log('\nContainer status:\n' + psPlain.stdout);
132
- }
133
- }
134
- }
135
-
136
- // Check bootstrap
137
- if (manifest) {
138
- const nativeSlug = manifest.native_agent?.profile_slug;
139
- const soulFile = path.join(root, 'Agents', 'profiles', nativeSlug, 'SOUL.md');
140
- if (!fs.existsSync(soulFile)) {
141
- issues.push('Native agent not bootstrapped. Run: ./scripts/bootstrap-native.sh');
142
- console.log('[FAIL] Bootstrap: native SOUL missing');
143
- } else {
144
- console.log('[ OK] Bootstrap: native SOUL present');
145
- }
146
- }
147
-
148
- // Check ports
149
- if (manifest) {
150
- const ports = manifest.ports;
151
- if (ports) {
152
- console.log(`\nPorts:`);
153
- console.log(` Gateway: ${ports.gateway}, Dashboard: ${ports.dashboard}, UI: ${ports.ui}, API: ${ports.api}`);
154
- }
155
- }
156
-
157
- // Agent DM runtime slots (explicit repair only with --repair)
158
- if (manifest && dockerCheck.status === 0) {
159
- const api = dockerCompose(root, ['ps', '--format', 'json'], { stdio: 'pipe' });
160
- const apiUp = api.status === 0 && (api.stdout || '').includes('workframe-api');
161
- if (apiUp) {
162
- const audit = doctorAgentDmRuntimes(root, { repair: false });
163
- if (audit.status === 0 && audit.stdout) {
164
- try {
165
- const data = JSON.parse(audit.stdout.trim().split('\n').pop());
166
- const missing = data.missing?.length ?? 0;
167
- if (missing > 0) {
168
- const msg = `${missing} agent DM runtime profile(s) missing`;
169
- if (repair) {
170
- const fixed = doctorAgentDmRuntimes(root, { repair: true });
171
- if (fixed.status === 0 && fixed.stdout) {
172
- const result = JSON.parse(fixed.stdout.trim().split('\n').pop());
173
- const repaired = result.repaired?.length ?? 0;
174
- const failed = result.failed?.length ?? 0;
175
- console.log(`[REPAIR] Agent DM runtimes: ${repaired} provisioned, ${failed} failed`);
176
- if (failed || (result.still_missing?.length ?? 0) > 0) {
177
- issues.push(msg);
178
- }
179
- } else {
180
- issues.push(`${msg} (repair failed)`);
181
- console.log('[FAIL] Agent DM runtime repair failed');
182
- }
183
- } else {
184
- issues.push(`${msg} — run: workframe doctor --repair`);
185
- console.log(`[WARN] Agent DM runtimes: ${msg}`);
186
- }
187
- } else {
188
- console.log('[ OK] Agent DM runtimes: all provisioned');
189
- }
190
- } catch {
191
- console.log('[skip] Agent DM runtime audit: could not parse API response');
192
- }
193
- } else {
194
- console.log('[skip] Agent DM runtime audit: workframe-api not reachable');
195
- }
196
- }
197
- }
198
-
199
- if (issues.length > 0) {
200
- console.log(`\n${issues.length} issue(s) found:\n`);
201
- issues.forEach((i) => console.log(` - ${i}`));
202
- process.exit(1);
203
- } else {
204
- console.log('\nAll checks passed. Workframe is healthy.');
205
- }
206
- }
207
-
208
- function cmdSetup(root) {
209
- const manifest = readManifest(root);
210
- const image = manifest?.docker?.image || 'nousresearch/hermes-agent:latest';
211
- const name = manifest?.docker?.stack || 'workframe';
212
- console.log('Opening Hermes setup (interactive)...');
213
- console.log('Credentials never belong in chat.\n');
214
- dockerCompose(root, ['pull'], { stdio: 'inherit' });
215
- const res = spawnSync('docker', [
216
- 'run', '--rm', '-it',
217
- '--name', `${name}-setup`,
218
- '--entrypoint', 'hermes',
219
- '-v', `${path.join(root, 'Agents')}:/opt/data`,
220
- '-v', `${path.join(root, 'Files')}:/workspace`,
221
- image, 'setup',
222
- ], { stdio: 'inherit' });
223
- if (res.status !== 0) {
224
- console.error('Setup failed or was cancelled.');
225
- process.exit(res.status || 1);
226
- }
227
- }
228
-
229
- function cmdStop(root) {
230
- console.log('Stopping Workframe stack...');
231
- const res = dockerCompose(root, ['down']);
232
- if (res.status !== 0) {
233
- console.error('Failed to stop stack.');
234
- process.exit(1);
235
- }
236
- console.log('Stack stopped.');
237
- }
238
-
239
- function cmdStart(root) {
240
- console.log('Starting Workframe stack...');
241
- const res = dockerCompose(root, ['up', '-d']);
242
- if (res.status !== 0) {
243
- console.error('Failed to start stack.');
244
- process.exit(1);
245
- }
246
- const manifest = readManifest(root);
247
- if (manifest?.ports) {
248
- console.log(`\nWorkframe UI: http://127.0.0.1:${manifest.ports.ui}/`);
249
- console.log(`Hermes chat: http://127.0.0.1:${manifest.ports.dashboard}/chat`);
250
- }
251
- console.log('Stack started.');
252
- }
253
-
254
- function cmdRestart(root) {
255
- console.log('Restarting Workframe stack...');
256
- const res = dockerCompose(root, ['restart']);
257
- if (res.status !== 0) {
258
- console.error('Failed to restart stack.');
259
- process.exit(1);
260
- }
261
- console.log('Stack restarted.');
262
- }
263
-
264
- function cmdStatus(root) {
265
- dockerCompose(root, ['ps'], { stdio: 'inherit' });
266
- }
267
-
268
- function cmdLogs(root, args) {
269
- const follow = args.includes('--follow') || args.includes('-f');
270
- const composeArgs = ['logs'];
271
- if (follow) composeArgs.push('--follow');
272
- composeArgs.push('gateway');
273
- dockerCompose(root, composeArgs, { stdio: 'inherit' });
274
- }
275
-
276
- function cmdUi(root) {
277
- const manifest = readManifest(root);
278
- const port = readEnvPort(root, 'WORKFRAME_UI_PORT') || manifest?.ports?.ui || '18644';
279
- const url = `http://127.0.0.1:${port}/`;
280
- console.log(`Opening Workframe UI: ${url}`);
281
- const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
282
- const child = spawn(openCmd, [url], { detached: true, stdio: 'ignore' });
283
- child.unref();
284
- }
285
-
286
- function usage() {
287
- console.log(`workframe — lifecycle CLI for Workframe projects
288
-
289
- Usage:
290
- workframe doctor [--repair] Diagnose stack; --repair provisions missing agent DM runtimes
291
- workframe setup Open Hermes setup (credentials)
292
- workframe start Start the full stack (docker compose up -d)
293
- workframe stop Stop all stack containers
294
- workframe restart Restart the full stack
295
- workframe status Show running containers
296
- workframe logs [--follow] Tail gateway logs
297
- workframe ui Open Workframe UI in browser
298
-
299
- Run from a Workframe project directory (where workframe-manifest.json lives).
300
- `);
301
- }
302
-
303
- const args = process.argv.slice(2);
304
- const command = args[0];
305
- const extraArgs = args.slice(1);
306
-
307
- const root = findProjectRoot();
308
- if (!root) {
309
- console.error('ERROR: Not in a Workframe project. No workframe-manifest.json found.');
310
- process.exit(1);
311
- }
312
-
313
- switch (command) {
314
- case 'doctor': cmdDoctor(root, extraArgs); break;
315
- case 'setup': cmdSetup(root); break;
316
- case 'stop': cmdStop(root); break;
317
- case 'start': cmdStart(root); break;
318
- case 'restart': cmdRestart(root); break;
319
- case 'status': cmdStatus(root); break;
320
- case 'logs': cmdLogs(root, extraArgs); break;
321
- case 'ui': cmdUi(root); break;
322
- case '--help':
323
- case '-h':
324
- case 'help': usage(); break;
325
- default:
326
- if (command) console.error(`Unknown command: ${command}`);
327
- usage();
328
- process.exit(command ? 1 : 0);
329
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * workframe — lifecycle CLI for generated Workframe projects.
4
+ *
5
+ * Commands:
6
+ * workframe doctor [--repair] Diagnose stack; --repair provisions missing agent DM runtimes
7
+ * workframe setup Open Hermes setup (credentials)
8
+ * workframe stop Stop all stack containers
9
+ * workframe start Start the full stack (docker compose up -d)
10
+ * workframe restart Restart the full stack
11
+ * workframe status Show running containers
12
+ * workframe logs Tail gateway logs
13
+ * workframe ui Open Workframe UI in browser
14
+ */
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import { spawn, spawnSync } from 'node:child_process';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+
22
+ function findProjectRoot() {
23
+ let dir = process.cwd();
24
+ while (true) {
25
+ if (fs.existsSync(path.join(dir, 'workframe-manifest.json'))) return dir;
26
+ if (fs.existsSync(path.join(dir, 'docker-compose.yml')) && fs.existsSync(path.join(dir, '.env'))) return dir;
27
+ const parent = path.dirname(dir);
28
+ if (parent === dir) return null;
29
+ dir = parent;
30
+ }
31
+ }
32
+
33
+ function readManifest(root) {
34
+ const p = path.join(root, 'workframe-manifest.json');
35
+ if (!fs.existsSync(p)) return null;
36
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
37
+ }
38
+
39
+ function readEnvPort(root, key) {
40
+ const envFile = path.join(root, '.env');
41
+ if (!fs.existsSync(envFile)) return null;
42
+ const line = fs.readFileSync(envFile, 'utf8').split('\n').find((l) => l.startsWith(`${key}=`));
43
+ return line ? line.split('=', 2)[1].trim() : null;
44
+ }
45
+
46
+ function dockerCompose(cwd, args, { stdio = 'inherit' } = {}) {
47
+ return spawnSync('docker', ['compose', ...args], { cwd, stdio, encoding: 'utf8' });
48
+ }
49
+
50
+ function doctorAgentDmRuntimes(root, { repair = false } = {}) {
51
+ const fn = repair ? 'doctor_repair_agent_dm_runtimes(repair=True)' : 'doctor_audit_agent_dm_runtimes()';
52
+ const py = `import json, server; print(json.dumps(server.${fn}))`;
53
+ return dockerCompose(root, ['exec', '-T', 'workframe-api', 'python3', '-c', py], { stdio: 'pipe' });
54
+ }
55
+
56
+ function cmdDoctor(root, extraArgs = []) {
57
+ const repair = extraArgs.includes('--repair');
58
+ const manifest = readManifest(root);
59
+ const issues = [];
60
+
61
+ console.log(repair ? 'workframe doctor --repair' : 'workframe doctor');
62
+ console.log('================\n');
63
+
64
+ // Check Docker
65
+ const dockerCheck = spawnSync('docker', ['info'], { encoding: 'utf8' });
66
+ if (dockerCheck.status !== 0) {
67
+ issues.push('Docker is not running or not installed.');
68
+ console.log('[FAIL] Docker: not running');
69
+ } else {
70
+ console.log('[ OK] Docker: running');
71
+ }
72
+
73
+ // Check manifest
74
+ if (!manifest) {
75
+ issues.push('No workframe-manifest.json found. Are you in a Workframe project?');
76
+ console.log('[FAIL] Manifest: not found');
77
+ } else {
78
+ console.log(`[ OK] Manifest: ${manifest.project_name} (${manifest.docker?.stack})`);
79
+ }
80
+
81
+ // Check .env
82
+ const envFile = path.join(root, '.env');
83
+ if (!fs.existsSync(envFile)) {
84
+ issues.push('.env file missing.');
85
+ console.log('[FAIL] .env: missing');
86
+ } else {
87
+ console.log('[ OK] .env: present');
88
+ }
89
+
90
+ // Check docker-compose.yml
91
+ const composeFile = path.join(root, 'docker-compose.yml');
92
+ if (!fs.existsSync(composeFile)) {
93
+ issues.push('docker-compose.yml missing.');
94
+ console.log('[FAIL] docker-compose.yml: missing');
95
+ } else {
96
+ console.log('[ OK] docker-compose.yml: present');
97
+ }
98
+
99
+ // Check required directories
100
+ for (const dir of ['Agents', 'Files']) {
101
+ if (!fs.existsSync(path.join(root, dir))) {
102
+ issues.push(`${dir}/ directory missing.`);
103
+ console.log(`[FAIL] ${dir}/: missing`);
104
+ } else {
105
+ console.log(`[ OK] ${dir}/: present`);
106
+ }
107
+ }
108
+
109
+ // Check containers
110
+ if (manifest && dockerCheck.status === 0) {
111
+ const ps = dockerCompose(root, ['ps', '--format', 'json']);
112
+ if (ps.status === 0 && ps.stdout) {
113
+ try {
114
+ const containers = JSON.parse(ps.stdout);
115
+ const expected = ['gateway', 'dashboard', 'workframe-api', 'workframe'];
116
+ for (const name of expected) {
117
+ const container = containers.find((c) => c.Name?.includes(name) || c.Service === name);
118
+ if (!container) {
119
+ issues.push(`${name} container not found.`);
120
+ console.log(`[FAIL] ${name}: not found`);
121
+ } else if (container.State !== 'running') {
122
+ issues.push(`${name} container is ${container.State}, not running.`);
123
+ console.log(`[FAIL] ${name}: ${container.State}`);
124
+ } else {
125
+ console.log(`[ OK] ${name}: running`);
126
+ }
127
+ }
128
+ } catch {
129
+ // Fallback: plain text parse
130
+ const psPlain = dockerCompose(root, ['ps']);
131
+ console.log('\nContainer status:\n' + psPlain.stdout);
132
+ }
133
+ }
134
+ }
135
+
136
+ // Check bootstrap
137
+ if (manifest) {
138
+ const nativeSlug = manifest.native_agent?.profile_slug;
139
+ const soulFile = path.join(root, 'Agents', 'profiles', nativeSlug, 'SOUL.md');
140
+ if (!fs.existsSync(soulFile)) {
141
+ issues.push('Native agent not bootstrapped. Run: ./scripts/bootstrap-native.sh');
142
+ console.log('[FAIL] Bootstrap: native SOUL missing');
143
+ } else {
144
+ console.log('[ OK] Bootstrap: native SOUL present');
145
+ }
146
+ }
147
+
148
+ // Check ports
149
+ if (manifest) {
150
+ const ports = manifest.ports;
151
+ if (ports) {
152
+ console.log(`\nPorts:`);
153
+ console.log(` Gateway: ${ports.gateway}, Dashboard: ${ports.dashboard}, UI: ${ports.ui}, API: ${ports.api}`);
154
+ }
155
+ }
156
+
157
+ // Agent DM runtime slots (explicit repair only with --repair)
158
+ if (manifest && dockerCheck.status === 0) {
159
+ const api = dockerCompose(root, ['ps', '--format', 'json'], { stdio: 'pipe' });
160
+ const apiUp = api.status === 0 && (api.stdout || '').includes('workframe-api');
161
+ if (apiUp) {
162
+ const audit = doctorAgentDmRuntimes(root, { repair: false });
163
+ if (audit.status === 0 && audit.stdout) {
164
+ try {
165
+ const data = JSON.parse(audit.stdout.trim().split('\n').pop());
166
+ const missing = data.missing?.length ?? 0;
167
+ if (missing > 0) {
168
+ const msg = `${missing} agent DM runtime profile(s) missing`;
169
+ if (repair) {
170
+ const fixed = doctorAgentDmRuntimes(root, { repair: true });
171
+ if (fixed.status === 0 && fixed.stdout) {
172
+ const result = JSON.parse(fixed.stdout.trim().split('\n').pop());
173
+ const repaired = result.repaired?.length ?? 0;
174
+ const failed = result.failed?.length ?? 0;
175
+ console.log(`[REPAIR] Agent DM runtimes: ${repaired} provisioned, ${failed} failed`);
176
+ if (failed || (result.still_missing?.length ?? 0) > 0) {
177
+ issues.push(msg);
178
+ }
179
+ } else {
180
+ issues.push(`${msg} (repair failed)`);
181
+ console.log('[FAIL] Agent DM runtime repair failed');
182
+ }
183
+ } else {
184
+ issues.push(`${msg} — run: workframe doctor --repair`);
185
+ console.log(`[WARN] Agent DM runtimes: ${msg}`);
186
+ }
187
+ } else {
188
+ console.log('[ OK] Agent DM runtimes: all provisioned');
189
+ }
190
+ } catch {
191
+ console.log('[skip] Agent DM runtime audit: could not parse API response');
192
+ }
193
+ } else {
194
+ console.log('[skip] Agent DM runtime audit: workframe-api not reachable');
195
+ }
196
+ }
197
+ }
198
+
199
+ if (issues.length > 0) {
200
+ console.log(`\n${issues.length} issue(s) found:\n`);
201
+ issues.forEach((i) => console.log(` - ${i}`));
202
+ process.exit(1);
203
+ } else {
204
+ console.log('\nAll checks passed. Workframe is healthy.');
205
+ }
206
+ }
207
+
208
+ function cmdSetup(root) {
209
+ const manifest = readManifest(root);
210
+ const image = manifest?.docker?.image || 'nousresearch/hermes-agent:latest';
211
+ const name = manifest?.docker?.stack || 'workframe';
212
+ console.log('Opening Hermes setup (interactive)...');
213
+ console.log('Credentials never belong in chat.\n');
214
+ dockerCompose(root, ['pull'], { stdio: 'inherit' });
215
+ const res = spawnSync('docker', [
216
+ 'run', '--rm', '-it',
217
+ '--name', `${name}-setup`,
218
+ '--entrypoint', 'hermes',
219
+ '-v', `${path.join(root, 'Agents')}:/opt/data`,
220
+ '-v', `${path.join(root, 'Files')}:/workspace`,
221
+ image, 'setup',
222
+ ], { stdio: 'inherit' });
223
+ if (res.status !== 0) {
224
+ console.error('Setup failed or was cancelled.');
225
+ process.exit(res.status || 1);
226
+ }
227
+ }
228
+
229
+ function cmdStop(root) {
230
+ console.log('Stopping Workframe stack...');
231
+ const res = dockerCompose(root, ['down']);
232
+ if (res.status !== 0) {
233
+ console.error('Failed to stop stack.');
234
+ process.exit(1);
235
+ }
236
+ console.log('Stack stopped.');
237
+ }
238
+
239
+ function cmdStart(root) {
240
+ console.log('Starting Workframe stack...');
241
+ const res = dockerCompose(root, ['up', '-d']);
242
+ if (res.status !== 0) {
243
+ console.error('Failed to start stack.');
244
+ process.exit(1);
245
+ }
246
+ const manifest = readManifest(root);
247
+ if (manifest?.ports) {
248
+ console.log(`\nWorkframe UI: http://127.0.0.1:${manifest.ports.ui}/`);
249
+ console.log(`Hermes chat: http://127.0.0.1:${manifest.ports.dashboard}/chat`);
250
+ }
251
+ console.log('Stack started.');
252
+ }
253
+
254
+ function cmdRestart(root) {
255
+ console.log('Restarting Workframe stack...');
256
+ const res = dockerCompose(root, ['restart']);
257
+ if (res.status !== 0) {
258
+ console.error('Failed to restart stack.');
259
+ process.exit(1);
260
+ }
261
+ console.log('Stack restarted.');
262
+ }
263
+
264
+ function cmdStatus(root) {
265
+ dockerCompose(root, ['ps'], { stdio: 'inherit' });
266
+ }
267
+
268
+ function cmdLogs(root, args) {
269
+ const follow = args.includes('--follow') || args.includes('-f');
270
+ const composeArgs = ['logs'];
271
+ if (follow) composeArgs.push('--follow');
272
+ composeArgs.push('gateway');
273
+ dockerCompose(root, composeArgs, { stdio: 'inherit' });
274
+ }
275
+
276
+ function cmdUi(root) {
277
+ const manifest = readManifest(root);
278
+ const port = readEnvPort(root, 'WORKFRAME_UI_PORT') || manifest?.ports?.ui || '18644';
279
+ const url = `http://127.0.0.1:${port}/`;
280
+ console.log(`Opening Workframe UI: ${url}`);
281
+ const openCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
282
+ const child = spawn(openCmd, [url], { detached: true, stdio: 'ignore' });
283
+ child.unref();
284
+ }
285
+
286
+ function usage() {
287
+ console.log(`workframe — lifecycle CLI for Workframe projects
288
+
289
+ Usage:
290
+ workframe doctor [--repair] Diagnose stack; --repair provisions missing agent DM runtimes
291
+ workframe setup Open Hermes setup (credentials)
292
+ workframe start Start the full stack (docker compose up -d)
293
+ workframe stop Stop all stack containers
294
+ workframe restart Restart the full stack
295
+ workframe status Show running containers
296
+ workframe logs [--follow] Tail gateway logs
297
+ workframe ui Open Workframe UI in browser
298
+
299
+ Run from a Workframe project directory (where workframe-manifest.json lives).
300
+ `);
301
+ }
302
+
303
+ const args = process.argv.slice(2);
304
+ const command = args[0];
305
+ const extraArgs = args.slice(1);
306
+
307
+ const root = findProjectRoot();
308
+ if (!root) {
309
+ console.error('ERROR: Not in a Workframe project. No workframe-manifest.json found.');
310
+ process.exit(1);
311
+ }
312
+
313
+ switch (command) {
314
+ case 'doctor': cmdDoctor(root, extraArgs); break;
315
+ case 'setup': cmdSetup(root); break;
316
+ case 'stop': cmdStop(root); break;
317
+ case 'start': cmdStart(root); break;
318
+ case 'restart': cmdRestart(root); break;
319
+ case 'status': cmdStatus(root); break;
320
+ case 'logs': cmdLogs(root, extraArgs); break;
321
+ case 'ui': cmdUi(root); break;
322
+ case '--help':
323
+ case '-h':
324
+ case 'help': usage(); break;
325
+ default:
326
+ if (command) console.error(`Unknown command: ${command}`);
327
+ usage();
328
+ process.exit(command ? 1 : 0);
329
+ }
@@ -5,7 +5,7 @@ Goal
5
5
 
6
6
  Concierge onboarding loop
7
7
  1) clarify mission, scope, constraints, success criteria
8
- 2) default to native-only starter, then expand with specialist packs or individual profiles on demand
8
+ 2) default to native-only starter, then expand with specialist packs or individual profiles on demand
9
9
  3) establish file-based source of truth
10
10
  4) route first tasks through Kanban
11
11
  5) persist outcomes in project files