@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
@@ -5,6 +5,16 @@ import * as path from 'node:path';
5
5
  * Sync builds one project staging bundle, then materializes per agent dir + overlay.
6
6
  */
7
7
 
8
+ /** @deprecated init option removed — maps to Antigravity IDE profile only */
9
+ const LEGACY_AGENT_ALIASES = {
10
+ gemini_antigravity: 'antigravity',
11
+ };
12
+
13
+ /** @param {string} agent */
14
+ export function resolveCanonicalAgentId(agent) {
15
+ return LEGACY_AGENT_ALIASES[agent] ?? agent;
16
+ }
17
+
8
18
  /** @type {Record<string, {
9
19
  * id: string;
10
20
  * dirName: string;
@@ -26,16 +36,26 @@ export const AGENT_PROFILES = {
26
36
  overlayFile: 'AGENTS.md',
27
37
  registryPathPrefix: '.cursor',
28
38
  },
29
- gemini_antigravity: {
30
- id: 'gemini_antigravity',
39
+ gemini: {
40
+ id: 'gemini',
31
41
  dirName: '.agents',
32
- mcpFile: 'mcp_config.json',
33
- label: 'Antigravity',
42
+ mcpFile: 'mcp.json',
43
+ label: 'Gemini (CLI / VS Code extension)',
34
44
  readTool: 'Read',
35
45
  writeTool: 'Write',
36
46
  overlayFile: 'AGENTS.md',
37
47
  registryPathPrefix: '.agents',
38
48
  },
49
+ antigravity: {
50
+ id: 'antigravity',
51
+ dirName: '.antigravity',
52
+ mcpFile: 'mcp_config.json',
53
+ label: 'Google Antigravity IDE',
54
+ readTool: 'Read',
55
+ writeTool: 'Write',
56
+ overlayFile: 'AGENTS.md',
57
+ registryPathPrefix: '.antigravity',
58
+ },
39
59
  claude: {
40
60
  id: 'claude',
41
61
  dirName: '.claude',
@@ -110,7 +130,10 @@ const DEFAULT_PROFILE = {
110
130
  };
111
131
 
112
132
  export function getAgentProfile(agent) {
113
- return AGENT_PROFILES[agent] ?? { ...DEFAULT_PROFILE, id: agent, label: agent };
133
+ const id = resolveCanonicalAgentId(agent);
134
+ const profile = AGENT_PROFILES[id];
135
+ if (profile) return profile;
136
+ return { ...DEFAULT_PROFILE, id: agent, label: agent };
114
137
  }
115
138
 
116
139
  export function resolveAgentDir(agent) {
@@ -123,5 +146,6 @@ export function resolveAgentMcpConfigPath(agent, agentDir) {
123
146
  }
124
147
 
125
148
  export function agentHarnessOverlayPath(packageRoot, agent) {
126
- return path.join(packageRoot, 'harness', 'agents', agent);
149
+ const id = resolveCanonicalAgentId(agent);
150
+ return path.join(packageRoot, 'harness', 'agents', id);
127
151
  }
@@ -34,7 +34,7 @@ export function printAuditHelp() {
34
34
  ' testcase TC-*.yaml (v2) or legacy plan; --bundle cross-ref bundle',
35
35
  ' fe-be bundle apiRef vs 01-backend-spec alignment',
36
36
  ' scenario cross-flow SC screens vs cases/** TC coverage',
37
- ' e2e TC YAML vs Playwright (--e2e-root, --tests-docs)',
37
+ ' e2e TC YAML vs Playwright (--id, config/MCP e2eRoot, --tests-docs)',
38
38
  ' legacy legacy adoption / target-id gaps',
39
39
  '',
40
40
  'Examples:',
@@ -26,6 +26,32 @@ function hasCommand(cmd) {
26
26
  return res.status === 0;
27
27
  }
28
28
 
29
+ /**
30
+ * @param {string} root realpath of package install
31
+ * @param {string} pnpmRoot output of `pnpm root -g`
32
+ */
33
+ export function isPnpmGlobalPackageRoot(root, pnpmRoot) {
34
+ if (!pnpmRoot || !fs.existsSync(pnpmRoot)) return false;
35
+ const pnpmReal = fs.realpathSync(pnpmRoot);
36
+ const pnpmGlobalBase = path.dirname(pnpmReal);
37
+ if (
38
+ root === pnpmReal ||
39
+ root.startsWith(pnpmReal + path.sep) ||
40
+ root.startsWith(pnpmGlobalBase + path.sep)
41
+ ) {
42
+ return true;
43
+ }
44
+ const slash = NPM_PACKAGE.indexOf('/');
45
+ const linkPath = path.join(pnpmReal, NPM_PACKAGE.slice(0, slash), NPM_PACKAGE.slice(slash + 1));
46
+ if (!fs.existsSync(linkPath)) return false;
47
+ try {
48
+ const linkReal = fs.realpathSync(linkPath);
49
+ return root === linkReal || root.startsWith(linkReal + path.sep);
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
29
55
  function readPackageVersion(packageRoot) {
30
56
  try {
31
57
  const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
@@ -54,11 +80,8 @@ export function detectInstallKind(packageRoot) {
54
80
  }
55
81
 
56
82
  const pnpmRoot = tryCommand('pnpm', ['root', '-g']);
57
- if (pnpmRoot && fs.existsSync(pnpmRoot)) {
58
- const pnpmReal = fs.realpathSync(pnpmRoot);
59
- if (root.startsWith(pnpmReal + path.sep) || root === pnpmReal) {
60
- return 'pnpm-global';
61
- }
83
+ if (pnpmRoot && isPnpmGlobalPackageRoot(root, pnpmRoot)) {
84
+ return 'pnpm-global';
62
85
  }
63
86
 
64
87
  const npmPrefix = tryCommand('npm', ['prefix', '-g']);
@@ -227,17 +250,34 @@ export function runCliUpdate(opts) {
227
250
  }
228
251
  code = updateGlobalPackageManager('npm', packageRoot, repo);
229
252
  break;
230
- default:
253
+ default: {
254
+ let pkgName = '';
255
+ try {
256
+ pkgName = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8')).name ?? '';
257
+ } catch {
258
+ /* ignore */
259
+ }
260
+ if (pkgName === NPM_PACKAGE && hasCommand('pnpm')) {
261
+ log(`Nhận diện ${NPM_PACKAGE} (layout pnpm) — nâng cấp qua pnpm global…`);
262
+ code = updateGlobalPackageManager('pnpm', packageRoot, repo);
263
+ break;
264
+ }
265
+ if (pkgName === NPM_PACKAGE && hasCommand('npm')) {
266
+ log(`Nhận diện ${NPM_PACKAGE} — nâng cấp qua npm global…`);
267
+ code = updateGlobalPackageManager('npm', packageRoot, repo);
268
+ break;
269
+ }
231
270
  log('Không xác định được cách cài. Thử một trong các lệnh sau:');
232
271
  if (isPackageOnRegistry()) {
233
- log(` pnpm add -g ${NPM_PACKAGE}`);
234
- log(` npm install -g ${NPM_PACKAGE}`);
272
+ log(` pnpm add -g ${NPM_PACKAGE}@latest ${PNPM_ALLOW_BUILD}`);
273
+ log(` npm install -g ${NPM_PACKAGE}@latest`);
235
274
  } else {
236
275
  log(` pnpm add -g ${GITHUB_PKG(repo)} ${PNPM_ALLOW_BUILD}`);
237
276
  log(` npm install -g ${GITHUB_PKG(repo)}`);
238
277
  }
239
278
  log(' Hoặc chạy lại install.sh (Linux/WSL) sau khi đã gỡ bản cũ.');
240
279
  return 1;
280
+ }
241
281
  }
242
282
 
243
283
  if (code !== 0) {
@@ -1,17 +1,18 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import { resolveAgentDir, writeRootAgentsMd } from './harness-overlay.mjs';
4
+ import { getAgentProfile, resolveCanonicalAgentId } from './agent-profiles.mjs';
4
5
  import { loadProjectConfig, runHarnessSync } from './harness-sync.mjs';
5
6
  import { AUDIT_ENGINES } from './audit-run.mjs';
7
+ import { compareConfigAndRepoMaps } from './repo-maps-align.mjs';
6
8
 
7
9
  function readJson(filePath) {
8
10
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
9
11
  }
10
12
 
11
13
  function mcpConfigPath(agentDir, agent) {
12
- return agent === 'gemini_antigravity'
13
- ? path.join(agentDir, 'mcp_config.json')
14
- : path.join(agentDir, 'mcp.json');
14
+ const { mcpFile } = getAgentProfile(agent);
15
+ return path.join(agentDir, mcpFile);
15
16
  }
16
17
 
17
18
  function walkHasUnrenderedPlaceholders(dir) {
@@ -65,6 +66,12 @@ export function runDoctor(opts) {
65
66
  const config = loadProjectConfig(projectRoot);
66
67
  const agents = config.agents?.length ? config.agents : [];
67
68
 
69
+ if (config.agents?.includes('gemini_antigravity')) {
70
+ warnings.push(
71
+ 'config.agents has deprecated gemini_antigravity — use gemini (.agents) and/or antigravity (.antigravity); run flowgrid harness sync after updating config.',
72
+ );
73
+ }
74
+
68
75
  if (!agents.length) {
69
76
  warnings.push('config.agents is empty — re-run init and select at least one agent.');
70
77
  }
@@ -78,6 +85,27 @@ export function runDoctor(opts) {
78
85
  config.type !== 'Document' &&
79
86
  Boolean(config.frontend?.docsRoot || config.backend?.docsRoot);
80
87
 
88
+ const needsTestsPointer =
89
+ config.type !== 'Test' &&
90
+ Boolean(config.frontend?.testsRoot || config.backend?.testsRoot);
91
+
92
+ const e2eRoot = config.frontend?.e2eRoot ?? config.backend?.e2eRoot;
93
+ if (
94
+ (config.type === 'Frontend' || config.type === 'Fullstack' || config.type === 'Backend') &&
95
+ needsTestsPointer &&
96
+ !e2eRoot
97
+ ) {
98
+ warnings.push(
99
+ 'e2eRoot unset (frontend.e2eRoot or backend.e2eRoot) — re-run init or set in .flowgrid/config.json; harness sync sets FLOWGRID_E2E_ROOT',
100
+ );
101
+ }
102
+
103
+ if (!config.stack && (config.type === 'Frontend' || config.type === 'Backend' || config.type === 'Fullstack')) {
104
+ warnings.push(
105
+ 'config missing stack/commands (ArtifactGraph allowlist) — re-run flowgrid init or merge stacks/*.json fields into config.json',
106
+ );
107
+ }
108
+
81
109
  for (const agent of agents) {
82
110
  const relDir = resolveAgentDir(agent);
83
111
  const agentDir = path.join(projectRoot, relDir);
@@ -107,6 +135,21 @@ export function runDoctor(opts) {
107
135
  );
108
136
  }
109
137
 
138
+ if (needsTestsPointer && !env.FLOWGRID_TESTS_DOC) {
139
+ issues.push(
140
+ `${relDir}: FLOWGRID_TESTS_DOC unset (tests-docs hub pointer for cases:gate / audit scenario / testcase:gen)`,
141
+ );
142
+ }
143
+
144
+ const needsE2ePointer =
145
+ needsTestsPointer &&
146
+ Boolean(config.frontend?.e2eRoot || config.backend?.e2eRoot);
147
+ if (needsE2ePointer && !env.FLOWGRID_E2E_ROOT) {
148
+ warnings.push(
149
+ `${relDir}: FLOWGRID_E2E_ROOT unset (automation root for audit e2e / testcase:gen — run harness sync)`,
150
+ );
151
+ }
152
+
110
153
  if (config.type === 'Document' && !env.FLOWGRID_DEFAULT_LANG && config.defaultLanguage) {
111
154
  warnings.push(
112
155
  `${relDir}: FLOWGRID_DEFAULT_LANG missing though config.defaultLanguage is set`,
@@ -130,6 +173,50 @@ export function runDoctor(opts) {
130
173
  for (const f of stale) {
131
174
  issues.push(`Unrendered {{FLOWGRID_*}} in ${path.relative(projectRoot, f)}`);
132
175
  }
176
+
177
+ const skillsDir = path.join(agentDir, 'skills');
178
+ if (!fs.existsSync(skillsDir)) {
179
+ issues.push(
180
+ `${relDir}: missing skills/ — run \`flowgrid harness sync --agent=${resolveCanonicalAgentId(agent)}\``,
181
+ );
182
+ } else {
183
+ const skillDirs = fs
184
+ .readdirSync(skillsDir, { withFileTypes: true })
185
+ .filter((e) => e.isDirectory());
186
+ if (skillDirs.length === 0) {
187
+ issues.push(
188
+ `${relDir}: skills/ is empty — run \`flowgrid harness sync --agent=${agent}\``,
189
+ );
190
+ }
191
+ }
192
+
193
+ const canonical = resolveCanonicalAgentId(agent);
194
+ if (canonical === 'antigravity') {
195
+ const hintPath = path.join(agentDir, 'rules', 'antigravity-mcp.mdc');
196
+ if (!fs.existsSync(hintPath)) {
197
+ warnings.push(`${relDir}: missing antigravity-mcp rule — re-run harness sync`);
198
+ }
199
+ }
200
+ if (canonical === 'gemini') {
201
+ const hintPath = path.join(agentDir, 'rules', 'gemini-mcp.mdc');
202
+ if (!fs.existsSync(hintPath)) {
203
+ warnings.push(`${relDir}: missing gemini-mcp rule — re-run harness sync`);
204
+ }
205
+ }
206
+ }
207
+
208
+ if (fs.existsSync(path.join(projectRoot, 'platform-repos.local.json'))) {
209
+ const cmp = compareConfigAndRepoMaps(projectRoot);
210
+ for (const c of cmp.conflicts) {
211
+ warnings.push(
212
+ `platform-repos.local.json "${c.key}" ≠ .flowgrid/config.json — run \`flowgrid repo-maps align\``,
213
+ );
214
+ }
215
+ if (cmp.missingInMap.length) {
216
+ warnings.push(
217
+ `platform-repos.local.json missing keys: ${cmp.missingInMap.join(', ')} — run \`flowgrid repo-maps sync --from-config\``,
218
+ );
219
+ }
133
220
  }
134
221
 
135
222
  let fixed = false;
@@ -1,6 +1,11 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { getAgentProfile, resolveAgentDir, AGENT_PROFILES } from './agent-profiles.mjs';
3
+ import {
4
+ getAgentProfile,
5
+ resolveAgentDir,
6
+ resolveCanonicalAgentId,
7
+ AGENT_PROFILES,
8
+ } from './agent-profiles.mjs';
4
9
 
5
10
  export { resolveAgentDir, AGENT_PROFILES as AGENT_META };
6
11
 
@@ -27,9 +32,11 @@ export function renderHarnessPlaceholders(text, ctx) {
27
32
  const OVERLAY_EXTENSIONS = new Set(['.md', '.mdc']);
28
33
 
29
34
  export function applyHarnessOverlay(agentDir, agentId) {
30
- const agent =
31
- agentId ??
32
- (path.basename(agentDir) === 'agents' ? 'gemini_antigravity' : path.basename(agentDir).replace(/^\./, ''));
35
+ const inferred =
36
+ path.basename(agentDir) === 'agents'
37
+ ? 'gemini'
38
+ : path.basename(agentDir).replace(/^\./, '');
39
+ const agent = resolveCanonicalAgentId(agentId ?? inferred);
33
40
  const ctx = buildOverlayContext(agent);
34
41
 
35
42
  const walk = (dir) => {
@@ -62,7 +69,7 @@ export function writeRootAgentsMd(projectRoot, agents) {
62
69
  for (const agent of agents) {
63
70
  const dir = resolveAgentDir(agent);
64
71
  const meta = getAgentProfile(agent);
65
- const overlayPkg = `harness/agents/${agent}/` ;
72
+ const overlayPkg = `harness/agents/${resolveCanonicalAgentId(agent)}/`;
66
73
  lines.push(
67
74
  `- **${meta.label}** (\`${agent}\`): [\`${dir}/${meta.overlayFile}\`](${dir}/${meta.overlayFile}) · MCP \`${meta.mcpFile}\` · overlay package \`${overlayPkg}\``,
68
75
  );
@@ -6,6 +6,7 @@ import {
6
6
  agentHarnessOverlayPath,
7
7
  getAgentProfile,
8
8
  resolveAgentDir,
9
+ resolveCanonicalAgentId,
9
10
  } from './agent-profiles.mjs';
10
11
  import { applyHarnessOverlay, writeRootAgentsMd } from './harness-overlay.mjs';
11
12
 
@@ -18,10 +19,12 @@ export const CONSUMER_DOC_SKILL_NAMES = new Set([
18
19
  'grill-bqa',
19
20
  'grill-dev',
20
21
  'grill-docs',
21
- 'grill-common-spec',
22
22
  'qa-resolve',
23
23
  'api-spec',
24
24
  'api-update',
25
+ 'grill-api-spec',
26
+ 'grill-api',
27
+ 'openapi',
25
28
  ]);
26
29
 
27
30
  export const CONSUMER_DOC_RULE_NAMES = new Set([
@@ -72,7 +75,16 @@ export function createCopyRecursive() {
72
75
  const items = fs.readdirSync(srcDir, { withFileTypes: true });
73
76
  for (const item of items) {
74
77
  if (
75
- ['node_modules', '.git', 'dist', '.agents', '.gemini', '.cursor', '.flowgrid'].includes(
78
+ [
79
+ 'node_modules',
80
+ '.git',
81
+ 'dist',
82
+ '.agents',
83
+ '.antigravity',
84
+ '.gemini',
85
+ '.cursor',
86
+ '.flowgrid',
87
+ ].includes(
76
88
  item.name,
77
89
  )
78
90
  ) {
@@ -345,6 +357,7 @@ export function syncAgentHarness(opts) {
345
357
  const selectedType = config.type;
346
358
  const feDocRoot = config.frontend?.docsRoot ?? config.backend?.docsRoot ?? '';
347
359
  const feTestRoot = config.frontend?.testsRoot ?? config.backend?.testsRoot ?? '';
360
+ const feE2eRoot = config.frontend?.e2eRoot ?? config.backend?.e2eRoot ?? '';
348
361
  const feAdapter = config.frontend?.adapter ?? '';
349
362
  const beAdapter = config.backend?.adapter ?? '';
350
363
  const defaultLanguage = config.defaultLanguage;
@@ -354,6 +367,7 @@ export function syncAgentHarness(opts) {
354
367
  selectedType,
355
368
  feDocRoot: selectedType === 'Document' ? projectRoot : feDocRoot,
356
369
  feTestRoot: selectedType === 'Test' ? projectRoot : feTestRoot,
370
+ feE2eRoot,
357
371
  feAdapter,
358
372
  beAdapter,
359
373
  defaultLanguage,
@@ -366,7 +380,7 @@ export function syncAgentHarness(opts) {
366
380
  env: mcpEnv,
367
381
  });
368
382
 
369
- applyHarnessOverlay(agentDir, agent);
383
+ applyHarnessOverlay(agentDir, resolveCanonicalAgentId(agent));
370
384
 
371
385
  return { agentDir, mcpEnv, profile };
372
386
  }
@@ -0,0 +1,75 @@
1
+ // Adapter ids must match adapters/<id> and harness/.../adapters/<id> when present.
2
+
3
+ export const FE_ADAPTER_OPTIONS = [
4
+ { value: 'nuxt4', label: 'Nuxt 4' },
5
+ { value: 'nextjs', label: 'Next.js' },
6
+ { value: 'dotnet-line', label: 'DotNet — line client (dotnet-line)' },
7
+ { value: 'custom', label: 'Custom (other Vue/React/Angular)' },
8
+ ];
9
+
10
+ export const BE_ADAPTER_OPTIONS = [
11
+ { value: 'nestjs', label: 'NestJS' },
12
+ { value: 'fastapi', label: 'FastAPI' },
13
+ { value: 'laravel', label: 'Laravel' },
14
+ { value: 'dotnet-integration', label: 'DotNet — external/partner stack (dotnet-integration)' },
15
+ { value: 'custom', label: 'Custom backend stack' },
16
+ ];
17
+
18
+ const FE_KNOWN = new Set(FE_ADAPTER_OPTIONS.map((o) => o.value).filter((v) => v !== 'custom'));
19
+ const BE_KNOWN = new Set(BE_ADAPTER_OPTIONS.map((o) => o.value).filter((v) => v !== 'custom'));
20
+
21
+ /**
22
+ * @param {import('@clack/prompts')} clack
23
+ * @param {object} opts
24
+ * @param {string} opts.message
25
+ * @param {{ value: string; label: string }[]} opts.options
26
+ * @param {Set<string>} opts.knownIds
27
+ * @param {string} [opts.customPlaceholder]
28
+ */
29
+ export async function promptAdapter(clack, opts) {
30
+ const { select, text, isCancel, cancel } = clack;
31
+ const choice = await select({
32
+ message: opts.message,
33
+ options: opts.options,
34
+ });
35
+ if (isCancel(choice)) {
36
+ cancel('Cancelled.');
37
+ process.exit(0);
38
+ }
39
+ if (choice !== 'custom') {
40
+ return choice;
41
+ }
42
+ const customAns = await text({
43
+ message: 'Custom adapter id (must match adapters/<id>/ in FlowGrid):',
44
+ placeholder: opts.customPlaceholder ?? 'custom',
45
+ });
46
+ if (isCancel(customAns)) {
47
+ cancel('Cancelled.');
48
+ process.exit(0);
49
+ }
50
+ const id = (customAns || opts.customPlaceholder || 'custom').trim();
51
+ if (!opts.knownIds.has(id) && id !== 'custom') {
52
+ console.log(
53
+ `\n ! Adapter "${id}" is not in the standard list — codegen/harness overlay may be limited until you add adapters/${id}/.\n`,
54
+ );
55
+ }
56
+ return id || 'custom';
57
+ }
58
+
59
+ export async function promptFeAdapter(clack) {
60
+ return promptAdapter(clack, {
61
+ message: 'Select Frontend technology:',
62
+ options: FE_ADAPTER_OPTIONS,
63
+ knownIds: FE_KNOWN,
64
+ customPlaceholder: 'custom',
65
+ });
66
+ }
67
+
68
+ export async function promptBeAdapter(clack) {
69
+ return promptAdapter(clack, {
70
+ message: 'Select Backend technology:',
71
+ options: BE_ADAPTER_OPTIONS,
72
+ knownIds: BE_KNOWN,
73
+ customPlaceholder: 'custom',
74
+ });
75
+ }
@@ -155,4 +155,13 @@ export function applyTestsHubScaffold(opts) {
155
155
  if (!fs.existsSync(destTestsRoot)) fs.mkdirSync(destTestsRoot, { recursive: true });
156
156
  copyTreeIfMissing(testsSkeleton, destTestsRoot, copyRecursive);
157
157
  }
158
+
159
+ const harnessTestTemplates = path.join(packageRoot, 'harness', 'tests', 'templates');
160
+ const destHarnessTpl = path.join(destTestsRoot, '_templates');
161
+ if (fs.existsSync(harnessTestTemplates)) {
162
+ console.log(
163
+ pc.blue(` + Tests-docs golden templates → ${path.relative(projectRoot, destHarnessTpl)}`),
164
+ );
165
+ copyTreeIfMissing(harnessTestTemplates, destHarnessTpl, copyRecursive);
166
+ }
158
167
  }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * npm scripts injected into consumer package.json after flowgrid init.
3
+ * SSOT names: flowgrid:* ; aliases match docs (spec:*, cases:*).
4
+ */
5
+
6
+ /**
7
+ * @param {object} pkg — package.json object (mutated)
8
+ * @param {object} opts
9
+ */
10
+ export function injectFlowgridConsumerScripts(pkg, opts) {
11
+ const {
12
+ selectedType,
13
+ feDocRoot,
14
+ feTestRoot,
15
+ docsHubInRepo,
16
+ testsHubInRepo,
17
+ } = opts;
18
+
19
+ pkg.scripts = pkg.scripts || {};
20
+
21
+ const auditScripts = {
22
+ 'flowgrid:audit': 'flowgrid audit',
23
+ 'flowgrid:audit:spec': 'flowgrid audit spec',
24
+ 'flowgrid:audit:flow': 'flowgrid audit flow',
25
+ 'flowgrid:audit:api': 'flowgrid audit api',
26
+ 'flowgrid:audit:testcase': 'flowgrid audit testcase',
27
+ 'flowgrid:audit:fe-be': 'flowgrid audit fe-be',
28
+ 'flowgrid:audit:scenario': 'flowgrid audit scenario',
29
+ 'flowgrid:audit:e2e': 'flowgrid audit e2e',
30
+ 'flowgrid:audit:legacy': 'flowgrid audit legacy',
31
+ 'flowgrid:harness-sync': 'flowgrid harness sync',
32
+ 'flowgrid:harness-sync:full-docs': 'flowgrid harness sync --full-docs',
33
+ 'flowgrid:doctor': 'flowgrid doctor',
34
+ 'flowgrid:doctor:fix': 'flowgrid doctor --fix',
35
+ };
36
+ Object.assign(pkg.scripts, auditScripts);
37
+
38
+ const injectDocsHubScripts =
39
+ selectedType === 'Document' || (feDocRoot && docsHubInRepo);
40
+ if (injectDocsHubScripts) {
41
+ Object.assign(pkg.scripts, {
42
+ 'flowgrid:split': 'flowgrid split',
43
+ 'flowgrid:split_all': 'flowgrid split_all',
44
+ 'flowgrid:merge': 'flowgrid merge',
45
+ 'flowgrid:check': 'flowgrid check',
46
+ 'flowgrid:render': 'flowgrid render',
47
+ 'flowgrid:openapi': 'flowgrid openapi_render',
48
+ 'flowgrid:openapi-gen': 'flowgrid openapi_gen',
49
+ 'flowgrid:api-check': 'flowgrid api:check',
50
+ 'flowgrid:openapi-ui': 'flowgrid openapi_build_ui',
51
+ 'flowgrid:publish': 'flowgrid publish',
52
+ 'flow:render': 'flowgrid render',
53
+ 'flow:publish': 'flowgrid publish',
54
+ 'spec:split': 'flowgrid split',
55
+ 'spec:merge': 'flowgrid merge',
56
+ 'spec:split:check': 'flowgrid check',
57
+ 'spec:split:all': 'flowgrid split_all',
58
+ 'openapi:gen': 'flowgrid openapi_gen',
59
+ 'openapi:render': 'flowgrid openapi_render',
60
+ });
61
+ }
62
+
63
+ const injectVitepressScripts =
64
+ selectedType === 'Document' ||
65
+ selectedType === 'Test' ||
66
+ Boolean(feDocRoot || feTestRoot);
67
+ if (injectVitepressScripts) {
68
+ pkg.scripts['flowgrid:dev'] = 'flowgrid dev';
69
+ pkg.scripts['flowgrid:build'] = 'flowgrid build';
70
+ if (injectDocsHubScripts || selectedType === 'Document') {
71
+ pkg.scripts['flow:dev'] = 'flowgrid dev';
72
+ }
73
+ }
74
+
75
+ if (selectedType === 'Frontend' || selectedType === 'Fullstack') {
76
+ Object.assign(pkg.scripts, {
77
+ 'flowgrid:gen': 'flowgrid gen',
78
+ 'flowgrid:unit': 'flowgrid unit-gen',
79
+ 'flowgrid:css': 'flowgrid gen-css',
80
+ 'flowgrid:registry-sync': 'flowgrid registry:sync',
81
+ });
82
+ }
83
+
84
+ if (selectedType === 'Backend' || selectedType === 'Fullstack') {
85
+ Object.assign(pkg.scripts, {
86
+ 'flowgrid:registry-sync': 'flowgrid registry:sync',
87
+ 'flowgrid:api-gen': 'flowgrid api-gen',
88
+ 'flowgrid:api-unit': 'flowgrid api-unit-gen',
89
+ 'flowgrid:openapi': 'flowgrid openapi_render',
90
+ 'flowgrid:openapi-gen': 'flowgrid openapi_gen',
91
+ 'flowgrid:api-check': 'flowgrid api:check',
92
+ });
93
+ }
94
+
95
+ if (selectedType === 'Frontend' || selectedType === 'Backend' || selectedType === 'Fullstack') {
96
+ pkg.scripts['flowgrid:contract'] = 'flowgrid contract-gen';
97
+ }
98
+
99
+ const injectTestsHubScripts = selectedType === 'Test' || Boolean(feTestRoot);
100
+ if (injectTestsHubScripts) {
101
+ Object.assign(pkg.scripts, {
102
+ 'flowgrid:cases': 'flowgrid cases:render',
103
+ 'flowgrid:cases-check': 'flowgrid cases:check',
104
+ 'flowgrid:cases-cov': 'flowgrid cases:coverage',
105
+ 'flowgrid:cases-gate': 'flowgrid cases:gate',
106
+ 'flowgrid:e2e-gen': 'flowgrid testcase:gen',
107
+ 'flowgrid:api-e2e-gen': 'flowgrid testcase:gen:api',
108
+ 'flowgrid:e2e-reg': 'flowgrid e2e-registry',
109
+ 'flowgrid:tests-publish': 'flowgrid tests:publish',
110
+ 'cases:check': 'flowgrid cases:check',
111
+ 'cases:gate': 'flowgrid cases:gate',
112
+ 'cases:render': 'flowgrid cases:render',
113
+ });
114
+ }
115
+ }