@shanyucoder/flowgrid 0.1.4 → 0.1.8

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 (157) hide show
  1. package/README.md +4 -1
  2. package/adapters/laravel/registries/codegen.registry.json +9 -9
  3. package/bin/flowgrid.mjs +353 -116
  4. package/bin/lib/agent-mcp.mjs +4 -0
  5. package/bin/lib/agent-profiles.mjs +30 -6
  6. package/bin/lib/audit-run.mjs +1 -1
  7. package/bin/lib/cli-update.mjs +48 -8
  8. package/bin/lib/doctor.mjs +90 -3
  9. package/bin/lib/harness-overlay.mjs +12 -5
  10. package/bin/lib/harness-sync.mjs +17 -3
  11. package/bin/lib/init-adapters.mjs +75 -0
  12. package/bin/lib/init-scaffold.mjs +9 -0
  13. package/bin/lib/inject-consumer-scripts.mjs +115 -0
  14. package/bin/lib/merge-stack-config.mjs +89 -0
  15. package/bin/lib/project-gitignore.mjs +1 -0
  16. package/bin/lib/repo-maps-align.mjs +203 -0
  17. package/dist/graph/config/load-config.js +7 -2
  18. package/dist/graph/config/load-config.js.map +1 -1
  19. package/dist/graph/mcp/tools.js +1 -1
  20. package/dist/graph/mcp/tools.js.map +1 -1
  21. package/dist/graph/registry/load-registries.d.ts +1 -0
  22. package/dist/graph/registry/load-registries.js +14 -1
  23. package/dist/graph/registry/load-registries.js.map +1 -1
  24. package/engines/cases/render-cases.mjs +67 -5
  25. package/engines/docs/lib/render-api-summary-markdown.mjs +28 -0
  26. package/engines/docs/lib/render-bundle-markdown.mjs +77 -2
  27. package/engines/docs/lib/render-data-model-markdown.mjs +171 -0
  28. package/engines/docs/lib/render-design-tables.mjs +89 -7
  29. package/engines/docs/lib/render-template.mjs +6 -0
  30. package/engines/docs/render-docs.mjs +6 -2
  31. package/engines/docs/vitepress/config.ts +7 -7
  32. package/engines/docs/vitepress/surfaces-nav.mjs +45 -14
  33. package/engines/openapi/check-backend-spec.mjs +2 -2
  34. package/engines/openapi/lib/markdown-table.mjs +8 -0
  35. package/engines/openapi/lib/render-backend-spec-markdown.mjs +78 -0
  36. package/engines/registry-sync/be-capabilities-sync.mjs +118 -0
  37. package/engines/registry-sync/fe-design-sync.mjs +258 -0
  38. package/engines/registry-sync/run-registry-sync.mjs +107 -0
  39. package/engines/shared/e2e-output-layout.mjs +68 -0
  40. package/engines/shared/flowgrid-e2e-root.mjs +19 -0
  41. package/engines/shared/resolve-flowgrid-context.mjs +176 -0
  42. package/engines/spec/lib/audit-api-gaps.mjs +1 -1
  43. package/engines/spec/lib/audit-bundle-gaps.mjs +92 -3
  44. package/engines/spec/lib/audit-db-tables.mjs +529 -0
  45. package/engines/spec/lib/audit-e2e-coverage.mjs +88 -30
  46. package/engines/spec/lib/bundle-schema.mjs +4 -1
  47. package/engines/spec/split-bundle.mjs +11 -1
  48. package/engines/testcase/runners/generate-api.mjs +23 -23
  49. package/engines/testcase/runners/generate.mjs +19 -17
  50. package/engines/testcase/runners/lib/bootstrap-context.mjs +44 -0
  51. package/engines/testcase/runners/lib/write-files.mjs +37 -9
  52. package/harness/agents/antigravity/rules/antigravity-mcp.mdc +12 -0
  53. package/harness/agents/gemini/rules/gemini-mcp.mdc +11 -0
  54. package/harness/agents/gemini_antigravity/rules/antigravity-mcp.mdc +8 -6
  55. package/harness/be/skills/{grill-api → audit-api}/SKILL.md +8 -7
  56. package/harness/common/extracts/artifact-graph.md +2 -2
  57. package/harness/common/extracts/artifactgraph-phase-hooks.md +2 -2
  58. package/harness/common/extracts/docs-mark-detect.md +2 -2
  59. package/harness/common/extracts/entity-relationship.md +23 -0
  60. package/harness/common/rules/flowgrid-ux-common.mdc +3 -3
  61. package/harness/common/skills/configure-repo-maps/SKILL.md +4 -2
  62. package/harness/docs/extracts/agent-execution-protocol.md +1 -1
  63. package/harness/docs/extracts/api-codegen-readiness.md +34 -0
  64. package/harness/docs/extracts/api-codegen-tags.md +30 -0
  65. package/harness/docs/extracts/api-contract.md +43 -0
  66. package/harness/docs/extracts/api-spec-sync.md +35 -0
  67. package/harness/docs/extracts/artifactgraph-hooks-docs.md +1 -1
  68. package/harness/docs/extracts/call-external.md +16 -0
  69. package/harness/docs/extracts/common-scope.md +9 -10
  70. package/harness/docs/extracts/db-audit-wizard.md +45 -0
  71. package/harness/docs/extracts/derived-data.md +18 -0
  72. package/harness/docs/extracts/design-leaf-signoff.md +16 -0
  73. package/harness/docs/extracts/extract-registry.docs.json +9 -1
  74. package/harness/docs/extracts/spec-core.md +7 -3
  75. package/harness/docs/extracts/spec-evolution.md +21 -0
  76. package/harness/docs/extracts/spec-prd-lite.md +19 -0
  77. package/harness/docs/extracts/spec-requirement.md +6 -2
  78. package/harness/docs/extracts/spec-ssot-prep.md +25 -0
  79. package/harness/docs/extracts/tpl-module.md +12 -0
  80. package/harness/docs/extracts/verify-gate.md +33 -0
  81. package/harness/docs/extracts/wire-spec-feedback.md +31 -0
  82. package/harness/docs/rules/agent-compliance.mdc +1 -1
  83. package/harness/docs/rules/team-flow-spec.mdc +3 -4
  84. package/harness/docs/skills/adopt/SKILL.md +2 -0
  85. package/harness/docs/skills/api/SKILL.md +4 -5
  86. package/harness/docs/skills/api-spec/SKILL.md +19 -6
  87. package/harness/docs/skills/api-update/SKILL.md +3 -3
  88. package/harness/docs/skills/architecture/SKILL.md +1 -1
  89. package/harness/docs/skills/business-process/SKILL.md +2 -0
  90. package/harness/docs/skills/common/SKILL.md +2 -2
  91. package/harness/docs/skills/common-spec/SKILL.md +10 -47
  92. package/harness/docs/skills/db-erd/SKILL.md +26 -0
  93. package/harness/docs/skills/grill/SKILL.md +28 -22
  94. package/harness/docs/skills/grill-api/SKILL.md +4 -6
  95. package/harness/docs/skills/grill-api-spec/SKILL.md +44 -24
  96. package/harness/docs/skills/grill-bqa/SKILL.md +28 -13
  97. package/harness/docs/skills/grill-common-spec/SKILL.md +10 -35
  98. package/harness/docs/skills/grill-dev/SKILL.md +7 -6
  99. package/harness/docs/skills/grill-docs/SKILL.md +10 -3
  100. package/harness/docs/skills/module/SKILL.md +3 -1
  101. package/harness/docs/skills/openapi/SKILL.md +2 -1
  102. package/harness/docs/skills/overview/SKILL.md +7 -1
  103. package/harness/docs/skills/spec/SKILL.md +42 -9
  104. package/harness/docs/skills/update-spec/SKILL.md +4 -1
  105. package/harness/fe/extracts/wire-audit-loop.md +72 -0
  106. package/harness/fe/extracts/wire-phase.md +45 -0
  107. package/harness/fe/rules/platform-design-vocabulary.mdc +1 -1
  108. package/harness/fe/rules/team-flow-prototype.mdc +8 -4
  109. package/harness/fe/skills/gen-common/SKILL.md +11 -84
  110. package/harness/fe/skills/grill-prototype/SKILL.md +51 -25
  111. package/harness/fe/skills/grill-test/SKILL.md +78 -20
  112. package/harness/fe/skills/grill-wire/SKILL.md +81 -0
  113. package/harness/fe/skills/prototype/SKILL.md +3 -2
  114. package/harness/fe/skills/wire/SKILL.md +7 -2
  115. package/harness/shared/AGENTS.md +3 -3
  116. package/harness/shared/SSOT_AGENT_PROTOCOL.md +3 -3
  117. package/harness/tests/extracts/grill-api-hook.md +69 -0
  118. package/harness/tests/extracts/grill-scenario-flow.md +39 -0
  119. package/harness/tests/extracts/grill-screen-tc.md +40 -0
  120. package/harness/tests/extracts/testcase-gen-cli.md +57 -0
  121. package/harness/tests/extracts/testcase-plan.md +29 -0
  122. package/harness/tests/extracts/tests-verify-gate.md +29 -0
  123. package/harness/tests/extracts/wire-test-handoff.md +37 -0
  124. package/harness/tests/skills/grill-testcase/SKILL.md +1 -0
  125. package/harness/tests/skills/test-api/SKILL.md +14 -6
  126. package/harness/tests/skills/testcase/SKILL.md +3 -1
  127. package/harness/tests/templates/TC.example-api.yaml +7 -1
  128. package/harness/tests/templates/TC.example.yaml +4 -1
  129. package/harness/tests/templates/tpl-testcase-plan.md +75 -0
  130. package/package.json +1 -1
  131. package/stacks/fastapi.json +1 -0
  132. package/stacks/laravel.json +1 -0
  133. package/stacks/nestjs.json +72 -0
  134. package/stacks/nextjs-nest.json +1 -0
  135. package/stacks/nuxt4-nest.json +1 -0
  136. package/templates/project-skeleton/architecture/03-business-process/FLOW-template.md +2 -0
  137. package/templates/project-skeleton/overview/index.md +68 -2
  138. package/templates/project-skeleton/overview/operational-areas/_template.md +37 -0
  139. package/templates/project-skeleton/surfaces/common/data-model/index.md +10 -2
  140. package/templates/shared/api-03-mock.stub.yaml +14 -0
  141. package/templates/shared/backend-api.bundle.yaml +3 -0
  142. package/templates/shared/backend-api.yaml +4 -0
  143. package/templates/shared/be-capabilities.registry.base.json +8 -0
  144. package/templates/shared/bundle-authoring.md +39 -3
  145. package/templates/shared/default-layout.ejs +131 -18
  146. package/templates/shared/design-spec.yaml +2 -3
  147. package/templates/shared/design.registry.base.json +38 -0
  148. package/templates/shared/feature.bundle.yaml +16 -9
  149. package/templates/shared/ir/generated/spec.md +281 -0
  150. package/templates/shared/qa-item.yaml +1 -1
  151. package/templates/shared/tpl-api-contract.md +133 -0
  152. package/templates/shared/tpl-screen-data-model.md +76 -0
  153. package/templates/tests-skeleton/cases/README.md +4 -0
  154. package/templates/tests-skeleton/catalog/locale.yaml +9 -0
  155. package/templates/tests-skeleton/tpl-testcase-plan.md +9 -0
  156. package/harness/docs/skills/api-integration/SKILL.md +0 -110
  157. package/harness/docs/skills/grill-integration-spec/SKILL.md +0 -51
