@gordon.gan/specflow 1.4.2-beta → 1.4.4-beta
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/dist/core/project-config.d.ts +4 -0
- package/dist/core/project-config.js +49 -0
- package/dist/core/project-conventions.d.ts +15 -0
- package/dist/core/project-conventions.js +66 -0
- package/dist/integrations/claude/adapter.js +2 -0
- package/dist/integrations/codex/adapter.js +3 -0
- package/dist/integrations/cursor/adapter.js +2 -0
- package/dist/integrations/shared/parity-comparator.js +1 -0
- package/dist/integrations/shared/parity-manifest.js +3 -0
- package/dist/integrations/shared/runtime-assets.d.ts +13 -0
- package/dist/integrations/shared/runtime-assets.js +41 -3
- package/package.json +1 -1
- package/prompts/approval/database-guidance.md +43 -17
- package/prompts/approval/frontend-guidance.md +249 -0
- package/prompts/approval/generate.md +292 -87
- package/prompts/approval/project-conventions-guidance.md +171 -0
- package/skills/GUIDANCE_PACKS.md +70 -0
- package/skills/database/README.md +24 -5
- package/skills/guidance-packs.yaml +10 -0
- package/skills/specflow-approval/SKILL.md +172 -77
- package/templates/approval.md +81 -310
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ npm install -g @gordon.gan/specflow
|
|
|
126
126
|
npm install -g github:Gordon-Gan-Jiang/specflow
|
|
127
127
|
|
|
128
128
|
# 验证
|
|
129
|
-
specflow --version # 以 npm / package.json 为准(当前 1.4.
|
|
129
|
+
specflow --version # 以 npm / package.json 为准(当前 1.4.4-beta)
|
|
130
130
|
specflow --help
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -6,6 +6,9 @@ export interface NormalizedReference {
|
|
|
6
6
|
readonly id: string;
|
|
7
7
|
readonly remote?: string;
|
|
8
8
|
}
|
|
9
|
+
export declare const CONVENTION_TOPICS: readonly ["architecture", "api", "database", "frontend"];
|
|
10
|
+
export type ConventionTopic = (typeof CONVENTION_TOPICS)[number];
|
|
11
|
+
export type ProjectConventions = Readonly<Partial<Record<ConventionTopic, readonly string[]>>>;
|
|
9
12
|
export interface ParsedProjectConfig {
|
|
10
13
|
readonly schema: string;
|
|
11
14
|
readonly context?: string;
|
|
@@ -14,6 +17,7 @@ export interface ParsedProjectConfig {
|
|
|
14
17
|
readonly workflowUpstream?: string;
|
|
15
18
|
readonly store?: string;
|
|
16
19
|
readonly references: readonly NormalizedReference[];
|
|
20
|
+
readonly conventions: ProjectConventions;
|
|
17
21
|
readonly diagnostics: readonly Diagnostic[];
|
|
18
22
|
}
|
|
19
23
|
export declare function parseProjectConfig(raw: unknown): ParsedProjectConfig;
|
|
@@ -12,6 +12,12 @@ const ReferenceObjectSchema = z
|
|
|
12
12
|
remote: z.string().url().optional(),
|
|
13
13
|
})
|
|
14
14
|
.strict();
|
|
15
|
+
export const CONVENTION_TOPICS = [
|
|
16
|
+
'architecture',
|
|
17
|
+
'api',
|
|
18
|
+
'database',
|
|
19
|
+
'frontend',
|
|
20
|
+
];
|
|
15
21
|
function referenceDiagnostic(message, target) {
|
|
16
22
|
return {
|
|
17
23
|
severity: 'warning',
|
|
@@ -155,6 +161,48 @@ export function parseProjectConfig(raw) {
|
|
|
155
161
|
fix: `Add '${workflowUpstream}' to specflow/config.yaml references.`,
|
|
156
162
|
});
|
|
157
163
|
}
|
|
164
|
+
const conventions = {};
|
|
165
|
+
if (record.conventions !== undefined) {
|
|
166
|
+
const conventionsRaw = typeof record.conventions === 'object' &&
|
|
167
|
+
record.conventions !== null &&
|
|
168
|
+
!Array.isArray(record.conventions)
|
|
169
|
+
? record.conventions
|
|
170
|
+
: undefined;
|
|
171
|
+
if (!conventionsRaw) {
|
|
172
|
+
diagnostics.push({
|
|
173
|
+
severity: 'error',
|
|
174
|
+
code: 'invalid_conventions_config',
|
|
175
|
+
message: 'Project conventions must be an object of topic → path arrays.',
|
|
176
|
+
target: 'config.conventions',
|
|
177
|
+
fix: 'Use conventions.architecture|api|database|frontend: [relative/path.md].',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
for (const topic of CONVENTION_TOPICS) {
|
|
182
|
+
const value = conventionsRaw[topic];
|
|
183
|
+
if (value === undefined) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === 'string')) {
|
|
187
|
+
diagnostics.push({
|
|
188
|
+
severity: 'error',
|
|
189
|
+
code: 'invalid_conventions_topic',
|
|
190
|
+
message: `conventions.${topic} must be an array of relative file path strings.`,
|
|
191
|
+
target: `config.conventions.${topic}`,
|
|
192
|
+
fix: `Set conventions.${topic} to e.g. ['docs/engineering/${topic}.md'].`,
|
|
193
|
+
});
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const paths = value
|
|
197
|
+
.map((item) => item.trim())
|
|
198
|
+
.filter((item) => item.length > 0)
|
|
199
|
+
.slice(0, 3);
|
|
200
|
+
if (paths.length > 0) {
|
|
201
|
+
conventions[topic] = paths;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
158
206
|
return {
|
|
159
207
|
schema,
|
|
160
208
|
context,
|
|
@@ -163,6 +211,7 @@ export function parseProjectConfig(raw) {
|
|
|
163
211
|
workflowUpstream,
|
|
164
212
|
store,
|
|
165
213
|
references,
|
|
214
|
+
conventions,
|
|
166
215
|
diagnostics,
|
|
167
216
|
};
|
|
168
217
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ConventionTopic, type ProjectConventions } from './project-config.js';
|
|
2
|
+
export type IdeFlavor = 'cursor' | 'claude' | 'codex';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves project convention files for an approval topic.
|
|
5
|
+
* Priority: config conventions.<topic> → repo-neutral docs.
|
|
6
|
+
* IDE-specific rule/skill globs are documented for the agent to scan; this helper
|
|
7
|
+
* covers the deterministic, config + neutral paths used in tests and tooling.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveProjectConventionPaths(input: {
|
|
10
|
+
readonly projectRoot: string;
|
|
11
|
+
readonly topic: ConventionTopic;
|
|
12
|
+
readonly conventions?: ProjectConventions;
|
|
13
|
+
}): readonly string[];
|
|
14
|
+
export declare function listConventionTopics(): readonly ConventionTopic[];
|
|
15
|
+
export declare function neutralCandidatesFor(topic: ConventionTopic): readonly string[];
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CONVENTION_TOPICS, } from './project-config.js';
|
|
4
|
+
const NEUTRAL_CANDIDATES = {
|
|
5
|
+
architecture: [
|
|
6
|
+
'docs/engineering/architecture.md',
|
|
7
|
+
'.specflow/conventions/architecture.md',
|
|
8
|
+
'ARCHITECTURE.md',
|
|
9
|
+
],
|
|
10
|
+
api: [
|
|
11
|
+
'docs/api/guidelines.md',
|
|
12
|
+
'docs/engineering/api.md',
|
|
13
|
+
'.specflow/conventions/api.md',
|
|
14
|
+
],
|
|
15
|
+
database: [
|
|
16
|
+
'docs/db/conventions.md',
|
|
17
|
+
'docs/engineering/database.md',
|
|
18
|
+
'.specflow/conventions/database.md',
|
|
19
|
+
],
|
|
20
|
+
frontend: [
|
|
21
|
+
'docs/frontend/conventions.md',
|
|
22
|
+
'docs/frontend/patterns.md',
|
|
23
|
+
'docs/frontend/testing.md',
|
|
24
|
+
'docs/engineering/frontend.md',
|
|
25
|
+
'.specflow/conventions/frontend.md',
|
|
26
|
+
'agent_docs/tech_stack.md',
|
|
27
|
+
'agent_docs/code_patterns.md',
|
|
28
|
+
'agent_docs/testing.md',
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
const MAX_FILES_PER_TOPIC = 3;
|
|
32
|
+
function existingRelative(projectRoot, relativePaths) {
|
|
33
|
+
const found = [];
|
|
34
|
+
for (const relative of relativePaths) {
|
|
35
|
+
const normalized = relative.replace(/^\.\//, '').replace(/\\/g, '/');
|
|
36
|
+
if (!normalized || normalized.includes('..')) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (existsSync(join(projectRoot, ...normalized.split('/')))) {
|
|
40
|
+
found.push(normalized);
|
|
41
|
+
}
|
|
42
|
+
if (found.length >= MAX_FILES_PER_TOPIC) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves project convention files for an approval topic.
|
|
50
|
+
* Priority: config conventions.<topic> → repo-neutral docs.
|
|
51
|
+
* IDE-specific rule/skill globs are documented for the agent to scan; this helper
|
|
52
|
+
* covers the deterministic, config + neutral paths used in tests and tooling.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveProjectConventionPaths(input) {
|
|
55
|
+
const configured = input.conventions?.[input.topic];
|
|
56
|
+
if (configured && configured.length > 0) {
|
|
57
|
+
return existingRelative(input.projectRoot, configured.slice(0, MAX_FILES_PER_TOPIC));
|
|
58
|
+
}
|
|
59
|
+
return existingRelative(input.projectRoot, NEUTRAL_CANDIDATES[input.topic]);
|
|
60
|
+
}
|
|
61
|
+
export function listConventionTopics() {
|
|
62
|
+
return CONVENTION_TOPICS;
|
|
63
|
+
}
|
|
64
|
+
export function neutralCandidatesFor(topic) {
|
|
65
|
+
return NEUTRAL_CANDIDATES[topic];
|
|
66
|
+
}
|
|
@@ -69,6 +69,7 @@ async function inspectClaude(projectRoot) {
|
|
|
69
69
|
const promptsDir = join(projectRoot, '.claude', 'specflow', 'prompts');
|
|
70
70
|
const schemasDir = join(projectRoot, '.claude', 'specflow', 'schemas');
|
|
71
71
|
const templatesDir = join(projectRoot, '.claude', 'specflow', 'templates');
|
|
72
|
+
const guidanceDir = join(projectRoot, '.claude', 'specflow', 'guidance');
|
|
72
73
|
const supported = [];
|
|
73
74
|
const skillContentsByCommand = {};
|
|
74
75
|
for (const command of COMMAND_CATALOG) {
|
|
@@ -98,6 +99,7 @@ async function inspectClaude(projectRoot) {
|
|
|
98
99
|
runtimePromptsDir: { exists: await directoryExists(promptsDir), hash: await hashDirectoryTree(promptsDir) },
|
|
99
100
|
runtimeSchemasDir: { exists: await directoryExists(schemasDir), hash: await hashDirectoryTree(schemasDir) },
|
|
100
101
|
runtimeTemplatesDir: { exists: await directoryExists(templatesDir), hash: await hashDirectoryTree(templatesDir) },
|
|
102
|
+
runtimeGuidanceDir: { exists: await directoryExists(guidanceDir), hash: await hashDirectoryTree(guidanceDir) },
|
|
101
103
|
},
|
|
102
104
|
};
|
|
103
105
|
}
|
|
@@ -40,6 +40,7 @@ function renderCodexAgentsBlock() {
|
|
|
40
40
|
'- Use artifacts under specflow/changes/<change>/ before coding',
|
|
41
41
|
'- Workflow skills: $specflow-explore, $specflow-propose, $specflow-refine, $specflow-apply, $specflow-review, $specflow-test, $specflow-verify, $specflow-archive, $specflow-fix, $specflow-snap',
|
|
42
42
|
'- Runtime prompts live under .agents/specflow/prompts/',
|
|
43
|
+
'- Guidance packs (non-IDE skills) live under .agents/specflow/guidance/',
|
|
43
44
|
'',
|
|
44
45
|
].join('\n');
|
|
45
46
|
}
|
|
@@ -96,6 +97,7 @@ async function inspectCodex(projectRoot) {
|
|
|
96
97
|
const promptsDir = join(projectRoot, '.agents', 'specflow', 'prompts');
|
|
97
98
|
const schemasDir = join(projectRoot, '.agents', 'specflow', 'schemas');
|
|
98
99
|
const templatesDir = join(projectRoot, '.agents', 'specflow', 'templates');
|
|
100
|
+
const guidanceDir = join(projectRoot, '.agents', 'specflow', 'guidance');
|
|
99
101
|
let agentsFileExists = false;
|
|
100
102
|
try {
|
|
101
103
|
const agentsContent = await fs.readFile(agentsFile, 'utf-8');
|
|
@@ -134,6 +136,7 @@ async function inspectCodex(projectRoot) {
|
|
|
134
136
|
runtimePromptsDir: { exists: await directoryExists(promptsDir), hash: await hashDirectoryTree(promptsDir) },
|
|
135
137
|
runtimeSchemasDir: { exists: await directoryExists(schemasDir), hash: await hashDirectoryTree(schemasDir) },
|
|
136
138
|
runtimeTemplatesDir: { exists: await directoryExists(templatesDir), hash: await hashDirectoryTree(templatesDir) },
|
|
139
|
+
runtimeGuidanceDir: { exists: await directoryExists(guidanceDir), hash: await hashDirectoryTree(guidanceDir) },
|
|
137
140
|
},
|
|
138
141
|
};
|
|
139
142
|
}
|
|
@@ -89,6 +89,7 @@ async function inspectCursor(projectRoot) {
|
|
|
89
89
|
const promptsDir = join(projectRoot, '.cursor', 'specflow', 'prompts');
|
|
90
90
|
const schemasDir = join(projectRoot, '.cursor', 'specflow', 'schemas');
|
|
91
91
|
const templatesDir = join(projectRoot, '.cursor', 'specflow', 'templates');
|
|
92
|
+
const guidanceDir = join(projectRoot, '.cursor', 'specflow', 'guidance');
|
|
92
93
|
const supported = [];
|
|
93
94
|
const skillContentsByCommand = {};
|
|
94
95
|
for (const command of COMMAND_CATALOG) {
|
|
@@ -119,6 +120,7 @@ async function inspectCursor(projectRoot) {
|
|
|
119
120
|
runtimePromptsDir: { exists: await directoryExists(promptsDir), hash: await hashDirectoryTree(promptsDir) },
|
|
120
121
|
runtimeSchemasDir: { exists: await directoryExists(schemasDir), hash: await hashDirectoryTree(schemasDir) },
|
|
121
122
|
runtimeTemplatesDir: { exists: await directoryExists(templatesDir), hash: await hashDirectoryTree(templatesDir) },
|
|
123
|
+
runtimeGuidanceDir: { exists: await directoryExists(guidanceDir), hash: await hashDirectoryTree(guidanceDir) },
|
|
122
124
|
},
|
|
123
125
|
};
|
|
124
126
|
}
|
|
@@ -4,6 +4,7 @@ import { CAPABILITY_MANIFEST, IDE_ASSET_MANIFEST } from './parity-manifest.js';
|
|
|
4
4
|
const PARITY_COMPARABLE_ASSET_KEYS = [
|
|
5
5
|
'runtimeSchemasDir',
|
|
6
6
|
'runtimeTemplatesDir',
|
|
7
|
+
'runtimeGuidanceDir',
|
|
7
8
|
];
|
|
8
9
|
export function validateReportAgainstManifest(report) {
|
|
9
10
|
const deltas = [];
|
|
@@ -42,6 +42,7 @@ export const IDE_ASSET_MANIFEST = [
|
|
|
42
42
|
'runtimePromptsDir',
|
|
43
43
|
'runtimeSchemasDir',
|
|
44
44
|
'runtimeTemplatesDir',
|
|
45
|
+
'runtimeGuidanceDir',
|
|
45
46
|
],
|
|
46
47
|
},
|
|
47
48
|
{
|
|
@@ -53,6 +54,7 @@ export const IDE_ASSET_MANIFEST = [
|
|
|
53
54
|
'runtimePromptsDir',
|
|
54
55
|
'runtimeSchemasDir',
|
|
55
56
|
'runtimeTemplatesDir',
|
|
57
|
+
'runtimeGuidanceDir',
|
|
56
58
|
],
|
|
57
59
|
},
|
|
58
60
|
{
|
|
@@ -63,6 +65,7 @@ export const IDE_ASSET_MANIFEST = [
|
|
|
63
65
|
'runtimePromptsDir',
|
|
64
66
|
'runtimeSchemasDir',
|
|
65
67
|
'runtimeTemplatesDir',
|
|
68
|
+
'runtimeGuidanceDir',
|
|
66
69
|
],
|
|
67
70
|
},
|
|
68
71
|
];
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import type { IdeTarget } from './types.js';
|
|
2
|
+
export declare function ideRootDir(ide: IdeTarget): string;
|
|
3
|
+
interface GuidancePackSpec {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly source: string;
|
|
6
|
+
readonly installAs: string;
|
|
7
|
+
readonly consumers?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Loads guidance pack registry from skills/guidance-packs.yaml.
|
|
11
|
+
* Missing or empty registry → no packs (non-fatal).
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadGuidancePacks(packageRoot: string): Promise<readonly GuidancePackSpec[]>;
|
|
2
14
|
export declare function copyRuntimeAssets(packageRoot: string, projectRoot: string, ide: IdeTarget): Promise<readonly string[]>;
|
|
15
|
+
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
1
2
|
import { join } from 'node:path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
2
4
|
import { copyDirRecursive, copyDirRecursiveRendered } from './asset-copy.js';
|
|
3
5
|
import { renderIdeContent } from './skill-renderer.js';
|
|
4
|
-
function ideRootDir(ide) {
|
|
6
|
+
export function ideRootDir(ide) {
|
|
5
7
|
if (ide === 'claude') {
|
|
6
8
|
return '.claude';
|
|
7
9
|
}
|
|
@@ -10,12 +12,48 @@ function ideRootDir(ide) {
|
|
|
10
12
|
}
|
|
11
13
|
return '.agents';
|
|
12
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Loads guidance pack registry from skills/guidance-packs.yaml.
|
|
17
|
+
* Missing or empty registry → no packs (non-fatal).
|
|
18
|
+
*/
|
|
19
|
+
export async function loadGuidancePacks(packageRoot) {
|
|
20
|
+
const registryPath = join(packageRoot, 'skills', 'guidance-packs.yaml');
|
|
21
|
+
let content;
|
|
22
|
+
try {
|
|
23
|
+
content = await fs.readFile(registryPath, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
const parsed = yaml.load(content);
|
|
29
|
+
if (!parsed || !Array.isArray(parsed.packs)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
return parsed.packs.filter((pack) => typeof pack?.id === 'string' &&
|
|
33
|
+
typeof pack?.source === 'string' &&
|
|
34
|
+
typeof pack?.installAs === 'string');
|
|
35
|
+
}
|
|
36
|
+
async function copyGuidancePacks(packageRoot, specflowRoot) {
|
|
37
|
+
const packs = await loadGuidancePacks(packageRoot);
|
|
38
|
+
for (const pack of packs) {
|
|
39
|
+
const src = join(packageRoot, ...pack.source.split('/'));
|
|
40
|
+
const dest = join(specflowRoot, ...pack.installAs.split('/'));
|
|
41
|
+
await copyDirRecursive(src, dest);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
13
44
|
export async function copyRuntimeAssets(packageRoot, projectRoot, ide) {
|
|
14
|
-
const
|
|
45
|
+
const ideRoot = ideRootDir(ide);
|
|
46
|
+
const root = join(projectRoot, ideRoot, 'specflow');
|
|
15
47
|
const promptsSrc = join(packageRoot, 'prompts');
|
|
16
48
|
const promptsDest = join(root, 'prompts');
|
|
17
49
|
await copyDirRecursiveRendered(promptsSrc, promptsDest, (content) => renderIdeContent(content, ide));
|
|
18
50
|
await copyDirRecursive(join(packageRoot, 'schemas'), join(root, 'schemas'));
|
|
19
51
|
await copyDirRecursive(join(packageRoot, 'templates'), join(root, 'templates'));
|
|
20
|
-
|
|
52
|
+
await copyGuidancePacks(packageRoot, root);
|
|
53
|
+
return [
|
|
54
|
+
`${ideRoot}/specflow/prompts`,
|
|
55
|
+
`${ideRoot}/specflow/schemas`,
|
|
56
|
+
`${ideRoot}/specflow/templates`,
|
|
57
|
+
`${ideRoot}/specflow/guidance`,
|
|
58
|
+
];
|
|
21
59
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Approval · Database Guidance Router
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
> **Not** an MCP tool
|
|
3
|
+
> **本地技能**: SpecFlow pack 在 `{ide}/specflow/guidance/database/`(见 `skills/guidance-packs.yaml`)。
|
|
4
|
+
> **项目约定**: 写 §4.4 **之前**还须执行 `prompts/approval/project-conventions-guidance.md`(`topic=database`)。
|
|
5
|
+
> **优先级**: 项目约定 + 现网 DDL **>** 本文件 SpecFlow guidance **>** LLM-fallback。
|
|
6
|
+
> **禁止**依赖远程 skill 仓库; **Not** an MCP tool; 禁止「invoke `/mysql` skill」。
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -28,18 +28,38 @@ Scan the **project under review** (change's repo root), not SpecFlow itself. Col
|
|
|
28
28
|
**Multi-hit**: pick primary OLTP store for §4.4 (prefer `mysql` / `postgresql` / `oracle` over
|
|
29
29
|
`redis` / `elasticsearch`). Mention secondary stores in §4.7/架构 if relevant.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Resolve guidance root (path priority)
|
|
34
|
+
|
|
35
|
+
Before reading pack files, resolve `guidanceRoot` for stack `<stack>`:
|
|
36
|
+
|
|
37
|
+
| Priority | Path | When |
|
|
38
|
+
|----------|------|------|
|
|
39
|
+
| 1 | `.cursor/specflow/guidance/database/<stack>/` | Cursor IDE assets present |
|
|
40
|
+
| 1 | `.claude/specflow/guidance/database/<stack>/` | Claude IDE assets present |
|
|
41
|
+
| 1 | `.agents/specflow/guidance/database/<stack>/` | Codex IDE assets present |
|
|
42
|
+
| 2 | `skills/database/<stack>/` | SpecFlow package / self-repo fallback (relative to SpecFlow package root) |
|
|
43
|
+
| 3 | *(missing)* | `LLM-fallback` |
|
|
44
|
+
|
|
45
|
+
If multiple IDE roots exist, prefer the IDE the user is running in; otherwise the first existing path among cursor → claude → agents.
|
|
46
|
+
|
|
47
|
+
**Announce** after detection + resolve:
|
|
32
48
|
|
|
33
49
|
```text
|
|
34
50
|
DB stack for approval: <mysql|postgresql|oracle|redis|elasticsearch|none>
|
|
35
|
-
Guidance:
|
|
51
|
+
Guidance: <.cursor|claude|agents>/specflow/guidance/database/<stack>/SKILL.md
|
|
52
|
+
| skills/database/<stack>/SKILL.md | LLM-fallback
|
|
36
53
|
```
|
|
37
54
|
|
|
38
55
|
---
|
|
39
56
|
|
|
40
|
-
##
|
|
57
|
+
## 3. On hit — load **local** guidance (Read, do not invent, do not fetch)
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
> 先完成 `project-conventions-guidance.md` 的 `topic=database` 解析并 Read 项目约定(若有)。
|
|
60
|
+
> 本表仅加载 **SpecFlow** guidance pack,用于补强 DDL 写法;不得覆盖项目禁令。
|
|
61
|
+
|
|
62
|
+
Base = resolved `guidanceRoot` above. From that directory:
|
|
43
63
|
|
|
44
64
|
| Stack | Must Read | Also Read when §4.4 needs it |
|
|
45
65
|
|-------|-----------|------------------------------|
|
|
@@ -49,31 +69,37 @@ Base: `skills/database/<stack>/`(相对 SpecFlow 仓库根;离线可读)
|
|
|
49
69
|
| `redis` | `SKILL.md` | Only if change is cache/key design — **not** a substitute for relational DDL |
|
|
50
70
|
| `elasticsearch` | `SKILL.md` | Only if change is index/mapping — **not** a substitute for relational DDL |
|
|
51
71
|
|
|
72
|
+
Point to files **from the router entry** (no reference→reference chains).
|
|
73
|
+
|
|
52
74
|
Apply guidance to §4.4 output:
|
|
53
75
|
|
|
54
76
|
1. Prefer engine/charset/collation idioms from the local skill (MySQL: InnoDB + utf8mb4…).
|
|
55
77
|
2. Prefer type/index gotchas from the skill (e.g. DECIMAL for money, no FLOAT amounts).
|
|
56
78
|
3. Keep SpecFlow hard rules (ER, full CREATE TABLE, 本迭代用法, G3/G4) — skill **supplements**, does not replace.
|
|
57
79
|
|
|
58
|
-
Cite in §4.4
|
|
80
|
+
Cite in §4.4 总则 the **actual** paths used:
|
|
81
|
+
- `项目约定: <path…> | 未发现`
|
|
82
|
+
- `DB 技能: .cursor/specflow/guidance/database/mysql` 或 `skills/database/mysql (package fallback)` 或 `LLM-fallback`
|
|
59
83
|
|
|
60
84
|
---
|
|
61
85
|
|
|
62
|
-
##
|
|
86
|
+
## 4. On miss — LLM fallback
|
|
63
87
|
|
|
64
|
-
If `DB stack = none
|
|
88
|
+
If `DB stack = none`, or stack set but **no** guidance root resolves:
|
|
65
89
|
|
|
66
90
|
1. **Do not** force-load mysql/postgresql skills.
|
|
67
91
|
2. Generate §4.4 with SpecFlow rules only (`generate.md` §4.4 + Quality Gates G3/G4).
|
|
68
92
|
3. If design/tasks claim a DB but signals are absent → `WARNING` in §8:
|
|
69
93
|
`design 声称落库但未检测到 DB 栈信号 — 已用 LLM 通用 DDL;建议 refine 标明引擎`.
|
|
70
|
-
4.
|
|
94
|
+
4. If stack detected but guidance files missing → `WARNING` in §8:
|
|
95
|
+
`dbStack=<stack> 但未找到 guidance/database — 已用 LLM-fallback;建议 specflow init --force-assets`.
|
|
96
|
+
5. Pure CLI/config changes → keep `不涉及数据库变更(...)`.
|
|
71
97
|
|
|
72
98
|
---
|
|
73
99
|
|
|
74
|
-
##
|
|
100
|
+
## 5. Why not remote skills / MCP / IDE skills?
|
|
75
101
|
|
|
76
|
-
- **Remote**: approval must work offline
|
|
77
|
-
|
|
78
|
-
- **
|
|
79
|
-
|
|
102
|
+
- **Remote**: approval must work offline; maintain pack in this repo + init sync.
|
|
103
|
+
- **MCP tools**: markdown guidance, not APIs; routing chooses which local files to `Read`.
|
|
104
|
+
- **IDE skills**: packs must **not** appear under `.cursor/skills` / `.claude/skills` / `.agents/skills`
|
|
105
|
+
(noise + false discoverability). They live only under `{ide}/specflow/guidance/`.
|