@shanyucoder/flowgrid 0.1.5 → 0.1.9

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 (169) hide show
  1. package/README.md +1 -1
  2. package/adapters/laravel/registries/codegen.registry.json +9 -9
  3. package/bin/flowgrid.mjs +326 -115
  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/qa-item.mjs +91 -0
  26. package/engines/docs/lib/render-api-summary-markdown.mjs +28 -0
  27. package/engines/docs/lib/render-bundle-markdown.mjs +77 -2
  28. package/engines/docs/lib/render-data-model-markdown.mjs +171 -0
  29. package/engines/docs/lib/render-design-tables.mjs +89 -7
  30. package/engines/docs/lib/render-qa-list.mjs +123 -25
  31. package/engines/docs/lib/render-template.mjs +6 -0
  32. package/engines/docs/render-docs.mjs +6 -2
  33. package/engines/docs/vitepress/config.ts +7 -7
  34. package/engines/docs/vitepress/surfaces-nav.mjs +45 -14
  35. package/engines/openapi/check-backend-spec.mjs +2 -2
  36. package/engines/openapi/lib/markdown-table.mjs +8 -0
  37. package/engines/openapi/lib/render-backend-spec-markdown.mjs +78 -0
  38. package/engines/registry-sync/be-capabilities-sync.mjs +118 -0
  39. package/engines/registry-sync/fe-design-sync.mjs +258 -0
  40. package/engines/registry-sync/run-registry-sync.mjs +107 -0
  41. package/engines/shared/e2e-output-layout.mjs +68 -0
  42. package/engines/shared/flowgrid-e2e-root.mjs +19 -0
  43. package/engines/shared/resolve-flowgrid-context.mjs +176 -0
  44. package/engines/spec/lib/audit-api-gaps.mjs +1 -1
  45. package/engines/spec/lib/audit-bundle-gaps.mjs +92 -3
  46. package/engines/spec/lib/audit-db-tables.mjs +529 -0
  47. package/engines/spec/lib/audit-e2e-coverage.mjs +88 -30
  48. package/engines/spec/lib/bundle-schema.mjs +4 -1
  49. package/engines/spec/lib/open-qa.mjs +71 -21
  50. package/engines/spec/split-bundle.mjs +11 -1
  51. package/engines/testcase/runners/generate-api.mjs +23 -23
  52. package/engines/testcase/runners/generate.mjs +19 -17
  53. package/engines/testcase/runners/lib/bootstrap-context.mjs +44 -0
  54. package/engines/testcase/runners/lib/write-files.mjs +37 -9
  55. package/harness/agents/antigravity/rules/antigravity-mcp.mdc +12 -0
  56. package/harness/agents/gemini/rules/gemini-mcp.mdc +11 -0
  57. package/harness/agents/gemini_antigravity/rules/antigravity-mcp.mdc +8 -6
  58. package/harness/be/skills/{grill-api → audit-api}/SKILL.md +8 -7
  59. package/harness/common/extracts/artifact-graph.md +2 -2
  60. package/harness/common/extracts/artifactgraph-phase-hooks.md +2 -2
  61. package/harness/common/extracts/docs-mark-detect.md +2 -2
  62. package/harness/common/extracts/entity-relationship.md +23 -0
  63. package/harness/common/rules/flowgrid-ux-common.mdc +3 -3
  64. package/harness/common/skills/configure-repo-maps/SKILL.md +4 -2
  65. package/harness/docs/extracts/agent-execution-protocol.md +3 -3
  66. package/harness/docs/extracts/api-codegen-readiness.md +34 -0
  67. package/harness/docs/extracts/api-codegen-tags.md +30 -0
  68. package/harness/docs/extracts/api-contract.md +43 -0
  69. package/harness/docs/extracts/api-spec-sync.md +35 -0
  70. package/harness/docs/extracts/artifactgraph-hooks-docs.md +1 -1
  71. package/harness/docs/extracts/call-external.md +16 -0
  72. package/harness/docs/extracts/common-scope.md +9 -10
  73. package/harness/docs/extracts/db-audit-wizard.md +45 -0
  74. package/harness/docs/extracts/derived-data.md +18 -0
  75. package/harness/docs/extracts/design-leaf-signoff.md +16 -0
  76. package/harness/docs/extracts/extract-registry.docs.json +11 -2
  77. package/harness/docs/extracts/qa-inbox.md +19 -10
  78. package/harness/docs/extracts/qa-team.md +32 -0
  79. package/harness/docs/extracts/spec-core.md +7 -3
  80. package/harness/docs/extracts/spec-evolution.md +21 -0
  81. package/harness/docs/extracts/spec-prd-lite.md +19 -0
  82. package/harness/docs/extracts/spec-requirement.md +6 -2
  83. package/harness/docs/extracts/spec-ssot-prep.md +25 -0
  84. package/harness/docs/extracts/tpl-module.md +12 -0
  85. package/harness/docs/extracts/verify-gate.md +33 -0
  86. package/harness/docs/extracts/wire-spec-feedback.md +31 -0
  87. package/harness/docs/rules/agent-compliance.mdc +1 -1
  88. package/harness/docs/rules/team-flow-spec.mdc +3 -4
  89. package/harness/docs/schemas/flowgrid-docs/qa-item.schema.json +65 -0
  90. package/harness/docs/skills/adopt/SKILL.md +2 -0
  91. package/harness/docs/skills/api/SKILL.md +4 -5
  92. package/harness/docs/skills/api-spec/SKILL.md +19 -6
  93. package/harness/docs/skills/api-update/SKILL.md +4 -4
  94. package/harness/docs/skills/architecture/SKILL.md +1 -1
  95. package/harness/docs/skills/business-process/SKILL.md +2 -0
  96. package/harness/docs/skills/common/SKILL.md +2 -2
  97. package/harness/docs/skills/common-spec/SKILL.md +10 -47
  98. package/harness/docs/skills/db-erd/SKILL.md +26 -0
  99. package/harness/docs/skills/grill/SKILL.md +28 -22
  100. package/harness/docs/skills/grill-api/SKILL.md +4 -6
  101. package/harness/docs/skills/grill-api-spec/SKILL.md +44 -24
  102. package/harness/docs/skills/grill-bqa/SKILL.md +28 -13
  103. package/harness/docs/skills/grill-common-spec/SKILL.md +10 -35
  104. package/harness/docs/skills/grill-dev/SKILL.md +8 -7
  105. package/harness/docs/skills/grill-docs/SKILL.md +11 -4
  106. package/harness/docs/skills/module/SKILL.md +3 -1
  107. package/harness/docs/skills/openapi/SKILL.md +2 -1
  108. package/harness/docs/skills/overview/SKILL.md +7 -1
  109. package/harness/docs/skills/qa-resolve/SKILL.md +13 -12
  110. package/harness/docs/skills/qa-review/SKILL.md +45 -0
  111. package/harness/docs/skills/spec/SKILL.md +43 -10
  112. package/harness/docs/skills/update-spec/SKILL.md +5 -2
  113. package/harness/fe/extracts/wire-audit-loop.md +72 -0
  114. package/harness/fe/extracts/wire-phase.md +45 -0
  115. package/harness/fe/rules/platform-design-vocabulary.mdc +1 -1
  116. package/harness/fe/rules/team-flow-prototype.mdc +8 -4
  117. package/harness/fe/skills/gen-common/SKILL.md +11 -84
  118. package/harness/fe/skills/grill-prototype/SKILL.md +51 -25
  119. package/harness/fe/skills/grill-test/SKILL.md +78 -20
  120. package/harness/fe/skills/grill-wire/SKILL.md +81 -0
  121. package/harness/fe/skills/prototype/SKILL.md +3 -2
  122. package/harness/fe/skills/wire/SKILL.md +8 -3
  123. package/harness/shared/AGENTS.md +3 -3
  124. package/harness/shared/SSOT_AGENT_PROTOCOL.md +3 -3
  125. package/harness/tests/extracts/grill-api-hook.md +69 -0
  126. package/harness/tests/extracts/grill-scenario-flow.md +39 -0
  127. package/harness/tests/extracts/grill-screen-tc.md +40 -0
  128. package/harness/tests/extracts/testcase-gen-cli.md +57 -0
  129. package/harness/tests/extracts/testcase-plan.md +29 -0
  130. package/harness/tests/extracts/tests-verify-gate.md +29 -0
  131. package/harness/tests/extracts/wire-test-handoff.md +37 -0
  132. package/harness/tests/skills/grill-testcase/SKILL.md +1 -0
  133. package/harness/tests/skills/test-api/SKILL.md +14 -6
  134. package/harness/tests/skills/testcase/SKILL.md +3 -1
  135. package/harness/tests/templates/TC.example-api.yaml +7 -1
  136. package/harness/tests/templates/TC.example.yaml +4 -1
  137. package/harness/tests/templates/tpl-testcase-plan.md +75 -0
  138. package/package.json +1 -1
  139. package/stacks/fastapi.json +1 -0
  140. package/stacks/laravel.json +1 -0
  141. package/stacks/nestjs.json +72 -0
  142. package/stacks/nextjs-nest.json +1 -0
  143. package/stacks/nuxt4-nest.json +1 -0
  144. package/templates/project-skeleton/architecture/03-business-process/FLOW-template.md +2 -0
  145. package/templates/project-skeleton/overview/index.md +68 -2
  146. package/templates/project-skeleton/overview/operational-areas/_template.md +37 -0
  147. package/templates/project-skeleton/qa/README.md +4 -8
  148. package/templates/project-skeleton/surfaces/common/data-model/index.md +10 -2
  149. package/templates/schemas/qa-item.schema.json +98 -0
  150. package/templates/shared/api-03-mock.stub.yaml +14 -0
  151. package/templates/shared/backend-api.bundle.yaml +3 -0
  152. package/templates/shared/backend-api.yaml +4 -0
  153. package/templates/shared/be-capabilities.registry.base.json +8 -0
  154. package/templates/shared/bundle-authoring.md +44 -7
  155. package/templates/shared/default-layout.ejs +131 -18
  156. package/templates/shared/design-spec.yaml +2 -3
  157. package/templates/shared/design.registry.base.json +38 -0
  158. package/templates/shared/feature.bundle.yaml +16 -9
  159. package/templates/shared/ir/generated/spec.md +281 -0
  160. package/templates/shared/ir-spec.yaml +1 -1
  161. package/templates/shared/qa-authoring.md +78 -0
  162. package/templates/shared/qa-item.yaml +35 -14
  163. package/templates/shared/tpl-api-contract.md +133 -0
  164. package/templates/shared/tpl-screen-data-model.md +76 -0
  165. package/templates/tests-skeleton/cases/README.md +4 -0
  166. package/templates/tests-skeleton/catalog/locale.yaml +9 -0
  167. package/templates/tests-skeleton/tpl-testcase-plan.md +9 -0
  168. package/harness/docs/skills/api-integration/SKILL.md +0 -110
  169. 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)), '..');