package/bin/flowgrid.mjs CHANGED
@@ -19,7 +19,21 @@ import {
19
19
  applyDocsHubScaffold,
20
20
  applyTestsHubScaffold,
21
21
  promptHubRoot,
22
+ hubPathUnderProject,
22
23
  } from './lib/init-scaffold.mjs';
24
+ import { promptFeAdapter, promptBeAdapter } from './lib/init-adapters.mjs';
25
+ import {
26
+ loadStackPresetJson,
27
+ mergeStackIntoFlowgridConfig,
28
+ resolveStackPresetKey,
29
+ } from './lib/merge-stack-config.mjs';
30
+ import { defaultE2eRootRel, DEFAULT_E2E_ROOT_REL } from '../engines/shared/flowgrid-e2e-root.mjs';
31
+ import {
32
+ resolveFlowgridContext,
33
+ flowgridContextToEnv,
34
+ ENV_DOCS_ROOT,
35
+ } from '../engines/shared/resolve-flowgrid-context.mjs';
36
+ import { injectFlowgridConsumerScripts } from './lib/inject-consumer-scripts.mjs';
23
37
 
24
38
  function packageRootFromMeta(metaUrl) {
25
39
  return path.resolve(path.dirname(fileURLToPath(metaUrl)), '..');
@@ -35,6 +49,27 @@ function assertNodeVersion() {
35
49
  }
36
50
  }
