aiwf 0.3.23 → 0.4.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 (321) hide show
  1. package/.claude-plugin/marketplace.json +146 -0
  2. package/CHANGELOG.md +49 -1
  3. package/README.ko.md +118 -309
  4. package/README.md +68 -254
  5. package/docs/modernization/BROWNFIELD-GREENFIELD.ko.md +105 -0
  6. package/docs/modernization/CLAUDE-PLAN-REVIEW-2026-10-03.ko.md +77 -0
  7. package/docs/modernization/CLI-PRODUCTIVITY-REVIEWED-2026-10-03.ko.md +177 -0
  8. package/docs/modernization/CLI-PRODUCTIVITY.ko.md +193 -0
  9. package/docs/modernization/CORE-PACKAGE-PLAN.md +7 -0
  10. package/docs/modernization/DEEP-REVERSE-ENGINEERING.ko.md +88 -0
  11. package/docs/modernization/DELEGATION-OPTIONAL.ko.md +57 -0
  12. package/docs/modernization/DIRECTION.ko.md +56 -0
  13. package/docs/modernization/FULL-TEST-2026-10-03.md +49 -0
  14. package/docs/modernization/LEGACY-REMOVAL-PLAN.md +9 -0
  15. package/docs/modernization/PILOT-RESULT-2026-10-03.ko.md +85 -0
  16. package/docs/modernization/PILOT-UC-001.ko.md +79 -0
  17. package/docs/modernization/PLAN.md +28 -0
  18. package/docs/modernization/SKILLS.ko.md +105 -0
  19. package/docs/modernization/SPRINTABLE.ko.md +47 -0
  20. package/docs/modernization/SYNC-DOCS-VALIDATION-2026-10-04.ko.md +38 -0
  21. package/docs/modernization/VALIDATION.md +79 -0
  22. package/docs/modernization/evidence/example-review-packet.json +115 -0
  23. package/docs/modernization/evidence/example-spec-pin.json +48 -0
  24. package/docs/modernization/evidence/full-test-20261003/claude-lint.md +22 -0
  25. package/docs/modernization/evidence/full-test-20261003/claude-review-retry.md +56 -0
  26. package/docs/modernization/evidence/full-test-20261003/codex-review-packet.json +159 -0
  27. package/docs/modernization/evidence/full-test-20261003/src/expense.mjs +40 -0
  28. package/docs/modernization/evidence/full-test-20261003/summary.json +106 -0
  29. package/docs/modernization/evidence/full-test-20261003/tests/expense.test.mjs +61 -0
  30. package/docs/modernization/evidence/local-pilot-20261003/README.ko.md +40 -0
  31. package/docs/modernization/evidence/local-pilot-20261003/execution.json +1055 -0
  32. package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.json +24 -0
  33. package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.stderr.txt +0 -0
  34. package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.stdout.txt +5 -0
  35. package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.json +24 -0
  36. package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.stderr.txt +0 -0
  37. package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.stdout.txt +6 -0
  38. package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.json +24 -0
  39. package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.stderr.txt +0 -0
  40. package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.stdout.txt +5 -0
  41. package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.json +24 -0
  42. package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.stderr.txt +0 -0
  43. package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.stdout.txt +6 -0
  44. package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.json +23 -0
  45. package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.stderr.txt +0 -0
  46. package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.stdout.txt +535 -0
  47. package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.json +24 -0
  48. package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.stderr.txt +0 -0
  49. package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.stdout.txt +5 -0
  50. package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.json +24 -0
  51. package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.stderr.txt +0 -0
  52. package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.stdout.txt +273 -0
  53. package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.json +24 -0
  54. package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.stderr.txt +0 -0
  55. package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.stdout.txt +7 -0
  56. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-service.stderr.txt +0 -0
  57. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-service.stdout.txt +148 -0
  58. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-structure.stderr.txt +0 -0
  59. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-structure.stdout.txt +9 -0
  60. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-check.stderr.txt +0 -0
  61. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-check.stdout.txt +67 -0
  62. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-evidence.json +34 -0
  63. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.json +128 -0
  64. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.stderr.txt +0 -0
  65. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.stdout.txt +128 -0
  66. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.json +54 -0
  67. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.stderr.txt +0 -0
  68. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.stdout.txt +60 -0
  69. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-readback.json +15 -0
  70. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-service.stderr.txt +0 -0
  71. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-service.stdout.txt +82 -0
  72. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-structure.stderr.txt +0 -0
  73. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-structure.stdout.txt +9 -0
  74. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-check.stderr.txt +0 -0
  75. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-check.stdout.txt +79 -0
  76. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-packet-refused.stderr.txt +0 -0
  77. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-packet-refused.stdout.txt +7 -0
  78. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin-refresh.stderr.txt +0 -0
  79. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin-refresh.stdout.txt +66 -0
  80. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin.json +60 -0
  81. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-structure.stderr.txt +0 -0
  82. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-structure.stdout.txt +9 -0
  83. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-check.stderr.txt +0 -0
  84. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-check.stdout.txt +73 -0
  85. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-evidence.json +34 -0
  86. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.json +134 -0
  87. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.stderr.txt +0 -0
  88. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.stdout.txt +134 -0
  89. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-readback.json +15 -0
  90. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-service.stderr.txt +0 -0
  91. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-service.stdout.txt +148 -0
  92. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-structure.stderr.txt +0 -0
  93. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-structure.stdout.txt +9 -0
  94. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/node-version.stderr.txt +0 -0
  95. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/node-version.stdout.txt +1 -0
  96. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/python-version.stderr.txt +0 -0
  97. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/python-version.stdout.txt +1 -0
  98. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-evidence.json +34 -0
  99. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.json +134 -0
  100. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.stderr.txt +0 -0
  101. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.stdout.txt +134 -0
  102. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-readback.json +15 -0
  103. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-service.stderr.txt +0 -0
  104. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-service.stdout.txt +266 -0
  105. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-structure.stderr.txt +0 -0
  106. package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-structure.stdout.txt +9 -0
  107. package/docs/modernization/evidence/local-pilot-20261003/project/docs/entity_model.md +33 -0
  108. package/docs/modernization/evidence/local-pilot-20261003/project/docs/glossary.md +9 -0
  109. package/docs/modernization/evidence/local-pilot-20261003/project/docs/plans/UC-001.md +20 -0
  110. package/docs/modernization/evidence/local-pilot-20261003/project/docs/requirements.md +10 -0
  111. package/docs/modernization/evidence/local-pilot-20261003/project/docs/test_cases/TC-001-submit-expense.md +37 -0
  112. package/docs/modernization/evidence/local-pilot-20261003/project/docs/test_cases/TC-002-description-limit.md +42 -0
  113. package/docs/modernization/evidence/local-pilot-20261003/project/docs/use_cases/UC-001-submit-expense.md +76 -0
  114. package/docs/modernization/evidence/local-pilot-20261003/project/docs/use_cases.puml +12 -0
  115. package/docs/modernization/evidence/local-pilot-20261003/project/docs/vision.md +21 -0
  116. package/docs/modernization/evidence/local-pilot-20261003/project/src/expense.mjs +48 -0
  117. package/docs/modernization/evidence/local-pilot-20261003/project/tests/expense.test.mjs +116 -0
  118. package/docs/modernization/evidence/local-pilot-20261003/scope.json +31 -0
  119. package/docs/modernization/reviews/2026-10-03-claude-cli/CONTRACTS.ko.md +122 -0
  120. package/docs/modernization/reviews/2026-10-03-claude-cli/DIRECTION.ko.md +84 -0
  121. package/examples/spec-workflow/README.md +54 -0
  122. package/examples/spec-workflow/docs/entity_model.md +33 -0
  123. package/examples/spec-workflow/docs/glossary.md +9 -0
  124. package/examples/spec-workflow/docs/requirements.md +10 -0
  125. package/examples/spec-workflow/docs/test_cases/TC-001-submit-expense.md +37 -0
  126. package/examples/spec-workflow/docs/use_cases/UC-001-submit-expense.md +63 -0
  127. package/examples/spec-workflow/docs/use_cases.puml +12 -0
  128. package/examples/spec-workflow/docs/vision.md +21 -0
  129. package/package.json +37 -105
  130. package/plugins/aiwf-angular-jpa/.claude-plugin/plugin.json +11 -0
  131. package/plugins/aiwf-angular-jpa/LICENSE +201 -0
  132. package/plugins/aiwf-angular-jpa/NOTICE +16 -0
  133. package/plugins/aiwf-angular-jpa/README.md +54 -0
  134. package/plugins/aiwf-angular-jpa/UPSTREAM.json +35 -0
  135. package/plugins/aiwf-angular-jpa/agents/uc-coverage.md +263 -0
  136. package/plugins/aiwf-angular-jpa/rules/mcp-servers.md +64 -0
  137. package/plugins/aiwf-angular-jpa/skills/coverage-check/SKILL.md +190 -0
  138. package/plugins/aiwf-angular-jpa/skills/flyway-migration/SKILL.md +119 -0
  139. package/plugins/aiwf-angular-jpa/skills/implement/SKILL.md +528 -0
  140. package/plugins/aiwf-angular-jpa/skills/implement/references/module-layout.md +95 -0
  141. package/plugins/aiwf-angular-jpa/skills/playwright-test/SKILL.md +360 -0
  142. package/plugins/aiwf-angular-jpa/skills/spring-boot-test/SKILL.md +504 -0
  143. package/plugins/aiwf-angular-jpa/skills/vitest-test/SKILL.md +309 -0
  144. package/plugins/aiwf-blazor-dotnet/.claude-plugin/plugin.json +11 -0
  145. package/plugins/aiwf-blazor-dotnet/LICENSE +201 -0
  146. package/plugins/aiwf-blazor-dotnet/NOTICE +16 -0
  147. package/plugins/aiwf-blazor-dotnet/README.md +53 -0
  148. package/plugins/aiwf-blazor-dotnet/UPSTREAM.json +31 -0
  149. package/plugins/aiwf-blazor-dotnet/rules/mcp-servers.md +25 -0
  150. package/plugins/aiwf-blazor-dotnet/skills/bunit-test/SKILL.md +110 -0
  151. package/plugins/aiwf-blazor-dotnet/skills/dotnet-test/SKILL.md +108 -0
  152. package/plugins/aiwf-blazor-dotnet/skills/ef-migration/SKILL.md +43 -0
  153. package/plugins/aiwf-blazor-dotnet/skills/implement/SKILL.md +149 -0
  154. package/plugins/aiwf-blazor-dotnet/skills/playwright-test/SKILL.md +130 -0
  155. package/plugins/aiwf-core/.claude-plugin/plugin.json +11 -0
  156. package/plugins/aiwf-core/LICENSE +201 -0
  157. package/plugins/aiwf-core/NOTICE +20 -0
  158. package/plugins/aiwf-core/README.md +38 -0
  159. package/plugins/aiwf-core/UPSTREAM.json +50 -0
  160. package/plugins/aiwf-core/skills/entity-model/SKILL.md +160 -0
  161. package/plugins/aiwf-core/skills/entity-model/references/REFERENCE.md +36 -0
  162. package/plugins/aiwf-core/skills/requirements/SKILL.md +157 -0
  163. package/plugins/aiwf-core/skills/requirements/references/REFERENCE.md +70 -0
  164. package/plugins/aiwf-core/skills/requirements/references/glossary.md +7 -0
  165. package/plugins/aiwf-core/skills/reverse-engineer/SKILL.md +509 -0
  166. package/plugins/aiwf-core/skills/reverse-engineer/references/stack-signals.md +210 -0
  167. package/plugins/aiwf-core/skills/spec-review/SKILL.md +197 -0
  168. package/plugins/aiwf-core/skills/spec-review/references/lint-codes.md +63 -0
  169. package/plugins/aiwf-core/skills/spec-review/references/review-checklist.md +189 -0
  170. package/plugins/aiwf-core/skills/spec-review/scripts/spec_lint.py +1216 -0
  171. package/plugins/aiwf-core/skills/test-case/SKILL.md +138 -0
  172. package/plugins/aiwf-core/skills/test-case/references/example-process.bpmn +118 -0
  173. package/plugins/aiwf-core/skills/test-case/references/example.md +38 -0
  174. package/plugins/aiwf-core/skills/test-case/references/test-case.md +35 -0
  175. package/plugins/aiwf-core/skills/test-case/scripts/bpmn_paths.py +542 -0
  176. package/plugins/aiwf-core/skills/use-case-diagram/SKILL.md +99 -0
  177. package/plugins/aiwf-core/skills/use-case-spec/SKILL.md +257 -0
  178. package/plugins/aiwf-core/skills/use-case-spec/references/clarify-checklist.md +70 -0
  179. package/plugins/aiwf-core/skills/use-case-spec/references/example.md +88 -0
  180. package/plugins/aiwf-core/skills/use-case-spec/references/format-spec.md +246 -0
  181. package/plugins/aiwf-core/skills/use-case-spec/references/use-case.md +50 -0
  182. package/plugins/aiwf-core/skills/use-case-spec/scripts/validate_use_case.py +941 -0
  183. package/plugins/aiwf-delegate-claude/.claude-plugin/plugin.json +9 -0
  184. package/plugins/aiwf-delegate-claude/LICENSE +21 -0
  185. package/plugins/aiwf-delegate-claude/NOTICE +4 -0
  186. package/plugins/aiwf-delegate-claude/README.md +10 -0
  187. package/plugins/aiwf-delegate-claude/plugin.json +13 -0
  188. package/plugins/aiwf-delegate-claude/skills/delegate-claude/LICENSE +21 -0
  189. package/plugins/aiwf-delegate-claude/skills/delegate-claude/NOTICE +4 -0
  190. package/plugins/aiwf-delegate-claude/skills/delegate-claude/SKILL.md +49 -0
  191. package/plugins/aiwf-delegate-claude/skills/delegate-claude/agents/openai.yaml +2 -0
  192. package/plugins/aiwf-delegate-codex/.claude-plugin/plugin.json +9 -0
  193. package/plugins/aiwf-delegate-codex/LICENSE +21 -0
  194. package/plugins/aiwf-delegate-codex/NOTICE +4 -0
  195. package/plugins/aiwf-delegate-codex/README.md +10 -0
  196. package/plugins/aiwf-delegate-codex/plugin.json +13 -0
  197. package/plugins/aiwf-delegate-codex/skills/delegate-codex/LICENSE +21 -0
  198. package/plugins/aiwf-delegate-codex/skills/delegate-codex/NOTICE +4 -0
  199. package/plugins/aiwf-delegate-codex/skills/delegate-codex/SKILL.md +47 -0
  200. package/plugins/aiwf-delegate-codex/skills/delegate-codex/agents/openai.yaml +2 -0
  201. package/plugins/aiwf-nestjs-nextjs/.claude-plugin/plugin.json +11 -0
  202. package/plugins/aiwf-nestjs-nextjs/LICENSE +201 -0
  203. package/plugins/aiwf-nestjs-nextjs/NOTICE +16 -0
  204. package/plugins/aiwf-nestjs-nextjs/README.md +53 -0
  205. package/plugins/aiwf-nestjs-nextjs/UPSTREAM.json +32 -0
  206. package/plugins/aiwf-nestjs-nextjs/rules/mcp-servers.md +59 -0
  207. package/plugins/aiwf-nestjs-nextjs/skills/drizzle-migration/SKILL.md +222 -0
  208. package/plugins/aiwf-nestjs-nextjs/skills/implement/SKILL.md +393 -0
  209. package/plugins/aiwf-nestjs-nextjs/skills/implement/references/project-layout.md +158 -0
  210. package/plugins/aiwf-nestjs-nextjs/skills/nest-test/SKILL.md +300 -0
  211. package/plugins/aiwf-nestjs-nextjs/skills/playwright-test/SKILL.md +245 -0
  212. package/plugins/aiwf-nestjs-nextjs/skills/react-test/SKILL.md +217 -0
  213. package/plugins/aiwf-spec/.claude-plugin/plugin.json +9 -0
  214. package/plugins/aiwf-spec/LICENSE +201 -0
  215. package/plugins/aiwf-spec/NOTICE +20 -0
  216. package/plugins/aiwf-spec/README.md +36 -0
  217. package/plugins/aiwf-spec/skills/sync-docs/SKILL.md +47 -0
  218. package/plugins/aiwf-spec/skills/workflow/SKILL.md +42 -0
  219. package/plugins/aiwf-vaadin-jooq/.claude-plugin/plugin.json +11 -0
  220. package/plugins/aiwf-vaadin-jooq/LICENSE +201 -0
  221. package/plugins/aiwf-vaadin-jooq/NOTICE +16 -0
  222. package/plugins/aiwf-vaadin-jooq/README.md +56 -0
  223. package/plugins/aiwf-vaadin-jooq/UPSTREAM.json +43 -0
  224. package/plugins/aiwf-vaadin-jooq/agents/uc-coverage.md +260 -0
  225. package/plugins/aiwf-vaadin-jooq/rules/mcp-servers.md +44 -0
  226. package/plugins/aiwf-vaadin-jooq/skills/browserless-test/SKILL.md +407 -0
  227. package/plugins/aiwf-vaadin-jooq/skills/browserless-test/references/UC001ManagePersonsTest.java +111 -0
  228. package/plugins/aiwf-vaadin-jooq/skills/coverage-check/SKILL.md +190 -0
  229. package/plugins/aiwf-vaadin-jooq/skills/flyway-migration/SKILL.md +70 -0
  230. package/plugins/aiwf-vaadin-jooq/skills/hilla-test/SKILL.md +350 -0
  231. package/plugins/aiwf-vaadin-jooq/skills/hilla-test/references/UC001ManagePersonsServiceTest.java +81 -0
  232. package/plugins/aiwf-vaadin-jooq/skills/hilla-test/references/UC001ManagePersonsViewTest.tsx +87 -0
  233. package/plugins/aiwf-vaadin-jooq/skills/implement/SKILL.md +196 -0
  234. package/plugins/aiwf-vaadin-jooq/skills/implement-hilla/SKILL.md +216 -0
  235. package/plugins/aiwf-vaadin-jooq/skills/karibu-test/SKILL.md +298 -0
  236. package/plugins/aiwf-vaadin-jooq/skills/karibu-test/references/UC001ManagePersonsTest.java +93 -0
  237. package/plugins/aiwf-vaadin-jooq/skills/playwright-test/SKILL.md +237 -0
  238. package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/ExampleViewIT.java +143 -0
  239. package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/TC001CustomerOnboardingIT.java +144 -0
  240. package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/dramafinder-api.md +190 -0
  241. package/scripts/check-dependencies.js +26 -96
  242. package/scripts/install-spec-skills.mjs +126 -0
  243. package/scripts/validate-spec-plugin.mjs +145 -0
  244. package/src/cli/spec-cli.js +292 -0
  245. package/src/lib/spec-workflow.js +982 -0
  246. package/docs/ADR_MANAGEMENT_GUIDE.ko.md +0 -602
  247. package/docs/ADR_MANAGEMENT_GUIDE.md +0 -602
  248. package/docs/AI-WORKFLOW.ko.md +0 -299
  249. package/docs/AI-WORKFLOW.md +0 -401
  250. package/docs/API_REFERENCE_FULL.ko.md +0 -1135
  251. package/docs/API_REFERENCE_FULL.md +0 -1135
  252. package/docs/ARCHITECTURE.ko.md +0 -314
  253. package/docs/ARCHITECTURE.md +0 -314
  254. package/docs/CLI_USAGE_GUIDE.ko.md +0 -634
  255. package/docs/CLI_USAGE_GUIDE.md +0 -640
  256. package/docs/CODE_CLEANUP_GUIDE.ko.md +0 -415
  257. package/docs/CODE_CLEANUP_GUIDE.md +0 -415
  258. package/docs/COMMANDS_GUIDE.ko.md +0 -1037
  259. package/docs/COMMANDS_GUIDE.md +0 -1037
  260. package/docs/CONTRIBUTING.ko.md +0 -408
  261. package/docs/CONTRIBUTING.md +0 -408
  262. package/docs/DEVELOPMENT_GUIDE.ko.md +0 -440
  263. package/docs/DEVELOPMENT_GUIDE.md +0 -727
  264. package/docs/EXAMPLES.ko.md +0 -695
  265. package/docs/EXAMPLES.md +0 -693
  266. package/docs/GETTING_STARTED.ko.md +0 -219
  267. package/docs/GETTING_STARTED.md +0 -476
  268. package/docs/MODULE_MANAGEMENT_GUIDE.ko.md +0 -289
  269. package/docs/MODULE_MANAGEMENT_GUIDE.md +0 -289
  270. package/docs/PERFORMANCE_ARCHITECTURE.md +0 -494
  271. package/docs/PERFORMANCE_GUIDELINES.ko.md +0 -388
  272. package/docs/PERFORMANCE_GUIDELINES.md +0 -553
  273. package/docs/PRD.ko.md +0 -148
  274. package/docs/PRD.md +0 -150
  275. package/docs/ROADMAP_v0.4.0.md +0 -286
  276. package/docs/STATE_MANAGEMENT_GUIDE.ko.md +0 -278
  277. package/docs/STATE_MANAGEMENT_GUIDE.md +0 -278
  278. package/docs/TROUBLESHOOTING.ko.md +0 -366
  279. package/docs/TROUBLESHOOTING.md +0 -722
  280. package/docs/VALIDATOR_API.ko.md +0 -324
  281. package/docs/VALIDATOR_API.md +0 -324
  282. package/docs/YOLO_SYSTEM_GUIDE.ko.md +0 -542
  283. package/docs/YOLO_SYSTEM_GUIDE.md +0 -542
  284. package/docs/designs/AI_PERSONA_SYSTEM_DESIGN.md +0 -516
  285. package/docs/designs/API_DOCUMENTATION.md +0 -932
  286. package/docs/designs/API_REFERENCE.md +0 -979
  287. package/docs/designs/Enhanced_Installation_Flow_Design.md +0 -498
  288. package/docs/designs/aiwf-metadata-system-prd.md +0 -127
  289. package/docs/designs/offline-template-cache.md +0 -323
  290. package/docs/designs/persona-aware-compression.md +0 -168
  291. package/docs/guides/ai-personas-guide-ko.md +0 -239
  292. package/docs/guides/ai-personas-guide.md +0 -239
  293. package/docs/guides/checkpoint-system-guide-ko.md +0 -356
  294. package/docs/guides/checkpoint-system-guide.md +0 -356
  295. package/docs/guides/context-compression-guide-ko.md +0 -313
  296. package/docs/guides/context-compression-guide.md +0 -313
  297. package/docs/guides/independent-sprint-guide-ko.md +0 -321
  298. package/docs/guides/independent-sprint-guide.md +0 -321
  299. package/rules/global/aiwf-code-style-guide.md +0 -30
  300. package/rules/global/aiwf-coding-principles.md +0 -33
  301. package/rules/global/aiwf-development-process.md +0 -41
  302. package/rules/global/aiwf-global-rules.md +0 -84
  303. package/rules/manual/aiwf-generate-plan-docs.md +0 -280
  304. package/scripts/run-integration-tests.js +0 -317
  305. package/scripts/update-file-lists.js +0 -267
  306. package/scripts/validate-commands.js +0 -254
  307. package/src/cli/index.js +0 -184
  308. package/src/commands/sprint-independent.js +0 -393
  309. package/src/commands/state.js +0 -1164
  310. package/src/commands/yolo-config.js +0 -502
  311. package/src/config/file-lists.js +0 -147
  312. package/src/config/yolo-config-template.yaml +0 -168
  313. package/src/lib/backup-manager.js +0 -271
  314. package/src/lib/file-downloader.js +0 -304
  315. package/src/lib/installer.js +0 -1270
  316. package/src/lib/rollback-manager.js +0 -418
  317. package/src/lib/validator.js +0 -376
  318. package/src/utils/checkpoint-manager.js +0 -435
  319. package/src/utils/language-utils.js +0 -331
  320. package/src/utils/messages.js +0 -190
  321. package/src/utils/paths.js +0 -112
