@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.
- package/README.md +4 -1
- package/adapters/laravel/registries/codegen.registry.json +9 -9
- package/bin/flowgrid.mjs +353 -116
- package/bin/lib/agent-mcp.mjs +4 -0
- package/bin/lib/agent-profiles.mjs +30 -6
- package/bin/lib/audit-run.mjs +1 -1
- package/bin/lib/cli-update.mjs +48 -8
- package/bin/lib/doctor.mjs +90 -3
- package/bin/lib/harness-overlay.mjs +12 -5
- package/bin/lib/harness-sync.mjs +17 -3
- package/bin/lib/init-adapters.mjs +75 -0
- package/bin/lib/init-scaffold.mjs +9 -0
- package/bin/lib/inject-consumer-scripts.mjs +115 -0
- package/bin/lib/merge-stack-config.mjs +89 -0
- package/bin/lib/project-gitignore.mjs +1 -0
- package/bin/lib/repo-maps-align.mjs +203 -0
- package/dist/graph/config/load-config.js +7 -2
- package/dist/graph/config/load-config.js.map +1 -1
- package/dist/graph/mcp/tools.js +1 -1
- package/dist/graph/mcp/tools.js.map +1 -1
- package/dist/graph/registry/load-registries.d.ts +1 -0
- package/dist/graph/registry/load-registries.js +14 -1
- package/dist/graph/registry/load-registries.js.map +1 -1
- package/engines/cases/render-cases.mjs +67 -5
- package/engines/docs/lib/render-api-summary-markdown.mjs +28 -0
- package/engines/docs/lib/render-bundle-markdown.mjs +77 -2
- package/engines/docs/lib/render-data-model-markdown.mjs +171 -0
- package/engines/docs/lib/render-design-tables.mjs +89 -7
- package/engines/docs/lib/render-template.mjs +6 -0
- package/engines/docs/render-docs.mjs +6 -2
- package/engines/docs/vitepress/config.ts +7 -7
- package/engines/docs/vitepress/surfaces-nav.mjs +45 -14
- package/engines/openapi/check-backend-spec.mjs +2 -2
- package/engines/openapi/lib/markdown-table.mjs +8 -0
- package/engines/openapi/lib/render-backend-spec-markdown.mjs +78 -0
- package/engines/registry-sync/be-capabilities-sync.mjs +118 -0
- package/engines/registry-sync/fe-design-sync.mjs +258 -0
- package/engines/registry-sync/run-registry-sync.mjs +107 -0
- package/engines/shared/e2e-output-layout.mjs +68 -0
- package/engines/shared/flowgrid-e2e-root.mjs +19 -0
- package/engines/shared/resolve-flowgrid-context.mjs +176 -0
- package/engines/spec/lib/audit-api-gaps.mjs +1 -1
- package/engines/spec/lib/audit-bundle-gaps.mjs +92 -3
- package/engines/spec/lib/audit-db-tables.mjs +529 -0
- package/engines/spec/lib/audit-e2e-coverage.mjs +88 -30
- package/engines/spec/lib/bundle-schema.mjs +4 -1
- package/engines/spec/split-bundle.mjs +11 -1
- package/engines/testcase/runners/generate-api.mjs +23 -23
- package/engines/testcase/runners/generate.mjs +19 -17
- package/engines/testcase/runners/lib/bootstrap-context.mjs +44 -0
- package/engines/testcase/runners/lib/write-files.mjs +37 -9
- package/harness/agents/antigravity/rules/antigravity-mcp.mdc +12 -0
- package/harness/agents/gemini/rules/gemini-mcp.mdc +11 -0
- package/harness/agents/gemini_antigravity/rules/antigravity-mcp.mdc +8 -6
- package/harness/be/skills/{grill-api → audit-api}/SKILL.md +8 -7
- package/harness/common/extracts/artifact-graph.md +2 -2
- package/harness/common/extracts/artifactgraph-phase-hooks.md +2 -2
- package/harness/common/extracts/docs-mark-detect.md +2 -2
- package/harness/common/extracts/entity-relationship.md +23 -0
- package/harness/common/rules/flowgrid-ux-common.mdc +3 -3
- package/harness/common/skills/configure-repo-maps/SKILL.md +4 -2
- package/harness/docs/extracts/agent-execution-protocol.md +1 -1
- package/harness/docs/extracts/api-codegen-readiness.md +34 -0
- package/harness/docs/extracts/api-codegen-tags.md +30 -0
- package/harness/docs/extracts/api-contract.md +43 -0
- package/harness/docs/extracts/api-spec-sync.md +35 -0
- package/harness/docs/extracts/artifactgraph-hooks-docs.md +1 -1
- package/harness/docs/extracts/call-external.md +16 -0
- package/harness/docs/extracts/common-scope.md +9 -10
- package/harness/docs/extracts/db-audit-wizard.md +45 -0
- package/harness/docs/extracts/derived-data.md +18 -0
- package/harness/docs/extracts/design-leaf-signoff.md +16 -0
- package/harness/docs/extracts/extract-registry.docs.json +9 -1
- package/harness/docs/extracts/spec-core.md +7 -3
- package/harness/docs/extracts/spec-evolution.md +21 -0
- package/harness/docs/extracts/spec-prd-lite.md +19 -0
- package/harness/docs/extracts/spec-requirement.md +6 -2
- package/harness/docs/extracts/spec-ssot-prep.md +25 -0
- package/harness/docs/extracts/tpl-module.md +12 -0
- package/harness/docs/extracts/verify-gate.md +33 -0
- package/harness/docs/extracts/wire-spec-feedback.md +31 -0
- package/harness/docs/rules/agent-compliance.mdc +1 -1
- package/harness/docs/rules/team-flow-spec.mdc +3 -4
- package/harness/docs/skills/adopt/SKILL.md +2 -0
- package/harness/docs/skills/api/SKILL.md +4 -5
- package/harness/docs/skills/api-spec/SKILL.md +19 -6
- package/harness/docs/skills/api-update/SKILL.md +3 -3
- package/harness/docs/skills/architecture/SKILL.md +1 -1
- package/harness/docs/skills/business-process/SKILL.md +2 -0
- package/harness/docs/skills/common/SKILL.md +2 -2
- package/harness/docs/skills/common-spec/SKILL.md +10 -47
- package/harness/docs/skills/db-erd/SKILL.md +26 -0
- package/harness/docs/skills/grill/SKILL.md +28 -22
- package/harness/docs/skills/grill-api/SKILL.md +4 -6
- package/harness/docs/skills/grill-api-spec/SKILL.md +44 -24
- package/harness/docs/skills/grill-bqa/SKILL.md +28 -13
- package/harness/docs/skills/grill-common-spec/SKILL.md +10 -35
- package/harness/docs/skills/grill-dev/SKILL.md +7 -6
- package/harness/docs/skills/grill-docs/SKILL.md +10 -3
- package/harness/docs/skills/module/SKILL.md +3 -1
- package/harness/docs/skills/openapi/SKILL.md +2 -1
- package/harness/docs/skills/overview/SKILL.md +7 -1
- package/harness/docs/skills/spec/SKILL.md +42 -9
- package/harness/docs/skills/update-spec/SKILL.md +4 -1
- package/harness/fe/extracts/wire-audit-loop.md +72 -0
- package/harness/fe/extracts/wire-phase.md +45 -0
- package/harness/fe/rules/platform-design-vocabulary.mdc +1 -1
- package/harness/fe/rules/team-flow-prototype.mdc +8 -4
- package/harness/fe/skills/gen-common/SKILL.md +11 -84
- package/harness/fe/skills/grill-prototype/SKILL.md +51 -25
- package/harness/fe/skills/grill-test/SKILL.md +78 -20
- package/harness/fe/skills/grill-wire/SKILL.md +81 -0
- package/harness/fe/skills/prototype/SKILL.md +3 -2
- package/harness/fe/skills/wire/SKILL.md +7 -2
- package/harness/shared/AGENTS.md +3 -3
- package/harness/shared/SSOT_AGENT_PROTOCOL.md +3 -3
- package/harness/tests/extracts/grill-api-hook.md +69 -0
- package/harness/tests/extracts/grill-scenario-flow.md +39 -0
- package/harness/tests/extracts/grill-screen-tc.md +40 -0
- package/harness/tests/extracts/testcase-gen-cli.md +57 -0
- package/harness/tests/extracts/testcase-plan.md +29 -0
- package/harness/tests/extracts/tests-verify-gate.md +29 -0
- package/harness/tests/extracts/wire-test-handoff.md +37 -0
- package/harness/tests/skills/grill-testcase/SKILL.md +1 -0
- package/harness/tests/skills/test-api/SKILL.md +14 -6
- package/harness/tests/skills/testcase/SKILL.md +3 -1
- package/harness/tests/templates/TC.example-api.yaml +7 -1
- package/harness/tests/templates/TC.example.yaml +4 -1
- package/harness/tests/templates/tpl-testcase-plan.md +75 -0
- package/package.json +1 -1
- package/stacks/fastapi.json +1 -0
- package/stacks/laravel.json +1 -0
- package/stacks/nestjs.json +72 -0
- package/stacks/nextjs-nest.json +1 -0
- package/stacks/nuxt4-nest.json +1 -0
- package/templates/project-skeleton/architecture/03-business-process/FLOW-template.md +2 -0
- package/templates/project-skeleton/overview/index.md +68 -2
- package/templates/project-skeleton/overview/operational-areas/_template.md +37 -0
- package/templates/project-skeleton/surfaces/common/data-model/index.md +10 -2
- package/templates/shared/api-03-mock.stub.yaml +14 -0
- package/templates/shared/backend-api.bundle.yaml +3 -0
- package/templates/shared/backend-api.yaml +4 -0
- package/templates/shared/be-capabilities.registry.base.json +8 -0
- package/templates/shared/bundle-authoring.md +39 -3
- package/templates/shared/default-layout.ejs +131 -18
- package/templates/shared/design-spec.yaml +2 -3
- package/templates/shared/design.registry.base.json +38 -0
- package/templates/shared/feature.bundle.yaml +16 -9
- package/templates/shared/ir/generated/spec.md +281 -0
- package/templates/shared/qa-item.yaml +1 -1
- package/templates/shared/tpl-api-contract.md +133 -0
- package/templates/shared/tpl-screen-data-model.md +76 -0
- package/templates/tests-skeleton/cases/README.md +4 -0
- package/templates/tests-skeleton/catalog/locale.yaml +9 -0
- package/templates/tests-skeleton/tpl-testcase-plan.md +9 -0
- package/harness/docs/skills/api-integration/SKILL.md +0 -110
- 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 {
|
|
69
|
-
|
|
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
|
|
72
|
-
const engineEnv =
|
|
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
|
|
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
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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 =
|
|
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 =
|
|
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, ...
|
|
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', ...
|
|
286
|
+
spawn('npx', ['vitepress', 'dev', testsRoot, '--port', '5174', ...ctx.argv], { stdio: 'inherit' });
|
|
223
287
|
}
|
|
224
288
|
if (!docsRoot && !testsRoot) {
|
|
225
|
-
spawn('npx', ['vitepress', 'dev', ...
|
|
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
|
|
235
|
-
|
|
236
|
-
|
|
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, ...
|
|
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, ...
|
|
307
|
+
spawnSync('npx', ['vitepress', 'build', testsRoot, ...ctx.argv], { stdio: 'inherit' });
|
|
248
308
|
}
|
|
249
309
|
if (!docsRoot && !testsRoot) {
|
|
250
|
-
spawnSync('npx', ['vitepress', 'build', ...
|
|
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 = [
|
|
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 =
|
|
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 =
|
|
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
|
|
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: '
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
-
{
|
|
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
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
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));
|