37
51
 
52
+ function printDefaultHelp() {
53
+ const lines = [
54
+ 'FlowGrid CLI — chạy trong thư mục repo đích (cd project trước khi init).',
55
+ '',
56
+ 'Cài một lần (global):',
57
+ ' npm install -g @shanyucoder/flowgrid',
58
+ ' pnpm add -g @shanyucoder/flowgrid',
59
+ '',
60
+ 'Khởi tạo repo đích:',
61
+ ' flowgrid init',
62
+ '',
63
+ 'Khác:',
64
+ ' flowgrid doctor · flowgrid update · flowgrid harness sync · flowgrid audit',
65
+ '',
66
+ 'npx (không cài global) — phải gọi bin flowgrid:',
67
+ ' npx -p @shanyucoder/flowgrid flowgrid init',
68
+ ' (không dùng: npx @shanyucoder/flowgrid — sẽ không truyền lệnh init)',
69
+ ];
70
+ console.log(lines.join('\n'));
71
+ }
72
+
38
73
  export async function main() {
39
74
  assertNodeVersion();
40
75
  const args = process.argv.slice(2);
@@ -46,6 +81,23 @@ export async function main() {
46
81
  }
47
82
 
48
83
  const command = args[0];
84
+
85
+ if (command === 'api:check') {
86
+ try {
87
+ const { checkBackendSpecFiles, parseApiCheckArgs } = await import(
88
+ '../engines/openapi/check-backend-spec.mjs'
89
+ );
90
+ const ctx = resolveFlowgridContext({ argv: args.slice(1) });
91
+ const checkArgv = parseApiCheckArgs(ctx.argv);
92
+ const cwd = ctx.docsRoot ?? ctx.projectRoot;
93
+ await checkBackendSpecFiles(checkArgv, cwd);
94
+ process.exit(0);
95
+ } catch (e) {
96
+ console.error(pc.red(e.message ?? String(e)));
97
+ process.exit(1);
98
+ }
99
+ }
100
+
49
101
  const engineCommands = ['split', 'merge', 'check', 'split_all', 'normalize', 'render', 'publish', 'legacy_validate', 'extract_i18n', 'openapi_render', 'openapi_build_ui', 'openapi_gen'];
50
102
 
51
103
  if (engineCommands.includes(command)) {
@@ -65,11 +117,14 @@ export async function main() {
65
117
  if (testCommands.includes(command)) {
66
118
  try {
67
119
  const { runEngine } = await import('../dist/test/engines/run.js');
68
- const { extractTestsDocsFlag, ENV_TESTS_DOC } = await import('../engines/shared/flowgrid-tests-docs.mjs');
69
- const root = process.cwd();
120
+ const { flowgridContextToEnv, resolveFlowgridContext } = await import(
121
+ '../engines/shared/resolve-flowgrid-context.mjs'
122
+ );
123
+ const ctx = resolveFlowgridContext({ argv: args.slice(1) });
124
+ const root = ctx.projectRoot;
70
125
  let engineRel = [];
71
- const { argv, testsDocs } = extractTestsDocsFlag(args.slice(1));
72
- const engineEnv = testsDocs ? { [ENV_TESTS_DOC]: testsDocs } : {};
126
+ const argv = ctx.argv;
127
+ const engineEnv = flowgridContextToEnv(ctx);
73
128
 
74
129
  if (command === 'cases:render') engineRel = ['cases', 'render-cases.mjs'];
75
130
  if (command === 'cases:check') engineRel = ['cases', 'check-plans.mjs'];
@@ -97,6 +152,17 @@ export async function main() {
97
152
  }
98
153
  }
99
154
 
155
+ if (command === 'registry:sync') {
156
+ try {
157
+ const { runRegistrySync } = await import('../engines/registry-sync/run-registry-sync.mjs');
158
+ const res = await runRegistrySync(args.slice(1));
159
+ process.exit(res.code ?? 0);
160
+ } catch (e) {
161
+ console.error(pc.red('registry:sync failed: ' + (e.message ?? String(e))));
162
+ process.exit(1);
163
+ }
164
+ }
165
+
100
166
  const codegenCommands = [
101
167
  'api-gen', 'api-gen:dry', 'api-registry', 'api-unit-gen', 'api-unit-gen:dry', 'api-unit-registry',
102
168
  'gen', 'gen:dry', 'registry', 'unit-gen', 'unit-gen:dry', 'unit-registry',
@@ -106,18 +172,19 @@ export async function main() {
106
172
 
107
173
  if (codegenCommands.includes(command)) {
108
174
  try {
109
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
110
- let config = {};
111
- if (fs.existsSync(configPath)) {
112
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
113
- }
114
175
  const root = process.cwd();
115
- const argv = args.slice(1);
176
+ const ctx = resolveFlowgridContext({ cwd: root, argv: args.slice(1) });
177
+ const config = ctx.config ?? {};
178
+ const argv = ctx.argv;
116
179
  const isDry = command.endsWith(':dry');
180
+ const docsRoot = ctx.docsRoot;
181
+ if (docsRoot && !process.env[ENV_DOCS_ROOT]) {
182
+ process.env[ENV_DOCS_ROOT] = docsRoot;
183
+ }
184
+ Object.assign(process.env, flowgridContextToEnv(ctx, {}));
117
185
 
118
186
  const beAdapter = config.backend?.adapter || 'fastapi';
119
187
  const feAdapter = config.frontend?.adapter || 'nextjs';
120
- const docsRoot = config.frontend?.docsRoot;
121
188
 
122
189
  if (command.startsWith('api-')) {
123
190
  const { runBeEngine } = await import('../dist/codegen/adapters/run-be.js');
@@ -175,22 +242,19 @@ export async function main() {
175
242
 
176
243
  if (command === 'dev' || command === 'serve') {
177
244
  const { spawn, spawnSync } = await import('node:child_process');
178
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
179
- let docsRoot, testsRoot, feAdapter;
180
- if (fs.existsSync(configPath)) {
181
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
182
- docsRoot = config.frontend?.docsRoot || config.backend?.docsRoot;
183
- testsRoot = config.frontend?.testsRoot ?? config.backend?.testsRoot;
184
- feAdapter = config.frontend?.adapter;
185
- }
245
+ const ctx = resolveFlowgridContext({ argv: args.slice(1) });
246
+ const config = ctx.config ?? {};
247
+ const docsRoot = ctx.docsRoot;
248
+ const testsRoot = ctx.testsDocs;
249
+ let feAdapter = config.frontend?.adapter;
186
250
  feAdapter = feAdapter || 'nextjs';
187
- const projectRoot = process.cwd();
251
+ const projectRoot = ctx.projectRoot;
188
252
 
189
253
  // ── gen-common: initial build (watch is handled by VitePress plugin) ──
190
254
  const webAdapters = new Set(['nuxt4', 'nextjs']);
191
255
  if (docsRoot && webAdapters.has(feAdapter)) {
192
256
  const resolvedDocsRoot = path.resolve(docsRoot);
193
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../..');
257
+ const packageRoot = packageRootFromMeta(import.meta.url);
194
258
  const commonEngine = path.join(packageRoot, 'adapters', 'shared', 'common-gen.mjs');
195
259
  const env = {
196
260
  ...process.env,
@@ -215,14 +279,14 @@ export async function main() {
215
279
  // ── VitePress dev servers ────────────────────────────────────────
216
280
  if (docsRoot) {
217
281
  console.log(pc.blue(`[Docs] Starting VitePress in ${docsRoot}...`));
218
- spawn('npx', ['vitepress', 'dev', docsRoot, ...args.slice(1)], { stdio: 'inherit' });
282
+ spawn('npx', ['vitepress', 'dev', docsRoot, ...ctx.argv], { stdio: 'inherit' });
219
283
  }
220
284
  if (testsRoot) {
221
285
  console.log(pc.blue(`[Tests] Starting VitePress in ${testsRoot} (Port 5174)...`));
222
- spawn('npx', ['vitepress', 'dev', testsRoot, '--port', '5174', ...args.slice(1)], { stdio: 'inherit' });
286
+ spawn('npx', ['vitepress', 'dev', testsRoot, '--port', '5174', ...ctx.argv], { stdio: 'inherit' });
223
287
  }
224
288
  if (!docsRoot && !testsRoot) {
225
- spawn('npx', ['vitepress', 'dev', ...args.slice(1)], { stdio: 'inherit' });
289
+ spawn('npx', ['vitepress', 'dev', ...ctx.argv], { stdio: 'inherit' });
226
290
  }
227
291
  // Keep alive for spawned processes
228
292
  setInterval(() => {}, 1000);
@@ -231,27 +295,97 @@ export async function main() {
231
295
 
232
296
  if (command === 'build') {
233
297
  const { spawnSync } = await import('node:child_process');
234
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
235
- let docsRoot, testsRoot;
236
- if (fs.existsSync(configPath)) {
237
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
238
- docsRoot = config.frontend?.docsRoot || config.backend?.docsRoot;
239
- testsRoot = config.frontend?.testsRoot ?? config.backend?.testsRoot;
240
- }
298
+ const ctx = resolveFlowgridContext({ argv: args.slice(1) });
299
+ const docsRoot = ctx.docsRoot;
300
+ const testsRoot = ctx.testsDocs;
241
301
  if (docsRoot) {
242
302
  console.log(pc.blue(`[Docs] Building VitePress in ${docsRoot}...`));
243
- spawnSync('npx', ['vitepress', 'build', docsRoot, ...args.slice(1)], { stdio: 'inherit' });
303
+ spawnSync('npx', ['vitepress', 'build', docsRoot, ...ctx.argv], { stdio: 'inherit' });
244
304
  }
245
305
  if (testsRoot) {
246
306
  console.log(pc.blue(`[Tests] Building VitePress in ${testsRoot}...`));
247
- spawnSync('npx', ['vitepress', 'build', testsRoot, ...args.slice(1)], { stdio: 'inherit' });
307
+ spawnSync('npx', ['vitepress', 'build', testsRoot, ...ctx.argv], { stdio: 'inherit' });
248
308
  }
249
309
  if (!docsRoot && !testsRoot) {
250
- spawnSync('npx', ['vitepress', 'build', ...args.slice(1)], { stdio: 'inherit' });
310
+ spawnSync('npx', ['vitepress', 'build', ...ctx.argv], { stdio: 'inherit' });
251
311
  }
252
312
  return;
253
313
  }
254
314
 
315
+ if (command === 'repo-maps') {
316
+ const sub = args[1] || 'check';
317
+ const {
318
+ compareConfigAndRepoMaps,
319
+ writeRepoMapsFromConfig,
320
+ writeConfigFromRepoMaps,
321
+ } = await import('./lib/repo-maps-align.mjs');
322
+ const projectRoot = process.cwd();
323
+ const cmp = compareConfigAndRepoMaps(projectRoot);
324
+
325
+ if (sub === 'check') {
326
+ if (cmp.conflicts.length) {
327
+ for (const c of cmp.conflicts) {
328
+ console.log(pc.yellow(`conflict ${c.key}: config=${c.configPath} map=${c.mapPath}`));
329
+ }
330
+ }
331
+ if (cmp.missingInMap.length) {
332
+ console.log(pc.gray(`missing in ${'platform-repos.local.json'}: ${cmp.missingInMap.join(', ')}`));
333
+ }
334
+ if (!cmp.conflicts.length && !cmp.missingInMap.length) {
335
+ console.log(pc.green('repo-maps: aligned with .flowgrid/config.json'));
336
+ } else {
337
+ console.log(pc.cyan('Fix: flowgrid repo-maps align (or repo-maps sync --from-config)'));
338
+ }
339
+ process.exit(cmp.conflicts.length ? 1 : 0);
340
+ }
341
+
342
+ if (sub === 'sync') {
343
+ const from = args.includes('--from-map') ? 'map' : 'config';
344
+ if (from === 'config') {
345
+ const { file } = writeRepoMapsFromConfig(projectRoot);
346
+ console.log(pc.green(`Updated ${path.relative(projectRoot, file)} from config`));
347
+ } else {
348
+ writeConfigFromRepoMaps(projectRoot);
349
+ console.log(pc.green('Updated .flowgrid/config.json from platform-repos.local.json'));
350
+ }
351
+ process.exit(0);
352
+ }
353
+
354
+ if (sub === 'align') {
355
+ if (!cmp.conflicts.length) {
356
+ if (cmp.missingInMap.length) {
357
+ const { file } = writeRepoMapsFromConfig(projectRoot);
358
+ console.log(pc.green(`Seeded ${path.relative(projectRoot, file)} from init config`));
359
+ } else {
360
+ console.log(pc.green('Already aligned — nothing to do'));
361
+ }
362
+ process.exit(0);
363
+ }
364
+ const { applyAlignChoices } = await import('./lib/repo-maps-align.mjs');
365
+ const choices = {};
366
+ for (const c of cmp.conflicts) {
367
+ const choice = await select({
368
+ message: `Chốt path cho hub "${c.key}" (config lúc init vs platform-repos.local):`,
369
+ options: [
370
+ { value: 'config', label: `Giữ config → ${c.configPath}` },
371
+ { value: 'map', label: `Giữ repo map → ${c.mapPath}` },
372
+ ],
373
+ });
374
+ if (isCancel(choice)) {
375
+ cancel('Cancelled.');
376
+ process.exit(0);
377
+ }
378
+ choices[c.key] = choice;
379
+ }
380
+ applyAlignChoices(projectRoot, choices);
381
+ console.log(pc.green('Aligned config + platform-repos.local.json — run flowgrid harness sync if MCP paths changed'));
382
+ process.exit(0);
383
+ }
384
+
385
+ console.log('Usage: flowgrid repo-maps check | align | sync [--from-config|--from-map]');
386
+ process.exit(1);
387
+ }
388
+
255
389
  if (command === 'update') {
256
390
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
257
391
  const { runCliUpdate } = await import('./lib/cli-update.mjs');
@@ -310,7 +444,18 @@ export async function main() {
310
444
  }
311
445
 
312
446
  if (!keepAgents) {
313
- const dirs = ['.agents', '.gemini', '.cursor', '.claude', '.codex', '.opencode', '.hermes', '.kiro', '.kilo'];
447
+ const dirs = [
448
+ '.agents',
449
+ '.antigravity',
450
+ '.gemini',
451
+ '.cursor',
452
+ '.claude',
453
+ '.codex',
454
+ '.opencode',
455
+ '.hermes',
456
+ '.kiro',
457
+ '.kilo',
458
+ ];
314
459
  for (const d of dirs) {
315
460
  const p = path.join(process.cwd(), d);
316
461
  if (fs.existsSync(p)) {
@@ -368,7 +513,7 @@ export async function main() {
368
513
 
369
514
  if (command === 'doctor') {
370
515
  try {
371
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
516
+ const packageRoot = packageRootFromMeta(import.meta.url);
372
517
  const fix = args.includes('--fix');
373
518
  const report = runDoctor({
374
519
  projectRoot: process.cwd(),
@@ -385,7 +530,7 @@ export async function main() {
385
530
 
386
531
  if (command === 'harness' && args[1] === 'sync') {
387
532
  try {
388
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
533
+ const packageRoot = packageRootFromMeta(import.meta.url);
389
534
  runHarnessSync(args.slice(2), { packageRoot });
390
535
  process.exit(0);
391
536
  } catch (e) {
@@ -404,7 +549,12 @@ export async function main() {
404
549
  process.exit(1);
405
550
  }
406
551
 
407
- if (command && command !== 'init') {
552
+ if (!command) {
553
+ printDefaultHelp();
554
+ return;
555
+ }
556
+
557
+ if (command !== 'init') {
408
558
  console.error(pc.red(`Lệnh không hợp lệ: ${command}`));
409
559
  console.log(`Chạy 'flowgrid init', 'flowgrid doctor', 'flowgrid update', 'flowgrid harness sync', hoặc 'flowgrid audit'.`);
410
560
  process.exit(1);
@@ -417,7 +567,8 @@ export async function main() {
417
567
  const agentsAns = await multiselect({
418
568
  message: 'Select Agents/Skills to integrate:',
419
569
  options: [
420
- { value: 'gemini_antigravity', label: 'Gemini & Antigravity' },
570
+ { value: 'gemini', label: 'Gemini (CLI / VS Code extension) → .agents/' },
571
+ { value: 'antigravity', label: 'Google Antigravity IDE → .antigravity/' },
421
572
  { value: 'cursor', label: 'Cursor' },
422
573
  { value: 'claude', label: 'Claude Code' },
423
574
  { value: 'codex', label: 'Codex CLI' },
@@ -433,6 +584,19 @@ export async function main() {
433
584
  process.exit(0);
434
585
  }
435
586
  const selectedAgents = agentsAns;
587
+ if (selectedAgents.includes('gemini')) {
588
+ console.log(pc.gray('Gemini: harness → .agents/ (mcp.json + .agents/skills/).\n'));
589
+ }
590
+ if (selectedAgents.includes('antigravity')) {
591
+ console.log(
592
+ pc.gray(
593
+ 'Antigravity IDE: harness → .antigravity/ (mcp_config.json + .antigravity/skills/) — không dùng .agents/.\n',
594
+ ),
595
+ );
596
+ }
597
+ if (selectedAgents.length === 0) {
598
+ console.log(pc.yellow('Không chọn agent — bỏ qua sync harness/MCP (chỉ .flowgrid + scaffold).\n'));
599
+ }
436
600
 
437
601
  // 2. Project Type
438
602
  const selectedType = await select({
@@ -459,6 +623,7 @@ export async function main() {
459
623
  let testsHubInRepo = false;
460
624
  let scaffoldDocs = false;
461
625
  let scaffoldTests = false;
626
+ let feE2eRoot = '';
462
627
  const projectRoot = process.cwd();
463
628
  let baseProfile = 'standard';
464
629
  let goldenSample = '';
@@ -495,22 +660,12 @@ export async function main() {
495
660
  });
496
661
  if (isCancel(feAns)) { cancel('Cancelled.'); process.exit(0); }
497
662
  feAdapter = feAns;
498
- beAdapter = 'nestjs';
663
+ beAdapter = await promptBeAdapter({ select, text, isCancel, cancel });
499
664
  console.log(pc.cyan(`=> Selected Fullstack: Frontend (${feAdapter}) + Backend (${beAdapter})`));
500
665
  } else if (selectedType === 'Frontend') {
501
- const feAns = await text({
502
- message: 'Enter Frontend technology (e.g. nuxt4, nextjs, or custom):',
503
- placeholder: baseProfile === 'custom' ? 'custom' : 'nuxt4'
504
- });
505
- if (isCancel(feAns)) { cancel('Cancelled.'); process.exit(0); }
506
- feAdapter = feAns || (baseProfile === 'custom' ? 'custom' : 'nuxt4');
666
+ feAdapter = await promptFeAdapter({ select, text, isCancel, cancel });
507
667
  } else if (selectedType === 'Backend') {
508
- const beAns = await text({
509
- message: 'Enter Backend technology (e.g. nestjs, fastapi, laravel, dotnet, or custom):',
510
- placeholder: baseProfile === 'custom' ? 'custom' : 'nestjs'
511
- });
512
- if (isCancel(beAns)) { cancel('Cancelled.'); process.exit(0); }
513
- beAdapter = beAns || (baseProfile === 'custom' ? 'custom' : 'nestjs');
668
+ beAdapter = await promptBeAdapter({ select, text, isCancel, cancel });
514
669
  }
515
670
 
516
671
  const needsDocsHub =
@@ -558,6 +713,34 @@ export async function main() {
558
713
  scaffoldTests = testsHub.scaffold;
559
714
  }
560
715
 
716
+ const needsE2eRoot =
717
+ selectedType === 'Frontend' ||
718
+ selectedType === 'Fullstack' ||
719
+ (selectedType === 'Backend' && feTestRoot);
720
+ if (needsE2eRoot) {
721
+ const e2eDefault = defaultE2eRootRel(selectedType);
722
+ const e2eAns = await text({
723
+ message:
724
+ selectedType === 'Backend'
725
+ ? 'Automation root (Playwright api-e2e / request specs; tùy chọn Newman; khác tests-docs YAML):'
726
+ : 'Playwright e2e-root (chứa *.spec.ts / Page Object; khác tests-docs hub YAML):',
727
+ defaultValue: e2eDefault,
728
+ placeholder: e2eDefault,
729
+ });
730
+ if (isCancel(e2eAns)) {
731
+ cancel('Cancelled.');
732
+ process.exit(0);
733
+ }
734
+ feE2eRoot = (e2eAns || e2eDefault).trim() || e2eDefault;
735
+ console.log(
736
+ pc.gray(
737
+ selectedType === 'Backend'
738
+ ? ` (tests-docs = TC-*.yaml; automation = tests/api-e2e + audit e2e — mặc định BE: ${e2eDefault})`
739
+ : ` (tests-docs = kế hoạch TC-*.yaml; e2e-root = code Playwright — mặc định ${e2eDefault})`,
740
+ ),
741
+ );
742
+ }
743
+
561
744
  if (selectedType === 'Test') {
562
745
  feTestRoot = '.';
563
746
  testsHubInRepo = true;
@@ -613,6 +796,13 @@ export async function main() {
613
796
  if (beAdapter) {
614
797
  console.log(`- Backend Adapter: ${beAdapter}`);
615
798
  }
799
+ if (feE2eRoot) {
800
+ const e2eLabel =
801
+ selectedType === 'Backend'
802
+ ? 'Automation root (Playwright api-e2e / Newman)'
803
+ : 'Playwright e2e-root';
804
+ console.log(`- ${e2eLabel}: ${feE2eRoot} (testcase:gen / testcase:gen:api / audit e2e)`);
805
+ }
616
806
  }
617
807
  console.log(`- Setup Agents: ${selectedAgents.length > 0 ? selectedAgents.join(', ') : 'No'}`);
618
808
  console.log(`- Destination folder: .flowgrid/`);
@@ -632,6 +822,8 @@ export async function main() {
632
822
  fs.mkdirSync(targetDir, { recursive: true });
633
823
  }
634
824
 
825
+ const packageRoot = packageRootFromMeta(import.meta.url);
826
+
635
827
  const projectConfig = {
636
828
  type: selectedType,
637
829
  agents: selectedAgents,
@@ -643,6 +835,7 @@ export async function main() {
643
835
  adapter: feAdapter,
644
836
  docsRoot: feDocRoot || undefined,
645
837
  testsRoot: feTestRoot || undefined,
838
+ e2eRoot: feE2eRoot || defaultE2eRootRel(selectedType),
646
839
  docsHubInRepo: feDocRoot ? docsHubInRepo : undefined,
647
840
  testsHubInRepo: feTestRoot ? testsHubInRepo : undefined,
648
841
  } : null,
@@ -650,21 +843,50 @@ export async function main() {
650
843
  adapter: beAdapter,
651
844
  docsRoot: (!feAdapter && feDocRoot) ? feDocRoot : undefined,
652
845
  testsRoot: (!feAdapter && feTestRoot) ? feTestRoot : undefined,
846
+ e2eRoot: (!feAdapter && feTestRoot)
847
+ ? (feE2eRoot || defaultE2eRootRel('Backend'))
848
+ : undefined,
653
849
  docsHubInRepo: (!feAdapter && feDocRoot) ? docsHubInRepo : undefined,
654
850
  testsHubInRepo: (!feAdapter && feTestRoot) ? testsHubInRepo : undefined,
655
851
  } : null
656
852
  };
853
+
854
+ const stackKey = resolveStackPresetKey({
855
+ type: selectedType,
856
+ feAdapter,
857
+ beAdapter,
858
+ });
859
+ const stackPreset = loadStackPresetJson(packageRoot, stackKey);
860
+ mergeStackIntoFlowgridConfig(projectConfig, stackPreset, process.cwd());
861
+ if (stackPreset?.stack) {
862
+ console.log(pc.gray(` + Stack preset: ${stackKey} (registries/commands for ArtifactGraph MCP)`));
863
+ }
864
+
657
865
  fs.writeFileSync(path.join(targetDir, 'config.json'), JSON.stringify(projectConfig, null, 2));
658
866
  console.log(" + Wrote config.json");
659
867
 
660
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
661
-
868
+ if (feDocRoot || feTestRoot || selectedType === 'Document' || selectedType === 'Test') {
869
+ try {
870
+ const { writeRepoMapsFromConfig } = await import('./lib/repo-maps-align.mjs');
871
+ writeRepoMapsFromConfig(process.cwd());
872
+ console.log(pc.gray(' + Seeded platform-repos.local.json from hub pointers (config SSOT)'));
873
+ } catch {
874
+ /* non-fatal */
875
+ }
876
+ }
877
+
662
878
  const copyRecursive = (srcDir, destDir, log = false) => {
663
879
  if (!fs.existsSync(srcDir)) return;
664
880
  if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
665
881
  const items = fs.readdirSync(srcDir, { withFileTypes: true });
666
882
  for (const item of items) {
667
- if (['node_modules', '.git', 'dist', '.agents', '.gemini', '.cursor', '.flowgrid'].includes(item.name)) continue;
883
+ if (
884
+ ['node_modules', '.git', 'dist', '.agents', '.antigravity', '.gemini', '.cursor', '.flowgrid'].includes(
885
+ item.name,
886
+ )
887
+ ) {
888
+ continue;
889
+ }
668
890
  if (item.name.endsWith('.db')) continue;
669
891
 
670
892
  const srcPath = path.join(srcDir, item.name);
@@ -723,6 +945,23 @@ export async function main() {
723
945
  });
724
946
  }
725
947
 
948
+ if (feE2eRoot && hubPathUnderProject(process.cwd(), feE2eRoot)) {
949
+ const e2eAbs = path.join(process.cwd(), feE2eRoot);
950
+ if (!fs.existsSync(e2eAbs)) {
951
+ fs.mkdirSync(e2eAbs, { recursive: true });
952
+ fs.writeFileSync(path.join(e2eAbs, '.gitkeep'), '');
953
+ console.log(pc.blue(` + Automation / e2e-root scaffold → ${feE2eRoot}/`));
954
+ }
955
+ if (selectedType === 'Backend' && testsHubInRepo) {
956
+ const apiE2eAbs = path.join(process.cwd(), 'tests', 'api-e2e');
957
+ if (!fs.existsSync(apiE2eAbs)) {
958
+ fs.mkdirSync(apiE2eAbs, { recursive: true });
959
+ fs.writeFileSync(path.join(apiE2eAbs, '.gitkeep'), '');
960
+ console.log(pc.blue(` + API hook specs scaffold → tests/api-e2e/`));
961
+ }
962
+ }
963
+ }
964
+
726
965
  console.log(pc.blue(`[INFO] Syncing global templates & schemas...`));
727
966
  const tplSrc = path.join(packageRoot, 'templates');
728
967
  const tplDest = path.join(targetDir, 'templates');
@@ -753,6 +992,19 @@ export async function main() {
753
992
  console.log(
754
993
  pc.gray(` + Wrote MCP config: ${path.relative(process.cwd(), mcpConfigPath) || mcpConfigPath}`)
755
994
  );
995
+ const skillsDir = path.join(agentDir, 'skills');
996
+ const skillCount = fs.existsSync(skillsDir)
997
+ ? fs.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).length
998
+ : 0;
999
+ const relAgent = path.relative(process.cwd(), agentDir) || profile.dirName;
1000
+ console.log(pc.gray(` + Skills: ${relAgent}/skills/ (${skillCount} skill folders)`));
1001
+ if (skillCount === 0) {
1002
+ console.log(
1003
+ pc.yellow(
1004
+ ` ! Không có skill — thử: flowgrid harness sync --agent=${agent}`,
1005
+ ),
1006
+ );
1007
+ }
756
1008
  }
757
1009
  writeRootAgentsMd(process.cwd(), selectedAgents);
758
1010
  console.log(pc.gray(' + Wrote root AGENTS.md'));
@@ -777,63 +1029,14 @@ export async function main() {
777
1029
  pkg.devDependencies['@braintree/sanitize-url'] = '^7.1.0';
778
1030
  }
779
1031
 
780
- pkg.scripts['flowgrid:audit'] = 'flowgrid audit';
781
- pkg.scripts['flowgrid:audit:spec'] = 'flowgrid audit spec';
782
- pkg.scripts['flowgrid:audit:flow'] = 'flowgrid audit flow';
783
- pkg.scripts['flowgrid:audit:api'] = 'flowgrid audit api';
784
- pkg.scripts['flowgrid:audit:testcase'] = 'flowgrid audit testcase';
785
- pkg.scripts['flowgrid:audit:fe-be'] = 'flowgrid audit fe-be';
786
- pkg.scripts['flowgrid:audit:scenario'] = 'flowgrid audit scenario';
787
- pkg.scripts['flowgrid:audit:e2e'] = 'flowgrid audit e2e';
788
- pkg.scripts['flowgrid:audit:legacy'] = 'flowgrid audit legacy';
789
- pkg.scripts['flowgrid:harness-sync'] = 'flowgrid harness sync';
790
- pkg.scripts['flowgrid:harness-sync:full-docs'] = 'flowgrid harness sync --full-docs';
791
- pkg.scripts['flowgrid:doctor'] = 'flowgrid doctor';
792
- pkg.scripts['flowgrid:doctor:fix'] = 'flowgrid doctor --fix';
793
-
794
- const injectDocsHubScripts =
795
- selectedType === 'Document' || (feDocRoot && docsHubInRepo);
796
- if (injectDocsHubScripts) {
797
- pkg.scripts['flowgrid:split'] = 'flowgrid split';
798
- pkg.scripts['flowgrid:split_all'] = 'flowgrid split_all';
799
- pkg.scripts['flowgrid:render'] = 'flowgrid render';
800
- pkg.scripts['flowgrid:openapi'] = 'flowgrid openapi_render';
801
- pkg.scripts['flowgrid:openapi-ui'] = 'flowgrid openapi_build_ui';
802
- pkg.scripts['flowgrid:dev'] = 'flowgrid dev';
803
- pkg.scripts['flowgrid:build'] = 'flowgrid build';
804
- pkg.scripts['flowgrid:publish'] = 'flowgrid publish';
805
- pkg.scripts['flow:dev'] = 'flowgrid dev';
806
- pkg.scripts['flow:render'] = 'flowgrid render';
807
- pkg.scripts['flow:publish'] = 'flowgrid publish';
808
- }
809
-
810
- if (selectedType === 'Frontend' || selectedType === 'Fullstack') {
811
- pkg.scripts['flowgrid:gen'] = 'flowgrid gen';
812
- pkg.scripts['flowgrid:unit'] = 'flowgrid unit-gen';
813
- pkg.scripts['flowgrid:css'] = 'flowgrid gen-css';
814
- }
815
-
816
- if (selectedType === 'Backend' || selectedType === 'Fullstack') {
817
- pkg.scripts['flowgrid:api-gen'] = 'flowgrid api-gen';
818
- pkg.scripts['flowgrid:api-unit'] = 'flowgrid api-unit-gen';
819
- pkg.scripts['flowgrid:openapi'] = 'flowgrid openapi_render';
820
- }
821
-
822
- if (selectedType === 'Frontend' || selectedType === 'Backend' || selectedType === 'Fullstack') {
823
- pkg.scripts['flowgrid:contract'] = 'flowgrid contract-gen';
824
- }
1032
+ injectFlowgridConsumerScripts(pkg, {
1033
+ selectedType,
1034
+ feDocRoot,
1035
+ feTestRoot,
1036
+ docsHubInRepo,
1037
+ testsHubInRepo,
1038
+ });
825
1039
 
826
- const injectTestsHubScripts = selectedType === 'Test' || Boolean(feTestRoot);
827
- if (injectTestsHubScripts) {
828
- pkg.scripts['flowgrid:cases'] = 'flowgrid cases:render';
829
- pkg.scripts['flowgrid:cases-check'] = 'flowgrid cases:check';
830
- pkg.scripts['flowgrid:cases-cov'] = 'flowgrid cases:coverage';
831
- pkg.scripts['flowgrid:cases-gate'] = 'flowgrid cases:gate';
832
- pkg.scripts['flowgrid:e2e-gen'] = 'flowgrid testcase:gen';
833
- pkg.scripts['flowgrid:api-e2e-gen'] = 'flowgrid testcase:gen:api';
834
- pkg.scripts['flowgrid:e2e-reg'] = 'flowgrid e2e-registry';
835
- }
836
-
837
1040
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
838
1041
  console.log(pc.blue(' + Injected flowgrid:* & flow:* scripts into package.json'));
839
1042
  } catch (e) {
@@ -862,6 +1065,20 @@ export async function main() {
862
1065
  console.log(pc.yellow(' ! Không thể khởi tạo SQLite cache (chưa có specs/registry): ' + e.message));
863
1066
  }
864
1067
 
1068
+ if (['Frontend', 'Backend', 'Fullstack'].includes(selectedType)) {
1069
+ try {
1070
+ const { runRegistrySync } = await import('../engines/registry-sync/run-registry-sync.mjs');
1071
+ console.log(pc.blue('\n[INFO] Quét base → DSL registry (registry:sync)...'));
1072
+ await runRegistrySync([], {
1073
+ projectRoot: process.cwd(),
1074
+ packageRoot: packageRootFromMeta(import.meta.url),
1075
+ config: projectConfig,
1076
+ });
1077
+ } catch (e) {
1078
+ console.log(pc.yellow(' ! registry:sync skipped: ' + e.message));
1079
+ }
1080
+ }
1081
+
865
1082
  const dslBackupDir = path.join(process.cwd(), 'artifactgraph');
866
1083
  if (!fs.existsSync(dslBackupDir)) {
867
1084
  fs.mkdirSync(dslBackupDir, { recursive: true });
@@ -884,7 +1101,10 @@ export async function main() {
884
1101
  const optionalAns = await multiselect({
885
1102
  message: 'Optional toolkits to initialize now (none = skip, add later):',
886
1103
  options: [
887
- { value: 'codegraph', label: 'Codegraph — semantic code intelligence' }
1104
+ {
1105
+ value: 'codegraph',
1106
+ label: 'Codegraph — semantic code intelligence (cần CLI `codegraph` đã cài)',
1107
+ },
888
1108
  ],
889
1109
  required: false
890
1110
  });
@@ -892,11 +1112,28 @@ export async function main() {
892
1112
  console.log(pc.blue('\n[INFO] Initializing Codegraph...'));
893
1113
  try {
894
1114
  const { spawnSync } = await import('node:child_process');
895
- const res = spawnSync('npx', ['codegraph', 'init'], { stdio: 'inherit' });
896
- if (res.status === 0) {
897
- console.log(' + Codegraph initialized successfully');
1115
+ const shell = process.platform === 'win32';
1116
+ const where = process.platform === 'win32' ? 'where' : 'which';
1117
+ const hasCodegraph =
1118
+ spawnSync(where, ['codegraph'], { encoding: 'utf8', shell }).status === 0;
1119
+ if (!hasCodegraph) {
1120
+ console.log(
1121
+ pc.yellow(
1122
+ ' ! CodeGraph CLI chưa có trên PATH — bỏ qua (init FlowGrid vẫn hoàn tất).',
1123
+ ),
1124
+ );
1125
+ console.log(
1126
+ pc.gray(
1127
+ ' Sau này: cài CodeGraph theo tài liệu team → trong repo này chạy `codegraph init` → MCP: `platform-dna codegraph:wire` (skill /configure-repo-maps).',
1128
+ ),
1129
+ );
898
1130
  } else {
899
- console.log(pc.yellow(` ! Codegraph initialization failed with status ${res.status}`));
1131
+ const res = spawnSync('codegraph', ['init'], { stdio: 'inherit', shell });
1132
+ if (res.status === 0) {
1133
+ console.log(' + Codegraph initialized successfully');
1134
+ } else {
1135
+ console.log(pc.yellow(` ! Codegraph init failed (status ${res.status})`));
1136
+ }
900
1137
  }
901
1138
  } catch (e) {
902
1139
  console.log(pc.yellow(' ! Failed to run Codegraph init: ' + e.message));