cc-devflow 1.0.1

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 (277) hide show
  1. package/.claude/CLAUDE.md +83 -0
  2. package/.claude/agents/architecture-designer.md +443 -0
  3. package/.claude/agents/bug-analyzer.md +382 -0
  4. package/.claude/agents/checklist-agent.md +175 -0
  5. package/.claude/agents/clarify-analyst.md +50 -0
  6. package/.claude/agents/code-reviewer.md +71 -0
  7. package/.claude/agents/codex-analyzer.md +39 -0
  8. package/.claude/agents/compatibility-checker.md +580 -0
  9. package/.claude/agents/consistency-checker.md +532 -0
  10. package/.claude/agents/impact-analyzer.md +441 -0
  11. package/.claude/agents/planner.md +230 -0
  12. package/.claude/agents/prd-writer.md +320 -0
  13. package/.claude/agents/project-guidelines-generator.md +1329 -0
  14. package/.claude/agents/qa-tester.md +313 -0
  15. package/.claude/agents/release-manager.md +295 -0
  16. package/.claude/agents/security-reviewer.md +314 -0
  17. package/.claude/agents/style-guide-generator.md +458 -0
  18. package/.claude/agents/tech-architect.md +516 -0
  19. package/.claude/agents/ui-designer.md +485 -0
  20. package/.claude/commands/code-review-high.md +58 -0
  21. package/.claude/commands/core-architecture.md +429 -0
  22. package/.claude/commands/core-guidelines.md +486 -0
  23. package/.claude/commands/core-roadmap.md +439 -0
  24. package/.claude/commands/core-style.md +293 -0
  25. package/.claude/commands/flow-archive.md +245 -0
  26. package/.claude/commands/flow-checklist.md +260 -0
  27. package/.claude/commands/flow-clarify.md +136 -0
  28. package/.claude/commands/flow-constitution.md +82 -0
  29. package/.claude/commands/flow-dev.md +134 -0
  30. package/.claude/commands/flow-epic.md +150 -0
  31. package/.claude/commands/flow-fix.md +104 -0
  32. package/.claude/commands/flow-ideate.md +214 -0
  33. package/.claude/commands/flow-init.md +313 -0
  34. package/.claude/commands/flow-new.md +394 -0
  35. package/.claude/commands/flow-prd.md +131 -0
  36. package/.claude/commands/flow-qa.md +93 -0
  37. package/.claude/commands/flow-release.md +92 -0
  38. package/.claude/commands/flow-restart.md +98 -0
  39. package/.claude/commands/flow-status.md +64 -0
  40. package/.claude/commands/flow-tech.md +142 -0
  41. package/.claude/commands/flow-ui.md +189 -0
  42. package/.claude/commands/flow-update.md +111 -0
  43. package/.claude/commands/flow-upgrade.md +115 -0
  44. package/.claude/commands/flow-verify.md +96 -0
  45. package/.claude/commands/problem-analyzer.md +60 -0
  46. package/.claude/config/quality-rules.yml +161 -0
  47. package/.claude/docs/SPEC_KIT_CONSTITUTION_ANALYSIS.md +426 -0
  48. package/.claude/docs/design/consistency-conflict-detection-algorithms.md +658 -0
  49. package/.claude/docs/design/intent-driven-input-design.md +380 -0
  50. package/.claude/docs/design/prd-version-management-design.md +437 -0
  51. package/.claude/docs/guides/INIT_TROUBLESHOOTING.md +117 -0
  52. package/.claude/docs/guides/NEW_TROUBLESHOOTING.md +151 -0
  53. package/.claude/docs/guides/ROADMAP_TROUBLESHOOTING.md +188 -0
  54. package/.claude/docs/guides/TASK_COMPLETION_MARKING.md +338 -0
  55. package/.claude/docs/templates/ARCHITECTURE_TEMPLATE.md +633 -0
  56. package/.claude/docs/templates/BACKLOG_TEMPLATE.md +261 -0
  57. package/.claude/docs/templates/CHECKLIST_TEMPLATE.md +52 -0
  58. package/.claude/docs/templates/CLARIFICATION_REPORT_TEMPLATE.md +206 -0
  59. package/.claude/docs/templates/CODE_REVIEW_TEMPLATE.md +71 -0
  60. package/.claude/docs/templates/EPIC_TEMPLATE.md +805 -0
  61. package/.claude/docs/templates/INIT_FLOW_TEMPLATE.md +213 -0
  62. package/.claude/docs/templates/INTENT_CLARIFICATION_TEMPLATE.md +57 -0
  63. package/.claude/docs/templates/NEW_ORCHESTRATION_TEMPLATE.md +148 -0
  64. package/.claude/docs/templates/PRD_TEMPLATE.md +562 -0
  65. package/.claude/docs/templates/RESEARCH_TEMPLATE.md +276 -0
  66. package/.claude/docs/templates/REVIEW-HIGH.md +57 -0
  67. package/.claude/docs/templates/ROADMAP_DIALOGUE_TEMPLATE.md +198 -0
  68. package/.claude/docs/templates/ROADMAP_TEMPLATE.md +310 -0
  69. package/.claude/docs/templates/STYLE_TEMPLATE.md +1266 -0
  70. package/.claude/docs/templates/TASKS_TEMPLATE.md +523 -0
  71. package/.claude/docs/templates/TECH_DESIGN_TEMPLATE.md +1019 -0
  72. package/.claude/docs/templates/UI_PROTOTYPE_TEMPLATE.md +1436 -0
  73. package/.claude/guides/agent-guides/agent-coordination-guide.md +459 -0
  74. package/.claude/guides/project-guidelines-system.md +463 -0
  75. package/.claude/guides/technical-guides/datetime-handling-guide.md +563 -0
  76. package/.claude/guides/technical-guides/git-github-guide.md +642 -0
  77. package/.claude/guides/technical-guides/test-execution-guide.md +618 -0
  78. package/.claude/guides/workflow-guides/bug-fix-orchestrator.md +217 -0
  79. package/.claude/guides/workflow-guides/flow-orchestrator.md +282 -0
  80. package/.claude/hooks/checklist-gate.js +397 -0
  81. package/.claude/hooks/error-handling-reminder.sh +12 -0
  82. package/.claude/hooks/error-handling-reminder.ts +459 -0
  83. package/.claude/hooks/post-tool-use-tracker.sh +280 -0
  84. package/.claude/hooks/pre-tool-use-guardrail.sh +36 -0
  85. package/.claude/hooks/pre-tool-use-guardrail.ts +342 -0
  86. package/.claude/hooks/skill-activation-prompt.sh +36 -0
  87. package/.claude/hooks/skill-activation-prompt.ts +214 -0
  88. package/.claude/hooks/state/skills-used-test-guard.json +3 -0
  89. package/.claude/rules/devflow-conventions.md +305 -0
  90. package/.claude/rules/project-constitution.md +748 -0
  91. package/.claude/schemas/constitution.schema.json +43 -0
  92. package/.claude/scripts/analyze-upgrade-impact.sh +200 -0
  93. package/.claude/scripts/archive-requirement.sh +351 -0
  94. package/.claude/scripts/calculate-checklist-completion.sh +243 -0
  95. package/.claude/scripts/calculate-quarter.sh +206 -0
  96. package/.claude/scripts/check-dependencies.sh +409 -0
  97. package/.claude/scripts/check-prerequisites.sh +232 -0
  98. package/.claude/scripts/check-task-status.sh +264 -0
  99. package/.claude/scripts/checklist-errors.sh +131 -0
  100. package/.claude/scripts/common.sh +570 -0
  101. package/.claude/scripts/consolidate-research.sh +182 -0
  102. package/.claude/scripts/create-requirement.sh +426 -0
  103. package/.claude/scripts/export-contracts.sh +117 -0
  104. package/.claude/scripts/extract-data-model.sh +78 -0
  105. package/.claude/scripts/generate-clarification-questions.sh +377 -0
  106. package/.claude/scripts/generate-clarification-report.sh +463 -0
  107. package/.claude/scripts/generate-quickstart.sh +146 -0
  108. package/.claude/scripts/generate-research-tasks.sh +157 -0
  109. package/.claude/scripts/generate-status-report.sh +523 -0
  110. package/.claude/scripts/generate-tech-analysis.sh +46 -0
  111. package/.claude/scripts/locate-requirement-in-roadmap.sh +233 -0
  112. package/.claude/scripts/manage-constitution.sh +602 -0
  113. package/.claude/scripts/mark-task-complete.sh +198 -0
  114. package/.claude/scripts/populate-research-tasks.sh +259 -0
  115. package/.claude/scripts/recover-workflow.sh +460 -0
  116. package/.claude/scripts/run-clarify-scan.sh +601 -0
  117. package/.claude/scripts/run-high-review.sh +62 -0
  118. package/.claude/scripts/run-problem-analysis.sh +68 -0
  119. package/.claude/scripts/setup-epic.sh +173 -0
  120. package/.claude/scripts/sync-roadmap-progress.sh +300 -0
  121. package/.claude/scripts/sync-task-marks.sh +199 -0
  122. package/.claude/scripts/test-clarify-scan.sh +515 -0
  123. package/.claude/scripts/update-agent-context.sh +806 -0
  124. package/.claude/scripts/validate-constitution.sh +567 -0
  125. package/.claude/scripts/validate-hooks.sh +487 -0
  126. package/.claude/scripts/validate-research.sh +332 -0
  127. package/.claude/scripts/validate-scope-boundary.sh +493 -0
  128. package/.claude/scripts/verify-setup.sh +37 -0
  129. package/.claude/settings.json +76 -0
  130. package/.claude/skills/_reference-implementations/README.md +96 -0
  131. package/.claude/skills/_reference-implementations/backend-express-prisma/SKILL.md +302 -0
  132. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/architecture-overview.md +451 -0
  133. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/async-and-errors.md +307 -0
  134. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/complete-examples.md +638 -0
  135. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/configuration.md +275 -0
  136. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/database-patterns.md +224 -0
  137. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/middleware-guide.md +213 -0
  138. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/routing-and-controllers.md +756 -0
  139. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/sentry-and-monitoring.md +336 -0
  140. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/services-and-repositories.md +789 -0
  141. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/testing-guide.md +235 -0
  142. package/.claude/skills/_reference-implementations/backend-express-prisma/resources/validation-patterns.md +754 -0
  143. package/.claude/skills/_reference-implementations/frontend-react-mui/SKILL.md +399 -0
  144. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/common-patterns.md +331 -0
  145. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/complete-examples.md +872 -0
  146. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/component-patterns.md +502 -0
  147. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/data-fetching.md +767 -0
  148. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/file-organization.md +502 -0
  149. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/loading-and-error-states.md +501 -0
  150. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/performance.md +406 -0
  151. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/routing-guide.md +364 -0
  152. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/styling-guide.md +428 -0
  153. package/.claude/skills/_reference-implementations/frontend-react-mui/resources/typescript-standards.md +418 -0
  154. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +229 -0
  155. package/.claude/skills/constitution-guardian/SKILL.md +306 -0
  156. package/.claude/skills/devflow-constitution-quick-ref/SKILL.md +374 -0
  157. package/.claude/skills/devflow-file-standards/SKILL.md +353 -0
  158. package/.claude/skills/devflow-tdd-enforcer/SKILL.md +192 -0
  159. package/.claude/skills/skill-developer/ADVANCED.md +197 -0
  160. package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +306 -0
  161. package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +152 -0
  162. package/.claude/skills/skill-developer/SKILL.md +426 -0
  163. package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +315 -0
  164. package/.claude/skills/skill-developer/TRIGGER_TYPES.md +305 -0
  165. package/.claude/skills/skill-developer/TROUBLESHOOTING.md +514 -0
  166. package/.claude/skills/skill-rules.json +213 -0
  167. package/.claude/tests/README.md +300 -0
  168. package/.claude/tests/TODO.md +69 -0
  169. package/.claude/tests/__pycache__/test_analyze_upgrade_impact.cpython-311-pytest-7.2.2.pyc +0 -0
  170. package/.claude/tests/__pycache__/test_consolidate_research.cpython-311-pytest-7.2.2.pyc +0 -0
  171. package/.claude/tests/__pycache__/test_export_contracts.cpython-311-pytest-7.2.2.pyc +0 -0
  172. package/.claude/tests/__pycache__/test_extract_data_model.cpython-311-pytest-7.2.2.pyc +0 -0
  173. package/.claude/tests/__pycache__/test_generate_quickstart.cpython-311-pytest-7.2.2.pyc +0 -0
  174. package/.claude/tests/__pycache__/test_generate_research_tasks.cpython-311-pytest-7.2.2.pyc +0 -0
  175. package/.claude/tests/constitution/run_all_constitution_tests.sh +111 -0
  176. package/.claude/tests/constitution/test_agent_assignment.sh +207 -0
  177. package/.claude/tests/constitution/test_article_coverage.sh +201 -0
  178. package/.claude/tests/constitution/test_template_completeness.sh +150 -0
  179. package/.claude/tests/constitution/test_version_consistency.sh +120 -0
  180. package/.claude/tests/fixtures/spec_delta_full.md +16 -0
  181. package/.claude/tests/fixtures/tasks_progress_sample.md +5 -0
  182. package/.claude/tests/run-all-tests.sh +229 -0
  183. package/.claude/tests/scripts/run.sh +30 -0
  184. package/.claude/tests/scripts/test-framework.sh +128 -0
  185. package/.claude/tests/scripts/test_check_prerequisites.sh +511 -0
  186. package/.claude/tests/scripts/test_check_prerequisites.sh.bak +504 -0
  187. package/.claude/tests/scripts/test_check_prerequisites.sh.bak2 +505 -0
  188. package/.claude/tests/scripts/test_check_prerequisites.sh.bak3 +506 -0
  189. package/.claude/tests/scripts/test_check_prerequisites.sh.bak4 +507 -0
  190. package/.claude/tests/scripts/test_check_prerequisites.sh.bak5 +508 -0
  191. package/.claude/tests/scripts/test_check_task_status.sh +499 -0
  192. package/.claude/tests/scripts/test_common.sh +244 -0
  193. package/.claude/tests/scripts/test_generate_status_report.sh +71 -0
  194. package/.claude/tests/scripts/test_mark_task_complete.sh +441 -0
  195. package/.claude/tests/scripts/test_mark_task_complete.sh.backup +410 -0
  196. package/.claude/tests/scripts/test_recover_workflow.sh +304 -0
  197. package/.claude/tests/scripts/test_setup_epic.sh +437 -0
  198. package/.claude/tests/scripts/test_sync_task_marks.sh +196 -0
  199. package/.claude/tests/scripts/test_validate_constitution.sh +74 -0
  200. package/.claude/tests/scripts/test_validate_research.sh +462 -0
  201. package/.claude/tests/slugify.bats +82 -0
  202. package/.claude/tests/test-framework.sh +732 -0
  203. package/.claude/tests/test_analyze_upgrade_impact.py +34 -0
  204. package/.claude/tests/test_consolidate_research.py +48 -0
  205. package/.claude/tests/test_export_contracts.py +43 -0
  206. package/.claude/tests/test_extract_data_model.py +33 -0
  207. package/.claude/tests/test_generate_quickstart.py +50 -0
  208. package/.claude/tests/test_generate_research_tasks.py +52 -0
  209. package/.claude/tsc-cache/6e64f818-6398-49ca-8623-581a9af85c44/edited-files.log +1 -0
  210. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +1 -0
  211. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/edited-files.log +1 -0
  212. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +1 -0
  213. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/edited-files.log +1 -0
  214. package/CHANGELOG.md +507 -0
  215. package/LICENSE +21 -0
  216. package/README.md +534 -0
  217. package/README.zh-CN.md +530 -0
  218. package/bin/adapt.js +240 -0
  219. package/bin/cc-devflow-cli.js +185 -0
  220. package/bin/cc-devflow.js +78 -0
  221. package/config/adapters.yml +5 -0
  222. package/config/schema/adapters.schema.json +44 -0
  223. package/docs/CLAUDE.md +26 -0
  224. package/docs/commands/README.md +61 -0
  225. package/docs/commands/README.zh-CN.md +55 -0
  226. package/docs/commands/core-roadmap.md +106 -0
  227. package/docs/commands/core-roadmap.zh-CN.md +102 -0
  228. package/docs/commands/core-style.md +405 -0
  229. package/docs/commands/core-style.zh-CN.md +405 -0
  230. package/docs/commands/flow-init.md +134 -0
  231. package/docs/commands/flow-init.zh-CN.md +163 -0
  232. package/docs/commands/flow-new.md +274 -0
  233. package/docs/commands/flow-new.zh-CN.md +270 -0
  234. package/docs/guides/getting-started.md +204 -0
  235. package/docs/guides/getting-started.zh-CN.md +152 -0
  236. package/lib/adapters/adapter-interface.js +57 -0
  237. package/lib/adapters/claude-adapter.js +74 -0
  238. package/lib/adapters/codex-adapter.js +40 -0
  239. package/lib/adapters/config-validator.js +68 -0
  240. package/lib/adapters/logger.js +42 -0
  241. package/lib/adapters/registry.js +153 -0
  242. package/lib/compiler/CLAUDE.md +92 -0
  243. package/lib/compiler/__tests__/drift.test.js +215 -0
  244. package/lib/compiler/__tests__/errors.test.js +184 -0
  245. package/lib/compiler/__tests__/incremental.test.js +174 -0
  246. package/lib/compiler/__tests__/integration.test.js +174 -0
  247. package/lib/compiler/__tests__/manifest.test.js +233 -0
  248. package/lib/compiler/__tests__/parser.test.js +456 -0
  249. package/lib/compiler/__tests__/schemas.test.js +301 -0
  250. package/lib/compiler/__tests__/skills-registry.test.js +125 -0
  251. package/lib/compiler/__tests__/transformer.test.js +286 -0
  252. package/lib/compiler/emitters/antigravity-emitter.js +171 -0
  253. package/lib/compiler/emitters/base-emitter.js +73 -0
  254. package/lib/compiler/emitters/codex-emitter.js +52 -0
  255. package/lib/compiler/emitters/cursor-emitter.js +31 -0
  256. package/lib/compiler/emitters/index.js +50 -0
  257. package/lib/compiler/emitters/qwen-emitter.js +39 -0
  258. package/lib/compiler/errors.js +119 -0
  259. package/lib/compiler/index.js +256 -0
  260. package/lib/compiler/manifest.js +242 -0
  261. package/lib/compiler/parser.js +258 -0
  262. package/lib/compiler/platforms.js +113 -0
  263. package/lib/compiler/resource-copier.js +320 -0
  264. package/lib/compiler/rules-emitters/__tests__/antigravity-rules-emitter.test.js +191 -0
  265. package/lib/compiler/rules-emitters/__tests__/codex-rules-emitter.test.js +109 -0
  266. package/lib/compiler/rules-emitters/__tests__/cursor-rules-emitter.test.js +123 -0
  267. package/lib/compiler/rules-emitters/__tests__/qwen-rules-emitter.test.js +123 -0
  268. package/lib/compiler/rules-emitters/antigravity-rules-emitter.js +253 -0
  269. package/lib/compiler/rules-emitters/base-rules-emitter.js +83 -0
  270. package/lib/compiler/rules-emitters/codex-rules-emitter.js +116 -0
  271. package/lib/compiler/rules-emitters/cursor-rules-emitter.js +98 -0
  272. package/lib/compiler/rules-emitters/index.js +71 -0
  273. package/lib/compiler/rules-emitters/qwen-rules-emitter.js +70 -0
  274. package/lib/compiler/schemas.js +144 -0
  275. package/lib/compiler/skills-registry.js +225 -0
  276. package/lib/compiler/transformer.js +236 -0
  277. package/package.json +50 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * T012: Transformer Tests
