@shanyucoder/flowgrid 0.1.5 → 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 +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/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
|
@@ -9,6 +9,9 @@ import fs from 'fs';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { parse } from 'yaml';
|
|
11
11
|
import { resolveTestsDocsRoot } from '../../shared/flowgrid-tests-docs.mjs';
|
|
12
|
+
import { resolveFlowgridContext } from '../../shared/resolve-flowgrid-context.mjs';
|
|
13
|
+
import { playwrightScanRoots } from '../../shared/e2e-output-layout.mjs';
|
|
14
|
+
import { resolveHubId } from '../../testcase/runners/lib/resolve-hub-id.mjs';
|
|
12
15
|
|
|
13
16
|
const TC_ID_PATTERN = /^TC-[A-Z0-9][A-Z0-9-]*$/;
|
|
14
17
|
const DESCRIBE_TC_RE = /test\.describe\s*\(\s*['"](TC-[^'"]+)['"]/g;
|
|
@@ -42,7 +45,21 @@ function walkFiles(root, onFile) {
|
|
|
42
45
|
/**
|
|
43
46
|
* @param {string} testsRoot
|
|
44
47
|
*/
|
|
45
|
-
|
|
48
|
+
function mergeScanState(into, from) {
|
|
49
|
+
for (const rec of from.specFiles) {
|
|
50
|
+
if (!into.specFiles.includes(rec)) into.specFiles.push(rec);
|
|
51
|
+
}
|
|
52
|
+
for (const [tcId, files] of from.tcIdToFiles.entries()) {
|
|
53
|
+
const list = into.tcIdToFiles.get(tcId) ?? [];
|
|
54
|
+
for (const f of files) {
|
|
55
|
+
if (!list.includes(f)) list.push(f);
|
|
56
|
+
}
|
|
57
|
+
into.tcIdToFiles.set(tcId, list);
|
|
58
|
+
}
|
|
59
|
+
for (const id of from.referencedTcIds) into.referencedTcIds.add(id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function scanPlaywrightTestsSingle(testsRoot) {
|
|
46
63
|
const specFiles = [];
|
|
47
64
|
const tcIdToFiles = new Map();
|
|
48
65
|
|
|
@@ -77,6 +94,10 @@ export function scanPlaywrightTests(testsRoot) {
|
|
|
77
94
|
|
|
78
95
|
const referencedTcIds = new Set(tcIdToFiles.keys());
|
|
79
96
|
|
|
97
|
+
return buildScanResult(specFiles, tcIdToFiles, referencedTcIds);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildScanResult(specFiles, tcIdToFiles, referencedTcIds) {
|
|
80
101
|
return {
|
|
81
102
|
specFiles,
|
|
82
103
|
specFileCount: specFiles.length,
|
|
@@ -92,6 +113,22 @@ export function scanPlaywrightTests(testsRoot) {
|
|
|
92
113
|
};
|
|
93
114
|
}
|
|
94
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @param {string | string[]} testsRootOrRoots
|
|
118
|
+
*/
|
|
119
|
+
export function scanPlaywrightTests(testsRootOrRoots) {
|
|
120
|
+
const roots = Array.isArray(testsRootOrRoots) ? testsRootOrRoots : [testsRootOrRoots];
|
|
121
|
+
const existing = roots.filter((r) => r && fs.existsSync(r));
|
|
122
|
+
if (!existing.length) {
|
|
123
|
+
return buildScanResult([], new Map(), new Set());
|
|
124
|
+
}
|
|
125
|
+
let merged = scanPlaywrightTestsSingle(existing[0]);
|
|
126
|
+
for (let i = 1; i < existing.length; i++) {
|
|
127
|
+
mergeScanState(merged, scanPlaywrightTestsSingle(existing[i]));
|
|
128
|
+
}
|
|
129
|
+
return merged;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
/**
|
|
96
133
|
* @param {string} casesRoot
|
|
97
134
|
* @param {{ screen?: string }} [filter]
|
|
@@ -150,11 +187,12 @@ function addGap(gaps, code, severity, fieldPath, message, suggestedFix) {
|
|
|
150
187
|
}
|
|
151
188
|
|
|
152
189
|
/**
|
|
153
|
-
* @param {{ tcDocuments: Array<{ path: string; id: string; screen: string | null; matrix: unknown[] }>; e2eRoot
|
|
190
|
+
* @param {{ tcDocuments: Array<{ path: string; id: string; screen: string | null; matrix: unknown[] }>; e2eRoot?: string; e2eScanRoots?: string[]; strict?: boolean; strictMatrix?: boolean }} opts
|
|
154
191
|
*/
|
|
155
192
|
export function auditE2eCoverage(opts) {
|
|
156
|
-
const { tcDocuments, e2eRoot, strict = false, strictMatrix = false } = opts;
|
|
157
|
-
const
|
|
193
|
+
const { tcDocuments, e2eRoot, e2eScanRoots, strict = false, strictMatrix = false } = opts;
|
|
194
|
+
const scanRoots = e2eScanRoots ?? (e2eRoot ? [e2eRoot] : []);
|
|
195
|
+
const pw = scanPlaywrightTests(scanRoots);
|
|
158
196
|
const gaps = [];
|
|
159
197
|
const missingInPlaywright = [];
|
|
160
198
|
const matrixRowsUncovered = [];
|
|
@@ -268,21 +306,21 @@ export function auditE2eCoverage(opts) {
|
|
|
268
306
|
}
|
|
269
307
|
|
|
270
308
|
function parseCliArgs(argv) {
|
|
271
|
-
|
|
272
|
-
let
|
|
309
|
+
const ctx = resolveFlowgridContext({ argv });
|
|
310
|
+
let e2eRoot = ctx.e2eRoot;
|
|
311
|
+
let testsDocs = ctx.testsDocs;
|
|
273
312
|
let screen = null;
|
|
274
313
|
let strict = false;
|
|
275
314
|
let strictMatrix = false;
|
|
315
|
+
let hubId = null;
|
|
276
316
|
const tcFiles = [];
|
|
277
317
|
|
|
278
|
-
for (let i = 0; i < argv.length; i++) {
|
|
279
|
-
const a = argv[i];
|
|
280
|
-
if (a === '--
|
|
281
|
-
|
|
282
|
-
} else if (a === '--
|
|
283
|
-
|
|
284
|
-
} else if (a === '--screen' && argv[i + 1]) {
|
|
285
|
-
screen = argv[++i];
|
|
318
|
+
for (let i = 0; i < ctx.argv.length; i++) {
|
|
319
|
+
const a = ctx.argv[i];
|
|
320
|
+
if (a === '--screen' && ctx.argv[i + 1]) {
|
|
321
|
+
screen = ctx.argv[++i];
|
|
322
|
+
} else if (a === '--id' && ctx.argv[i + 1]) {
|
|
323
|
+
hubId = ctx.argv[++i];
|
|
286
324
|
} else if (a === '--strict') {
|
|
287
325
|
strict = true;
|
|
288
326
|
strictMatrix = true;
|
|
@@ -291,22 +329,32 @@ function parseCliArgs(argv) {
|
|
|
291
329
|
} else if (a === '--help' || a === '-h') {
|
|
292
330
|
return { help: true };
|
|
293
331
|
} else if (!a.startsWith('-')) {
|
|
294
|
-
tcFiles.push(path.resolve(
|
|
332
|
+
tcFiles.push(path.resolve(ctx.projectRoot, a));
|
|
295
333
|
}
|
|
296
334
|
}
|
|
297
335
|
|
|
298
|
-
return {
|
|
336
|
+
return {
|
|
337
|
+
e2eRoot,
|
|
338
|
+
testsDocs,
|
|
339
|
+
screen,
|
|
340
|
+
strict,
|
|
341
|
+
strictMatrix,
|
|
342
|
+
tcFiles,
|
|
343
|
+
hubId,
|
|
344
|
+
projectRoot: ctx.projectRoot,
|
|
345
|
+
};
|
|
299
346
|
}
|
|
300
347
|
|
|
301
348
|
function printUsage() {
|
|
302
349
|
console.log(
|
|
303
350
|
[
|
|
304
|
-
'Usage:
|
|
305
|
-
' flowgrid audit e2e
|
|
306
|
-
' flowgrid audit e2e --e2e-root <dir> <TC-*.yaml> [more…]',
|
|
351
|
+
'Usage: flowgrid audit e2e [--id TC-*|W-*|suite] [options]',
|
|
352
|
+
' flowgrid audit e2e <TC-*.yaml> [more…]',
|
|
307
353
|
'',
|
|
308
354
|
'Options:',
|
|
309
|
-
' --
|
|
355
|
+
' --e2e-root Playwright scan root (default: config/MCP FLOWGRID_E2E_ROOT)',
|
|
356
|
+
' --tests-docs Tests hub (TC YAML); or FLOWGRID_TESTS_DOC / config',
|
|
357
|
+
' --id Resolve TC YAML via tests hub index (TC-*, screen W-*, suite)',
|
|
310
358
|
' --screen W-* Filter TC docs by refs.screen',
|
|
311
359
|
' --strict Treat missing TC spec + matrix gaps as critical',
|
|
312
360
|
' --strict-matrix Require each testMatrix row signal in Playwright text',
|
|
@@ -327,19 +375,27 @@ if (parsed.help) {
|
|
|
327
375
|
process.exit(0);
|
|
328
376
|
}
|
|
329
377
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
378
|
+
const e2eScanRoots = playwrightScanRoots(parsed.projectRoot, parsed.e2eRoot).filter((r) =>
|
|
379
|
+
fs.existsSync(r),
|
|
380
|
+
);
|
|
381
|
+
if (!e2eScanRoots.length) {
|
|
382
|
+
console.error(
|
|
383
|
+
JSON.stringify({
|
|
384
|
+
error:
|
|
385
|
+
'No e2e scan directory found — set frontend/backend.e2eRoot, FLOWGRID_E2E_ROOT, or pass --e2e-root',
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
337
388
|
process.exit(1);
|
|
338
389
|
}
|
|
339
390
|
|
|
340
391
|
let tcDocuments = [];
|
|
341
392
|
try {
|
|
342
|
-
if (parsed.
|
|
393
|
+
if (parsed.hubId) {
|
|
394
|
+
const resolved = resolveHubId(parsed.projectRoot, parsed.hubId, 'testcase');
|
|
395
|
+
for (const f of resolved.paths) {
|
|
396
|
+
tcDocuments.push(loadTcDocument(f));
|
|
397
|
+
}
|
|
398
|
+
} else if (parsed.tcFiles.length) {
|
|
343
399
|
for (const f of parsed.tcFiles) {
|
|
344
400
|
if (!fs.existsSync(f)) {
|
|
345
401
|
console.error(JSON.stringify({ error: `TC file not found: ${f}` }));
|
|
@@ -352,7 +408,7 @@ try {
|
|
|
352
408
|
if (!testsDocs) {
|
|
353
409
|
console.error(
|
|
354
410
|
JSON.stringify({
|
|
355
|
-
error: 'Provide --tests-docs, FLOWGRID_TESTS_DOC, or explicit TC-*.yaml paths',
|
|
411
|
+
error: 'Provide --id, --tests-docs, FLOWGRID_TESTS_DOC, or explicit TC-*.yaml paths',
|
|
356
412
|
}),
|
|
357
413
|
);
|
|
358
414
|
process.exit(1);
|
|
@@ -370,10 +426,12 @@ try {
|
|
|
370
426
|
|
|
371
427
|
const report = auditE2eCoverage({
|
|
372
428
|
tcDocuments,
|
|
373
|
-
|
|
429
|
+
e2eScanRoots,
|
|
430
|
+
e2eRoot: e2eScanRoots[0],
|
|
374
431
|
strict: parsed.strict,
|
|
375
432
|
strictMatrix: parsed.strictMatrix,
|
|
376
433
|
});
|
|
434
|
+
report.e2eScanRoots = e2eScanRoots;
|
|
377
435
|
if (parsed.testsDocs ?? resolveTestsDocsRoot()) {
|
|
378
436
|
report.testsDocs = parsed.testsDocs ?? resolveTestsDocsRoot();
|
|
379
437
|
}
|
|
@@ -12,6 +12,8 @@ export const BUNDLE_META_KEYS = [
|
|
|
12
12
|
'status',
|
|
13
13
|
'owner',
|
|
14
14
|
'summary',
|
|
15
|
+
'successMetrics',
|
|
16
|
+
'nonGoals',
|
|
15
17
|
'userStories',
|
|
16
18
|
'specOrigin',
|
|
17
19
|
'grillStatus'
|
|
@@ -36,7 +38,8 @@ export const DESIGN_LAYOUT_KEYS = [
|
|
|
36
38
|
'behavior',
|
|
37
39
|
'actions',
|
|
38
40
|
'deviations',
|
|
39
|
-
'i18n'
|
|
41
|
+
'i18n',
|
|
42
|
+
'dataModel'
|
|
40
43
|
]
|
|
41
44
|
|
|
42
45
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { checkSplitBundle, splitBundleFile } from './lib/bundle-ir.mjs'
|
|
4
5
|
import { writeBundleMarkdownFile } from '../docs/lib/render-bundle-markdown.mjs'
|
|
@@ -30,7 +31,16 @@ async function main() {
|
|
|
30
31
|
} else {
|
|
31
32
|
const { irDir } = await splitBundleFile(resolved)
|
|
32
33
|
const mdOut = await writeBundleMarkdownFile(resolved)
|
|
33
|
-
const
|
|
34
|
+
const mdRel = mdOut ? path.relative(process.cwd(), mdOut) : null
|
|
35
|
+
const genDir = mdOut ? path.dirname(mdOut) : null
|
|
36
|
+
const extra = genDir
|
|
37
|
+
? ['data-model.md', 'api.md']
|
|
38
|
+
.filter((name) => fs.existsSync(path.join(genDir, name)))
|
|
39
|
+
.map((name) => path.relative(process.cwd(), path.join(genDir, name)))
|
|
40
|
+
: []
|
|
41
|
+
const mdNote = mdRel
|
|
42
|
+
? ` + ${mdRel}${extra.length ? ` + ${extra.join(' + ')}` : ''}`
|
|
43
|
+
: ' (no MD; missing ir/spec.yaml)'
|
|
34
44
|
console.log(`spec:split: ${path.relative(process.cwd(), resolved)} → ${path.relative(process.cwd(), irDir)}/${mdNote}`)
|
|
35
45
|
}
|
|
36
46
|
} catch (error) {
|
|
@@ -2,33 +2,34 @@ import path from 'node:path'
|
|
|
2
2
|
|
|
3
3
|
import Handlebars from 'handlebars'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { readTestcaseFile } from './lib/read-testcase.mjs'
|
|
6
6
|
import { buildApiTestcaseContext, isApiE2eTestcase } from './lib/api-plan.mjs'
|
|
7
7
|
import { renderTemplate } from './lib/render.mjs'
|
|
8
8
|
import { writeOutputs } from './lib/write-files.mjs'
|
|
9
9
|
import { resolveHubId, resolveProjectRoot, loadTestsIndex } from './lib/resolve-hub-id.mjs'
|
|
10
|
-
import {
|
|
10
|
+
import { ENV_TESTS_DOC } from '../../shared/flowgrid-tests-docs.mjs'
|
|
11
|
+
import {
|
|
12
|
+
bootstrapTestcaseRun,
|
|
13
|
+
applyApiOutputLayout,
|
|
14
|
+
} from './lib/bootstrap-context.mjs'
|
|
11
15
|
|
|
12
|
-
const
|
|
16
|
+
const boot = bootstrapTestcaseRun(process.argv.slice(2))
|
|
17
|
+
const root = boot.ctx.projectRoot
|
|
18
|
+
const codegenLayout = boot.layout
|
|
13
19
|
|
|
14
20
|
Handlebars.registerHelper('json', (value) => JSON.stringify(value, null, 2))
|
|
15
21
|
|
|
16
22
|
function parseArgs(argv) {
|
|
17
|
-
const { argv: stripped, testsDocs } = extractTestsDocsFlag(argv)
|
|
18
|
-
if (testsDocs) {
|
|
19
|
-
process.env[ENV_TESTS_DOC] = testsDocs
|
|
20
|
-
}
|
|
21
|
-
|
|
22
23
|
const options = { dryRun: false, force: false, testcase: null, feature: null, id: null, all: false }
|
|
23
24
|
|
|
24
|
-
for (let i = 0; i <
|
|
25
|
-
const arg =
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const arg = argv[i]
|
|
26
27
|
if (arg === '--dry-run' || arg === '--dry') options.dryRun = true
|
|
27
28
|
else if (arg === '--force') options.force = true
|
|
28
29
|
else if (arg === '--all') options.all = true
|
|
29
|
-
else if (arg === '--testcase') options.testcase =
|
|
30
|
-
else if (arg === '--feature') options.feature =
|
|
31
|
-
else if (arg === '--id') options.id =
|
|
30
|
+
else if (arg === '--testcase') options.testcase = argv[++i]
|
|
31
|
+
else if (arg === '--feature') options.feature = argv[++i]
|
|
32
|
+
else if (arg === '--id') options.id = argv[++i]
|
|
32
33
|
else if (!arg.startsWith('-') && !options.testcase && !options.feature && !options.id && !options.all) {
|
|
33
34
|
options.testcase = arg
|
|
34
35
|
}
|
|
@@ -55,12 +56,14 @@ async function generateOne(testcasePath, options) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const ctx = buildApiTestcaseContext(testcase, testcaseFile)
|
|
59
|
+
applyApiOutputLayout(ctx, codegenLayout)
|
|
58
60
|
const specContent = await renderTemplate('api.spec.ts.hbs', ctx)
|
|
59
61
|
const outputs = [{ relativePath: ctx.outputs.spec, content: specContent }]
|
|
60
62
|
|
|
61
63
|
const { written, skipped } = await writeOutputs(root, outputs, {
|
|
62
64
|
dryRun: options.dryRun,
|
|
63
65
|
force: options.force,
|
|
66
|
+
allowedRelRoots: codegenLayout.allowedRelRoots,
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
console.log(`testcase-gen:api id=${ctx.testcaseId} module=${ctx.module}`)
|
|
@@ -76,13 +79,13 @@ async function generateOne(testcasePath, options) {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
async function main() {
|
|
79
|
-
const options = parseArgs(
|
|
82
|
+
const options = parseArgs(boot.argv)
|
|
80
83
|
const paths = []
|
|
81
84
|
|
|
82
85
|
if (options.all) {
|
|
83
|
-
const testsRoot =
|
|
84
|
-
? path.resolve(process.env[ENV_TESTS_DOC])
|
|
85
|
-
|
|
86
|
+
const testsRoot = boot.ctx.testsDocs
|
|
87
|
+
?? (process.env[ENV_TESTS_DOC] ? path.resolve(process.env[ENV_TESTS_DOC]) : null)
|
|
88
|
+
?? resolveProjectRoot(root, 'tests')
|
|
86
89
|
const index = loadTestsIndex(testsRoot)
|
|
87
90
|
for (const [id, rel] of Object.entries(index.codeIds || {})) {
|
|
88
91
|
if (id.startsWith('TC-')) paths.push(path.resolve(testsRoot, rel))
|
|
@@ -91,12 +94,9 @@ async function main() {
|
|
|
91
94
|
paths.push(path.resolve(root, options.testcase))
|
|
92
95
|
} else if (options.feature || options.id) {
|
|
93
96
|
const hubId = options.id ?? options.feature
|
|
94
|
-
const resolved =
|
|
95
|
-
if (resolved.
|
|
96
|
-
paths.push(resolved.
|
|
97
|
-
} else if (resolved.kind === 'feature' || resolved.kind === 'screen') {
|
|
98
|
-
const list = await listFeatureTestcases(resolved.absPath, { repoRoot: root })
|
|
99
|
-
paths.push(...list)
|
|
97
|
+
const resolved = resolveHubId(root, hubId, 'testcase')
|
|
98
|
+
if (resolved.paths?.length) {
|
|
99
|
+
paths.push(...resolved.paths)
|
|
100
100
|
} else {
|
|
101
101
|
throw new Error(`Could not resolve --id ${hubId} to testcase(s)`)
|
|
102
102
|
}
|
|
@@ -9,9 +9,14 @@ import { renderTemplate } from './lib/render.mjs'
|
|
|
9
9
|
import { resolveSemanticPlan } from './lib/semantic-plan.mjs'
|
|
10
10
|
import { writeOutputs } from './lib/write-files.mjs'
|
|
11
11
|
import { resolveHubId, resolveProjectRoot, loadTestsIndex } from './lib/resolve-hub-id.mjs'
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
bootstrapTestcaseRun,
|
|
14
|
+
applyUiOutputLayout,
|
|
15
|
+
} from './lib/bootstrap-context.mjs'
|
|
13
16
|
|
|
14
|
-
const
|
|
17
|
+
const boot = bootstrapTestcaseRun(process.argv.slice(2))
|
|
18
|
+
const root = boot.ctx.projectRoot
|
|
19
|
+
const codegenLayout = boot.layout
|
|
15
20
|
|
|
16
21
|
Handlebars.registerHelper('eq', (a, b) => a === b)
|
|
17
22
|
Handlebars.registerHelper('and', (a, b) => a && b)
|
|
@@ -29,21 +34,16 @@ Handlebars.registerHelper('pascalCase', (value) => {
|
|
|
29
34
|
})
|
|
30
35
|
|
|
31
36
|
function parseArgs(argv) {
|
|
32
|
-
const { argv: stripped, testsDocs } = extractTestsDocsFlag(argv)
|
|
33
|
-
if (testsDocs) {
|
|
34
|
-
process.env[ENV_TESTS_DOC] = testsDocs
|
|
35
|
-
}
|
|
36
|
-
|
|
37
37
|
const options = { dryRun: false, force: false, testcase: null, feature: null, id: null, all: false }
|
|
38
38
|
|
|
39
|
-
for (let i = 0; i <
|
|
40
|
-
const arg =
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const arg = argv[i]
|
|
41
41
|
if (arg === '--dry-run' || arg === '--dry') options.dryRun = true
|
|
42
42
|
else if (arg === '--force') options.force = true
|
|
43
43
|
else if (arg === '--all') options.all = true
|
|
44
|
-
else if (arg === '--testcase') options.testcase =
|
|
45
|
-
else if (arg === '--feature') options.feature =
|
|
46
|
-
else if (arg === '--id') options.id =
|
|
44
|
+
else if (arg === '--testcase') options.testcase = argv[++i]
|
|
45
|
+
else if (arg === '--feature') options.feature = argv[++i]
|
|
46
|
+
else if (arg === '--id') options.id = argv[++i]
|
|
47
47
|
else if (!arg.startsWith('-') && !options.testcase && !options.feature && !options.id && !options.all) {
|
|
48
48
|
options.testcase = arg
|
|
49
49
|
}
|
|
@@ -65,6 +65,7 @@ async function generateOne(testcasePath, options) {
|
|
|
65
65
|
const { testcase, spec, testcaseFile, specFile } = await readTestcaseFile(testcasePath, { repoRoot: root })
|
|
66
66
|
const ctx = buildTestcaseContext(testcase, spec, testcaseFile)
|
|
67
67
|
ctx.specFile = specFile
|
|
68
|
+
applyUiOutputLayout(ctx, codegenLayout)
|
|
68
69
|
|
|
69
70
|
const semantic = await resolveSemanticPlan(root, testcase)
|
|
70
71
|
ctx.useSemanticFixture = semantic.useSemanticFixture
|
|
@@ -84,7 +85,8 @@ async function generateOne(testcasePath, options) {
|
|
|
84
85
|
|
|
85
86
|
const { written, skipped } = await writeOutputs(root, outputs, {
|
|
86
87
|
dryRun: options.dryRun,
|
|
87
|
-
force: options.force
|
|
88
|
+
force: options.force,
|
|
89
|
+
allowedRelRoots: codegenLayout.allowedRelRoots,
|
|
88
90
|
})
|
|
89
91
|
|
|
90
92
|
console.log(`testcase-gen: id=${ctx.testcaseId} module=${ctx.module}`)
|
|
@@ -104,12 +106,12 @@ async function generateOne(testcasePath, options) {
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
async function main() {
|
|
107
|
-
const options = parseArgs(
|
|
109
|
+
const options = parseArgs(boot.argv)
|
|
108
110
|
|
|
109
111
|
if (options.all) {
|
|
110
|
-
const testsRoot =
|
|
111
|
-
? path.resolve(process.env.FLOWGRID_TESTS_DOC)
|
|
112
|
-
|
|
112
|
+
const testsRoot = boot.ctx.testsDocs
|
|
113
|
+
?? (process.env.FLOWGRID_TESTS_DOC ? path.resolve(process.env.FLOWGRID_TESTS_DOC) : null)
|
|
114
|
+
?? resolveProjectRoot(root, 'tests')
|
|
113
115
|
const index = loadTestsIndex(testsRoot)
|
|
114
116
|
const paths = []
|
|
115
117
|
for (const [id, rel] of Object.entries(index.codeIds || {})) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resolveFlowgridContext, flowgridContextToEnv } from '../../../shared/resolve-flowgrid-context.mjs';
|
|
2
|
+
import { codegenOutputLayout } from '../../../shared/e2e-output-layout.mjs';
|
|
3
|
+
import { extractTestsDocsFlag, ENV_TESTS_DOC } from '../../../shared/flowgrid-tests-docs.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve project/tests/e2e pointers and apply FLOWGRID_* env before testcase engines run.
|
|
7
|
+
* @param {string[]} argv raw process argv slice (after command name)
|
|
8
|
+
*/
|
|
9
|
+
export function bootstrapTestcaseRun(argv) {
|
|
10
|
+
const { argv: afterTestsFlag, testsDocs: testsFromFlag } = extractTestsDocsFlag(argv);
|
|
11
|
+
const ctx = resolveFlowgridContext({ argv: afterTestsFlag });
|
|
12
|
+
if (testsFromFlag) {
|
|
13
|
+
ctx.testsDocs = testsFromFlag;
|
|
14
|
+
}
|
|
15
|
+
Object.assign(process.env, flowgridContextToEnv(ctx));
|
|
16
|
+
if (ctx.testsDocs) {
|
|
17
|
+
process.env[ENV_TESTS_DOC] = ctx.testsDocs;
|
|
18
|
+
}
|
|
19
|
+
const layout = codegenOutputLayout(ctx.projectRoot, ctx.e2eRoot, {
|
|
20
|
+
projectType: ctx.config?.type,
|
|
21
|
+
});
|
|
22
|
+
return { ctx, layout, argv: ctx.argv };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('../../../shared/e2e-output-layout.mjs').codegenOutputLayout} layout
|
|
27
|
+
* @param {{ outputs: { pageObject: string, spec: string }, module: string, pageClassName: string, testcaseId: string }} ctx
|
|
28
|
+
*/
|
|
29
|
+
export function applyUiOutputLayout(ctx, layout) {
|
|
30
|
+
ctx.outputs = {
|
|
31
|
+
pageObject: `${layout.uiPrefix}/pages/${ctx.module}/${ctx.pageClassName}.ts`,
|
|
32
|
+
spec: `${layout.uiPrefix}/${ctx.module}/${ctx.testcaseId}.spec.ts`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {{ outputs: { spec: string }, module: string, testcaseId: string }} ctx
|
|
38
|
+
* @param layout
|
|
39
|
+
*/
|
|
40
|
+
export function applyApiOutputLayout(ctx, layout) {
|
|
41
|
+
ctx.outputs = {
|
|
42
|
+
spec: `${layout.apiPrefix}/${ctx.module}/${ctx.testcaseId}.api.spec.ts`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { lstat, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_E2E_ROOT_REL,
|
|
5
|
+
E2E_ROOT_DIR_PARTS,
|
|
6
|
+
API_E2E_ROOT_DIR_PARTS,
|
|
7
|
+
} from '../../../shared/flowgrid-e2e-root.mjs'
|
|
3
8
|
|
|
4
|
-
export const E2E_OUTPUT_ROOT = path.join(
|
|
9
|
+
export const E2E_OUTPUT_ROOT = path.join(...E2E_ROOT_DIR_PARTS)
|
|
10
|
+
export const API_E2E_OUTPUT_ROOT = path.join(...API_E2E_ROOT_DIR_PARTS)
|
|
11
|
+
export { DEFAULT_E2E_ROOT_REL }
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Preflight every output path before any write or dry-run result is produced.
|
|
8
15
|
*
|
|
9
16
|
* A target is accepted only when, lexically (without following symlinks), it
|
|
10
|
-
* resolves strictly beneath `<root>/tests/e2e`. Rejected: absolute paths,
|
|
17
|
+
* resolves strictly beneath `<root>/tests/e2e` or `<root>/tests/api-e2e`. Rejected: absolute paths,
|
|
11
18
|
* `..` traversal, targets outside `tests/e2e` (including sibling prefixes
|
|
12
19
|
* such as `tests/e2e-evil`), existing symlink ancestors that could redirect
|
|
13
20
|
* the write, and existing targets that are not regular files.
|
|
@@ -17,9 +24,28 @@ export const E2E_OUTPUT_ROOT = path.join('tests', 'e2e')
|
|
|
17
24
|
* @param {string} root project root
|
|
18
25
|
* @param {{ relativePath: string }[]} outputs
|
|
19
26
|
*/
|
|
20
|
-
|
|
27
|
+
function allowedAbsRoots(resolvedRoot, allowedRelRoots) {
|
|
28
|
+
const rels =
|
|
29
|
+
allowedRelRoots?.length
|
|
30
|
+
? allowedRelRoots
|
|
31
|
+
: [E2E_OUTPUT_ROOT, API_E2E_OUTPUT_ROOT]
|
|
32
|
+
return rels.map((rel) => path.join(resolvedRoot, rel.replace(/\//g, path.sep)))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isUnderCodegenTree(resolvedRoot, absolutePath, allowedRelRoots) {
|
|
36
|
+
return allowedAbsRoots(resolvedRoot, allowedRelRoots).some(
|
|
37
|
+
(base) => absolutePath === base || absolutePath.startsWith(base + path.sep),
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} root
|
|
43
|
+
* @param {{ relativePath: string }[]} outputs
|
|
44
|
+
* @param {{ allowedRelRoots?: string[] }} [opts]
|
|
45
|
+
*/
|
|
46
|
+
export async function preflightOutputPaths(root, outputs, opts = {}) {
|
|
21
47
|
const resolvedRoot = path.resolve(root)
|
|
22
|
-
const
|
|
48
|
+
const allowedLabel = (opts.allowedRelRoots ?? [E2E_OUTPUT_ROOT, API_E2E_OUTPUT_ROOT]).join(', ')
|
|
23
49
|
const errors = []
|
|
24
50
|
|
|
25
51
|
for (const { relativePath } of outputs) {
|
|
@@ -31,22 +57,22 @@ export async function preflightOutputPaths(root, outputs) {
|
|
|
31
57
|
}
|
|
32
58
|
if (path.isAbsolute(relativePath)) {
|
|
33
59
|
errors.push(
|
|
34
|
-
`output ${label}: absolute paths are not allowed — use a path relative to the project root, under ${
|
|
60
|
+
`output ${label}: absolute paths are not allowed — use a path relative to the project root, under: ${allowedLabel}`,
|
|
35
61
|
)
|
|
36
62
|
continue
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
if (relativePath.split(/[\\/]+/).includes('..')) {
|
|
40
66
|
errors.push(
|
|
41
|
-
`output ${label}: ".." traversal is not allowed — outputs must stay under ${
|
|
67
|
+
`output ${label}: ".." traversal is not allowed — outputs must stay under: ${allowedLabel}`,
|
|
42
68
|
)
|
|
43
69
|
continue
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
const absolutePath = path.resolve(resolvedRoot, relativePath)
|
|
47
|
-
if (!
|
|
73
|
+
if (!isUnderCodegenTree(resolvedRoot, absolutePath, opts.allowedRelRoots)) {
|
|
48
74
|
errors.push(
|
|
49
|
-
`output ${label}: resolves outside
|
|
75
|
+
`output ${label}: resolves outside allowed codegen trees (${allowedLabel})` +
|
|
50
76
|
' (check module/testcase ids used to build output paths)',
|
|
51
77
|
)
|
|
52
78
|
continue
|
|
@@ -108,7 +134,9 @@ async function findUnsafePathComponent(resolvedRoot, absolutePath) {
|
|
|
108
134
|
* @param {{ dryRun?: boolean, force?: boolean }} options
|
|
109
135
|
*/
|
|
110
136
|
export async function writeOutputs(root, outputs, options = {}) {
|
|
111
|
-
await preflightOutputPaths(root, outputs
|
|
137
|
+
await preflightOutputPaths(root, outputs, {
|
|
138
|
+
allowedRelRoots: options.allowedRelRoots,
|
|
139
|
+
})
|
|
112
140
|
|
|
113
141
|
const written = []
|
|
114
142
|
const skipped = []
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Google Antigravity IDE — FlowGrid MCP (mcp_config.json under .antigravity)
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
# Antigravity IDE + FlowGrid
|
|
6
|
+
|
|
7
|
+
- Workspace harness: **`.antigravity/`** (Antigravity IDE hardcodes this path; it does **not** fall back to `.agents/`).
|
|
8
|
+
- MCP config: **`.antigravity/mcp_config.json`** (not `mcp.json`).
|
|
9
|
+
- Server name: `flowgrid` → `bin/flowgrid-mcp.mjs` from the FlowGrid package used at `flowgrid init`.
|
|
10
|
+
- Skills: **`.antigravity/skills/<name>/SKILL.md`**; rules: **`.antigravity/rules/`.
|
|
11
|
+
- Re-sync: `flowgrid harness sync --agent=antigravity`.
|
|
12
|
+
- **Gemini CLI / VS Code** use **`.agents/`** — select **Gemini (CLI / extension)** in init (`--agent=gemini`).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Gemini CLI / extension — FlowGrid MCP (mcp.json under .agents)
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
# Gemini + FlowGrid
|
|
6
|
+
|
|
7
|
+
- Workspace harness: **`.agents/`** (open Agent Skills standard; Gemini CLI, VS Code Gemini extension, tools compatible with `.agents` / `.agent`).
|
|
8
|
+
- MCP config: **`.agents/mcp.json`** (server `flowgrid` → `bin/flowgrid-mcp.mjs` from the FlowGrid package used at `flowgrid init`).
|
|
9
|
+
- Skills: **`.agents/skills/<name>/SKILL.md`**; rules: **`.agents/rules/`.
|
|
10
|
+
- Re-sync: `flowgrid harness sync --agent=gemini`.
|
|
11
|
+
- **Antigravity IDE** uses a separate folder — select **Google Antigravity IDE** in init (`--agent=antigravity` → `.antigravity/`), not this profile.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: DEPRECATED — use gemini or antigravity agent profiles
|
|
3
3
|
alwaysApply: false
|
|
4
4
|
---
|
|
5
|
-
#
|
|
5
|
+
# Deprecated combined profile
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
`gemini_antigravity` was split:
|
|
8
|
+
|
|
9
|
+
- **Gemini CLI / VS Code** → `flowgrid harness sync --agent=gemini` → `.agents/`
|
|
10
|
+
- **Antigravity IDE** → `flowgrid harness sync --agent=antigravity` → `.antigravity/`
|
|
11
|
+
|
|
12
|
+
Update `.flowgrid/config.json` `agents` and re-run init or harness sync.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: /
|
|
2
|
+
name: audit-api
|
|
3
|
+
description: /audit-api — audit generated BE routes/code against 01-backend-spec.yaml before wire.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /
|
|
7
|
+
# /audit-api — BE implementation vs contract
|
|
8
8
|
|
|
9
9
|
Run after `npm run codegen:api:dry` / `flowgrid api-gen:dry` and after implementation.
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ Run after `npm run codegen:api:dry` / `flowgrid api-gen:dry` and after implement
|
|
|
13
13
|
## Target / ID Resolution Rule
|
|
14
14
|
|
|
15
15
|
- User prompt MAY specify a function ID, API slug, or short name (e.g. `API-AUTH-001`, `login`, `CMP-ADM-009`).
|
|
16
|
-
- Agent MUST resolve **`…/api/<seq>/01-backend-spec.yaml`** via `FLOWGRID_DOCS_ROOT` or `flowgrid_docs_route` (screen leaf `CMP-*/<NN…>/api/<seq>/`, or `common/yaml/<slug>/`, or
|
|
16
|
+
- Agent MUST resolve **`…/api/<seq>/01-backend-spec.yaml`** via `FLOWGRID_DOCS_ROOT` or `flowgrid_docs_route` (screen leaf `CMP-*/<NN…>/api/<seq>/`, or `common/yaml/<slug>/`, or external-channel surface `…/api/<seq>/`).
|
|
17
17
|
- **Read the entire `01-backend-spec.yaml`**. Do **not** use `ir/design.yaml` or `ir/spec.yaml` as BE contract.
|
|
18
18
|
- Compare generated routes/code against that 01 file. Missing 01 → STOP, hand off to docs `/grill-api-spec`.
|
|
19
19
|
- Do NOT demand full filesystem paths from the user if an ID is given.
|
|
@@ -36,6 +36,8 @@ Check:
|
|
|
36
36
|
- Writes are transaction-safe; async retries are idempotent.
|
|
37
37
|
- Generated placeholders are replaced before ship.
|
|
38
38
|
|
|
39
|
+
**CLI (lượng):** `flowgrid audit api` on `01`; when portal bundle exists → `flowgrid audit fe-be` on sibling `*.bundle.yaml`.
|
|
40
|
+
|
|
39
41
|
## Accelerators (optional)
|
|
40
42
|
|
|
41
43
|
```text
|
|
@@ -48,9 +50,8 @@ else: targeted repository search
|
|
|
48
50
|
architecture IDs / C4 → bộ docs (FLOWGRID_DOCS_ROOT), never CodeGraph
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
Missing accelerators never block the
|
|
52
|
-
targeted-local fallback first, then follow
|
|
53
|
-
the remainder of this skill.
|
|
53
|
+
Missing accelerators never block the audit. Complete each scoped model or
|
|
54
|
+
targeted-local fallback first, then follow the remainder of this skill.
|
|
54
55
|
|
|
55
56
|
## Translation Rule (i18n)
|
|
56
57
|
Always wrap static text with native framework i18n helpers (e.g. `__('key')`, `_localizer["key"]`, `i18n.t('key')`).
|