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.
- package/.claude-plugin/marketplace.json +146 -0
- package/CHANGELOG.md +49 -1
- package/README.ko.md +118 -309
- package/README.md +68 -254
- package/docs/modernization/BROWNFIELD-GREENFIELD.ko.md +105 -0
- package/docs/modernization/CLAUDE-PLAN-REVIEW-2026-10-03.ko.md +77 -0
- package/docs/modernization/CLI-PRODUCTIVITY-REVIEWED-2026-10-03.ko.md +177 -0
- package/docs/modernization/CLI-PRODUCTIVITY.ko.md +193 -0
- package/docs/modernization/CORE-PACKAGE-PLAN.md +7 -0
- package/docs/modernization/DEEP-REVERSE-ENGINEERING.ko.md +88 -0
- package/docs/modernization/DELEGATION-OPTIONAL.ko.md +57 -0
- package/docs/modernization/DIRECTION.ko.md +56 -0
- package/docs/modernization/FULL-TEST-2026-10-03.md +49 -0
- package/docs/modernization/LEGACY-REMOVAL-PLAN.md +9 -0
- package/docs/modernization/PILOT-RESULT-2026-10-03.ko.md +85 -0
- package/docs/modernization/PILOT-UC-001.ko.md +79 -0
- package/docs/modernization/PLAN.md +28 -0
- package/docs/modernization/SKILLS.ko.md +105 -0
- package/docs/modernization/SPRINTABLE.ko.md +47 -0
- package/docs/modernization/SYNC-DOCS-VALIDATION-2026-10-04.ko.md +38 -0
- package/docs/modernization/VALIDATION.md +79 -0
- package/docs/modernization/evidence/example-review-packet.json +115 -0
- package/docs/modernization/evidence/example-spec-pin.json +48 -0
- package/docs/modernization/evidence/full-test-20261003/claude-lint.md +22 -0
- package/docs/modernization/evidence/full-test-20261003/claude-review-retry.md +56 -0
- package/docs/modernization/evidence/full-test-20261003/codex-review-packet.json +159 -0
- package/docs/modernization/evidence/full-test-20261003/src/expense.mjs +40 -0
- package/docs/modernization/evidence/full-test-20261003/summary.json +106 -0
- package/docs/modernization/evidence/full-test-20261003/tests/expense.test.mjs +61 -0
- package/docs/modernization/evidence/local-pilot-20261003/README.ko.md +40 -0
- package/docs/modernization/evidence/local-pilot-20261003/execution.json +1055 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/dependencies.stdout.txt +5 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/docs.stdout.txt +6 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/example.stdout.txt +5 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/local-docs.stdout.txt +6 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.json +23 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/node.stdout.txt +535 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/provenance.stdout.txt +5 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/readme-replay.stdout.txt +273 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.json +24 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/maintenance/upstream.stdout.txt +7 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-service.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-service.stdout.txt +148 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-structure.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/archive-structure.stdout.txt +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-check.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-check.stdout.txt +67 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-evidence.json +34 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.json +128 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-packet.stdout.txt +128 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.json +54 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-pin.stdout.txt +60 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-readback.json +15 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-service.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-service.stdout.txt +82 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-structure.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/baseline-structure.stdout.txt +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-check.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-check.stdout.txt +79 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-packet-refused.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-packet-refused.stdout.txt +7 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin-refresh.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin-refresh.stdout.txt +66 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-pin.json +60 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-structure.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/changed-structure.stdout.txt +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-check.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-check.stdout.txt +73 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-evidence.json +34 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.json +134 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-packet.stdout.txt +134 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-readback.json +15 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-service.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-service.stdout.txt +148 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-structure.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/final-structure.stdout.txt +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/node-version.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/node-version.stdout.txt +1 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/python-version.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/python-version.stdout.txt +1 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-evidence.json +34 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.json +134 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-packet.stdout.txt +134 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-readback.json +15 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-service.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-service.stdout.txt +266 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-structure.stderr.txt +0 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/artifacts/red-structure.stdout.txt +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/entity_model.md +33 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/glossary.md +9 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/plans/UC-001.md +20 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/requirements.md +10 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/test_cases/TC-001-submit-expense.md +37 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/test_cases/TC-002-description-limit.md +42 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/use_cases/UC-001-submit-expense.md +76 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/use_cases.puml +12 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/docs/vision.md +21 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/src/expense.mjs +48 -0
- package/docs/modernization/evidence/local-pilot-20261003/project/tests/expense.test.mjs +116 -0
- package/docs/modernization/evidence/local-pilot-20261003/scope.json +31 -0
- package/docs/modernization/reviews/2026-10-03-claude-cli/CONTRACTS.ko.md +122 -0
- package/docs/modernization/reviews/2026-10-03-claude-cli/DIRECTION.ko.md +84 -0
- package/examples/spec-workflow/README.md +54 -0
- package/examples/spec-workflow/docs/entity_model.md +33 -0
- package/examples/spec-workflow/docs/glossary.md +9 -0
- package/examples/spec-workflow/docs/requirements.md +10 -0
- package/examples/spec-workflow/docs/test_cases/TC-001-submit-expense.md +37 -0
- package/examples/spec-workflow/docs/use_cases/UC-001-submit-expense.md +63 -0
- package/examples/spec-workflow/docs/use_cases.puml +12 -0
- package/examples/spec-workflow/docs/vision.md +21 -0
- package/package.json +37 -105
- package/plugins/aiwf-angular-jpa/.claude-plugin/plugin.json +11 -0
- package/plugins/aiwf-angular-jpa/LICENSE +201 -0
- package/plugins/aiwf-angular-jpa/NOTICE +16 -0
- package/plugins/aiwf-angular-jpa/README.md +54 -0
- package/plugins/aiwf-angular-jpa/UPSTREAM.json +35 -0
- package/plugins/aiwf-angular-jpa/agents/uc-coverage.md +263 -0
- package/plugins/aiwf-angular-jpa/rules/mcp-servers.md +64 -0
- package/plugins/aiwf-angular-jpa/skills/coverage-check/SKILL.md +190 -0
- package/plugins/aiwf-angular-jpa/skills/flyway-migration/SKILL.md +119 -0
- package/plugins/aiwf-angular-jpa/skills/implement/SKILL.md +528 -0
- package/plugins/aiwf-angular-jpa/skills/implement/references/module-layout.md +95 -0
- package/plugins/aiwf-angular-jpa/skills/playwright-test/SKILL.md +360 -0
- package/plugins/aiwf-angular-jpa/skills/spring-boot-test/SKILL.md +504 -0
- package/plugins/aiwf-angular-jpa/skills/vitest-test/SKILL.md +309 -0
- package/plugins/aiwf-blazor-dotnet/.claude-plugin/plugin.json +11 -0
- package/plugins/aiwf-blazor-dotnet/LICENSE +201 -0
- package/plugins/aiwf-blazor-dotnet/NOTICE +16 -0
- package/plugins/aiwf-blazor-dotnet/README.md +53 -0
- package/plugins/aiwf-blazor-dotnet/UPSTREAM.json +31 -0
- package/plugins/aiwf-blazor-dotnet/rules/mcp-servers.md +25 -0
- package/plugins/aiwf-blazor-dotnet/skills/bunit-test/SKILL.md +110 -0
- package/plugins/aiwf-blazor-dotnet/skills/dotnet-test/SKILL.md +108 -0
- package/plugins/aiwf-blazor-dotnet/skills/ef-migration/SKILL.md +43 -0
- package/plugins/aiwf-blazor-dotnet/skills/implement/SKILL.md +149 -0
- package/plugins/aiwf-blazor-dotnet/skills/playwright-test/SKILL.md +130 -0
- package/plugins/aiwf-core/.claude-plugin/plugin.json +11 -0
- package/plugins/aiwf-core/LICENSE +201 -0
- package/plugins/aiwf-core/NOTICE +20 -0
- package/plugins/aiwf-core/README.md +38 -0
- package/plugins/aiwf-core/UPSTREAM.json +50 -0
- package/plugins/aiwf-core/skills/entity-model/SKILL.md +160 -0
- package/plugins/aiwf-core/skills/entity-model/references/REFERENCE.md +36 -0
- package/plugins/aiwf-core/skills/requirements/SKILL.md +157 -0
- package/plugins/aiwf-core/skills/requirements/references/REFERENCE.md +70 -0
- package/plugins/aiwf-core/skills/requirements/references/glossary.md +7 -0
- package/plugins/aiwf-core/skills/reverse-engineer/SKILL.md +509 -0
- package/plugins/aiwf-core/skills/reverse-engineer/references/stack-signals.md +210 -0
- package/plugins/aiwf-core/skills/spec-review/SKILL.md +197 -0
- package/plugins/aiwf-core/skills/spec-review/references/lint-codes.md +63 -0
- package/plugins/aiwf-core/skills/spec-review/references/review-checklist.md +189 -0
- package/plugins/aiwf-core/skills/spec-review/scripts/spec_lint.py +1216 -0
- package/plugins/aiwf-core/skills/test-case/SKILL.md +138 -0
- package/plugins/aiwf-core/skills/test-case/references/example-process.bpmn +118 -0
- package/plugins/aiwf-core/skills/test-case/references/example.md +38 -0
- package/plugins/aiwf-core/skills/test-case/references/test-case.md +35 -0
- package/plugins/aiwf-core/skills/test-case/scripts/bpmn_paths.py +542 -0
- package/plugins/aiwf-core/skills/use-case-diagram/SKILL.md +99 -0
- package/plugins/aiwf-core/skills/use-case-spec/SKILL.md +257 -0
- package/plugins/aiwf-core/skills/use-case-spec/references/clarify-checklist.md +70 -0
- package/plugins/aiwf-core/skills/use-case-spec/references/example.md +88 -0
- package/plugins/aiwf-core/skills/use-case-spec/references/format-spec.md +246 -0
- package/plugins/aiwf-core/skills/use-case-spec/references/use-case.md +50 -0
- package/plugins/aiwf-core/skills/use-case-spec/scripts/validate_use_case.py +941 -0
- package/plugins/aiwf-delegate-claude/.claude-plugin/plugin.json +9 -0
- package/plugins/aiwf-delegate-claude/LICENSE +21 -0
- package/plugins/aiwf-delegate-claude/NOTICE +4 -0
- package/plugins/aiwf-delegate-claude/README.md +10 -0
- package/plugins/aiwf-delegate-claude/plugin.json +13 -0
- package/plugins/aiwf-delegate-claude/skills/delegate-claude/LICENSE +21 -0
- package/plugins/aiwf-delegate-claude/skills/delegate-claude/NOTICE +4 -0
- package/plugins/aiwf-delegate-claude/skills/delegate-claude/SKILL.md +49 -0
- package/plugins/aiwf-delegate-claude/skills/delegate-claude/agents/openai.yaml +2 -0
- package/plugins/aiwf-delegate-codex/.claude-plugin/plugin.json +9 -0
- package/plugins/aiwf-delegate-codex/LICENSE +21 -0
- package/plugins/aiwf-delegate-codex/NOTICE +4 -0
- package/plugins/aiwf-delegate-codex/README.md +10 -0
- package/plugins/aiwf-delegate-codex/plugin.json +13 -0
- package/plugins/aiwf-delegate-codex/skills/delegate-codex/LICENSE +21 -0
- package/plugins/aiwf-delegate-codex/skills/delegate-codex/NOTICE +4 -0
- package/plugins/aiwf-delegate-codex/skills/delegate-codex/SKILL.md +47 -0
- package/plugins/aiwf-delegate-codex/skills/delegate-codex/agents/openai.yaml +2 -0
- package/plugins/aiwf-nestjs-nextjs/.claude-plugin/plugin.json +11 -0
- package/plugins/aiwf-nestjs-nextjs/LICENSE +201 -0
- package/plugins/aiwf-nestjs-nextjs/NOTICE +16 -0
- package/plugins/aiwf-nestjs-nextjs/README.md +53 -0
- package/plugins/aiwf-nestjs-nextjs/UPSTREAM.json +32 -0
- package/plugins/aiwf-nestjs-nextjs/rules/mcp-servers.md +59 -0
- package/plugins/aiwf-nestjs-nextjs/skills/drizzle-migration/SKILL.md +222 -0
- package/plugins/aiwf-nestjs-nextjs/skills/implement/SKILL.md +393 -0
- package/plugins/aiwf-nestjs-nextjs/skills/implement/references/project-layout.md +158 -0
- package/plugins/aiwf-nestjs-nextjs/skills/nest-test/SKILL.md +300 -0
- package/plugins/aiwf-nestjs-nextjs/skills/playwright-test/SKILL.md +245 -0
- package/plugins/aiwf-nestjs-nextjs/skills/react-test/SKILL.md +217 -0
- package/plugins/aiwf-spec/.claude-plugin/plugin.json +9 -0
- package/plugins/aiwf-spec/LICENSE +201 -0
- package/plugins/aiwf-spec/NOTICE +20 -0
- package/plugins/aiwf-spec/README.md +36 -0
- package/plugins/aiwf-spec/skills/sync-docs/SKILL.md +47 -0
- package/plugins/aiwf-spec/skills/workflow/SKILL.md +42 -0
- package/plugins/aiwf-vaadin-jooq/.claude-plugin/plugin.json +11 -0
- package/plugins/aiwf-vaadin-jooq/LICENSE +201 -0
- package/plugins/aiwf-vaadin-jooq/NOTICE +16 -0
- package/plugins/aiwf-vaadin-jooq/README.md +56 -0
- package/plugins/aiwf-vaadin-jooq/UPSTREAM.json +43 -0
- package/plugins/aiwf-vaadin-jooq/agents/uc-coverage.md +260 -0
- package/plugins/aiwf-vaadin-jooq/rules/mcp-servers.md +44 -0
- package/plugins/aiwf-vaadin-jooq/skills/browserless-test/SKILL.md +407 -0
- package/plugins/aiwf-vaadin-jooq/skills/browserless-test/references/UC001ManagePersonsTest.java +111 -0
- package/plugins/aiwf-vaadin-jooq/skills/coverage-check/SKILL.md +190 -0
- package/plugins/aiwf-vaadin-jooq/skills/flyway-migration/SKILL.md +70 -0
- package/plugins/aiwf-vaadin-jooq/skills/hilla-test/SKILL.md +350 -0
- package/plugins/aiwf-vaadin-jooq/skills/hilla-test/references/UC001ManagePersonsServiceTest.java +81 -0
- package/plugins/aiwf-vaadin-jooq/skills/hilla-test/references/UC001ManagePersonsViewTest.tsx +87 -0
- package/plugins/aiwf-vaadin-jooq/skills/implement/SKILL.md +196 -0
- package/plugins/aiwf-vaadin-jooq/skills/implement-hilla/SKILL.md +216 -0
- package/plugins/aiwf-vaadin-jooq/skills/karibu-test/SKILL.md +298 -0
- package/plugins/aiwf-vaadin-jooq/skills/karibu-test/references/UC001ManagePersonsTest.java +93 -0
- package/plugins/aiwf-vaadin-jooq/skills/playwright-test/SKILL.md +237 -0
- package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/ExampleViewIT.java +143 -0
- package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/TC001CustomerOnboardingIT.java +144 -0
- package/plugins/aiwf-vaadin-jooq/skills/playwright-test/references/dramafinder-api.md +190 -0
- package/scripts/check-dependencies.js +26 -96
- package/scripts/install-spec-skills.mjs +126 -0
- package/scripts/validate-spec-plugin.mjs +145 -0
- package/src/cli/spec-cli.js +292 -0
- package/src/lib/spec-workflow.js +982 -0
- package/docs/ADR_MANAGEMENT_GUIDE.ko.md +0 -602
- package/docs/ADR_MANAGEMENT_GUIDE.md +0 -602
- package/docs/AI-WORKFLOW.ko.md +0 -299
- package/docs/AI-WORKFLOW.md +0 -401
- package/docs/API_REFERENCE_FULL.ko.md +0 -1135
- package/docs/API_REFERENCE_FULL.md +0 -1135
- package/docs/ARCHITECTURE.ko.md +0 -314
- package/docs/ARCHITECTURE.md +0 -314
- package/docs/CLI_USAGE_GUIDE.ko.md +0 -634
- package/docs/CLI_USAGE_GUIDE.md +0 -640
- package/docs/CODE_CLEANUP_GUIDE.ko.md +0 -415
- package/docs/CODE_CLEANUP_GUIDE.md +0 -415
- package/docs/COMMANDS_GUIDE.ko.md +0 -1037
- package/docs/COMMANDS_GUIDE.md +0 -1037
- package/docs/CONTRIBUTING.ko.md +0 -408
- package/docs/CONTRIBUTING.md +0 -408
- package/docs/DEVELOPMENT_GUIDE.ko.md +0 -440
- package/docs/DEVELOPMENT_GUIDE.md +0 -727
- package/docs/EXAMPLES.ko.md +0 -695
- package/docs/EXAMPLES.md +0 -693
- package/docs/GETTING_STARTED.ko.md +0 -219
- package/docs/GETTING_STARTED.md +0 -476
- package/docs/MODULE_MANAGEMENT_GUIDE.ko.md +0 -289
- package/docs/MODULE_MANAGEMENT_GUIDE.md +0 -289
- package/docs/PERFORMANCE_ARCHITECTURE.md +0 -494
- package/docs/PERFORMANCE_GUIDELINES.ko.md +0 -388
- package/docs/PERFORMANCE_GUIDELINES.md +0 -553
- package/docs/PRD.ko.md +0 -148
- package/docs/PRD.md +0 -150
- package/docs/ROADMAP_v0.4.0.md +0 -286
- package/docs/STATE_MANAGEMENT_GUIDE.ko.md +0 -278
- package/docs/STATE_MANAGEMENT_GUIDE.md +0 -278
- package/docs/TROUBLESHOOTING.ko.md +0 -366
- package/docs/TROUBLESHOOTING.md +0 -722
- package/docs/VALIDATOR_API.ko.md +0 -324
- package/docs/VALIDATOR_API.md +0 -324
- package/docs/YOLO_SYSTEM_GUIDE.ko.md +0 -542
- package/docs/YOLO_SYSTEM_GUIDE.md +0 -542
- package/docs/designs/AI_PERSONA_SYSTEM_DESIGN.md +0 -516
- package/docs/designs/API_DOCUMENTATION.md +0 -932
- package/docs/designs/API_REFERENCE.md +0 -979
- package/docs/designs/Enhanced_Installation_Flow_Design.md +0 -498
- package/docs/designs/aiwf-metadata-system-prd.md +0 -127
- package/docs/designs/offline-template-cache.md +0 -323
- package/docs/designs/persona-aware-compression.md +0 -168
- package/docs/guides/ai-personas-guide-ko.md +0 -239
- package/docs/guides/ai-personas-guide.md +0 -239
- package/docs/guides/checkpoint-system-guide-ko.md +0 -356
- package/docs/guides/checkpoint-system-guide.md +0 -356
- package/docs/guides/context-compression-guide-ko.md +0 -313
- package/docs/guides/context-compression-guide.md +0 -313
- package/docs/guides/independent-sprint-guide-ko.md +0 -321
- package/docs/guides/independent-sprint-guide.md +0 -321
- package/rules/global/aiwf-code-style-guide.md +0 -30
- package/rules/global/aiwf-coding-principles.md +0 -33
- package/rules/global/aiwf-development-process.md +0 -41
- package/rules/global/aiwf-global-rules.md +0 -84
- package/rules/manual/aiwf-generate-plan-docs.md +0 -280
- package/scripts/run-integration-tests.js +0 -317
- package/scripts/update-file-lists.js +0 -267
- package/scripts/validate-commands.js +0 -254
- package/src/cli/index.js +0 -184
- package/src/commands/sprint-independent.js +0 -393
- package/src/commands/state.js +0 -1164
- package/src/commands/yolo-config.js +0 -502
- package/src/config/file-lists.js +0 -147
- package/src/config/yolo-config-template.yaml +0 -168
- package/src/lib/backup-manager.js +0 -271
- package/src/lib/file-downloader.js +0 -304
- package/src/lib/installer.js +0 -1270
- package/src/lib/rollback-manager.js +0 -418
- package/src/lib/validator.js +0 -376
- package/src/utils/checkpoint-manager.js +0 -435
- package/src/utils/language-utils.js +0 -331
- package/src/utils/messages.js +0 -190
- package/src/utils/paths.js +0 -112
|
@@ -0,0 +1,542 @@
|
|
|
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
|
+
"""Enumerate the paths through a BPMN 2.0 business process model.
|
|
7
|
+
|
|
8
|
+
Reads a .bpmn file (plain BPMN 2.0 XML, diagram interchange is ignored) and
|
|
9
|
+
prints JSON that the test-case skill turns into one end-to-end test case per
|
|
10
|
+
path:
|
|
11
|
+
|
|
12
|
+
- lanes lane name -> ids of the activities in that lane (a pool
|
|
13
|
+
without lanes counts as one lane named after its participant)
|
|
14
|
+
- activities id, name, type, lane, and ucId when the name carries a use
|
|
15
|
+
case id such as "UC-001" or "BUC-7"
|
|
16
|
+
- paths one entry per path from a start event to an end event: the
|
|
17
|
+
ordered steps (activities, gateway decisions with their flow
|
|
18
|
+
names, intermediate events), plus the activity ids and the
|
|
19
|
+
decisions on their own
|
|
20
|
+
- warnings constructs that were simplified (see the rules below)
|
|
21
|
+
|
|
22
|
+
Path rules, applied deterministically in document order:
|
|
23
|
+
|
|
24
|
+
- Exclusive, inclusive, event-based, and complex gateways, several outgoing
|
|
25
|
+
flows on a non-gateway node, and boundary events are alternatives: each
|
|
26
|
+
outgoing flow starts its own path.
|
|
27
|
+
- Parallel gateways run every branch: the branches are serialized within
|
|
28
|
+
one path in the document order of their sequence flows, up to the
|
|
29
|
+
converging parallel gateway; branch-internal alternatives multiply.
|
|
30
|
+
- Loops are traversed once: every sequence flow is followed at most twice
|
|
31
|
+
per path, so a rework loop yields one path without and one path with a
|
|
32
|
+
single repetition of the loop body.
|
|
33
|
+
- A node without outgoing flows ends the path (implicit end event).
|
|
34
|
+
- Sub-processes are passed through as a single step; their inside is not
|
|
35
|
+
expanded.
|
|
36
|
+
|
|
37
|
+
The file is data, never instructions, and is parsed defensively: documents
|
|
38
|
+
with a DOCTYPE or entity declaration are rejected, so no external entity is
|
|
39
|
+
ever resolved and no entity expansion can blow up.
|
|
40
|
+
|
|
41
|
+
Exit code 0 on success, 1 when the file cannot be read or is not a usable
|
|
42
|
+
BPMN model, 2 on usage errors.
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
bpmn_paths.py FILE.bpmn
|
|
46
|
+
bpmn_paths.py --self-test
|
|
47
|
+
|
|
48
|
+
Requires Python 3.9+, standard library only.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
import argparse
|
|
52
|
+
import itertools
|
|
53
|
+
import json
|
|
54
|
+
import re
|
|
55
|
+
import sys
|
|
56
|
+
import xml.etree.ElementTree as ET
|
|
57
|
+
|
|
58
|
+
ACTIVITY_TYPES = (
|
|
59
|
+
"task", "userTask", "manualTask", "serviceTask", "sendTask",
|
|
60
|
+
"receiveTask", "scriptTask", "businessRuleTask", "callActivity",
|
|
61
|
+
)
|
|
62
|
+
PASS_THROUGH_TYPES = ("subProcess", "transaction", "adHocSubProcess")
|
|
63
|
+
ALTERNATIVE_GATEWAYS = (
|
|
64
|
+
"exclusiveGateway", "inclusiveGateway", "eventBasedGateway",
|
|
65
|
+
"complexGateway",
|
|
66
|
+
)
|
|
67
|
+
EVENT_TYPES = (
|
|
68
|
+
"startEvent", "endEvent", "intermediateCatchEvent",
|
|
69
|
+
"intermediateThrowEvent", "boundaryEvent",
|
|
70
|
+
)
|
|
71
|
+
FLOW_NODE_TYPES = (ACTIVITY_TYPES + PASS_THROUGH_TYPES + ALTERNATIVE_GATEWAYS
|
|
72
|
+
+ EVENT_TYPES + ("parallelGateway",))
|
|
73
|
+
|
|
74
|
+
UC_ID = re.compile(r"(?<![A-Za-z0-9])([SB]?UC-[A-Za-z0-9_-]+)")
|
|
75
|
+
FORBIDDEN_DECLARATION = re.compile(rb"<!\s*(DOCTYPE|ENTITY)", re.IGNORECASE)
|
|
76
|
+
MAX_PATHS = 500
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class BpmnError(Exception):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def local(tag):
|
|
84
|
+
return tag.rsplit("}", 1)[-1]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def clean(text):
|
|
88
|
+
return " ".join((text or "").split())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def uc_id(name):
|
|
92
|
+
match = UC_ID.search(name)
|
|
93
|
+
return match.group(1).rstrip("-_") if match else None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Parsing
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def parse_xml(data):
|
|
101
|
+
if FORBIDDEN_DECLARATION.search(data):
|
|
102
|
+
raise BpmnError("DOCTYPE or ENTITY declarations are not allowed "
|
|
103
|
+
"in a BPMN file")
|
|
104
|
+
try:
|
|
105
|
+
root = ET.fromstring(data)
|
|
106
|
+
except ET.ParseError as exc:
|
|
107
|
+
raise BpmnError("not well-formed XML: " + str(exc))
|
|
108
|
+
if local(root.tag) != "definitions":
|
|
109
|
+
raise BpmnError("root element is <" + local(root.tag)
|
|
110
|
+
+ ">, expected BPMN <definitions>")
|
|
111
|
+
return root
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Process:
|
|
115
|
+
def __init__(self, element, pool_name):
|
|
116
|
+
self.id = element.get("id", "")
|
|
117
|
+
self.nodes = {} # id -> (type, name), direct children only
|
|
118
|
+
self.order = [] # node ids in document order
|
|
119
|
+
self.flows = [] # (id, name, source, target) in document order
|
|
120
|
+
self.boundaries = {} # activity id -> [boundary event ids]
|
|
121
|
+
self.lane_of = {} # node id -> lane name
|
|
122
|
+
for child in element:
|
|
123
|
+
kind = local(child.tag)
|
|
124
|
+
node_id = child.get("id")
|
|
125
|
+
if kind in FLOW_NODE_TYPES and node_id:
|
|
126
|
+
self.nodes[node_id] = (kind, clean(child.get("name")))
|
|
127
|
+
self.order.append(node_id)
|
|
128
|
+
if kind == "boundaryEvent" and child.get("attachedToRef"):
|
|
129
|
+
self.boundaries.setdefault(
|
|
130
|
+
child.get("attachedToRef"), []).append(node_id)
|
|
131
|
+
elif kind == "sequenceFlow":
|
|
132
|
+
self.flows.append((child.get("id", ""),
|
|
133
|
+
clean(child.get("name")),
|
|
134
|
+
child.get("sourceRef"),
|
|
135
|
+
child.get("targetRef")))
|
|
136
|
+
elif kind == "laneSet":
|
|
137
|
+
self._read_lanes(child)
|
|
138
|
+
if not self.lane_of and pool_name:
|
|
139
|
+
for node_id in self.order:
|
|
140
|
+
self.lane_of[node_id] = pool_name
|
|
141
|
+
self.outgoing = {}
|
|
142
|
+
self.incoming = {}
|
|
143
|
+
for flow in self.flows:
|
|
144
|
+
if flow[2] in self.nodes and flow[3] in self.nodes:
|
|
145
|
+
self.outgoing.setdefault(flow[2], []).append(flow)
|
|
146
|
+
self.incoming.setdefault(flow[3], []).append(flow)
|
|
147
|
+
|
|
148
|
+
def _read_lanes(self, lane_set):
|
|
149
|
+
# Nested lanes: the innermost lane wins because it is visited last.
|
|
150
|
+
for lane in lane_set:
|
|
151
|
+
if local(lane.tag) != "lane":
|
|
152
|
+
continue
|
|
153
|
+
name = clean(lane.get("name")) or lane.get("id", "")
|
|
154
|
+
for ref in lane:
|
|
155
|
+
if local(ref.tag) == "flowNodeRef" and ref.text:
|
|
156
|
+
self.lane_of[ref.text.strip()] = name
|
|
157
|
+
elif local(ref.tag) == "childLaneSet":
|
|
158
|
+
self._read_lanes(ref)
|
|
159
|
+
|
|
160
|
+
def kind(self, node_id):
|
|
161
|
+
return self.nodes[node_id][0]
|
|
162
|
+
|
|
163
|
+
def name(self, node_id):
|
|
164
|
+
return self.nodes[node_id][1]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
# Path enumeration
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
|
|
171
|
+
class Walker:
|
|
172
|
+
"""Enumerates (steps, terminal, followed flows) for one process.
|
|
173
|
+
|
|
174
|
+
terminal is ("end", node id) or ("join", parallel gateway id); a join
|
|
175
|
+
terminal is only produced inside a parallel branch and is consumed by
|
|
176
|
+
the split that started the branch.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self, process, warnings):
|
|
180
|
+
self.p = process
|
|
181
|
+
self.warnings = warnings
|
|
182
|
+
self.truncated = False
|
|
183
|
+
|
|
184
|
+
def warn(self, message):
|
|
185
|
+
if message not in self.warnings:
|
|
186
|
+
self.warnings.append(message)
|
|
187
|
+
|
|
188
|
+
def step(self, node_id):
|
|
189
|
+
kind = self.p.kind(node_id)
|
|
190
|
+
entry = {"id": node_id, "name": self.p.name(node_id), "type": kind}
|
|
191
|
+
if kind in ACTIVITY_TYPES:
|
|
192
|
+
entry["kind"] = "activity"
|
|
193
|
+
elif kind in PASS_THROUGH_TYPES:
|
|
194
|
+
entry["kind"] = "subprocess"
|
|
195
|
+
else:
|
|
196
|
+
entry["kind"] = "event"
|
|
197
|
+
return entry
|
|
198
|
+
|
|
199
|
+
def choices(self, node_id):
|
|
200
|
+
"""Outgoing alternatives: sequence flows plus attached boundary events."""
|
|
201
|
+
result = [(flow, flow[3]) for flow in self.p.outgoing.get(node_id, [])]
|
|
202
|
+
for boundary in self.p.boundaries.get(node_id, []):
|
|
203
|
+
result.append((None, boundary))
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
def walk(self, node_id, used, in_split):
|
|
207
|
+
kind = self.p.kind(node_id)
|
|
208
|
+
|
|
209
|
+
if kind == "parallelGateway":
|
|
210
|
+
if len(self.p.incoming.get(node_id, [])) > 1 and in_split:
|
|
211
|
+
return [([], ("join", node_id), used)]
|
|
212
|
+
outgoing = self.p.outgoing.get(node_id, [])
|
|
213
|
+
if len(outgoing) > 1:
|
|
214
|
+
return self.split(node_id, outgoing, used, in_split)
|
|
215
|
+
return self.follow(node_id, [], outgoing, used, in_split, None)
|
|
216
|
+
|
|
217
|
+
if kind in ALTERNATIVE_GATEWAYS:
|
|
218
|
+
return self.follow(node_id, [], self.p.outgoing.get(node_id, []),
|
|
219
|
+
used, in_split, node_id)
|
|
220
|
+
|
|
221
|
+
prefix = [self.step(node_id)]
|
|
222
|
+
if kind == "endEvent":
|
|
223
|
+
return [(prefix, ("end", node_id), used)]
|
|
224
|
+
if kind in PASS_THROUGH_TYPES:
|
|
225
|
+
self.warn("sub-process " + node_id
|
|
226
|
+
+ " is passed through as one step; its inside is "
|
|
227
|
+
"not expanded")
|
|
228
|
+
choices = self.choices(node_id)
|
|
229
|
+
if not choices:
|
|
230
|
+
self.warn("node " + node_id + " has no outgoing flow and ends "
|
|
231
|
+
"the path as an implicit end event")
|
|
232
|
+
return [(prefix, ("end", node_id), used)]
|
|
233
|
+
results = []
|
|
234
|
+
for flow, target in choices:
|
|
235
|
+
if flow is None:
|
|
236
|
+
decision = {"kind": "decision", "gateway": node_id,
|
|
237
|
+
"gatewayName": self.p.name(node_id),
|
|
238
|
+
"flow": None,
|
|
239
|
+
"flowName": "boundary: "
|
|
240
|
+
+ (self.p.name(target) or target),
|
|
241
|
+
"target": target}
|
|
242
|
+
for steps, terminal, u in self.walk(target, used, in_split):
|
|
243
|
+
results.append((prefix + [decision] + steps, terminal, u))
|
|
244
|
+
else:
|
|
245
|
+
decision_at = node_id if len(choices) > 1 else None
|
|
246
|
+
results.extend(self.follow(node_id, prefix, [flow], used,
|
|
247
|
+
in_split, decision_at))
|
|
248
|
+
self.check_limit(results)
|
|
249
|
+
return results
|
|
250
|
+
|
|
251
|
+
def follow(self, node_id, prefix, flows, used, in_split, decision_at):
|
|
252
|
+
"""Continue along each flow; records a decision when decision_at is set."""
|
|
253
|
+
if not flows:
|
|
254
|
+
self.warn("node " + node_id + " has no outgoing flow and ends "
|
|
255
|
+
"the path")
|
|
256
|
+
return [(prefix, ("end", node_id), used)]
|
|
257
|
+
alternatives = decision_at is not None and len(flows) > 1
|
|
258
|
+
if decision_at is not None and not alternatives and \
|
|
259
|
+
self.p.kind(node_id) in ALTERNATIVE_GATEWAYS:
|
|
260
|
+
decision_at = None # converging gateway: no decision
|
|
261
|
+
results = []
|
|
262
|
+
for flow in flows:
|
|
263
|
+
flow_id, flow_name, _, target = flow
|
|
264
|
+
if used.count(flow_id) >= 2:
|
|
265
|
+
continue # loop already traversed once
|
|
266
|
+
head = list(prefix)
|
|
267
|
+
if decision_at is not None:
|
|
268
|
+
head.append({"kind": "decision", "gateway": decision_at,
|
|
269
|
+
"gatewayName": self.p.name(decision_at),
|
|
270
|
+
"flow": flow_id, "flowName": flow_name,
|
|
271
|
+
"target": target})
|
|
272
|
+
for steps, terminal, u in self.walk(target, used + (flow_id,),
|
|
273
|
+
in_split):
|
|
274
|
+
results.append((head + steps, terminal, u))
|
|
275
|
+
self.check_limit(results)
|
|
276
|
+
return results
|
|
277
|
+
|
|
278
|
+
def split(self, node_id, outgoing, used, in_split):
|
|
279
|
+
branches = []
|
|
280
|
+
for flow in outgoing:
|
|
281
|
+
if used.count(flow[0]) >= 2:
|
|
282
|
+
continue
|
|
283
|
+
branches.append(self.walk(flow[3], used + (flow[0],),
|
|
284
|
+
in_split + 1))
|
|
285
|
+
branches = [b for b in branches if b]
|
|
286
|
+
if not branches:
|
|
287
|
+
return []
|
|
288
|
+
results = []
|
|
289
|
+
for combination in itertools.product(*branches):
|
|
290
|
+
steps = []
|
|
291
|
+
merged = used
|
|
292
|
+
joins = []
|
|
293
|
+
last_end = None
|
|
294
|
+
for branch_steps, terminal, u in combination:
|
|
295
|
+
steps.extend(branch_steps)
|
|
296
|
+
merged += u[len(used):]
|
|
297
|
+
if terminal[0] == "join":
|
|
298
|
+
if terminal[1] not in joins:
|
|
299
|
+
joins.append(terminal[1])
|
|
300
|
+
else:
|
|
301
|
+
last_end = terminal
|
|
302
|
+
if not joins:
|
|
303
|
+
results.append((steps, last_end, merged))
|
|
304
|
+
continue
|
|
305
|
+
if len(joins) > 1:
|
|
306
|
+
self.warn("parallel gateway " + node_id + " converges in "
|
|
307
|
+
"several gateways; continuing after " + joins[0])
|
|
308
|
+
join = joins[0]
|
|
309
|
+
for rest, terminal, u in self.follow(
|
|
310
|
+
join, [], self.p.outgoing.get(join, []), merged,
|
|
311
|
+
in_split, None):
|
|
312
|
+
results.append((steps + rest, terminal, u))
|
|
313
|
+
self.check_limit(results)
|
|
314
|
+
return results
|
|
315
|
+
|
|
316
|
+
def check_limit(self, results):
|
|
317
|
+
if len(results) > MAX_PATHS:
|
|
318
|
+
del results[MAX_PATHS:]
|
|
319
|
+
if not self.truncated:
|
|
320
|
+
self.truncated = True
|
|
321
|
+
self.warn("more than %d paths; the rest were dropped"
|
|
322
|
+
% MAX_PATHS)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def analyze(data):
|
|
326
|
+
root = parse_xml(data)
|
|
327
|
+
pools = {}
|
|
328
|
+
for element in root.iter():
|
|
329
|
+
if local(element.tag) == "participant" and element.get("processRef"):
|
|
330
|
+
pools[element.get("processRef")] = clean(element.get("name"))
|
|
331
|
+
|
|
332
|
+
processes = [Process(el, pools.get(el.get("id"))) for el in root
|
|
333
|
+
if local(el.tag) == "process"]
|
|
334
|
+
if not processes:
|
|
335
|
+
raise BpmnError("the model contains no <process>")
|
|
336
|
+
|
|
337
|
+
warnings = []
|
|
338
|
+
lanes = {}
|
|
339
|
+
activities = []
|
|
340
|
+
paths = []
|
|
341
|
+
for process in processes:
|
|
342
|
+
for node_id in process.order:
|
|
343
|
+
kind = process.kind(node_id)
|
|
344
|
+
if kind not in ACTIVITY_TYPES:
|
|
345
|
+
continue
|
|
346
|
+
name = process.name(node_id)
|
|
347
|
+
lane = process.lane_of.get(node_id)
|
|
348
|
+
activity = {"id": node_id, "name": name, "type": kind,
|
|
349
|
+
"lane": lane, "process": process.id}
|
|
350
|
+
if uc_id(name):
|
|
351
|
+
activity["ucId"] = uc_id(name)
|
|
352
|
+
if not name:
|
|
353
|
+
warnings.append("activity " + node_id + " has no name")
|
|
354
|
+
activities.append(activity)
|
|
355
|
+
if lane:
|
|
356
|
+
lanes.setdefault(lane, []).append(node_id)
|
|
357
|
+
|
|
358
|
+
starts = [n for n in process.order
|
|
359
|
+
if process.kind(n) == "startEvent"
|
|
360
|
+
and not process.incoming.get(n)]
|
|
361
|
+
if not starts:
|
|
362
|
+
warnings.append("process " + process.id + " has no start event")
|
|
363
|
+
walker = Walker(process, warnings)
|
|
364
|
+
for start in starts:
|
|
365
|
+
found = walker.walk(start, (), 0)
|
|
366
|
+
if not found:
|
|
367
|
+
warnings.append("no complete path from start event " + start)
|
|
368
|
+
for steps, terminal, _ in found:
|
|
369
|
+
if len(paths) >= MAX_PATHS:
|
|
370
|
+
break
|
|
371
|
+
paths.append(make_path(len(paths) + 1, process, start,
|
|
372
|
+
terminal, steps))
|
|
373
|
+
|
|
374
|
+
return {"lanes": lanes, "activities": activities, "paths": paths,
|
|
375
|
+
"warnings": warnings}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def make_path(number, process, start, terminal, steps):
|
|
379
|
+
end = terminal[1] if terminal else None
|
|
380
|
+
return {
|
|
381
|
+
"id": "P%d" % number,
|
|
382
|
+
"process": process.id,
|
|
383
|
+
"start": start,
|
|
384
|
+
"startName": process.name(start),
|
|
385
|
+
"end": end,
|
|
386
|
+
"endName": process.name(end) if end else "",
|
|
387
|
+
"activities": [s["id"] for s in steps if s["kind"] == "activity"],
|
|
388
|
+
"decisions": [{k: v for k, v in s.items() if k != "kind"}
|
|
389
|
+
for s in steps if s["kind"] == "decision"],
|
|
390
|
+
"steps": steps,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
# Self-test
|
|
396
|
+
# ---------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
NS = ('xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" '
|
|
399
|
+
'id="defs" targetNamespace="http://example.com/bpmn"')
|
|
400
|
+
|
|
401
|
+
EXCLUSIVE_WITH_LANES = """<?xml version="1.0" encoding="UTF-8"?>
|
|
402
|
+
<definitions %s>
|
|
403
|
+
<collaboration id="c"><participant id="pool" name="Shop" processRef="p"/></collaboration>
|
|
404
|
+
<process id="p">
|
|
405
|
+
<laneSet id="ls">
|
|
406
|
+
<lane id="l1" name="Customer"><flowNodeRef>s</flowNodeRef><flowNodeRef>a</flowNodeRef></lane>
|
|
407
|
+
<lane id="l2" name="Clerk"><flowNodeRef>g</flowNodeRef><flowNodeRef>b</flowNodeRef>
|
|
408
|
+
<flowNodeRef>c</flowNodeRef><flowNodeRef>e1</flowNodeRef><flowNodeRef>e2</flowNodeRef></lane>
|
|
409
|
+
</laneSet>
|
|
410
|
+
<startEvent id="s" name="Order wanted"/>
|
|
411
|
+
<userTask id="a" name="UC-001 Place Order"/>
|
|
412
|
+
<exclusiveGateway id="g" name="In stock?"/>
|
|
413
|
+
<serviceTask id="b" name="Ship Order (UC-002)"/>
|
|
414
|
+
<task id="c" name="Cancel Order"/>
|
|
415
|
+
<endEvent id="e1" name="Shipped"/>
|
|
416
|
+
<endEvent id="e2" name="Cancelled"/>
|
|
417
|
+
<sequenceFlow id="f1" sourceRef="s" targetRef="a"/>
|
|
418
|
+
<sequenceFlow id="f2" sourceRef="a" targetRef="g"/>
|
|
419
|
+
<sequenceFlow id="f3" name="yes" sourceRef="g" targetRef="b"/>
|
|
420
|
+
<sequenceFlow id="f4" name="no" sourceRef="g" targetRef="c"/>
|
|
421
|
+
<sequenceFlow id="f5" sourceRef="b" targetRef="e1"/>
|
|
422
|
+
<sequenceFlow id="f6" sourceRef="c" targetRef="e2"/>
|
|
423
|
+
</process>
|
|
424
|
+
</definitions>
|
|
425
|
+
""" % NS
|
|
426
|
+
|
|
427
|
+
PARALLEL_AND_LOOP = """<?xml version="1.0" encoding="UTF-8"?>
|
|
428
|
+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" id="d">
|
|
429
|
+
<bpmn:process id="p">
|
|
430
|
+
<bpmn:startEvent id="s"/>
|
|
431
|
+
<bpmn:parallelGateway id="fork"/>
|
|
432
|
+
<bpmn:task id="a" name="Pack"/>
|
|
433
|
+
<bpmn:task id="b" name="Invoice"/>
|
|
434
|
+
<bpmn:parallelGateway id="join"/>
|
|
435
|
+
<bpmn:task id="r" name="Review"/>
|
|
436
|
+
<bpmn:exclusiveGateway id="ok" name="Approved?"/>
|
|
437
|
+
<bpmn:task id="fix" name="Rework"/>
|
|
438
|
+
<bpmn:endEvent id="e"/>
|
|
439
|
+
<bpmn:sequenceFlow id="f1" sourceRef="s" targetRef="fork"/>
|
|
440
|
+
<bpmn:sequenceFlow id="f2" sourceRef="fork" targetRef="a"/>
|
|
441
|
+
<bpmn:sequenceFlow id="f3" sourceRef="fork" targetRef="b"/>
|
|
442
|
+
<bpmn:sequenceFlow id="f4" sourceRef="a" targetRef="join"/>
|
|
443
|
+
<bpmn:sequenceFlow id="f5" sourceRef="b" targetRef="join"/>
|
|
444
|
+
<bpmn:sequenceFlow id="f6" sourceRef="join" targetRef="r"/>
|
|
445
|
+
<bpmn:sequenceFlow id="f7" sourceRef="r" targetRef="ok"/>
|
|
446
|
+
<bpmn:sequenceFlow id="f8" name="yes" sourceRef="ok" targetRef="e"/>
|
|
447
|
+
<bpmn:sequenceFlow id="f9" name="no" sourceRef="ok" targetRef="fix"/>
|
|
448
|
+
<bpmn:sequenceFlow id="f10" sourceRef="fix" targetRef="r"/>
|
|
449
|
+
</bpmn:process>
|
|
450
|
+
</bpmn:definitions>
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
XXE = """<?xml version="1.0"?>
|
|
454
|
+
<!DOCTYPE definitions [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
|
|
455
|
+
<definitions %s><process id="p"><task id="t" name="&xxe;"/></process></definitions>
|
|
456
|
+
""" % NS
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def self_test():
|
|
460
|
+
failures = []
|
|
461
|
+
|
|
462
|
+
def check(name, condition):
|
|
463
|
+
if not condition:
|
|
464
|
+
failures.append(name)
|
|
465
|
+
|
|
466
|
+
result = analyze(EXCLUSIVE_WITH_LANES.encode("utf-8"))
|
|
467
|
+
check("exclusive: two paths", len(result["paths"]) == 2)
|
|
468
|
+
check("exclusive: path activities",
|
|
469
|
+
[p["activities"] for p in result["paths"]] == [["a", "b"],
|
|
470
|
+
["a", "c"]])
|
|
471
|
+
check("exclusive: flow names",
|
|
472
|
+
[p["decisions"][0]["flowName"] for p in result["paths"]]
|
|
473
|
+
== ["yes", "no"])
|
|
474
|
+
check("exclusive: lanes",
|
|
475
|
+
result["lanes"] == {"Customer": ["a"], "Clerk": ["b", "c"]})
|
|
476
|
+
ucs = {a["id"]: a.get("ucId") for a in result["activities"]}
|
|
477
|
+
check("exclusive: uc ids", ucs == {"a": "UC-001", "b": "UC-002",
|
|
478
|
+
"c": None})
|
|
479
|
+
check("exclusive: name whitespace normalized",
|
|
480
|
+
result["activities"][0]["name"] == "UC-001 Place Order")
|
|
481
|
+
check("exclusive: no warnings", result["warnings"] == [])
|
|
482
|
+
|
|
483
|
+
result = analyze(PARALLEL_AND_LOOP.encode("utf-8"))
|
|
484
|
+
check("parallel+loop: activity sequences",
|
|
485
|
+
[p["activities"] for p in result["paths"]]
|
|
486
|
+
== [["a", "b", "r"], ["a", "b", "r", "fix", "r"]])
|
|
487
|
+
check("parallel+loop: rework decision recorded",
|
|
488
|
+
[d["flowName"] for d in result["paths"][1]["decisions"]]
|
|
489
|
+
== ["no", "yes"])
|
|
490
|
+
|
|
491
|
+
try:
|
|
492
|
+
analyze(XXE.encode("utf-8"))
|
|
493
|
+
failures.append("xxe: DOCTYPE was accepted")
|
|
494
|
+
except BpmnError:
|
|
495
|
+
pass
|
|
496
|
+
try:
|
|
497
|
+
analyze(b"<html/>")
|
|
498
|
+
failures.append("non-bpmn: accepted")
|
|
499
|
+
except BpmnError:
|
|
500
|
+
pass
|
|
501
|
+
|
|
502
|
+
if failures:
|
|
503
|
+
for failure in failures:
|
|
504
|
+
print("SELF-TEST FAIL: " + failure)
|
|
505
|
+
return 1
|
|
506
|
+
print("self-test passed")
|
|
507
|
+
return 0
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
# ---------------------------------------------------------------------------
|
|
511
|
+
# CLI
|
|
512
|
+
# ---------------------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
def main(argv):
|
|
515
|
+
parser = argparse.ArgumentParser(
|
|
516
|
+
description="Enumerate the paths through a BPMN 2.0 process model.")
|
|
517
|
+
parser.add_argument("file", nargs="?", help=".bpmn file to analyze")
|
|
518
|
+
parser.add_argument("--self-test", action="store_true",
|
|
519
|
+
help="run the built-in fixtures and exit")
|
|
520
|
+
args = parser.parse_args(argv)
|
|
521
|
+
|
|
522
|
+
if args.self_test:
|
|
523
|
+
return self_test()
|
|
524
|
+
if not args.file:
|
|
525
|
+
parser.print_usage()
|
|
526
|
+
return 2
|
|
527
|
+
try:
|
|
528
|
+
with open(args.file, "rb") as handle:
|
|
529
|
+
result = analyze(handle.read())
|
|
530
|
+
except OSError as exc:
|
|
531
|
+
print(args.file + ": ERROR IO: " + str(exc), file=sys.stderr)
|
|
532
|
+
return 1
|
|
533
|
+
except BpmnError as exc:
|
|
534
|
+
print(args.file + ": ERROR: " + str(exc), file=sys.stderr)
|
|
535
|
+
return 1
|
|
536
|
+
result = dict({"file": args.file}, **result)
|
|
537
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
538
|
+
return 0
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
if __name__ == "__main__":
|
|
542
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-case-diagram
|
|
3
|
+
description: >
|
|
4
|
+
Creates or updates PlantUML use case diagrams defining actors, use cases,
|
|
5
|
+
and their relationships from requirements. Use when the user asks to
|
|
6
|
+
"create a use case diagram", "draw a UML diagram", "map actors to use cases",
|
|
7
|
+
"generate a .puml file", or mentions PlantUML, use case overview, actor
|
|
8
|
+
diagram, or system use cases.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<!--
|
|
12
|
+
Copyright 2025-2026 Simon Martinelli and the AI Unified Process contributors.
|
|
13
|
+
Part of the AI Unified Process — https://unifiedprocess.ai
|
|
14
|
+
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
# Use Case Diagram
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
|
|
21
|
+
Create or update the PlantUML use case diagram at `docs/use_cases.puml` based on `docs/requirements.md`.
|
|
22
|
+
|
|
23
|
+
## DO NOT
|
|
24
|
+
|
|
25
|
+
- Create diagrams without reading the requirements first
|
|
26
|
+
- Use non-standard PlantUML syntax
|
|
27
|
+
- Include implementation details in use case names
|
|
28
|
+
- Model technical steps such as validating, loading, or persisting data as separate use cases (see "Goal level")
|
|
29
|
+
|
|
30
|
+
## Template
|
|
31
|
+
|
|
32
|
+
```plantuml
|
|
33
|
+
@startuml Use Cases Overview
|
|
34
|
+
left to right direction
|
|
35
|
+
|
|
36
|
+
actor "User" as user
|
|
37
|
+
actor "Administrator" as admin
|
|
38
|
+
|
|
39
|
+
rectangle "System Name" {
|
|
40
|
+
usecase "UC-001\nDescription" as UC001
|
|
41
|
+
usecase "UC-002\nDescription" as UC002
|
|
42
|
+
usecase "UC-003\nDescription" as UC003
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
admin --> UC001
|
|
46
|
+
user --> UC002
|
|
47
|
+
user --> UC003
|
|
48
|
+
|
|
49
|
+
@enduml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Goal level
|
|
53
|
+
|
|
54
|
+
Every use case in the diagram is a **user goal** — what Cockburn calls the sea level: one actor, one sitting, a
|
|
55
|
+
result the primary actor walks away with. Test each use case with one question:
|
|
56
|
+
|
|
57
|
+
> Is this use case a complete goal that the primary actor would recognize as valuable?
|
|
58
|
+
|
|
59
|
+
- **Too low (subfunction):** a step of a larger goal, often a technical one — "Validate METAR", "Load NOTAM",
|
|
60
|
+
"Persist Result". No dispatcher sits down to validate a METAR; they want to know whether an airport is suitable.
|
|
61
|
+
Fold such steps into the user goal they serve ("Determine Airport Suitability"), where they become steps of its
|
|
62
|
+
main success scenario. Keep a subfunction as its own use case only when several user goals share it, and then draw
|
|
63
|
+
it with `<<include>>` from each of them.
|
|
64
|
+
- **Too high (summary):** a whole area of work that spans many sittings — "Manage Flight Operations". Split it into
|
|
65
|
+
the user goals it is made of. A use case is a summary, too, when its flow hands the work over to another role,
|
|
66
|
+
waits for an outside event or a deadline, or runs branches in parallel for different actors — "Process Insurance
|
|
67
|
+
Claim" with a clerk, an assessor, and a payout after approval. Each role's part is a user goal of its own; the
|
|
68
|
+
flow that connects them is a business process, modeled in BPMN in `docs/processes/`, whose activities are those
|
|
69
|
+
use cases and from which `/test-case` derives the test cases. Do not describe the process inside a use case or
|
|
70
|
+
attach a process model to it.
|
|
71
|
+
|
|
72
|
+
A functional requirement that describes a step rather than a goal traces to the user goal use case that contains
|
|
73
|
+
the step.
|
|
74
|
+
|
|
75
|
+
## Conventions
|
|
76
|
+
|
|
77
|
+
- Each use case has a unique id and a description
|
|
78
|
+
- Use Case ID: UC-{3-digit} (UC-001, UC-002, ...)
|
|
79
|
+
- Each use case should trace to at least one functional requirement
|
|
80
|
+
- Secondary actors (supporting roles and external systems such as a payment or weather service) are drawn as
|
|
81
|
+
actors too and connected to the use cases they support; they appear as `**Secondary Actors:**` in the use case
|
|
82
|
+
specification
|
|
83
|
+
- Add notes sparingly, only where relationships need clarification
|
|
84
|
+
|
|
85
|
+
## Workflow
|
|
86
|
+
|
|
87
|
+
1. Read the requirements at `docs/requirements.md`
|
|
88
|
+
2. Read existing diagram at `docs/use_cases.puml` (if exists)
|
|
89
|
+
3. Identify actors and use cases from requirements
|
|
90
|
+
4. Check every use case against the question in "Goal level": fold subfunctions into the user goal they serve,
|
|
91
|
+
split summary goals, and tell the user which use cases you merged or split and why
|
|
92
|
+
5. Create/update the PlantUML use case diagram
|
|
93
|
+
6. Validate the diagram:
|
|
94
|
+
- Each use case is a user goal (see "Goal level"); a subfunction appears only as an `<<include>>` shared by
|
|
95
|
+
several use cases
|
|
96
|
+
- Each use case traces to at least one functional requirement in `docs/requirements.md`
|
|
97
|
+
- All actors are connected to at least one use case
|
|
98
|
+
- Use case IDs follow the UC-{3-digit} convention
|
|
99
|
+
- PlantUML syntax is valid (no missing `@enduml`, proper arrow syntax)
|