@@ -0,0 +1,941 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2025-2026 Simon Martinelli and the AI Unified Process contributors.
4
+ # Part of the AI Unified Process — https://unifiedprocess.ai
5
+ # Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
6
+ """Validate AI Unified Process use case specification documents.
7
+
8
+ Checks UC-*.md files against the normative format described in
9
+ references/format-spec.md. The grammar and every tolerance mirror the
10
+ Studio structured editor's parser (UseCaseSpecificationDocument.java),
11
+ which is the executable source of truth for what "parses":
12
+
13
+ - ERROR = the Studio structured editor cannot read the document
14
+ (it would open read-only in the plain markdown editor).
15
+ - WARN = Studio tolerates it, but it violates the use-case-spec skill
16
+ contract or is silently rewritten/flattened on save in Studio.
17
+
18
+ Exit code 0 when clean, 1 when any ERROR was found (with --strict also
19
+ when any WARN was found), 2 on usage errors.
20
+
21
+ Usage:
22
+ validate_use_case.py [--strict] [--quiet] FILE...
23
+ validate_use_case.py --self-test
24
+
25
+ Requires Python 3.9+, standard library only.
26
+ """
27
+
28
+ import argparse
29
+ import os
30
+ import re
31
+ import sys
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Language definitions (UseCaseLanguage.java)
35
+ # ---------------------------------------------------------------------------
36
+
37
+ LANGUAGES = {
38
+ "en": {
39
+ "headings": {
40
+ "overview": "## Overview",
41
+ "preconditions": "## Preconditions",
42
+ "main_scenario": "## Main Success Scenario",
43
+ "alternative_flows": "## Alternative Flows",
44
+ "postconditions": "## Postconditions",
45
+ "success": "### Success Postconditions",
46
+ "failure": "### Failure Postconditions",
47
+ "business_rules": "## Business Rules",
48
+ },
49
+ "fields": {
50
+ "id": "**Use Case ID:**",
51
+ "name": "**Use Case Name:**",
52
+ "actor": "**Primary Actor:**",
53
+ "secondary_actors": "**Secondary Actors:**",
54
+ "goal": "**Goal:**",
55
+ "requirements": "**Requirements:**",
56
+ "uc_trigger": "**Trigger:**",
57
+ "trigger": "**Trigger:**",
58
+ "flow": "**Flow:**",
59
+ },
60
+ "uc_trigger_aliases": ["**Trigger:**"],
61
+ "trigger_aliases": ["**Trigger:**"],
62
+ "rule_prefix": "BR",
63
+ },
64
+ "de": {
65
+ "headings": {
66
+ "overview": "## Übersicht",
67
+ "preconditions": "## Vorbedingungen",
68
+ "main_scenario": "## Hauptablauf",
69
+ "alternative_flows": "## Alternativabläufe",
70
+ "postconditions": "## Nachbedingungen",
71
+ "success": "### Erfolgsfall",
72
+ "failure": "### Fehlerfall",
73
+ "business_rules": "## Geschäftsregeln",
74
+ },
75
+ "fields": {
76
+ "id": "**Use-Case-ID:**",
77
+ "name": "**Use-Case-Name:**",
78
+ "actor": "**Primärer Akteur:**",
79
+ "secondary_actors": "**Sekundäre Akteure:**",
80
+ "goal": "**Ziel:**",
81
+ "requirements": "**Anforderungen:**",
82
+ # distinct from the alternative-flow label "Auslöser"
83
+ "uc_trigger": "**Auslösendes Ereignis:**",
84
+ "trigger": "**Auslöser:**",
85
+ "flow": "**Ablauf:**",
86
+ },
87
+ "uc_trigger_aliases": ["**Auslösendes Ereignis:**", "**Trigger:**"],
88
+ # German documents in the wild often keep the English "Trigger" label
89
+ "trigger_aliases": ["**Auslöser:**", "**Trigger:**"],
90
+ "rule_prefix": "GR",
91
+ },
92
+ }
93
+
94
+ TITLE_PREFIX = "# Use Case:"
95
+ STATUS_FIELD = "**Status:**"
96
+
97
+ # UseCaseStatus.java: English enum names and German values, matched
98
+ # case-insensitively after stripping non-letter decoration.
99
+ STATUS_VALUES = [
100
+ "Draft", "Reviewed", "Approved", "Implemented", "Tested", "Done",
101
+ "Obsolete",
102
+ "Entwurf", "Geprüft", "Genehmigt", "Implementiert", "Getestet",
103
+ "Abgeschlossen", "Obsolet",
104
+ ]
105
+
106
+ NUMBERED_ITEM = re.compile(r"(\d+)\.\s+(.*)")
107
+ ID_TITLE = re.compile(r"#\s+[SB]?UC-[A-Za-z0-9_-]+\s*:.*")
108
+ FLOW_LABEL = re.compile(r"A\d+\s*:\s*(.*)")
109
+ # Hyphenated tails like BR-USER-050 are valid Navigator-style rule ids.
110
+ RULE_LABEL = re.compile(r"(?:BR|GR)-[A-Za-z0-9_-]+\s*:\s*.*")
111
+ RULE_NUMBER = re.compile(r"(?:BR|GR)-(\d+)\s*:")
112
+ PLACEHOLDER = re.compile(r"_[^_].*_|\*[^*].*\*")
113
+ UC_ID_GRAMMAR = re.compile(r"[SB]?UC-[A-Za-z0-9_-]+")
114
+
115
+ STEP_REFERENCE = re.compile(r"\((?:step|schritt)\s*\d", re.IGNORECASE)
116
+ FLOW_TERMINATION = re.compile(
117
+ r"continues at step \d+|use case ends"
118
+ r"|wird bei schritt \d+ fortgesetzt|use case endet",
119
+ re.IGNORECASE,
120
+ )
121
+
122
+ # Implementation-level terms banned from scenario and flow steps by the
123
+ # use-case-spec skill ("Step writing guidelines").
124
+ TECHNICAL_TERMS = [
125
+ (re.compile(r"\bSMTP\b", re.IGNORECASE), "SMTP"),
126
+ (re.compile(r"\bemail server\b", re.IGNORECASE), "email server"),
127
+ (re.compile(r"\bJWT\b", re.IGNORECASE), "JWT"),
128
+ (re.compile(r"\bbcrypt\b", re.IGNORECASE), "bcrypt"),
129
+ (re.compile(r"\bhash(?:es|ed|ing)?\b", re.IGNORECASE), "hash"),
130
+ (re.compile(r"\bsalt(?:ed)?\b", re.IGNORECASE), "salt"),
131
+ (re.compile(r"\btokens?\b", re.IGNORECASE), "token"),
132
+ (re.compile(r"\bSHA-?\d*\b"), "SHA"),
133
+ (re.compile(r"\bSQL\b"), "SQL"),
134
+ (re.compile(r"\bSELECT\b"), "SELECT"),
135
+ (re.compile(r"\bINSERT\b"), "INSERT"),
136
+ ]
137
+
138
+ ERROR = "ERROR"
139
+ WARN = "WARN"
140
+
141
+
142
+ class Problem:
143
+ def __init__(self, line, severity, code, message):
144
+ self.line = line
145
+ self.severity = severity
146
+ self.code = code
147
+ self.message = message
148
+
149
+
150
+ class Document:
151
+ """The structured content collected while validating (for warn checks)."""
152
+
153
+ def __init__(self):
154
+ self.language = "en"
155
+ self.overview = {}
156
+ self.overview_seen = False
157
+ self.sections_seen = set()
158
+ self.preconditions = []
159
+ self.main_scenario = []
160
+ self.main_scenario_numbers = []
161
+ self.main_scenario_placeholder = False
162
+ self.flows = [] # dicts: heading, line, trigger, steps
163
+ self.flows_placeholder = False
164
+ self.success_post = []
165
+ self.success_placeholder = False
166
+ self.failure_post = []
167
+ self.failure_placeholder = False
168
+ self.rules = [] # dicts: heading, line
169
+ self.problems = []
170
+
171
+ def add(self, line, severity, code, message):
172
+ self.problems.append(Problem(line, severity, code, message))
173
+
174
+
175
+ # ---------------------------------------------------------------------------
176
+ # Low-level helpers (mirroring UseCaseSpecificationDocument.java)
177
+ # ---------------------------------------------------------------------------
178
+
179
+ def detect_language(lines):
180
+ scores = {"en": 0, "de": 0}
181
+ for raw in lines:
182
+ line = raw.strip()
183
+ for key, lang in LANGUAGES.items():
184
+ if line in lang["headings"].values():
185
+ scores[key] += 1
186
+ continue
187
+ markers = list(lang["fields"].values())
188
+ if any(line.startswith(field) for field in markers):
189
+ scores[key] += 1
190
+ return "de" if scores["de"] > scores["en"] else "en"
191
+
192
+
193
+ def parse_status(value):
194
+ """UseCaseStatus.parse: decoration before/after the value is ignored."""
195
+ start, end = 0, len(value)
196
+ while start < end and not value[start].isalpha():
197
+ start += 1
198
+ while end > start and not value[end - 1].isalpha():
199
+ end -= 1
200
+ candidate = value[start:end]
201
+ for status in STATUS_VALUES:
202
+ if candidate.lower().startswith(status.lower()):
203
+ if len(candidate) == len(status) or not candidate[len(status)].isalpha():
204
+ return status
205
+ return None
206
+
207
+
208
+ def skip_blank(lines, index):
209
+ while index < len(lines) and not lines[index].strip():
210
+ index += 1
211
+ return index
212
+
213
+
214
+ def is_continuation(line):
215
+ if not line.strip() or line.startswith("#") or line.startswith("**") \
216
+ or line.startswith("- "):
217
+ return False
218
+ return not NUMBERED_ITEM.fullmatch(line.strip()) or line.startswith(" ")
219
+
220
+
221
+ def join_continuation(doc, lines, index, parts):
222
+ """Joins wrapped lines into the current item; a joined line that itself
223
+ looks like a nested list item is flattened by Studio on save (WARN)."""
224
+ while index < len(lines) and is_continuation(lines[index]):
225
+ stripped = lines[index].strip()
226
+ if stripped.startswith(("- ", "* ")) or NUMBERED_ITEM.fullmatch(stripped):
227
+ doc.add(index + 1, WARN, "SUBBULLET_FLATTENED",
228
+ "nested list item is joined into the line above when "
229
+ "Studio saves the document: " + stripped)
230
+ parts.append(stripped)
231
+ index += 1
232
+ return index
233
+
234
+
235
+ def is_placeholder_start(line):
236
+ return line.strip().startswith(("_", "*"))
237
+
238
+
239
+ def read_placeholder(doc, lines, index, placeholders):
240
+ parts = [lines[index].strip()]
241
+ nxt = join_continuation(doc, lines, index + 1, parts)
242
+ text = " ".join(parts)
243
+ if PLACEHOLDER.fullmatch(text):
244
+ placeholders.append((index + 1, text))
245
+ else:
246
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", text)
247
+ return nxt
248
+
249
+
250
+ def flush_placeholders(doc, placeholders, section_empty):
251
+ """An italic paragraph standing in for an empty section is data; next to
252
+ real content it is unexpected."""
253
+ for line, text in placeholders:
254
+ if not section_empty:
255
+ doc.add(line, ERROR, "UNEXPECTED_CONTENT", text)
256
+ return bool(placeholders) and section_empty
257
+
258
+
259
+ # ---------------------------------------------------------------------------
260
+ # Section parsers
261
+ # ---------------------------------------------------------------------------
262
+
263
+ def parse_title(doc, lines):
264
+ index = skip_blank(lines, 0)
265
+ if index < len(lines) and (lines[index].startswith(TITLE_PREFIX)
266
+ or ID_TITLE.fullmatch(lines[index])):
267
+ return index + 1
268
+ found = lines[index] if index < len(lines) else ""
269
+ doc.add(index + 1, ERROR, "TITLE_MISSING",
270
+ "expected '# Use Case: <name>' (or '# UC-XXX: <name>') as the "
271
+ "first line, found: " + (found or "<end of file>"))
272
+ return index
273
+
274
+
275
+ def parse_overview(doc, lines, index):
276
+ lang = LANGUAGES[doc.language]
277
+ fields = lang["fields"]
278
+ overview = doc.overview
279
+ while index < len(lines) and not lines[index].startswith("## "):
280
+ line = lines[index]
281
+ if not line.strip():
282
+ index += 1
283
+ elif line.startswith(fields["id"]):
284
+ overview["id"] = line[len(fields["id"]):].strip()
285
+ index += 1
286
+ elif line.startswith(fields["name"]):
287
+ overview["name"] = line[len(fields["name"]):].strip()
288
+ index += 1
289
+ elif line.startswith(fields["actor"]):
290
+ overview["actor"] = line[len(fields["actor"]):].strip()
291
+ index += 1
292
+ elif line.startswith(fields["secondary_actors"]):
293
+ overview["secondary_actors"] = \
294
+ line[len(fields["secondary_actors"]):].strip()
295
+ index += 1
296
+ elif line.startswith(fields["goal"]):
297
+ parts = [line[len(fields["goal"]):].strip()]
298
+ index = join_continuation(doc, lines, index + 1, parts)
299
+ overview["goal"] = " ".join(parts)
300
+ elif line.startswith(STATUS_FIELD):
301
+ parts = [line[len(STATUS_FIELD):].strip()]
302
+ status_line = index + 1
303
+ index = join_continuation(doc, lines, index + 1, parts)
304
+ overview["status"] = " ".join(parts)
305
+ overview["status_line"] = status_line
306
+ elif line.startswith(fields["requirements"]):
307
+ overview["requirements"] = \
308
+ line[len(fields["requirements"]):].strip()
309
+ index += 1
310
+ elif any(line.startswith(a) for a in lang["uc_trigger_aliases"]):
311
+ alias = next(a for a in lang["uc_trigger_aliases"]
312
+ if line.startswith(a))
313
+ parts = [line[len(alias):].strip()]
314
+ overview["trigger_line"] = index + 1
315
+ index = join_continuation(doc, lines, index + 1, parts)
316
+ overview["trigger"] = " ".join(parts)
317
+ else:
318
+ # unknown overview lines are kept verbatim by Studio (pass-through)
319
+ index += 1
320
+ for key in ("id", "name", "actor", "goal", "status"):
321
+ if key not in overview:
322
+ label = STATUS_FIELD if key == "status" else fields[key]
323
+ doc.add(0, ERROR, "FIELD_MISSING",
324
+ "mandatory overview field missing: " + label)
325
+ return index
326
+
327
+
328
+ def parse_items(doc, lines, index, items):
329
+ """A bullet list ('- '); returns (index, placeholder_present)."""
330
+ placeholders = []
331
+ while index < len(lines) and not lines[index].startswith("#"):
332
+ line = lines[index]
333
+ if not line.strip():
334
+ index += 1
335
+ elif line.startswith("- "):
336
+ parts = [line[2:].strip()]
337
+ index = join_continuation(doc, lines, index + 1, parts)
338
+ items.append(" ".join(parts))
339
+ elif is_placeholder_start(line):
340
+ index = read_placeholder(doc, lines, index, placeholders)
341
+ else:
342
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
343
+ index += 1
344
+ return index, flush_placeholders(doc, placeholders, not items)
345
+
346
+
347
+ def parse_numbered_items(doc, lines, index, items, numbers):
348
+ placeholders = []
349
+ while index < len(lines) and not lines[index].startswith("#"):
350
+ line = lines[index]
351
+ if not line.strip():
352
+ index += 1
353
+ continue
354
+ match = NUMBERED_ITEM.fullmatch(line.strip())
355
+ if match and not line.startswith(" "):
356
+ numbers.append(int(match.group(1)))
357
+ item_line = index + 1
358
+ parts = [match.group(2).strip()]
359
+ index = join_continuation(doc, lines, index + 1, parts)
360
+ items.append((item_line, " ".join(parts)))
361
+ elif is_placeholder_start(line):
362
+ index = read_placeholder(doc, lines, index, placeholders)
363
+ else:
364
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
365
+ index += 1
366
+ return index, flush_placeholders(doc, placeholders, not items)
367
+
368
+
369
+ def is_note_start(doc, line):
370
+ lang = LANGUAGES[doc.language]
371
+ stripped = line.strip()
372
+ if stripped == lang["fields"]["flow"] \
373
+ or any(stripped.startswith(a) for a in lang["trigger_aliases"]):
374
+ return False
375
+ return stripped.startswith(("**", "_", "*", ">"))
376
+
377
+
378
+ def read_flow_notes(doc, lines, index):
379
+ index = skip_blank(lines, index)
380
+ while index < len(lines) and not lines[index].startswith("#") \
381
+ and is_note_start(doc, lines[index]) \
382
+ and not NUMBERED_ITEM.fullmatch(lines[index].strip()):
383
+ if lines[index].startswith(">"):
384
+ while index < len(lines) and lines[index].startswith(">"):
385
+ index += 1
386
+ else:
387
+ parts = [lines[index].strip()]
388
+ index = join_continuation(doc, lines, index + 1, parts)
389
+ index = skip_blank(lines, index)
390
+ return index
391
+
392
+
393
+ def parse_flow(doc, lines, index):
394
+ lang = LANGUAGES[doc.language]
395
+ line = lines[index]
396
+ if not line.strip():
397
+ return index + 1
398
+ if not line.startswith("### "):
399
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
400
+ return index + 1
401
+ heading_line = index + 1
402
+ heading = line[4:].strip()
403
+ index = read_flow_notes(doc, lines, index + 1)
404
+
405
+ trigger = None
406
+ if index < len(lines):
407
+ alias = next((a for a in lang["trigger_aliases"]
408
+ if lines[index].startswith(a)), None)
409
+ if alias:
410
+ parts = [lines[index][len(alias):].strip()]
411
+ index = join_continuation(doc, lines, index + 1, parts)
412
+ trigger = " ".join(parts)
413
+ index = read_flow_notes(doc, lines, index)
414
+ flow_field_seen = False
415
+ if index < len(lines) and lines[index].strip() == lang["fields"]["flow"]:
416
+ flow_field_seen = True
417
+ index += 1
418
+
419
+ steps = []
420
+ while index < len(lines) and not lines[index].startswith("#"):
421
+ body = lines[index]
422
+ if not body.strip():
423
+ index += 1
424
+ continue
425
+ match = NUMBERED_ITEM.fullmatch(body.strip())
426
+ if match and not body.startswith(" "):
427
+ step_line = index + 1
428
+ parts = [match.group(2).strip()]
429
+ index = join_continuation(doc, lines, index + 1, parts)
430
+ steps.append((step_line, " ".join(parts)))
431
+ elif is_note_start(doc, body):
432
+ index = read_flow_notes(doc, lines, index)
433
+ else:
434
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", body.strip())
435
+ index += 1
436
+
437
+ if trigger is None or not flow_field_seen or not steps:
438
+ missing = [name for present, name in [
439
+ (trigger is not None, "trigger line (" + lang["fields"]["trigger"] + ")"),
440
+ (flow_field_seen, "flow field line (" + lang["fields"]["flow"] + ")"),
441
+ (bool(steps), "numbered steps"),
442
+ ] if not present]
443
+ doc.add(heading_line, ERROR, "FLOW_INCOMPLETE",
444
+ "alternative flow '" + heading + "' is missing: "
445
+ + ", ".join(missing))
446
+ else:
447
+ doc.flows.append({"heading": heading, "line": heading_line,
448
+ "trigger": trigger, "steps": steps})
449
+ return index
450
+
451
+
452
+ def parse_alternative_flows(doc, lines, index):
453
+ placeholders = []
454
+ while index < len(lines) and not lines[index].startswith("## "):
455
+ line = lines[index]
456
+ if line.strip() and is_placeholder_start(line):
457
+ index = read_placeholder(doc, lines, index, placeholders)
458
+ else:
459
+ index = parse_flow(doc, lines, index)
460
+ doc.flows_placeholder = flush_placeholders(doc, placeholders,
461
+ not doc.flows)
462
+ return index
463
+
464
+
465
+ def parse_postconditions(doc, lines, index):
466
+ lang = LANGUAGES[doc.language]
467
+ placeholders = []
468
+ while index < len(lines) and not lines[index].startswith("## "):
469
+ line = lines[index]
470
+ if not line.strip():
471
+ index += 1
472
+ elif line.strip() == lang["headings"]["success"]:
473
+ doc.sections_seen.add("success")
474
+ index, doc.success_placeholder = \
475
+ parse_items(doc, lines, index + 1, doc.success_post)
476
+ elif line.strip() == lang["headings"]["failure"]:
477
+ doc.sections_seen.add("failure")
478
+ index, doc.failure_placeholder = \
479
+ parse_items(doc, lines, index + 1, doc.failure_post)
480
+ elif is_placeholder_start(line):
481
+ index = read_placeholder(doc, lines, index, placeholders)
482
+ else:
483
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
484
+ index += 1
485
+ flush_placeholders(doc, placeholders,
486
+ not doc.success_post and not doc.failure_post)
487
+ return index
488
+
489
+
490
+ def parse_rule(doc, lines, index):
491
+ line = lines[index]
492
+ if not line.strip():
493
+ return index + 1
494
+ if not line.startswith("### "):
495
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
496
+ return index + 1
497
+ doc.rules.append({"heading": line[4:].strip(), "line": index + 1})
498
+ index += 1
499
+ while index < len(lines) and not lines[index].startswith("### ") \
500
+ and not lines[index].startswith("## "):
501
+ index += 1
502
+ return index
503
+
504
+
505
+ def parse_business_rules(doc, lines, index):
506
+ placeholders = []
507
+ while index < len(lines) and not lines[index].startswith("## "):
508
+ line = lines[index]
509
+ if line.strip() and is_placeholder_start(line):
510
+ index = read_placeholder(doc, lines, index, placeholders)
511
+ else:
512
+ index = parse_rule(doc, lines, index)
513
+ flush_placeholders(doc, placeholders, not doc.rules)
514
+ return index
515
+
516
+
517
+ def parse_extra_section(lines, index):
518
+ """A section outside the template: kept verbatim by Studio."""
519
+ index += 1
520
+ while index < len(lines) and not lines[index].startswith("## "):
521
+ index += 1
522
+ return index
523
+
524
+
525
+ # ---------------------------------------------------------------------------
526
+ # Document validation
527
+ # ---------------------------------------------------------------------------
528
+
529
+ def parse_document(text):
530
+ doc = Document()
531
+ lines = [line.rstrip() for line in text.splitlines()]
532
+ doc.language = detect_language(lines)
533
+ lang = LANGUAGES[doc.language]
534
+ headings = lang["headings"]
535
+ index = parse_title(doc, lines)
536
+ while index < len(lines):
537
+ line = lines[index]
538
+ if not line.strip():
539
+ index += 1
540
+ elif line == headings["overview"]:
541
+ doc.overview_seen = True
542
+ doc.sections_seen.add("overview")
543
+ index = parse_overview(doc, lines, index + 1)
544
+ elif line == headings["preconditions"]:
545
+ doc.sections_seen.add("preconditions")
546
+ index, _ = parse_items(doc, lines, index + 1, doc.preconditions)
547
+ elif line == headings["main_scenario"]:
548
+ doc.sections_seen.add("main_scenario")
549
+ index, doc.main_scenario_placeholder = parse_numbered_items(
550
+ doc, lines, index + 1, doc.main_scenario,
551
+ doc.main_scenario_numbers)
552
+ elif line == headings["alternative_flows"]:
553
+ doc.sections_seen.add("alternative_flows")
554
+ index = parse_alternative_flows(doc, lines, index + 1)
555
+ elif line == headings["postconditions"]:
556
+ doc.sections_seen.add("postconditions")
557
+ index = parse_postconditions(doc, lines, index + 1)
558
+ elif line == headings["business_rules"]:
559
+ doc.sections_seen.add("business_rules")
560
+ index = parse_business_rules(doc, lines, index + 1)
561
+ elif line.startswith("#"):
562
+ index = parse_extra_section(lines, index)
563
+ else:
564
+ doc.add(index + 1, ERROR, "UNEXPECTED_CONTENT", line.strip())
565
+ index += 1
566
+
567
+ if not doc.overview_seen:
568
+ doc.add(0, ERROR, "OVERVIEW_MISSING",
569
+ "section missing: " + headings["overview"])
570
+ status = doc.overview.get("status")
571
+ if doc.overview_seen and status is not None and parse_status(status) is None:
572
+ doc.add(doc.overview.get("status_line", 0), ERROR, "STATUS_INVALID",
573
+ "not a recognizable status value: " + status)
574
+ return doc
575
+
576
+
577
+ def check_contract(doc, path):
578
+ """Skill-contract checks beyond what Studio's parser enforces (WARN)."""
579
+ lang = LANGUAGES[doc.language]
580
+ headings = lang["headings"]
581
+
582
+ for key in ("preconditions", "main_scenario", "alternative_flows",
583
+ "postconditions", "business_rules"):
584
+ if key not in doc.sections_seen:
585
+ doc.add(0, WARN, "SECTION_MISSING",
586
+ "template section missing: " + headings[key])
587
+ if "postconditions" in doc.sections_seen:
588
+ for key in ("success", "failure"):
589
+ if key not in doc.sections_seen:
590
+ doc.add(0, WARN, "SECTION_MISSING",
591
+ "postconditions subsection missing: " + headings[key])
592
+
593
+ uc_id = doc.overview.get("id")
594
+ if uc_id and not UC_ID_GRAMMAR.fullmatch(uc_id):
595
+ doc.add(0, WARN, "ID_GRAMMAR",
596
+ "use case id does not match [SB]?UC-[A-Za-z0-9_-]+: " + uc_id)
597
+ basename = os.path.basename(path)
598
+ if uc_id and re.match(r"[SB]?UC", basename) \
599
+ and not basename.startswith(uc_id):
600
+ doc.add(0, WARN, "ID_FILENAME_MISMATCH",
601
+ "filename does not start with the use case id " + uc_id)
602
+
603
+ # The use case trigger is optional (older documents have none), but when
604
+ # present it must name the starting event, not restate a precondition.
605
+ trigger = doc.overview.get("trigger")
606
+ if trigger is not None:
607
+ trigger_line = doc.overview.get("trigger_line", 0)
608
+ if not trigger.strip():
609
+ doc.add(trigger_line, WARN, "UC_TRIGGER_EMPTY",
610
+ "the use case trigger line names no event")
611
+ elif STEP_REFERENCE.search(trigger):
612
+ doc.add(trigger_line, WARN, "UC_TRIGGER_STEP_REF",
613
+ "the use case trigger references a step; '(step N)' "
614
+ "belongs to alternative-flow triggers only")
615
+ normalized = trigger.strip().rstrip(".").lower()
616
+ for condition in doc.preconditions:
617
+ if condition.strip().rstrip(".").lower() == normalized:
618
+ doc.add(trigger_line, WARN, "UC_TRIGGER_IS_PRECONDITION",
619
+ "the use case trigger repeats a precondition; a "
620
+ "trigger is the event that starts the use case, a "
621
+ "precondition is what is already true: " + trigger)
622
+
623
+ if not doc.main_scenario and not doc.main_scenario_placeholder \
624
+ and "main_scenario" in doc.sections_seen:
625
+ doc.add(0, WARN, "MAIN_SCENARIO_EMPTY",
626
+ "the main success scenario has no steps")
627
+ if doc.main_scenario_numbers != list(
628
+ range(1, len(doc.main_scenario_numbers) + 1)):
629
+ doc.add(0, WARN, "NUMBERING",
630
+ "main scenario step numbers are not 1..n without gaps "
631
+ "(Studio renumbers them on save)")
632
+
633
+ if not doc.flows and not doc.flows_placeholder \
634
+ and "alternative_flows" in doc.sections_seen:
635
+ doc.add(0, WARN, "NO_ALTERNATIVE_FLOWS",
636
+ "no alternative flow is defined; analyze every main-scenario "
637
+ "step for alternative or exception conditions, and when "
638
+ "there really is none, state it with an italic placeholder "
639
+ "such as '_None — no step can fail or branch._' instead of "
640
+ "inventing a flow")
641
+ for flow in doc.flows:
642
+ if not STEP_REFERENCE.search(flow["trigger"]):
643
+ doc.add(flow["line"], WARN, "TRIGGER_STEP_REF",
644
+ "trigger of '" + flow["heading"] + "' does not reference "
645
+ "a main-scenario step as '(step N)'")
646
+ if flow["steps"] and not FLOW_TERMINATION.search(flow["steps"][-1][1]):
647
+ doc.add(flow["line"], WARN, "FLOW_TERMINATION",
648
+ "'" + flow["heading"] + "' does not end with 'Use case "
649
+ "continues at step N.' or 'Use case ends.'")
650
+
651
+ if "success" in doc.sections_seen and not doc.success_post \
652
+ and not doc.success_placeholder:
653
+ doc.add(0, WARN, "POSTCONDITIONS_EMPTY",
654
+ "success postconditions are empty")
655
+ if "failure" in doc.sections_seen and not doc.failure_post \
656
+ and not doc.failure_placeholder:
657
+ doc.add(0, WARN, "POSTCONDITIONS_EMPTY",
658
+ "failure postconditions are empty")
659
+
660
+ prefix = lang["rule_prefix"]
661
+ for rule in doc.rules:
662
+ if not RULE_LABEL.fullmatch(rule["heading"]):
663
+ doc.add(rule["line"], WARN, "RULE_LABEL_MISSING",
664
+ "business rule heading has no " + prefix + "-XXX label: "
665
+ + rule["heading"])
666
+ # Rule ids are scoped to their use case: every document numbers its rules
667
+ # from BR-001 without gaps (Studio renumbers them so on save). Checked only
668
+ # when every label is plainly numeric — Navigator-style ids (BR-USER-050)
669
+ # follow their own scheme.
670
+ numbers = [RULE_NUMBER.match(rule["heading"]) for rule in doc.rules]
671
+ if numbers and all(numbers):
672
+ values = [int(match.group(1)) for match in numbers]
673
+ if values != list(range(1, len(values) + 1)):
674
+ doc.add(doc.rules[0]["line"], WARN, "RULE_NUMBERING",
675
+ "business rule ids are not " + prefix + "-001.."
676
+ + prefix + ("-%03d" % len(values)) + " without gaps "
677
+ "(ids restart per document; Studio renumbers on save)")
678
+
679
+ steps = doc.main_scenario + [s for f in doc.flows for s in f["steps"]]
680
+ for step_line, step in steps:
681
+ for pattern, term in TECHNICAL_TERMS:
682
+ if pattern.search(step):
683
+ doc.add(step_line, WARN, "TECHNICAL_TERM",
684
+ "implementation-level term '" + term + "' in step: "
685
+ + step)
686
+
687
+
688
+ def validate_file(path):
689
+ with open(path, encoding="utf-8") as handle:
690
+ text = handle.read()
691
+ doc = parse_document(text)
692
+ check_contract(doc, path)
693
+ return doc.problems
694
+
695
+
696
+ # ---------------------------------------------------------------------------
697
+ # Self test
698
+ # ---------------------------------------------------------------------------
699
+
700
+ VALID_EN = """\
701
+ # Use Case: Create Reservation
702
+
703
+ ## Overview
704
+
705
+ **Use Case ID:** UC-001
706
+ **Use Case Name:** Create Reservation
707
+ **Primary Actor:** Clerk
708
+ **Goal:** Create a reservation for a guest
709
+ **Trigger:** A guest asks the clerk to book a room
710
+ **Status:** Approved
711
+
712
+ **Requirements:** [FR-001, FR-002](../requirements.md)
713
+
714
+ ## Preconditions
715
+
716
+ - Clerk is logged into the system
717
+
718
+ ## Main Success Scenario
719
+
720
+ 1. Clerk selects "New Reservation".
721
+ 2. System displays the reservation form.
722
+ 3. Clerk confirms the reservation.
723
+ 4. System creates the reservation and displays a confirmation number.
724
+
725
+ ## Alternative Flows
726
+
727
+ ### A1: Guest Already Exists
728
+
729
+ **Trigger:** Guest email matches existing record (step 2)
730
+ **Flow:**
731
+
732
+ 1. System displays existing guest information.
733
+ 2. Use case continues at step 3.
734
+
735
+ ## Postconditions
736
+
737
+ ### Success Postconditions
738
+
739
+ - Reservation is stored
740
+
741
+ ### Failure Postconditions
742
+
743
+ - No reservation is created
744
+
745
+ ## Business Rules
746
+
747
+ ### BR-001: Minimum Stay
748
+
749
+ Reservations must be for at least one night.
750
+ """
751
+
752
+ VALID_DE_TOLERANT = """\
753
+ # UC-013a: Mitteilung erfassen
754
+
755
+ ## Übersicht
756
+
757
+ **Use-Case-ID:** UC-013a
758
+ **Use-Case-Name:** Mitteilung erfassen
759
+ **Primärer Akteur:** SachbearbeiterIn
760
+ **Sekundäre Akteure:** TeamleiterIn
761
+ **Ziel:** Eine Mitteilung erfassen und dem Team zustellen
762
+ **Auslösendes Ereignis:** Eine Kundin meldet einen Sachverhalt telefonisch
763
+ **Status:** ✅ Implementiert (2025-07-11)
764
+
765
+ **Priorität:** Hoch
766
+
767
+ ## Vorbedingungen
768
+
769
+ - Der Benutzer ist eingeloggt
770
+
771
+ ## Suchkriterien
772
+
773
+ Die Suche akzeptiert Name und Nummer.
774
+
775
+ ## Hauptablauf
776
+
777
+ 1. Der Benutzer öffnet die Erfassung.
778
+ 2. Das System speichert die Mitteilung.
779
+
780
+ ## Alternativabläufe
781
+
782
+ ### A1: Pflichtfeld fehlt
783
+
784
+ **Trigger:** Ein Pflichtfeld ist leer (Schritt 1)
785
+
786
+ > Hinweis: Die Feldliste ist konfigurierbar.
787
+
788
+ **Ablauf:**
789
+
790
+ 1. Das System zeigt eine Fehlermeldung.
791
+ 2. Der Use Case wird bei Schritt 1 fortgesetzt.
792
+
793
+ ## Nachbedingungen
794
+
795
+ ### Erfolgsfall
796
+
797
+ - Die Mitteilung ist gespeichert
798
+
799
+ ### Fehlerfall
800
+
801
+ _Keine — die Erfassung ist wiederholbar._
802
+
803
+ ## Geschäftsregeln
804
+
805
+ ### GR-001: Zustellung
806
+
807
+ Mitteilungen werden nur dem eigenen Team zugestellt.
808
+ """
809
+
810
+ INVALID = """\
811
+ # Use Case: Broken
812
+
813
+ ## Overview
814
+
815
+ **Use Case ID:** UC-002
816
+ **Use Case Name:** Broken
817
+ **Primary Actor:** User
818
+ **Goal:** Show every error class
819
+ **Trigger:** User is logged in
820
+ **Status:** In Progress
821
+
822
+ ## Preconditions
823
+
824
+ - User is logged in
825
+
826
+ The user is logged in.
827
+
828
+ ## Main Success Scenario
829
+
830
+ 1. User does something.
831
+
832
+ ## Alternative Flows
833
+
834
+ ### A1: No Trigger
835
+
836
+ **Flow:**
837
+
838
+ 1. System shows an error.
839
+
840
+ ## Postconditions
841
+
842
+ ### Success Postconditions
843
+
844
+ - Something happened
845
+
846
+ ### Failure Postconditions
847
+
848
+ - Nothing happened
849
+
850
+ ## Business Rules
851
+ """
852
+
853
+
854
+ def self_test():
855
+ failures = []
856
+
857
+ def expect(name, problems, expected_codes, forbidden_severity=None):
858
+ codes = {p.code for p in problems}
859
+ for code in expected_codes:
860
+ if code not in codes:
861
+ failures.append(name + ": expected " + code + ", got "
862
+ + str(sorted(codes)))
863
+ if forbidden_severity:
864
+ bad = [p for p in problems if p.severity == forbidden_severity]
865
+ for p in bad:
866
+ failures.append(name + ": unexpected " + p.severity + " "
867
+ + p.code + ": " + p.message)
868
+
869
+ doc = parse_document(VALID_EN)
870
+ check_contract(doc, "UC-001-create-reservation.md")
871
+ expect("valid-en", doc.problems, [], forbidden_severity=ERROR)
872
+ expect("valid-en", doc.problems, [], forbidden_severity=WARN)
873
+
874
+ doc = parse_document(VALID_DE_TOLERANT)
875
+ check_contract(doc, "UC-013a-mitteilung-erfassen.md")
876
+ if doc.language != "de":
877
+ failures.append("valid-de: language not detected as German")
878
+ expect("valid-de", doc.problems, [], forbidden_severity=ERROR)
879
+ expect("valid-de", doc.problems, [], forbidden_severity=WARN)
880
+
881
+ doc = parse_document(INVALID)
882
+ check_contract(doc, "UC-002-broken.md")
883
+ expect("invalid", doc.problems,
884
+ ["STATUS_INVALID", "UNEXPECTED_CONTENT", "FLOW_INCOMPLETE",
885
+ "UC_TRIGGER_IS_PRECONDITION"])
886
+
887
+ if failures:
888
+ for failure in failures:
889
+ print("SELF-TEST FAIL: " + failure)
890
+ return 1
891
+ print("self-test passed")
892
+ return 0
893
+
894
+
895
+ # ---------------------------------------------------------------------------
896
+ # CLI
897
+ # ---------------------------------------------------------------------------
898
+
899
+ def main(argv):
900
+ parser = argparse.ArgumentParser(
901
+ description="Validate AI Unified Process use case specifications.")
902
+ parser.add_argument("files", nargs="*", help="UC-*.md files to validate")
903
+ parser.add_argument("--strict", action="store_true",
904
+ help="treat warnings as failures")
905
+ parser.add_argument("--quiet", action="store_true",
906
+ help="print problems only, no summary")
907
+ parser.add_argument("--self-test", action="store_true",
908
+ help="run the built-in fixtures and exit")
909
+ args = parser.parse_args(argv)
910
+
911
+ if args.self_test:
912
+ return self_test()
913
+ if not args.files:
914
+ parser.print_usage()
915
+ return 2
916
+
917
+ errors = warnings = 0
918
+ for path in args.files:
919
+ try:
920
+ problems = validate_file(path)
921
+ except OSError as exc:
922
+ print(path + ": ERROR IO: " + str(exc))
923
+ errors += 1
924
+ continue
925
+ for problem in sorted(problems, key=lambda p: p.line):
926
+ print("%s:%d: %s %s: %s" % (path, problem.line, problem.severity,
927
+ problem.code, problem.message))
928
+ if problem.severity == ERROR:
929
+ errors += 1
930
+ else:
931
+ warnings += 1
932
+ if not args.quiet:
933
+ print("%d file(s) checked: %d error(s), %d warning(s)"
934
+ % (len(args.files), errors, warnings))
935
+ if errors or (args.strict and warnings):
936
+ return 1
937
+ return 0
938
+
939
+
940
+ if __name__ == "__main__":
941
+ sys.exit(main(sys.argv[1:]))