@@ -67,6 +81,23 @@ export async function main() {
67
81
  }
68
82
 
69
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
+
70
101
  const engineCommands = ['split', 'merge', 'check', 'split_all', 'normalize', 'render', 'publish', 'legacy_validate', 'extract_i18n', 'openapi_render', 'openapi_build_ui', 'openapi_gen'];
71
102
 
72
103
  if (engineCommands.includes(command)) {
@@ -86,11 +117,14 @@ export async function main() {
86
117
  if (testCommands.includes(command)) {
87
118
  try {
88
119
  const { runEngine } = await import('../dist/test/engines/run.js');
89
- const { extractTestsDocsFlag, ENV_TESTS_DOC } = await import('../engines/shared/flowgrid-tests-docs.mjs');
90
- 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;
91
125
  let engineRel = [];
92
- const { argv, testsDocs } = extractTestsDocsFlag(args.slice(1));
93
- const engineEnv = testsDocs ? { [ENV_TESTS_DOC]: testsDocs } : {};
126
+ const argv = ctx.argv;
127
+ const engineEnv = flowgridContextToEnv(ctx);
94
128
 
95
129
  if (command === 'cases:render') engineRel = ['cases', 'render-cases.mjs'];
96
130
  if (command === 'cases:check') engineRel = ['cases', 'check-plans.mjs'];
@@ -118,6 +152,17 @@ export async function main() {
118
152
  }
119
153
  }
120
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
+
121
166
  const codegenCommands = [
122
167
  'api-gen', 'api-gen:dry', 'api-registry', 'api-unit-gen', 'api-unit-gen:dry', 'api-unit-registry',
123
168
  'gen', 'gen:dry', 'registry', 'unit-gen', 'unit-gen:dry', 'unit-registry',
@@ -127,18 +172,19 @@ export async function main() {
127
172
 
128
173
  if (codegenCommands.includes(command)) {
129
174
  try {
130
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
131
- let config = {};
132
- if (fs.existsSync(configPath)) {
133
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
134
- }
135
175
  const root = process.cwd();
136
- 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;
137
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, {}));
138
185
 
139
186
  const beAdapter = config.backend?.adapter || 'fastapi';
140
187
  const feAdapter = config.frontend?.adapter || 'nextjs';
141
- const docsRoot = config.frontend?.docsRoot;
142
188
 
143
189
  if (command.startsWith('api-')) {
144
190
  const { runBeEngine } = await import('../dist/codegen/adapters/run-be.js');
@@ -196,22 +242,19 @@ export async function main() {
196
242
 
197
243
  if (command === 'dev' || command === 'serve') {
198
244
  const { spawn, spawnSync } = await import('node:child_process');
199
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
200
- let docsRoot, testsRoot, feAdapter;
201
- if (fs.existsSync(configPath)) {
202
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
203
- docsRoot = config.frontend?.docsRoot || config.backend?.docsRoot;
204
- testsRoot = config.frontend?.testsRoot ?? config.backend?.testsRoot;
205
- feAdapter = config.frontend?.adapter;
206
- }
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;
207
250
  feAdapter = feAdapter || 'nextjs';
208
- const projectRoot = process.cwd();
251
+ const projectRoot = ctx.projectRoot;
209
252
 
210
253
  // ── gen-common: initial build (watch is handled by VitePress plugin) ──
211
254
  const webAdapters = new Set(['nuxt4', 'nextjs']);
212
255
  if (docsRoot && webAdapters.has(feAdapter)) {
213
256
  const resolvedDocsRoot = path.resolve(docsRoot);
214
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../..');
257
+ const packageRoot = packageRootFromMeta(import.meta.url);
215
258
  const commonEngine = path.join(packageRoot, 'adapters', 'shared', 'common-gen.mjs');
216
259
  const env = {
217
260
  ...process.env,
@@ -236,14 +279,14 @@ export async function main() {
236
279
  // ── VitePress dev servers ────────────────────────────────────────
237
280
  if (docsRoot) {
238
281
  console.log(pc.blue(`[Docs] Starting VitePress in ${docsRoot}...`));
239
- spawn('npx', ['vitepress', 'dev', docsRoot, ...args.slice(1)], { stdio: 'inherit' });
282
+ spawn('npx', ['vitepress', 'dev', docsRoot, ...ctx.argv], { stdio: 'inherit' });
240
283
  }
241
284
  if (testsRoot) {
242
285
  console.log(pc.blue(`[Tests] Starting VitePress in ${testsRoot} (Port 5174)...`));
243
- spawn('npx', ['vitepress', 'dev', testsRoot, '--port', '5174', ...args.slice(1)], { stdio: 'inherit' });
286
+ spawn('npx', ['vitepress', 'dev', testsRoot, '--port', '5174', ...ctx.argv], { stdio: 'inherit' });
244
287
  }
245
288
  if (!docsRoot && !testsRoot) {
246
- spawn('npx', ['vitepress', 'dev', ...args.slice(1)], { stdio: 'inherit' });
289
+ spawn('npx', ['vitepress', 'dev', ...ctx.argv], { stdio: 'inherit' });
247
290
  }
248
291
  // Keep alive for spawned processes
249
292
  setInterval(() => {}, 1000);
@@ -252,27 +295,97 @@ export async function main() {
252
295
 
253
296
  if (command === 'build') {
254
297
  const { spawnSync } = await import('node:child_process');
255
- const configPath = path.join(process.cwd(), '.flowgrid', 'config.json');
256
- let docsRoot, testsRoot;
257
- if (fs.existsSync(configPath)) {
258
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
259
- docsRoot = config.frontend?.docsRoot || config.backend?.docsRoot;
260
- testsRoot = config.frontend?.testsRoot ?? config.backend?.testsRoot;
261
- }
298
+ const ctx = resolveFlowgridContext({ argv: args.slice(1) });
299
+ const docsRoot = ctx.docsRoot;
300
+ const testsRoot = ctx.testsDocs;
262
301
  if (docsRoot) {
263
302
  console.log(pc.blue(`[Docs] Building VitePress in ${docsRoot}...`));
264
- spawnSync('npx', ['vitepress', 'build', docsRoot, ...args.slice(1)], { stdio: 'inherit' });
303
+ spawnSync('npx', ['vitepress', 'build', docsRoot, ...ctx.argv], { stdio: 'inherit' });
265
304
  }
266
305
  if (testsRoot) {
267
306
  console.log(pc.blue(`[Tests] Building VitePress in ${testsRoot}...`));
268
- spawnSync('npx', ['vitepress', 'build', testsRoot, ...args.slice(1)], { stdio: 'inherit' });
307
+ spawnSync('npx', ['vitepress', 'build', testsRoot, ...ctx.argv], { stdio: 'inherit' });
269
308
  }
270
309
  if (!docsRoot && !testsRoot) {
271
- spawnSync('npx', ['vitepress', 'build', ...args.slice(1)], { stdio: 'inherit' });
310
+ spawnSync('npx', ['vitepress', 'build', ...ctx.argv], { stdio: 'inherit' });
272
311
  }
273
312
  return;
274
313
  }
275
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
+
276
389
  if (command === 'update') {
277
390
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
278
391
  const { runCliUpdate } = await import('./lib/cli-update.mjs');
@@ -331,7 +444,18 @@ export async function main() {
331
444
  }
332
445
 
333
446
  if (!keepAgents) {
334
- 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
+ ];
335
459
  for (const d of dirs) {
336
460
  const p = path.join(process.cwd(), d);
337
461
  if (fs.existsSync(p)) {
@@ -389,7 +513,7 @@ export async function main() {
389
513
 
390
514
  if (command === 'doctor') {
391
515
  try {
392
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
516
+ const packageRoot = packageRootFromMeta(import.meta.url);
393
517
  const fix = args.includes('--fix');
394
518
  const report = runDoctor({
395
519
  projectRoot: process.cwd(),
@@ -406,7 +530,7 @@ export async function main() {
406
530
 
407
531
  if (command === 'harness' && args[1] === 'sync') {
408
532
  try {
409
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
533
+ const packageRoot = packageRootFromMeta(import.meta.url);
410
534
  runHarnessSync(args.slice(2), { packageRoot });
411
535
  process.exit(0);
412
536
  } catch (e) {
@@ -443,7 +567,8 @@ export async function main() {
443
567
  const agentsAns = await multiselect({
444
568
  message: 'Select Agents/Skills to integrate:',
445
569
  options: [
446
- { value: 'gemini_antigravity', label: 'Gemini & Antigravity' },
570
+ { value: 'gemini', label: 'Gemini (CLI / VS Code extension) → .agents/' },
571
+ { value: 'antigravity', label: 'Google Antigravity IDE → .antigravity/' },
447
572
  { value: 'cursor', label: 'Cursor' },
448
573
  { value: 'claude', label: 'Claude Code' },
449
574
  { value: 'codex', label: 'Codex CLI' },
@@ -459,6 +584,19 @@ export async function main() {
459
584
  process.exit(0);
460
585
  }
461
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
+ }
462
600
 
463
601
  // 2. Project Type
464
602
  const selectedType = await select({
@@ -485,6 +623,7 @@ export async function main() {
485
623
  let testsHubInRepo = false;
486
624
  let scaffoldDocs = false;
487
625
  let scaffoldTests = false;
626
+ let feE2eRoot = '';
488
627
  const projectRoot = process.cwd();
489
628
  let baseProfile = 'standard';
490
629
  let goldenSample = '';
@@ -521,22 +660,12 @@ export async function main() {
521
660
  });
522
661
  if (isCancel(feAns)) { cancel('Cancelled.'); process.exit(0); }
523
662
  feAdapter = feAns;
524
- beAdapter = 'nestjs';
663
+ beAdapter = await promptBeAdapter({ select, text, isCancel, cancel });
525
664
  console.log(pc.cyan(`=> Selected Fullstack: Frontend (${feAdapter}) + Backend (${beAdapter})`));
526
665
  } else if (selectedType === 'Frontend') {
527
- const feAns = await text({
528
- message: 'Enter Frontend technology (e.g. nuxt4, nextjs, or custom):',
529
- placeholder: baseProfile === 'custom' ? 'custom' : 'nuxt4'
530
- });
531
- if (isCancel(feAns)) { cancel('Cancelled.'); process.exit(0); }
532
- feAdapter = feAns || (baseProfile === 'custom' ? 'custom' : 'nuxt4');
666
+ feAdapter = await promptFeAdapter({ select, text, isCancel, cancel });
533
667
  } else if (selectedType === 'Backend') {
534
- const beAns = await text({
535
- message: 'Enter Backend technology (e.g. nestjs, fastapi, laravel, dotnet, or custom):',
536
- placeholder: baseProfile === 'custom' ? 'custom' : 'nestjs'
537
- });
538
- if (isCancel(beAns)) { cancel('Cancelled.'); process.exit(0); }
539
- beAdapter = beAns || (baseProfile === 'custom' ? 'custom' : 'nestjs');
668
+ beAdapter = await promptBeAdapter({ select, text, isCancel, cancel });
540
669
  }
541
670
 
542
671
  const needsDocsHub =
@@ -584,6 +713,34 @@ export async function main() {
584
713
  scaffoldTests = testsHub.scaffold;
585
714
  }
586
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
+
587
744
  if (selectedType === 'Test') {
588
745
  feTestRoot = '.';
589
746
  testsHubInRepo = true;
@@ -639,6 +796,13 @@ export async function main() {
639
796
  if (beAdapter) {
640
797
  console.log(`- Backend Adapter: ${beAdapter}`);
641
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
+ }
642
806
  }
643
807
  console.log(`- Setup Agents: ${selectedAgents.length > 0 ? selectedAgents.join(', ') : 'No'}`);
644
808
  console.log(`- Destination folder: .flowgrid/`);
@@ -658,6 +822,8 @@ export async function main() {
658
822
  fs.mkdirSync(targetDir, { recursive: true });
659
823
  }
660
824
 
825
+ const packageRoot = packageRootFromMeta(import.meta.url);
826
+
661
827
  const projectConfig = {
662
828
  type: selectedType,
663
829
  agents: selectedAgents,
@@ -669,6 +835,7 @@ export async function main() {
669
835
  adapter: feAdapter,
670
836
  docsRoot: feDocRoot || undefined,
671
837
  testsRoot: feTestRoot || undefined,
838
+ e2eRoot: feE2eRoot || defaultE2eRootRel(selectedType),
672
839
  docsHubInRepo: feDocRoot ? docsHubInRepo : undefined,
673
840
  testsHubInRepo: feTestRoot ? testsHubInRepo : undefined,
674
841
  } : null,
@@ -676,21 +843,50 @@ export async function main() {
676
843
  adapter: beAdapter,
677
844
  docsRoot: (!feAdapter && feDocRoot) ? feDocRoot : undefined,
678
845
  testsRoot: (!feAdapter && feTestRoot) ? feTestRoot : undefined,
846
+ e2eRoot: (!feAdapter && feTestRoot)
847
+ ? (feE2eRoot || defaultE2eRootRel('Backend'))
848
+ : undefined,
679
849
  docsHubInRepo: (!feAdapter && feDocRoot) ? docsHubInRepo : undefined,
680
850
  testsHubInRepo: (!feAdapter && feTestRoot) ? testsHubInRepo : undefined,
681
851
  } : null
682
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
+
683
865
  fs.writeFileSync(path.join(targetDir, 'config.json'), JSON.stringify(projectConfig, null, 2));
684
866
  console.log(" + Wrote config.json");
685
867
 
686
- const packageRoot = path.resolve(new URL(import.meta.url).pathname, '../../');
687
-
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
+
688
878
  const copyRecursive = (srcDir, destDir, log = false) => {
689
879
  if (!fs.existsSync(srcDir)) return;
690
880
  if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
691
881
  const items = fs.readdirSync(srcDir, { withFileTypes: true });
692
882
  for (const item of items) {
693
- 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
+ }
694
890
  if (item.name.endsWith('.db')) continue;
695
891
 
696
892
  const srcPath = path.join(srcDir, item.name);
@@ -749,6 +945,23 @@ export async function main() {
749
945
  });
750
946
  }
751
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
+
752
965
  console.log(pc.blue(`[INFO] Syncing global templates & schemas...`));
753
966
  const tplSrc = path.join(packageRoot, 'templates');
754
967
  const tplDest = path.join(targetDir, 'templates');
@@ -779,6 +992,19 @@ export async function main() {
779
992
  console.log(
780
993
  pc.gray(` + Wrote MCP config: ${path.relative(process.cwd(), mcpConfigPath) || mcpConfigPath}`)
781
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
+ }
782
1008
  }
783
1009
  writeRootAgentsMd(process.cwd(), selectedAgents);
784
1010
  console.log(pc.gray(' + Wrote root AGENTS.md'));
@@ -803,63 +1029,14 @@ export async function main() {
803
1029
  pkg.devDependencies['@braintree/sanitize-url'] = '^7.1.0';
804
1030
  }
805
1031
 
806
- pkg.scripts['flowgrid:audit'] = 'flowgrid audit';
807
- pkg.scripts['flowgrid:audit:spec'] = 'flowgrid audit spec';
808
- pkg.scripts['flowgrid:audit:flow'] = 'flowgrid audit flow';
809
- pkg.scripts['flowgrid:audit:api'] = 'flowgrid audit api';
810
- pkg.scripts['flowgrid:audit:testcase'] = 'flowgrid audit testcase';
811
- pkg.scripts['flowgrid:audit:fe-be'] = 'flowgrid audit fe-be';
812
- pkg.scripts['flowgrid:audit:scenario'] = 'flowgrid audit scenario';
813
- pkg.scripts['flowgrid:audit:e2e'] = 'flowgrid audit e2e';
814
- pkg.scripts['flowgrid:audit:legacy'] = 'flowgrid audit legacy';
815
- pkg.scripts['flowgrid:harness-sync'] = 'flowgrid harness sync';
816
- pkg.scripts['flowgrid:harness-sync:full-docs'] = 'flowgrid harness sync --full-docs';
817
- pkg.scripts['flowgrid:doctor'] = 'flowgrid doctor';
818
- pkg.scripts['flowgrid:doctor:fix'] = 'flowgrid doctor --fix';
819
-
820
- const injectDocsHubScripts =
821
- selectedType === 'Document' || (feDocRoot && docsHubInRepo);
822
- if (injectDocsHubScripts) {
823
- pkg.scripts['flowgrid:split'] = 'flowgrid split';
824
- pkg.scripts['flowgrid:split_all'] = 'flowgrid split_all';
825
- pkg.scripts['flowgrid:render'] = 'flowgrid render';
826
- pkg.scripts['flowgrid:openapi'] = 'flowgrid openapi_render';
827
- pkg.scripts['flowgrid:openapi-ui'] = 'flowgrid openapi_build_ui';
828
- pkg.scripts['flowgrid:dev'] = 'flowgrid dev';
829
- pkg.scripts['flowgrid:build'] = 'flowgrid build';
830
- pkg.scripts['flowgrid:publish'] = 'flowgrid publish';
831
- pkg.scripts['flow:dev'] = 'flowgrid dev';
832
- pkg.scripts['flow:render'] = 'flowgrid render';
833
- pkg.scripts['flow:publish'] = 'flowgrid publish';
834
- }
835
-
836
- if (selectedType === 'Frontend' || selectedType === 'Fullstack') {
837
- pkg.scripts['flowgrid:gen'] = 'flowgrid gen';
838
- pkg.scripts['flowgrid:unit'] = 'flowgrid unit-gen';
839
- pkg.scripts['flowgrid:css'] = 'flowgrid gen-css';
840
- }
841
-
842
- if (selectedType === 'Backend' || selectedType === 'Fullstack') {
843
- pkg.scripts['flowgrid:api-gen'] = 'flowgrid api-gen';
844
- pkg.scripts['flowgrid:api-unit'] = 'flowgrid api-unit-gen';
845
- pkg.scripts['flowgrid:openapi'] = 'flowgrid openapi_render';
846
- }
847
-
848
- if (selectedType === 'Frontend' || selectedType === 'Backend' || selectedType === 'Fullstack') {
849
- pkg.scripts['flowgrid:contract'] = 'flowgrid contract-gen';
850
- }
1032
+ injectFlowgridConsumerScripts(pkg, {
1033
+ selectedType,
1034
+ feDocRoot,
1035
+ feTestRoot,
1036
+ docsHubInRepo,
1037
+ testsHubInRepo,
1038
+ });
851
1039
 
852
- const injectTestsHubScripts = selectedType === 'Test' || Boolean(feTestRoot);
853
- if (injectTestsHubScripts) {
854
- pkg.scripts['flowgrid:cases'] = 'flowgrid cases:render';
855
- pkg.scripts['flowgrid:cases-check'] = 'flowgrid cases:check';
856
- pkg.scripts['flowgrid:cases-cov'] = 'flowgrid cases:coverage';
857
- pkg.scripts['flowgrid:cases-gate'] = 'flowgrid cases:gate';
858
- pkg.scripts['flowgrid:e2e-gen'] = 'flowgrid testcase:gen';
859
- pkg.scripts['flowgrid:api-e2e-gen'] = 'flowgrid testcase:gen:api';
860
- pkg.scripts['flowgrid:e2e-reg'] = 'flowgrid e2e-registry';
861
- }
862
-
863
1040
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
864
1041
  console.log(pc.blue(' + Injected flowgrid:* & flow:* scripts into package.json'));
865
1042
  } catch (e) {
@@ -888,6 +1065,20 @@ export async function main() {
888
1065
  console.log(pc.yellow(' ! Không thể khởi tạo SQLite cache (chưa có specs/registry): ' + e.message));
889
1066
  }
890
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
+
891
1082
  const dslBackupDir = path.join(process.cwd(), 'artifactgraph');
892
1083
  if (!fs.existsSync(dslBackupDir)) {
893
1084
  fs.mkdirSync(dslBackupDir, { recursive: true });
@@ -910,7 +1101,10 @@ export async function main() {
910
1101
  const optionalAns = await multiselect({
911
1102
  message: 'Optional toolkits to initialize now (none = skip, add later):',
912
1103
  options: [
913
- { value: 'codegraph', label: 'Codegraph — semantic code intelligence' }
1104
+ {
1105
+ value: 'codegraph',
1106
+ label: 'Codegraph — semantic code intelligence (cần CLI `codegraph` đã cài)',
1107
+ },
914
1108
  ],
915
1109
  required: false
916
1110
  });
@@ -918,11 +1112,28 @@ export async function main() {
918
1112
  console.log(pc.blue('\n[INFO] Initializing Codegraph...'));
919
1113
  try {
920
1114
  const { spawnSync } = await import('node:child_process');
921
- const res = spawnSync('npx', ['codegraph', 'init'], { stdio: 'inherit' });
922
- if (res.status === 0) {
923
- 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
+ );
924
1130
  } else {
925
- 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
+ }
926
1137
  }
927
1138
  } catch (e) {
928
1139
  console.log(pc.yellow(' ! Failed to run Codegraph init: ' + e.message));
@@ -10,6 +10,7 @@ export function buildMcpEnv({
10
10
  selectedType,
11
11
  feDocRoot,
12
12
  feTestRoot,
13
+ feE2eRoot,
13
14
  feAdapter,
14
15
  beAdapter,
15
16
  defaultLanguage,
@@ -23,6 +24,9 @@ export function buildMcpEnv({
23
24
  if (feTestRoot) {
24
25
  env.FLOWGRID_TESTS_DOC = path.resolve(base, feTestRoot);
25
26
  }
27
+ if (feE2eRoot) {
28
+ env.FLOWGRID_E2E_ROOT = path.resolve(base, feE2eRoot);
29
+ }
26
30
  }
27
31
  if (feAdapter) {
28
32
  env.FLOWGRID_ADAPTER = feAdapter;