@deepstorm/cli 0.1.0
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 +72 -0
- package/dist/agents/reef-inspect-figma.md +77 -0
- package/dist/agents/reef-review-backend.md.tmpl +112 -0
- package/dist/agents/reef-review-frontend.md.tmpl +78 -0
- package/dist/agents/reef-review-infra.md +47 -0
- package/dist/agents/reef-review-security.md.tmpl +80 -0
- package/dist/agents/reef-scope-analysis.md +64 -0
- package/dist/build-registry.js +375 -0
- package/dist/cli.js +8581 -0
- package/dist/config-schema.json +133 -0
- package/dist/env-examples/context7.env-example +19 -0
- package/dist/env-examples/feishu-wiki.env-example +16 -0
- package/dist/env-examples/figma.env-example +16 -0
- package/dist/env-examples/github.env-example +20 -0
- package/dist/env-examples/jira.env-example +20 -0
- package/dist/hooks/mcp-hook.sh +77 -0
- package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
- package/dist/hooks/reef-block-dangerous.sh +70 -0
- package/dist/hooks/reef-hooks.json +72 -0
- package/dist/hooks/reef-intent-detect.sh +129 -0
- package/dist/hooks/reef-protect-files.sh +55 -0
- package/dist/hooks/reef-run-tests.sh +84 -0
- package/dist/hooks/reef-scope-check.sh +386 -0
- package/dist/hooks/reef-scope-ci.sh +28 -0
- package/dist/hooks/reef-scope-gate.sh +115 -0
- package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
- package/dist/hooks/reef-scope-setup.sh +204 -0
- package/dist/hooks/reef-scope-split.sh +203 -0
- package/dist/hooks/sweep-hooks.json +14 -0
- package/dist/hooks/sweep-mcp-hook.sh +77 -0
- package/dist/hooks/tide-hooks.json +14 -0
- package/dist/hooks/tide-session-preload.sh +17 -0
- package/dist/mcp/code-hosting/github.json +20 -0
- package/dist/mcp/design-tools/figma.json +19 -0
- package/dist/mcp/docs-reference/context7.json +28 -0
- package/dist/mcp/e2e-testing/playwright.json +13 -0
- package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
- package/dist/mcp/project-management/jira.json +27 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
- package/dist/registry.json +818 -0
- package/dist/skills/atoll-ops/SKILL.md +46 -0
- package/dist/skills/reef-commit/SKILL.md +127 -0
- package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
- package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
- package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
- package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
- package/dist/skills/reef-harden/EXAMPLES.md +89 -0
- package/dist/skills/reef-harden/SKILL.md +136 -0
- package/dist/skills/reef-pr/SKILL.md +97 -0
- package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
- package/dist/skills/reef-scope/SKILL.md +134 -0
- package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
- package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
- package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
- package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
- package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
- package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
- package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
- package/dist/skills/reef-testcase/SKILL.md +138 -0
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
- package/dist/skills/sweep-init/SKILL.md +286 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
- package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
- package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
- package/dist/skills/sweep-run/SKILL.md +437 -0
- package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
- package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
- package/dist/skills/tide-discuss/references/checklists.md +88 -0
- package/dist/skills/tide-discuss/references/data-format.md +237 -0
- package/dist/skills/tide-discuss/references/prd-template.md +134 -0
- package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
- package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
- package/package.json +38 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Spec Compiler — 从 flow-parser 的解析结果生成 Playwright .spec.ts
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node spec-compiler.mjs <path-to-flow.md> # CLI: parse + compile, write .flow.spec.ts
|
|
8
|
+
* import { compile } from './spec-compiler.mjs' # API: compile from parsed result
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
|
|
12
|
+
import { basename, dirname, resolve } from 'node:path';
|
|
13
|
+
import { parse } from './flow-parser.mjs';
|
|
14
|
+
|
|
15
|
+
// ── Operation to Playwright API mapping ───────────────────────────
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Infer a Playwright action from a step description.
|
|
19
|
+
* Returns { comment, action, args }.
|
|
20
|
+
*/
|
|
21
|
+
function inferAction(description, index) {
|
|
22
|
+
const desc = description.trim();
|
|
23
|
+
|
|
24
|
+
// Navigation
|
|
25
|
+
if (/导航|打开|goto/i.test(desc)) {
|
|
26
|
+
const urlMatch = desc.match(/`([^`]+)`/);
|
|
27
|
+
const url = urlMatch ? urlMatch[1] : 'process.env.BASE_URL || \'/\'';
|
|
28
|
+
return {
|
|
29
|
+
comment: desc,
|
|
30
|
+
action: 'page.goto',
|
|
31
|
+
args: [url],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Click
|
|
36
|
+
if (/点击|单击|按下|click/i.test(desc)) {
|
|
37
|
+
const targetMatch = desc.match(/[""「」](.+?)[""」』]/);
|
|
38
|
+
const selector = targetMatch ? `text="${targetMatch[1]}"` : inferSelector(desc);
|
|
39
|
+
return {
|
|
40
|
+
comment: desc,
|
|
41
|
+
action: 'page.click',
|
|
42
|
+
args: [selector],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fill / type
|
|
47
|
+
if (/输入|键入|填写|fill|type/i.test(desc)) {
|
|
48
|
+
const valueMatch = desc.match(/`([^`]+)`/);
|
|
49
|
+
const value = valueMatch ? valueMatch[1] : '';
|
|
50
|
+
const targetMatch = desc.match(/[""「」](.+?)[""」』]/);
|
|
51
|
+
const selector = targetMatch ? `[placeholder="${targetMatch[1]}" i]` : `input${index > 0 ? `:nth-child(${index})` : ''}`;
|
|
52
|
+
return {
|
|
53
|
+
comment: desc,
|
|
54
|
+
action: 'page.fill',
|
|
55
|
+
args: [selector, value],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Select
|
|
60
|
+
if (/选择|select|选中/i.test(desc)) {
|
|
61
|
+
return {
|
|
62
|
+
comment: desc,
|
|
63
|
+
action: 'page.selectOption',
|
|
64
|
+
args: ['select', ''],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Wait
|
|
69
|
+
if (/等待|wait|暂停/i.test(desc)) {
|
|
70
|
+
return {
|
|
71
|
+
comment: desc,
|
|
72
|
+
action: 'page.waitForTimeout',
|
|
73
|
+
args: [1000],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Default: log as unsupported comment
|
|
78
|
+
return {
|
|
79
|
+
comment: desc,
|
|
80
|
+
action: null,
|
|
81
|
+
args: [],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Infer a CSS selector from a description.
|
|
87
|
+
* Heuristic: extract quoted text or noun phrases.
|
|
88
|
+
*/
|
|
89
|
+
function inferSelector(desc) {
|
|
90
|
+
// Try to match quoted text first
|
|
91
|
+
const quoted = desc.match(/[""「」『』](.+?)[""」』『']/);
|
|
92
|
+
if (quoted) return `text="${quoted[1]}"`;
|
|
93
|
+
|
|
94
|
+
// Try to match role-like phrases
|
|
95
|
+
if (/按钮/.test(desc)) return 'button';
|
|
96
|
+
if (/链接/.test(desc)) return 'a';
|
|
97
|
+
if (/输入框|输入|文本框/.test(desc)) return 'input';
|
|
98
|
+
if (/菜单|下拉/.test(desc)) return 'select';
|
|
99
|
+
if (/复选框|checkbox/.test(desc)) return '[type="checkbox"]';
|
|
100
|
+
|
|
101
|
+
return 'body';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Validation to Playwright assertion mapping ────────────────────
|
|
105
|
+
|
|
106
|
+
function inferAssertion(validation) {
|
|
107
|
+
const v = validation.trim();
|
|
108
|
+
|
|
109
|
+
if (/标题/.test(v) && /包含|包含/.test(v)) {
|
|
110
|
+
const title = v.match(/[""「」](.+?)[""」』]/);
|
|
111
|
+
const expected = title ? title[1] : '';
|
|
112
|
+
return { assertion: 'toHaveTitle', args: [expected || /.*/], raw: v };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (/URL|url|跳转|跳转到|路径/.test(v)) {
|
|
116
|
+
const url = v.match(/`([^`]+)`/);
|
|
117
|
+
const expected = url ? url[1] : '';
|
|
118
|
+
return { assertion: 'toHaveURL', args: [expected || /.*/], raw: v };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (/可见|显示|出现|元素/.test(v)) {
|
|
122
|
+
const target = v.match(/[""「」](.+?)[""」』]/);
|
|
123
|
+
const selector = target ? `text="${target[1]}"` : inferSelector(v);
|
|
124
|
+
return { assertion: 'toBeVisible', args: [selector], raw: v };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (/文本|内容|文字|包含|值为/.test(v)) {
|
|
128
|
+
const text = v.match(/[""「」『』](.+?)[""」』『']/);
|
|
129
|
+
const expected = text ? text[1] : '';
|
|
130
|
+
const selector = inferSelector(v);
|
|
131
|
+
return { assertion: 'toHaveText', args: [selector, expected], raw: v };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (/不跳转|不跳|相同|不变/.test(v)) {
|
|
135
|
+
return { assertion: 'toHaveURL', args: [/.*/], raw: v };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Default: generic visibility check
|
|
139
|
+
const selector = inferSelector(v);
|
|
140
|
+
return { assertion: 'toBeVisible', args: [selector], raw: v };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Code generation ───────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate Playwright .spec.ts code from parsed .flow.md result.
|
|
147
|
+
* @param {import('./flow-parser.mjs').FlowParseResult} parsed
|
|
148
|
+
* @returns {string} TypeScript test file content
|
|
149
|
+
*/
|
|
150
|
+
export function compile(parsed) {
|
|
151
|
+
const lines = [];
|
|
152
|
+
const indent = ' ';
|
|
153
|
+
|
|
154
|
+
lines.push("import { test, expect } from '@playwright/test';");
|
|
155
|
+
lines.push('');
|
|
156
|
+
|
|
157
|
+
for (const flow of parsed.flows) {
|
|
158
|
+
lines.push(`${indent}test('${escapeStr(parsed.featureName)} - ${escapeStr(flow.id + ' - ' + flow.title)}', async ({ page }) => {`);
|
|
159
|
+
|
|
160
|
+
// Preconditions as comments
|
|
161
|
+
if (flow.preconditions) {
|
|
162
|
+
lines.push(`${indent}${indent}// Preconditions:`);
|
|
163
|
+
for (const line of flow.preconditions.split('\n')) {
|
|
164
|
+
lines.push(`${indent}${indent}// ${line}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// AI_REQUIRED marker — skip entire flow if any step needs AI
|
|
169
|
+
const hasAiRequired = flow.steps.some(s =>
|
|
170
|
+
s.description.includes('AI_REQUIRED') || s.description.includes('<!-- AI_REQUIRED -->')
|
|
171
|
+
);
|
|
172
|
+
if (hasAiRequired) {
|
|
173
|
+
lines.push(`${indent}${indent}// AI_REQUIRED — cannot execute natively, AI will handle`);
|
|
174
|
+
lines.push(`${indent}${indent}test.skip();`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const step of flow.steps) {
|
|
178
|
+
const desc = step.description;
|
|
179
|
+
|
|
180
|
+
lines.push(`${indent}${indent}// Step ${step.order}: ${desc.replace(/`/g, '')}`);
|
|
181
|
+
|
|
182
|
+
const action = inferAction(desc, step.order);
|
|
183
|
+
if (action.action) {
|
|
184
|
+
const args = action.args.map(a => typeof a === 'string' ? `'${escapeStr(a)}'` : a).join(', ');
|
|
185
|
+
lines.push(`${indent}${indent}await ${action.action}(${args});`);
|
|
186
|
+
} else {
|
|
187
|
+
// Cannot map to Playwright API — keep as TODO comment
|
|
188
|
+
lines.push(`${indent}${indent}// UNSUPPORTED: ${desc}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Validation points
|
|
192
|
+
for (const validation of step.validations) {
|
|
193
|
+
const va = inferAssertion(validation);
|
|
194
|
+
lines.push(`${indent}${indent}// ✅ ${va.raw}`);
|
|
195
|
+
|
|
196
|
+
if (va.assertion === 'toBeVisible') {
|
|
197
|
+
lines.push(`${indent}${indent}await expect(page.locator('${escapeStr(va.args[0])}')).toBeVisible();`);
|
|
198
|
+
} else if (va.assertion === 'toHaveText') {
|
|
199
|
+
lines.push(`${indent}${indent}await expect(page.locator('${escapeStr(va.args[0])}')).toHaveText('${escapeStr(va.args[1])}');`);
|
|
200
|
+
} else if (va.assertion === 'toHaveTitle') {
|
|
201
|
+
lines.push(`${indent}${indent}await expect(page).toHaveTitle(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`);
|
|
202
|
+
} else if (va.assertion === 'toHaveURL') {
|
|
203
|
+
lines.push(`${indent}${indent}await expect(page).toHaveURL(${typeof va.args[0] === 'string' ? `'${escapeStr(va.args[0])}'` : va.args[0]});`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
lines.push('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Close test block
|
|
211
|
+
lines.push(`${indent}});`);
|
|
212
|
+
lines.push('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Derive .flow.spec.ts path from .flow.md path.
|
|
220
|
+
*/
|
|
221
|
+
export function outputPath(flowMdPath) {
|
|
222
|
+
if (flowMdPath.endsWith('.flow.md')) {
|
|
223
|
+
return flowMdPath.slice(0, -7) + 'flow.spec.ts';
|
|
224
|
+
}
|
|
225
|
+
return flowMdPath.replace(/\.md$/, '.flow.spec.ts');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse .flow.md and compile to .spec.ts file.
|
|
230
|
+
* Returns the path to the generated .spec.ts file.
|
|
231
|
+
*/
|
|
232
|
+
export function compileFromFile(flowMdPath) {
|
|
233
|
+
if (!existsSync(flowMdPath)) {
|
|
234
|
+
throw new Error(`File not found: ${flowMdPath}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const flowContent = readFileSync(flowMdPath, 'utf-8');
|
|
238
|
+
const parsed = parse(flowContent);
|
|
239
|
+
const specContent = compile(parsed);
|
|
240
|
+
const outPath = outputPath(flowMdPath);
|
|
241
|
+
|
|
242
|
+
writeFileSync(outPath, specContent, 'utf-8');
|
|
243
|
+
return resolve(outPath);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Check if the .spec.ts file is up-to-date with the .flow.md file.
|
|
248
|
+
* Returns true if .spec.ts exists and is newer than .flow.md.
|
|
249
|
+
*/
|
|
250
|
+
export function isUpToDate(flowMdPath) {
|
|
251
|
+
const specPath = outputPath(flowMdPath);
|
|
252
|
+
if (!existsSync(specPath)) return false;
|
|
253
|
+
try {
|
|
254
|
+
const flowTime = statSync(flowMdPath).mtimeMs;
|
|
255
|
+
const specTime = statSync(specPath).mtimeMs;
|
|
256
|
+
return specTime >= flowTime;
|
|
257
|
+
} catch {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Compile all .flow.md files in a directory tree.
|
|
264
|
+
* Returns array of { flowMdPath, specPath, skipped } objects.
|
|
265
|
+
*/
|
|
266
|
+
export function compileAllFromDir(dirPath) {
|
|
267
|
+
const results = [];
|
|
268
|
+
// Traverse on CLI — SKILL.md calls node with specific paths
|
|
269
|
+
// This function is reserved for future batch compilation
|
|
270
|
+
throw new Error('compileAllFromDir not implemented — call compileFromFile for each .flow.md');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
function escapeStr(s) {
|
|
276
|
+
if (typeof s !== 'string') return '';
|
|
277
|
+
return s.replace(/'/g, "\\'").replace(/\n/g, '\\n');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ── CLI entry point ───────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
if (process.argv[1] === import.meta.filename) {
|
|
283
|
+
const filePath = process.argv[2];
|
|
284
|
+
if (!filePath) {
|
|
285
|
+
console.error('Usage: node spec-compiler.mjs <path-to-flow.md>');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 自动 freshness 检查:如果 .flow.spec.ts 已是最新,跳过编译
|
|
290
|
+
if (isUpToDate(filePath)) {
|
|
291
|
+
const specPath = outputPath(filePath);
|
|
292
|
+
console.log(`⏭ SKIP: ${specPath} is up-to-date`);
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const outPath = compileFromFile(filePath);
|
|
298
|
+
console.log(`✅ Generated: ${outPath}`);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
console.error(`Error: ${err.message}`);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
}
|