@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.
Files changed (148) hide show
  1. package/README.md +72 -0
  2. package/dist/agents/reef-inspect-figma.md +77 -0
  3. package/dist/agents/reef-review-backend.md.tmpl +112 -0
  4. package/dist/agents/reef-review-frontend.md.tmpl +78 -0
  5. package/dist/agents/reef-review-infra.md +47 -0
  6. package/dist/agents/reef-review-security.md.tmpl +80 -0
  7. package/dist/agents/reef-scope-analysis.md +64 -0
  8. package/dist/build-registry.js +375 -0
  9. package/dist/cli.js +8581 -0
  10. package/dist/config-schema.json +133 -0
  11. package/dist/env-examples/context7.env-example +19 -0
  12. package/dist/env-examples/feishu-wiki.env-example +16 -0
  13. package/dist/env-examples/figma.env-example +16 -0
  14. package/dist/env-examples/github.env-example +20 -0
  15. package/dist/env-examples/jira.env-example +20 -0
  16. package/dist/hooks/mcp-hook.sh +77 -0
  17. package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
  18. package/dist/hooks/reef-block-dangerous.sh +70 -0
  19. package/dist/hooks/reef-hooks.json +72 -0
  20. package/dist/hooks/reef-intent-detect.sh +129 -0
  21. package/dist/hooks/reef-protect-files.sh +55 -0
  22. package/dist/hooks/reef-run-tests.sh +84 -0
  23. package/dist/hooks/reef-scope-check.sh +386 -0
  24. package/dist/hooks/reef-scope-ci.sh +28 -0
  25. package/dist/hooks/reef-scope-gate.sh +115 -0
  26. package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
  27. package/dist/hooks/reef-scope-setup.sh +204 -0
  28. package/dist/hooks/reef-scope-split.sh +203 -0
  29. package/dist/hooks/sweep-hooks.json +14 -0
  30. package/dist/hooks/sweep-mcp-hook.sh +77 -0
  31. package/dist/hooks/tide-hooks.json +14 -0
  32. package/dist/hooks/tide-session-preload.sh +17 -0
  33. package/dist/mcp/code-hosting/github.json +20 -0
  34. package/dist/mcp/design-tools/figma.json +19 -0
  35. package/dist/mcp/docs-reference/context7.json +28 -0
  36. package/dist/mcp/e2e-testing/playwright.json +13 -0
  37. package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
  38. package/dist/mcp/project-management/jira.json +27 -0
  39. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
  40. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
  41. package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
  42. package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
  43. package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
  44. package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
  45. package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
  46. package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
  47. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
  48. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
  49. package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
  50. package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
  51. package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
  52. package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
  53. package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
  54. package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
  55. package/dist/registry.json +818 -0
  56. package/dist/skills/atoll-ops/SKILL.md +46 -0
  57. package/dist/skills/reef-commit/SKILL.md +127 -0
  58. package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
  59. package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
  60. package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
  61. package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
  62. package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
  63. package/dist/skills/reef-harden/EXAMPLES.md +89 -0
  64. package/dist/skills/reef-harden/SKILL.md +136 -0
  65. package/dist/skills/reef-pr/SKILL.md +97 -0
  66. package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
  67. package/dist/skills/reef-scope/SKILL.md +134 -0
  68. package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
  69. package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
  70. package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
  71. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
  72. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
  73. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
  74. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
  75. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
  76. package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
  77. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
  78. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
  79. package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
  80. package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
  81. package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
  82. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
  83. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
  84. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
  85. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
  86. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
  87. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
  88. package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
  89. package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
  90. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
  91. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
  92. package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
  93. package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
  94. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
  95. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
  96. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
  97. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
  98. package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
  99. package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
  100. package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
  101. package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
  102. package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
  103. package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
  104. package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
  105. package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
  106. package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
  107. package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
  108. package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
  109. package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
  110. package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
  111. package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
  112. package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
  113. package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
  114. package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
  115. package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
  116. package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
  117. package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
  118. package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
  119. package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
  120. package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
  121. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
  122. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
  123. package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
  124. package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
  125. package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
  126. package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
  127. package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
  128. package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
  129. package/dist/skills/reef-testcase/SKILL.md +138 -0
  130. package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
  131. package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
  132. package/dist/skills/sweep-init/SKILL.md +286 -0
  133. package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
  134. package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
  135. package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
  136. package/dist/skills/sweep-run/SKILL.md +437 -0
  137. package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
  138. package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
  139. package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
  140. package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
  141. package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
  142. package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
  143. package/dist/skills/tide-discuss/references/checklists.md +88 -0
  144. package/dist/skills/tide-discuss/references/data-format.md +237 -0
  145. package/dist/skills/tide-discuss/references/prd-template.md +134 -0
  146. package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
  147. package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
  148. 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
+ }