3
+ * Tests for placeholder expansion and argument mapping
4
+ * Expected: All tests FAIL (transformer not implemented)
5
+ */
6
+
7
+ // ============================================================
8
+ // Transformer imports - will fail until lib/compiler/transformer.js exists
9
+ // ============================================================
10
+ let transformer;
11
+ let errors;
12
+ try {
13
+ transformer = require('../transformer.js');
14
+ errors = require('../errors.js');
15
+ } catch (e) {
16
+ transformer = null;
17
+ errors = null;
18
+ }
19
+
20
+ // Sample CommandIR for testing
21
+ const createTestIR = (overrides = {}) => ({
22
+ source: {
23
+ path: '/test/flow-test.md',
24
+ filename: 'flow-test',
25
+ hash: 'sha256:abc123'
26
+ },
27
+ frontmatter: {
28
+ name: 'flow-test',
29
+ description: 'Test command',
30
+ scripts: {
31
+ prereq: '.claude/scripts/check-prerequisites.sh',
32
+ validate: '.claude/scripts/validate.sh'
33
+ },
34
+ ...overrides.frontmatter
35
+ },
36
+ body: overrides.body || 'Run {SCRIPT:prereq} and validate with {SCRIPT:validate}',
37
+ placeholders: overrides.placeholders || [
38
+ { type: 'SCRIPT', raw: '{SCRIPT:prereq}', alias: 'prereq', position: { start: 4, end: 19 } },
39
+ { type: 'SCRIPT', raw: '{SCRIPT:validate}', alias: 'validate', position: { start: 40, end: 57 } }
40
+ ]
41
+ });
42
+
43
+ describe('Transformer Module', () => {
44
+ beforeEach(() => {
45
+ if (!transformer) {
46
+ throw new Error('transformer.js not implemented');
47
+ }
48
+ });
49
+
50
+ // ----------------------------------------------------------
51
+ // AC1: {SCRIPT:prereq} expanded to "bash .claude/scripts/..."
52
+ // ----------------------------------------------------------
53
+ describe('AC1: SCRIPT placeholder expansion', () => {
54
+ it('should expand {SCRIPT:prereq} to bash path', () => {
55
+ const ir = createTestIR({
56
+ body: 'Run {SCRIPT:prereq}',
57
+ placeholders: [
58
+ { type: 'SCRIPT', raw: '{SCRIPT:prereq}', alias: 'prereq', position: { start: 4, end: 19 } }
59
+ ]
60
+ });
61
+ const result = transformer.transformForPlatform(ir, 'codex');
62
+ expect(result.body).toContain('bash .claude/scripts/check-prerequisites.sh');
63
+ expect(result.body).not.toContain('{SCRIPT:prereq}');
64
+ });
65
+
66
+ it('should expand multiple {SCRIPT:*} placeholders', () => {
67
+ const ir = createTestIR();
68
+ const result = transformer.transformForPlatform(ir, 'codex');
69
+ expect(result.body).toContain('bash .claude/scripts/check-prerequisites.sh');
70
+ expect(result.body).toContain('bash .claude/scripts/validate.sh');
71
+ expect(result.body).not.toContain('{SCRIPT:');
72
+ });
73
+ });
74
+
75
+ // ----------------------------------------------------------
76
+ // AC2: $ARGUMENTS -> {{args}} for Qwen
77
+ // ----------------------------------------------------------
78
+ describe('AC2: $ARGUMENTS -> {{args}} for Qwen', () => {
79
+ it('should map $ARGUMENTS to {{args}} for Qwen', () => {
80
+ const ir = createTestIR({
81
+ body: 'Input: $ARGUMENTS',
82
+ placeholders: [
83
+ { type: 'ARGUMENTS', raw: '$ARGUMENTS', position: { start: 7, end: 17 } }
84
+ ]
85
+ });
86
+ const result = transformer.transformForPlatform(ir, 'qwen');
87
+ expect(result.body).toContain('{{args}}');
88
+ expect(result.body).not.toContain('$ARGUMENTS');
89
+ });
90
+ });
91
+
92
+ // ----------------------------------------------------------
93
+ // AC3: $ARGUMENTS -> [arguments] for Antigravity
94
+ // ----------------------------------------------------------
95
+ describe('AC3: $ARGUMENTS -> [arguments] for Antigravity', () => {
96
+ it('should map $ARGUMENTS to [arguments] for Antigravity', () => {
97
+ const ir = createTestIR({
98
+ body: 'Input: $ARGUMENTS',
99
+ placeholders: [
100
+ { type: 'ARGUMENTS', raw: '$ARGUMENTS', position: { start: 7, end: 17 } }
101
+ ]
102
+ });
103
+ const result = transformer.transformForPlatform(ir, 'antigravity');
104
+ expect(result.body).toContain('[arguments]');
105
+ expect(result.body).not.toContain('$ARGUMENTS');
106
+ });
107
+ });
108
+
109
+ // ----------------------------------------------------------
110
+ // AC4: $ARGUMENTS unchanged for Codex/Cursor
111
+ // ----------------------------------------------------------
112
+ describe('AC4: $ARGUMENTS unchanged for Codex/Cursor', () => {
113
+ it('should keep $ARGUMENTS unchanged for Codex', () => {
114
+ const ir = createTestIR({
115
+ body: 'Input: $ARGUMENTS',
116
+ placeholders: [
117
+ { type: 'ARGUMENTS', raw: '$ARGUMENTS', position: { start: 7, end: 17 } }
118
+ ]
119
+ });
120
+ const result = transformer.transformForPlatform(ir, 'codex');
121
+ expect(result.body).toContain('$ARGUMENTS');
122
+ });
123
+
124
+ it('should keep $ARGUMENTS unchanged for Cursor', () => {
125
+ const ir = createTestIR({
126
+ body: 'Input: $ARGUMENTS',
127
+ placeholders: [
128
+ { type: 'ARGUMENTS', raw: '$ARGUMENTS', position: { start: 7, end: 17 } }
129
+ ]
130
+ });
131
+ const result = transformer.transformForPlatform(ir, 'cursor');
132
+ expect(result.body).toContain('$ARGUMENTS');
133
+ });
134
+ });
135
+
136
+ // ----------------------------------------------------------
137
+ // AC5: {AGENT_SCRIPT} expansion with platform-specific marker
138
+ // ----------------------------------------------------------
139
+ describe('AC5: AGENT_SCRIPT expansion', () => {
140
+ it('should expand {AGENT_SCRIPT} with shell script content', () => {
141
+ const ir = createTestIR({
142
+ frontmatter: {
143
+ name: 'test',
144
+ description: 'Test',
145
+ agent_scripts: {
146
+ sh: 'echo "Hello from __AGENT__"'
147
+ }
148
+ },
149
+ body: 'Execute: {AGENT_SCRIPT}',
150
+ placeholders: [
151
+ { type: 'AGENT_SCRIPT', raw: '{AGENT_SCRIPT}', position: { start: 9, end: 23 } }
152
+ ]
153
+ });
154
+
155
+ const result = transformer.transformForPlatform(ir, 'codex');
156
+ expect(result.body).not.toContain('{AGENT_SCRIPT}');
157
+ expect(result.body).toContain('echo "Hello from codex"');
158
+ });
159
+
160
+ it('should substitute __AGENT__ with platform name', () => {
161
+ const ir = createTestIR({
162
+ frontmatter: {
163
+ name: 'test',
164
+ description: 'Test',
165
+ agent_scripts: {
166
+ sh: 'agent=__AGENT__'
167
+ }
168
+ },
169
+ body: '{AGENT_SCRIPT}',
170
+ placeholders: [
171
+ { type: 'AGENT_SCRIPT', raw: '{AGENT_SCRIPT}', position: { start: 0, end: 14 } }
172
+ ]
173
+ });
174
+
175
+ expect(transformer.transformForPlatform(ir, 'codex').body).toContain('agent=codex');
176
+ expect(transformer.transformForPlatform(ir, 'cursor').body).toContain('agent=cursor');
177
+ expect(transformer.transformForPlatform(ir, 'qwen').body).toContain('agent=qwen');
178
+ expect(transformer.transformForPlatform(ir, 'antigravity').body).toContain('agent=antigravity');
179
+ });
180
+ });
181
+
182
+ // ----------------------------------------------------------
183
+ // transformForPlatform() tests
184
+ // ----------------------------------------------------------
185
+ describe('transformForPlatform()', () => {
186
+ it('should return transformed content with frontmatter', () => {
187
+ const ir = createTestIR({ body: 'Simple body', placeholders: [] });
188
+ const result = transformer.transformForPlatform(ir, 'codex');
189
+ expect(result).toHaveProperty('body');
190
+ expect(result).toHaveProperty('frontmatter');
191
+ });
192
+
193
+ it('should preserve frontmatter in result', () => {
194
+ const ir = createTestIR({ body: 'Test', placeholders: [] });
195
+ const result = transformer.transformForPlatform(ir, 'codex');
196
+ expect(result.frontmatter.name).toBe('flow-test');
197
+ expect(result.frontmatter.description).toBe('Test command');
198
+ });
199
+
200
+ it('should handle all four platforms', () => {
201
+ const platforms = ['codex', 'cursor', 'qwen', 'antigravity'];
202
+ const ir = createTestIR({ body: 'Test $ARGUMENTS', placeholders: [
203
+ { type: 'ARGUMENTS', raw: '$ARGUMENTS', position: { start: 5, end: 15 } }
204
+ ]});
205
+
206
+ platforms.forEach(platform => {
207
+ expect(() => transformer.transformForPlatform(ir, platform)).not.toThrow();
208
+ });
209
+ });
210
+ });
211
+
212
+ // ----------------------------------------------------------
213
+ // expandScriptPlaceholders() tests
214
+ // ----------------------------------------------------------
215
+ describe('expandScriptPlaceholders()', () => {
216
+ it('should expand {SCRIPT:alias} to bash path', () => {
217
+ const scripts = { prereq: '.claude/scripts/prereq.sh' };
218
+ const result = transformer.expandScriptPlaceholders('Run {SCRIPT:prereq}', scripts);
219
+ expect(result).toBe('Run bash .claude/scripts/prereq.sh');
220
+ });
221
+
222
+ it('should handle multiple placeholders', () => {
223
+ const scripts = { a: 'path/a.sh', b: 'path/b.sh' };
224
+ const result = transformer.expandScriptPlaceholders('{SCRIPT:a} then {SCRIPT:b}', scripts);
225
+ expect(result).toBe('bash path/a.sh then bash path/b.sh');
226
+ });
227
+
228
+ it('should handle content without placeholders', () => {
229
+ const result = transformer.expandScriptPlaceholders('No placeholders here', {});
230
+ expect(result).toBe('No placeholders here');
231
+ });
232
+ });
233
+
234
+ // ----------------------------------------------------------
235
+ // expandAgentScript() tests
236
+ // ----------------------------------------------------------
237
+ describe('expandAgentScript()', () => {
238
+ it('should expand {AGENT_SCRIPT} with sh content', () => {
239
+ const agentScripts = { sh: 'echo "test"' };
240
+ const result = transformer.expandAgentScript('Run: {AGENT_SCRIPT}', agentScripts, 'codex');
241
+ expect(result).toContain('echo "test"');
242
+ expect(result).not.toContain('{AGENT_SCRIPT}');
243
+ });
244
+
245
+ it('should replace __AGENT__ marker', () => {
246
+ const agentScripts = { sh: 'platform=__AGENT__' };
247
+ const result = transformer.expandAgentScript('{AGENT_SCRIPT}', agentScripts, 'qwen');
248
+ expect(result).toContain('platform=qwen');
249
+ });
250
+
251
+ it('should handle missing agent_scripts gracefully', () => {
252
+ const result = transformer.expandAgentScript('{AGENT_SCRIPT}', undefined, 'codex');
253
+ expect(result).toBe('{AGENT_SCRIPT}');
254
+ });
255
+ });
256
+
257
+ // ----------------------------------------------------------
258
+ // mapArguments() tests
259
+ // ----------------------------------------------------------
260
+ describe('mapArguments()', () => {
261
+ it('should keep $ARGUMENTS for codex', () => {
262
+ const result = transformer.mapArguments('Input: $ARGUMENTS', 'codex');
263
+ expect(result).toBe('Input: $ARGUMENTS');
264
+ });
265
+
266
+ it('should keep $ARGUMENTS for cursor', () => {
267
+ const result = transformer.mapArguments('Input: $ARGUMENTS', 'cursor');
268
+ expect(result).toBe('Input: $ARGUMENTS');
269
+ });
270
+
271
+ it('should map to {{args}} for qwen', () => {
272
+ const result = transformer.mapArguments('Input: $ARGUMENTS', 'qwen');
273
+ expect(result).toBe('Input: {{args}}');
274
+ });
275
+
276
+ it('should map to [arguments] for antigravity', () => {
277
+ const result = transformer.mapArguments('Input: $ARGUMENTS', 'antigravity');
278
+ expect(result).toBe('Input: [arguments]');
279
+ });
280
+
281
+ it('should handle multiple $ARGUMENTS occurrences', () => {
282
+ const result = transformer.mapArguments('$ARGUMENTS and $ARGUMENTS again', 'qwen');
283
+ expect(result).toBe('{{args}} and {{args}} again');
284
+ });
285
+ });
286
+ });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * T035: AntigravityEmitter - Antigravity 平台输出
3
+ *
4
+ * 输出格式: Markdown + YAML frontmatter
5
+ * 目录: .agent/workflows/
6
+ * 限制: 单文件 <= 12,000 字符
7
+ * 超限时自动拆分
8
+ */
9
+ const yaml = require('js-yaml');
10
+ const BaseEmitter = require('./base-emitter.js');
11
+
12
+ const CONTENT_LIMIT = 12000;
13
+ const DESCRIPTION_LIMIT = 250;
14
+
15
+ class AntigravityEmitter extends BaseEmitter {
16
+ static CONTENT_LIMIT = CONTENT_LIMIT;
17
+
18
+ get name() {
19
+ return 'antigravity';
20
+ }
21
+
22
+ get outputDir() {
23
+ return '.agent/workflows';
24
+ }
25
+
26
+ get fileExtension() {
27
+ return '.md';
28
+ }
29
+
30
+ get contentLimit() {
31
+ return CONTENT_LIMIT;
32
+ }
33
+
34
+ /**
35
+ * 格式化为 Antigravity 格式
36
+ * - YAML frontmatter: description (max 250 chars)
37
+ * - Markdown body
38
+ * - 超过 12K 时返回拆分数组
39
+ */
40
+ format(ir, transformedContent) {
41
+ // 截断 description 到 250 字符
42
+ const description = ir.frontmatter.description.substring(0, DESCRIPTION_LIMIT);
43
+
44
+ const frontmatterData = { description };
45
+ const yamlStr = yaml.dump(frontmatterData, {
46
+ lineWidth: -1,
47
+ quotingType: '"',
48
+ forceQuotes: false
49
+ });
50
+
51
+ const content = `---\n${yamlStr}---\n\n${transformedContent}`;
52
+
53
+ // 检查是否超过限制
54
+ if (content.length > CONTENT_LIMIT) {
55
+ return this.splitContent(ir, content);
56
+ }
57
+
58
+ return content;
59
+ }
60
+
61
+ /**
62
+ * 拆分超长内容为多个文件
63
+ * 策略: 按章节(# 或 ## 标题)拆分,超大章节再硬拆分
64
+ * 所有拆分文件都带完整 frontmatter (description)
65
+ */
66
+ splitContent(ir, content) {
67
+ const filename = ir.source.filename;
68
+ const description = ir.frontmatter.description.substring(0, DESCRIPTION_LIMIT);
69
+
70
+ // 计算 frontmatter 固定开销
71
+ const fmOverhead = this.createFrontmatter(description, 1, 1).length;
72
+ const maxBodySize = CONTENT_LIMIT - fmOverhead - 50; // 留 50 字符余量
73
+
74
+ // 移除原有 frontmatter,只处理 body
75
+ const bodyOnly = content.replace(/^---[\s\S]*?---\n*/m, '');
76
+
77
+ // 按 # 或 ## 标题拆分章节
78
+ const sections = bodyOnly.split(/(?=^#{1,2}\s)/m).filter(s => s.trim());
79
+
80
+ // 如果没有章节标题,直接硬拆分
81
+ if (sections.length <= 1) {
82
+ return this.hardSplit(bodyOnly, filename, description, maxBodySize);
83
+ }
84
+
85
+ // 预处理:将超大章节先硬拆分
86
+ const normalizedSections = [];
87
+ for (const section of sections) {
88
+ if (section.length > maxBodySize) {
89
+ // 超大章节需要硬拆分
90
+ const subParts = this.splitLargeSection(section, maxBodySize);
91
+ normalizedSections.push(...subParts);
92
+ } else {
93
+ normalizedSections.push(section);
94
+ }
95
+ }
96
+
97
+ // 贪心合并:尽量把多个小章节合并到一个文件
98
+ const mergedParts = [];
99
+ let currentPart = '';
100
+
101
+ for (const section of normalizedSections) {
102
+ const testContent = currentPart + section;
103
+ if (testContent.length > maxBodySize && currentPart) {
104
+ // 当前 part 已满,保存并开始新 part
105
+ mergedParts.push(currentPart);
106
+ currentPart = section;
107
+ } else {
108
+ currentPart = testContent;
109
+ }
110
+ }
111
+
112
+ // 保存最后一个 part
113
+ if (currentPart) {
114
+ mergedParts.push(currentPart);
115
+ }
116
+
117
+ // 生成最终文件列表,带序号 frontmatter
118
+ const totalParts = mergedParts.length;
119
+ return mergedParts.map((body, index) => ({
120
+ filename: totalParts === 1 ? filename : `${filename}-part${index + 1}`,
121
+ content: this.createFrontmatter(description, index + 1, totalParts) + body
122
+ }));
123
+ }
124
+
125
+ /**
126
+ * 创建带分页信息的 frontmatter
127
+ */
128
+ createFrontmatter(description, partNum, totalParts) {
129
+ let desc = description;
130
+ if (totalParts > 1) {
131
+ desc = `${description} (Part ${partNum}/${totalParts})`;
132
+ }
133
+ const fm = { description: desc };
134
+ // lineWidth: -1 强制单行输出,避免 >- 多行格式
135
+ return `---\n${yaml.dump(fm, { lineWidth: -1 })}---\n\n`;
136
+ }
137
+
138
+ /**
139
+ * 拆分超大章节
140
+ * 策略:直接按 maxSize 硬拆分,不尝试保持段落完整性
141
+ * (因为按段落拆分会导致标题和内容分离等问题)
142
+ */
143
+ splitLargeSection(section, maxSize) {
144
+ const parts = [];
145
+
146
+ for (let i = 0; i < section.length; i += maxSize) {
147
+ parts.push(section.substring(i, i + maxSize));
148
+ }
149
+
150
+ return parts;
151
+ }
152
+
153
+ /**
154
+ * 纯硬拆分(无章节标题时的后备方案)
155
+ */
156
+ hardSplit(content, filename, description, maxBodySize) {
157
+ const parts = [];
158
+
159
+ for (let i = 0; i < content.length; i += maxBodySize) {
160
+ parts.push(content.substring(i, i + maxBodySize));
161
+ }
162
+
163
+ const totalParts = parts.length;
164
+ return parts.map((body, index) => ({
165
+ filename: totalParts === 1 ? filename : `${filename}-part${index + 1}`,
166
+ content: this.createFrontmatter(description, index + 1, totalParts) + body
167
+ }));
168
+ }
169
+ }
170
+
171
+ module.exports = AntigravityEmitter;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * T031: BaseEmitter - Emitter 基类
3
+ *
4
+ * 定义 Emitter 接口:
5
+ * - name: 平台名称
6
+ * - outputDir: 输出目录
7
+ * - fileExtension: 文件扩展名
8
+ * - format(ir, transformedContent): 格式化输出
9
+ * - emit(filename, content): 写入文件
10
+ */
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const crypto = require('crypto');
14
+
15
+ // ============================================================
16
+ // SECURITY CONFIGURATION (FINDING-003)
17
+ // ============================================================
18
+ const MAX_OUTPUT_SIZE = 2 * 1024 * 1024; // 2MB limit
19
+
20
+ class BaseEmitter {
21
+ // ----------------------------------------------------------
22
+ // Abstract properties - 子类必须覆盖
23
+ // ----------------------------------------------------------
24
+ get name() {
25
+ throw new Error('Not implemented');
26
+ }
27
+
28
+ get outputDir() {
29
+ throw new Error('Not implemented');
30
+ }
31
+
32
+ get fileExtension() {
33
+ throw new Error('Not implemented');
34
+ }
35
+
36
+ // ----------------------------------------------------------
37
+ // format() - 子类必须实现
38
+ // ----------------------------------------------------------
39
+ format(ir, transformedContent) {
40
+ throw new Error('Not implemented');
41
+ }
42
+
43
+ // ----------------------------------------------------------
44
+ // emit() - 写入文件到输出目录
45
+ // ----------------------------------------------------------
46
+ async emit(filename, content) {
47
+ // ✅ SECURITY FIX (FINDING-003): Check output size
48
+ if (content.length > MAX_OUTPUT_SIZE) {
49
+ throw new Error(`Output too large: ${filename} (${content.length} bytes > ${MAX_OUTPUT_SIZE} bytes)`);
50
+ }
51
+
52
+ const outputDir = this.outputDir;
53
+ const ext = this.fileExtension;
54
+ const filePath = path.join(outputDir, `${filename}${ext}`);
55
+
56
+ // ✅ SECURITY FIX (FINDING-005): Set explicit directory permissions
57
+ await fs.promises.mkdir(outputDir, { recursive: true, mode: 0o755 });
58
+
59
+ // ✅ SECURITY FIX (FINDING-005): Set explicit file permissions
60
+ await fs.promises.writeFile(filePath, content, { encoding: 'utf8', mode: 0o644 });
61
+
62
+ // 计算哈希
63
+ const hash = crypto.createHash('sha256').update(content).digest('hex');
64
+
65
+ return {
66
+ path: filePath,
67
+ hash,
68
+ timestamp: new Date().toISOString()
69
+ };
70
+ }
71
+ }
72
+
73
+ module.exports = BaseEmitter;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * T032: CodexEmitter - Codex 平台输出
3
+ *
4
+ * 输出格式: Markdown + YAML frontmatter
5
+ * 目录: .codex/prompts/
6
+ * Frontmatter 字段: description, argument-hint
7
+ */
8
+ const yaml = require('js-yaml');
9
+ const BaseEmitter = require('./base-emitter.js');
10
+
11
+ class CodexEmitter extends BaseEmitter {
12
+ get name() {
13
+ return 'codex';
14
+ }
15
+
16
+ get outputDir() {
17
+ return '.codex/prompts';
18
+ }
19
+
20
+ get fileExtension() {
21
+ return '.md';
22
+ }
23
+
24
+ /**
25
+ * 格式化为 Codex 格式
26
+ * - YAML frontmatter: description, argument-hint
27
+ * - Markdown body
28
+ */
29
+ format(ir, transformedContent) {
30
+ const frontmatterData = {
31
+ description: ir.frontmatter.description
32
+ };
33
+
34
+ // 添加 argument-hint(如有 scripts)
35
+ if (ir.frontmatter.scripts) {
36
+ const aliases = Object.keys(ir.frontmatter.scripts);
37
+ if (aliases.length > 0) {
38
+ frontmatterData['argument-hint'] = aliases.join(', ');
39
+ }
40
+ }
41
+
42
+ const yamlStr = yaml.dump(frontmatterData, {
43
+ lineWidth: -1,
44
+ quotingType: '"',
45
+ forceQuotes: false
46
+ });
47
+
48
+ return `---\n${yamlStr}---\n\n${transformedContent}`;
49
+ }
50
+ }
51
+
52
+ module.exports = CodexEmitter;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * T033: CursorEmitter - Cursor 平台输出
3
+ *
4
+ * 输出格式: 纯 Markdown (无 frontmatter)
5
+ * 目录: .cursor/commands/
6
+ */
7
+ const BaseEmitter = require('./base-emitter.js');
8
+
9
+ class CursorEmitter extends BaseEmitter {
10
+ get name() {
11
+ return 'cursor';
12
+ }
13
+
14
+ get outputDir() {
15
+ return '.cursor/commands';
16
+ }
17
+
18
+ get fileExtension() {
19
+ return '.md';
20
+ }
21
+
22
+ /**
23
+ * 格式化为 Cursor 格式
24
+ * - 纯 Markdown,无 frontmatter
25
+ */
26
+ format(ir, transformedContent) {
27
+ return transformedContent;
28
+ }
29
+ }
30
+
31
+ module.exports = CursorEmitter;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * T036: Emitter Index
3
+ *
4
+ * 导出所有 Emitter 类
5
+ * 提供 getEmitter(platform) 工厂函数
6
+ */
7
+ const BaseEmitter = require('./base-emitter.js');
8
+ const CodexEmitter = require('./codex-emitter.js');
9
+ const CursorEmitter = require('./cursor-emitter.js');
10
+ const QwenEmitter = require('./qwen-emitter.js');
11
+ const AntigravityEmitter = require('./antigravity-emitter.js');
12
+
13
+ // ============================================================
14
+ // Emitter Registry
15
+ // ============================================================
16
+ const EMITTERS = {
17
+ codex: CodexEmitter,
18
+ cursor: CursorEmitter,
19
+ qwen: QwenEmitter,
20
+ antigravity: AntigravityEmitter
21
+ };
22
+
23
+ // ============================================================
24
+ // getEmitter - 工厂函数
25
+ // ============================================================
26
+ function getEmitter(platform) {
27
+ const EmitterClass = EMITTERS[platform];
28
+ if (!EmitterClass) {
29
+ throw new Error(`Unknown platform: ${platform}`);
30
+ }
31
+ return new EmitterClass();
32
+ }
33
+
34
+ // ============================================================
35
+ // getAllEmitters - 获取所有平台 Emitter 实例
36
+ // ============================================================
37
+ function getAllEmitters() {
38
+ return Object.keys(EMITTERS).map(platform => getEmitter(platform));
39
+ }
40
+
41
+ module.exports = {
42
+ BaseEmitter,
43
+ CodexEmitter,
44
+ CursorEmitter,
45
+ QwenEmitter,
46
+ AntigravityEmitter,
47
+ getEmitter,
48
+ getAllEmitters,
49
+ PLATFORMS: Object.keys(EMITTERS)
50
+ };