@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.
- package/README.md +1 -1
- package/adapters/laravel/registries/codegen.registry.json +9 -9
- package/bin/flowgrid.mjs +326 -115
- 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/qa-item.mjs +91 -0
- 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-qa-list.mjs +123 -25
- 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/lib/open-qa.mjs +71 -21
- 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 +3 -3
- 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 +11 -2
- package/harness/docs/extracts/qa-inbox.md +19 -10
- package/harness/docs/extracts/qa-team.md +32 -0
- 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/schemas/flowgrid-docs/qa-item.schema.json +65 -0
- 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 +4 -4
- 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 +8 -7
- package/harness/docs/skills/grill-docs/SKILL.md +11 -4
- 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/qa-resolve/SKILL.md +13 -12
- package/harness/docs/skills/qa-review/SKILL.md +45 -0
- package/harness/docs/skills/spec/SKILL.md +43 -10
- package/harness/docs/skills/update-spec/SKILL.md +5 -2
- 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 +8 -3
- 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/qa/README.md +4 -8
- package/templates/project-skeleton/surfaces/common/data-model/index.md +10 -2
- package/templates/schemas/qa-item.schema.json +98 -0
- 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 +44 -7
- 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/ir-spec.yaml +1 -1
- package/templates/shared/qa-authoring.md +78 -0
- package/templates/shared/qa-item.yaml +35 -14
- 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
|
@@ -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
|
-
|
|
30
|
-
id: '
|
|
39
|
+
gemini: {
|
|
40
|
+
id: 'gemini',
|
|
31
41
|
dirName: '.agents',
|
|
32
|
-
mcpFile: '
|
|
33
|
-
label: '
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
const id = resolveCanonicalAgentId(agent);
|
|
150
|
+
return path.join(packageRoot, 'harness', 'agents', id);
|
|
127
151
|
}
|
package/bin/lib/audit-run.mjs
CHANGED
|
@@ -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 (--
|
|
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:',
|
package/bin/lib/cli-update.mjs
CHANGED
|
@@ -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 &&
|
|
58
|
-
|
|
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) {
|
package/bin/lib/doctor.mjs
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
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 {
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
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
|
);
|
package/bin/lib/harness-sync.mjs
CHANGED
|
@@ -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
|
-
[
|
|
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
|
+
}
|