specweave 0.18.0 → 0.20.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.md +229 -1817
- package/README.md +68 -0
- package/bin/specweave.js +62 -6
- package/dist/locales/de/.gitkeep +0 -0
- package/dist/locales/de/cli.json +108 -0
- package/dist/locales/en/cli.json +287 -0
- package/dist/locales/en/errors.json +7 -0
- package/dist/locales/en/templates.json +6 -0
- package/dist/locales/es/.gitkeep +0 -0
- package/dist/locales/es/cli.json +41 -0
- package/dist/locales/fr/.gitkeep +0 -0
- package/dist/locales/fr/cli.json +108 -0
- package/dist/locales/ja/.gitkeep +0 -0
- package/dist/locales/ja/cli.json +108 -0
- package/dist/locales/ko/.gitkeep +0 -0
- package/dist/locales/ko/cli.json +108 -0
- package/dist/locales/pt/.gitkeep +0 -0
- package/dist/locales/pt/cli.json +108 -0
- package/dist/locales/ru/.gitkeep +0 -0
- package/dist/locales/ru/cli.json +269 -0
- package/dist/locales/zh/.gitkeep +0 -0
- package/dist/locales/zh/cli.json +108 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.d.ts.map +1 -1
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js +3 -0
- package/dist/plugins/specweave/lib/hooks/sync-living-docs.js.map +1 -1
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts +21 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.d.ts.map +1 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js +162 -0
- package/dist/plugins/specweave/lib/hooks/update-ac-status.js.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js +65 -6
- package/dist/plugins/specweave-ado/lib/ado-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts +25 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js +191 -0
- package/dist/plugins/specweave-ado/lib/enhanced-ado-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts +112 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js +301 -0
- package/dist/plugins/specweave-github/lib/completion-calculator.js.map +1 -0
- package/dist/plugins/specweave-github/lib/duplicate-detector.d.ts +3 -3
- package/dist/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts +70 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +258 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +14 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +51 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts +2 -2
- package/dist/plugins/specweave-github/lib/github-epic-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-epic-sync.js +20 -5
- package/dist/plugins/specweave-github/lib/github-epic-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +87 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +412 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js +64 -13
- package/dist/plugins/specweave-github/lib/github-spec-content-sync.js.map +1 -1
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts +78 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js +237 -0
- package/dist/plugins/specweave-github/lib/progress-comment-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js +301 -0
- package/dist/plugins/specweave-github/lib/user-story-content-builder.js.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts +83 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js +386 -0
- package/dist/plugins/specweave-github/lib/user-story-issue-builder.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts +28 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +156 -0
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.d.ts +57 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.js +248 -0
- package/dist/plugins/specweave-kafka/lib/cli/kcat-wrapper.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.d.ts +82 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.js +13 -0
- package/dist/plugins/specweave-kafka/lib/cli/types.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.d.ts +49 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.js +316 -0
- package/dist/plugins/specweave-kafka/lib/mcp/detector.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.d.ts +70 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.js +23 -0
- package/dist/plugins/specweave-kafka/lib/mcp/types.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.d.ts +85 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.js +281 -0
- package/dist/plugins/specweave-kafka/lib/utils/partitioning.js.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.d.ts +75 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.d.ts.map +1 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.js +238 -0
- package/dist/plugins/specweave-kafka/lib/utils/sizing.js.map +1 -0
- package/dist/spec-parser.js +629 -0
- package/dist/src/cli/commands/import-docs.js +4 -4
- package/dist/src/cli/commands/import-docs.js.map +1 -1
- package/dist/src/cli/commands/init-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/init-multiproject.js +17 -18
- package/dist/src/cli/commands/init-multiproject.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +107 -3
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.js +8 -4
- package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
- package/dist/src/cli/commands/switch-project.d.ts.map +1 -1
- package/dist/src/cli/commands/switch-project.js +9 -26
- package/dist/src/cli/commands/switch-project.js.map +1 -1
- package/dist/src/cli/commands/sync-spec-content.js +3 -0
- package/dist/src/cli/commands/sync-spec-content.js.map +1 -1
- package/dist/src/core/deduplication/command-deduplicator.d.ts +166 -0
- package/dist/src/core/deduplication/command-deduplicator.d.ts.map +1 -0
- package/dist/src/core/deduplication/command-deduplicator.js +254 -0
- package/dist/src/core/deduplication/command-deduplicator.js.map +1 -0
- package/dist/src/core/increment/active-increment-manager.d.ts +42 -15
- package/dist/src/core/increment/active-increment-manager.d.ts.map +1 -1
- package/dist/src/core/increment/active-increment-manager.js +113 -46
- package/dist/src/core/increment/active-increment-manager.js.map +1 -1
- package/dist/src/core/increment/conflict-resolver.d.ts +40 -0
- package/dist/src/core/increment/conflict-resolver.d.ts.map +1 -0
- package/dist/src/core/increment/conflict-resolver.js +219 -0
- package/dist/src/core/increment/conflict-resolver.js.map +1 -0
- package/dist/src/core/increment/discipline-checker.d.ts.map +1 -1
- package/dist/src/core/increment/discipline-checker.js +7 -1
- package/dist/src/core/increment/discipline-checker.js.map +1 -1
- package/dist/src/core/increment/duplicate-detector.d.ts +52 -0
- package/dist/src/core/increment/duplicate-detector.d.ts.map +1 -0
- package/dist/src/core/increment/duplicate-detector.js +276 -0
- package/dist/src/core/increment/duplicate-detector.js.map +1 -0
- package/dist/src/core/increment/increment-archiver.d.ts +90 -0
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -0
- package/dist/src/core/increment/increment-archiver.js +368 -0
- package/dist/src/core/increment/increment-archiver.js.map +1 -0
- package/dist/src/core/increment/increment-reopener.d.ts +165 -0
- package/dist/src/core/increment/increment-reopener.d.ts.map +1 -0
- package/dist/src/core/increment/increment-reopener.js +390 -0
- package/dist/src/core/increment/increment-reopener.js.map +1 -0
- package/dist/src/core/increment/metadata-manager.d.ts +26 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +143 -5
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/recent-work-scanner.d.ts +121 -0
- package/dist/src/core/increment/recent-work-scanner.d.ts.map +1 -0
- package/dist/src/core/increment/recent-work-scanner.js +303 -0
- package/dist/src/core/increment/recent-work-scanner.js.map +1 -0
- package/dist/src/core/increment/types.d.ts +1 -0
- package/dist/src/core/increment/types.d.ts.map +1 -1
- package/dist/src/core/increment-utils.d.ts +112 -0
- package/dist/src/core/increment-utils.d.ts.map +1 -0
- package/dist/src/core/increment-utils.js +210 -0
- package/dist/src/core/increment-utils.js.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts +65 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js +175 -0
- package/dist/src/core/living-docs/ac-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts +130 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.js +549 -0
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts +81 -0
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -0
- package/dist/src/core/living-docs/feature-id-manager.js +339 -0
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -0
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts +144 -83
- package/dist/src/core/living-docs/hierarchy-mapper.d.ts.map +1 -1
- package/dist/src/core/living-docs/hierarchy-mapper.js +488 -270
- package/dist/src/core/living-docs/hierarchy-mapper.js.map +1 -1
- package/dist/src/core/living-docs/index.d.ts +6 -0
- package/dist/src/core/living-docs/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/index.js +6 -0
- package/dist/src/core/living-docs/index.js.map +1 -1
- package/dist/src/core/living-docs/project-detector.d.ts +6 -0
- package/dist/src/core/living-docs/project-detector.d.ts.map +1 -1
- package/dist/src/core/living-docs/project-detector.js +35 -1
- package/dist/src/core/living-docs/project-detector.js.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.d.ts +100 -26
- package/dist/src/core/living-docs/spec-distributor.d.ts.map +1 -1
- package/dist/src/core/living-docs/spec-distributor.js +1275 -258
- package/dist/src/core/living-docs/spec-distributor.js.map +1 -1
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts +109 -0
- package/dist/src/core/living-docs/task-project-specific-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js +221 -0
- package/dist/src/core/living-docs/task-project-specific-generator.js.map +1 -0
- package/dist/src/core/living-docs/types.d.ts +143 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/project-manager.d.ts +2 -17
- package/dist/src/core/project-manager.d.ts.map +1 -1
- package/dist/src/core/project-manager.js +68 -48
- package/dist/src/core/project-manager.js.map +1 -1
- package/dist/src/core/spec-content-sync.d.ts +1 -1
- package/dist/src/core/spec-content-sync.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.d.ts +32 -54
- package/dist/src/core/sync/enhanced-content-builder.d.ts.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.js +142 -138
- package/dist/src/core/sync/enhanced-content-builder.js.map +1 -1
- package/dist/src/core/sync/performance-optimizer.d.ts +153 -0
- package/dist/src/core/sync/performance-optimizer.d.ts.map +1 -0
- package/dist/src/core/sync/performance-optimizer.js +220 -0
- package/dist/src/core/sync/performance-optimizer.js.map +1 -0
- package/dist/src/core/sync/retry-handler.d.ts +98 -0
- package/dist/src/core/sync/retry-handler.d.ts.map +1 -0
- package/dist/src/core/sync/retry-handler.js +196 -0
- package/dist/src/core/sync/retry-handler.js.map +1 -0
- package/dist/src/core/sync/spec-content-sync.d.ts +88 -0
- package/dist/src/core/sync/spec-content-sync.d.ts.map +1 -0
- package/dist/src/core/sync/spec-content-sync.js +5 -0
- package/dist/src/core/sync/spec-content-sync.js.map +1 -0
- package/dist/src/core/sync/types.d.ts +52 -0
- package/dist/src/core/sync/types.d.ts.map +1 -0
- package/dist/src/core/sync/types.js +5 -0
- package/dist/src/core/sync/types.js.map +1 -0
- package/dist/src/core/types/config.d.ts +125 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +25 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +10 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js +10 -1
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-incremental-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-incremental-mapper.js.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-mapper.js +4 -8
- package/dist/src/integrations/jira/jira-mapper.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/plugins/specweave/COMMANDS.md +13 -4
- package/plugins/specweave/agents/pm/AGENT.md +159 -12
- package/plugins/specweave/commands/specweave-abandon.md +22 -20
- package/plugins/specweave/commands/specweave-archive-features.md +121 -0
- package/plugins/specweave/commands/specweave-archive-increments.md +82 -0
- package/plugins/specweave/commands/specweave-archive.md +363 -0
- package/plugins/specweave/commands/specweave-backlog.md +211 -0
- package/plugins/specweave/commands/specweave-fix-duplicates.md +517 -0
- package/plugins/specweave/commands/specweave-increment.md +4 -3
- package/plugins/specweave/commands/specweave-progress.md +176 -27
- package/plugins/specweave/commands/specweave-reopen.md +391 -0
- package/plugins/specweave/commands/specweave-restore-feature.md +90 -0
- package/plugins/specweave/commands/specweave-restore.md +309 -0
- package/plugins/specweave/commands/specweave-resume.md +51 -23
- package/plugins/specweave/commands/specweave-status.md +41 -7
- package/plugins/specweave/commands/specweave-sync-specs.md +425 -0
- package/plugins/specweave/commands/specweave.md +70 -405
- package/plugins/specweave/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -2
- package/plugins/specweave/hooks/post-increment-planning.sh +26 -2
- package/plugins/specweave/hooks/post-task-completion.sh +39 -0
- package/plugins/specweave/hooks/pre-command-deduplication.sh +83 -0
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/lib/hooks/sync-living-docs.js +2 -0
- package/plugins/specweave/lib/hooks/sync-living-docs.ts +4 -0
- package/plugins/specweave/lib/hooks/update-ac-status.js +102 -0
- package/plugins/specweave/lib/hooks/update-ac-status.ts +192 -0
- package/plugins/specweave/skills/archive-increments/SKILL.md +198 -0
- package/plugins/specweave/skills/increment-planner/scripts/feature-utils.js +14 -0
- package/plugins/specweave/skills/smart-reopen-detector/SKILL.md +244 -0
- package/plugins/specweave-ado/lib/ado-spec-content-sync.js +49 -5
- package/plugins/specweave-ado/lib/ado-spec-content-sync.ts +72 -6
- package/plugins/specweave-ado/lib/enhanced-ado-sync.js +170 -0
- package/plugins/specweave-confluent/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-confluent/README.md +375 -0
- package/plugins/specweave-confluent/agents/confluent-architect/AGENT.md +306 -0
- package/plugins/specweave-confluent/skills/confluent-kafka-connect/SKILL.md +453 -0
- package/plugins/specweave-confluent/skills/confluent-ksqldb/SKILL.md +470 -0
- package/plugins/specweave-confluent/skills/confluent-schema-registry/SKILL.md +316 -0
- package/plugins/specweave-github/agents/github-task-splitter/AGENT.md +2 -2
- package/plugins/specweave-github/agents/user-story-updater/AGENT.md +148 -0
- package/plugins/specweave-github/commands/specweave-github-cleanup-duplicates.md +1 -1
- package/plugins/specweave-github/commands/specweave-github-update-user-story.md +156 -0
- package/plugins/specweave-github/hooks/post-task-completion.sh +42 -9
- package/plugins/specweave-github/lib/completion-calculator.js +262 -0
- package/plugins/specweave-github/lib/completion-calculator.ts +434 -0
- package/plugins/specweave-github/lib/duplicate-detector.js +3 -3
- package/plugins/specweave-github/lib/duplicate-detector.ts +4 -4
- package/plugins/specweave-github/lib/epic-content-builder.js +265 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +376 -0
- package/plugins/specweave-github/lib/github-client-v2.js +49 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +59 -0
- package/plugins/specweave-github/lib/github-epic-sync.js +23 -24
- package/plugins/specweave-github/lib/github-epic-sync.ts +30 -5
- package/plugins/specweave-github/lib/github-feature-sync.js +381 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +568 -0
- package/plugins/specweave-github/lib/github-spec-content-sync.js +40 -10
- package/plugins/specweave-github/lib/github-spec-content-sync.ts +82 -14
- package/plugins/specweave-github/lib/progress-comment-builder.js +229 -0
- package/plugins/specweave-github/lib/progress-comment-builder.ts +324 -0
- package/plugins/specweave-github/lib/user-story-content-builder.js +299 -0
- package/plugins/specweave-github/lib/user-story-content-builder.ts +413 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.js +344 -0
- package/plugins/specweave-github/lib/user-story-issue-builder.ts +543 -0
- package/plugins/specweave-github/skills/github-issue-standard/SKILL.md +189 -0
- package/plugins/specweave-jira/lib/enhanced-jira-sync.js +134 -0
- package/plugins/specweave-jira/lib/{enhanced-jira-sync.ts.disabled → enhanced-jira-sync.ts} +26 -52
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +26 -0
- package/plugins/specweave-kafka/IMPLEMENTATION-COMPLETE.md +483 -0
- package/plugins/specweave-kafka/README.md +242 -0
- package/plugins/specweave-kafka/agents/kafka-architect/AGENT.md +235 -0
- package/plugins/specweave-kafka/agents/kafka-devops/AGENT.md +209 -0
- package/plugins/specweave-kafka/agents/kafka-observability/AGENT.md +266 -0
- package/plugins/specweave-kafka/commands/deploy.md +99 -0
- package/plugins/specweave-kafka/commands/dev-env.md +176 -0
- package/plugins/specweave-kafka/commands/mcp-configure.md +101 -0
- package/plugins/specweave-kafka/commands/monitor-setup.md +96 -0
- package/plugins/specweave-kafka/docker/kafka-local/docker-compose.yml +187 -0
- package/plugins/specweave-kafka/docker/redpanda/docker-compose.yml +199 -0
- package/plugins/specweave-kafka/docker/templates/consumer-nodejs.js +225 -0
- package/plugins/specweave-kafka/docker/templates/consumer-python.py +220 -0
- package/plugins/specweave-kafka/docker/templates/producer-nodejs.js +168 -0
- package/plugins/specweave-kafka/docker/templates/producer-python.py +167 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.js +438 -0
- package/plugins/specweave-kafka/lib/adapters/apache-kafka-adapter.ts +541 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.js +47 -0
- package/plugins/specweave-kafka/lib/adapters/platform-adapter.ts +343 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.js +258 -0
- package/plugins/specweave-kafka/lib/cli/kcat-wrapper.ts +298 -0
- package/plugins/specweave-kafka/lib/cli/types.js +10 -0
- package/plugins/specweave-kafka/lib/cli/types.ts +92 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.js +305 -0
- package/plugins/specweave-kafka/lib/connectors/connector-catalog.ts +528 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.js +114 -0
- package/plugins/specweave-kafka/lib/documentation/diagram-generator.ts +195 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.js +210 -0
- package/plugins/specweave-kafka/lib/documentation/exporter.ts +338 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.js +60 -0
- package/plugins/specweave-kafka/lib/documentation/schema-catalog-generator.ts +130 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.js +143 -0
- package/plugins/specweave-kafka/lib/documentation/topology-generator.ts +290 -0
- package/plugins/specweave-kafka/lib/mcp/detector.js +298 -0
- package/plugins/specweave-kafka/lib/mcp/detector.ts +352 -0
- package/plugins/specweave-kafka/lib/mcp/types.js +21 -0
- package/plugins/specweave-kafka/lib/mcp/types.ts +77 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.js +193 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-config-manager.ts +362 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.js +188 -0
- package/plugins/specweave-kafka/lib/multi-cluster/cluster-switcher.ts +359 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.js +195 -0
- package/plugins/specweave-kafka/lib/multi-cluster/health-aggregator.ts +380 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.js +209 -0
- package/plugins/specweave-kafka/lib/observability/opentelemetry-kafka.ts +358 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.js +354 -0
- package/plugins/specweave-kafka/lib/patterns/advanced-ksqldb-patterns.ts +563 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.js +259 -0
- package/plugins/specweave-kafka/lib/patterns/circuit-breaker-resilience.ts +516 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.js +233 -0
- package/plugins/specweave-kafka/lib/patterns/dead-letter-queue.ts +423 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.js +266 -0
- package/plugins/specweave-kafka/lib/patterns/exactly-once-semantics.ts +445 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.js +312 -0
- package/plugins/specweave-kafka/lib/patterns/flink-kafka-integration.ts +561 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.js +289 -0
- package/plugins/specweave-kafka/lib/patterns/multi-dc-replication.ts +607 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.js +264 -0
- package/plugins/specweave-kafka/lib/patterns/rate-limiting-backpressure.ts +498 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.js +263 -0
- package/plugins/specweave-kafka/lib/patterns/stream-processing-optimization.ts +549 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.js +205 -0
- package/plugins/specweave-kafka/lib/patterns/tiered-storage-compaction.ts +399 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.js +249 -0
- package/plugins/specweave-kafka/lib/performance/performance-optimizer.ts +427 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.js +252 -0
- package/plugins/specweave-kafka/lib/security/kafka-security.ts +494 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.js +203 -0
- package/plugins/specweave-kafka/lib/utils/capacity-planner.ts +469 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.js +419 -0
- package/plugins/specweave-kafka/lib/utils/config-validator.ts +564 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.js +329 -0
- package/plugins/specweave-kafka/lib/utils/partitioning.ts +473 -0
- package/plugins/specweave-kafka/lib/utils/sizing.js +221 -0
- package/plugins/specweave-kafka/lib/utils/sizing.ts +374 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-broker-metrics.json +628 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-cluster-overview.json +564 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-consumer-lag.json +509 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-jvm-metrics.json +674 -0
- package/plugins/specweave-kafka/monitoring/grafana/dashboards/kafka-topic-metrics.json +578 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/dashboards/kafka.yml +17 -0
- package/plugins/specweave-kafka/monitoring/grafana/provisioning/datasources/prometheus.yml +17 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-alerts.yml +415 -0
- package/plugins/specweave-kafka/monitoring/prometheus/kafka-jmx-exporter.yml +256 -0
- package/plugins/specweave-kafka/package.json +41 -0
- package/plugins/specweave-kafka/skills/kafka-architecture/SKILL.md +647 -0
- package/plugins/specweave-kafka/skills/kafka-cli-tools/SKILL.md +433 -0
- package/plugins/specweave-kafka/skills/kafka-iac-deployment/SKILL.md +449 -0
- package/plugins/specweave-kafka/skills/kafka-kubernetes/SKILL.md +667 -0
- package/plugins/specweave-kafka/skills/kafka-mcp-integration/SKILL.md +273 -0
- package/plugins/specweave-kafka/skills/kafka-observability/SKILL.md +576 -0
- package/plugins/specweave-kafka/templates/config/broker-production.properties +254 -0
- package/plugins/specweave-kafka/templates/config/consumer-low-latency.properties +112 -0
- package/plugins/specweave-kafka/templates/config/producer-high-throughput.properties +120 -0
- package/plugins/specweave-kafka/templates/migration/mirrormaker2-config.properties +234 -0
- package/plugins/specweave-kafka/templates/monitoring/grafana/multi-cluster-dashboard.json +686 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/main.tf +347 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/outputs.tf +107 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/templates/kafka-broker-init.sh.tpl +216 -0
- package/plugins/specweave-kafka/terraform/apache-kafka/variables.tf +156 -0
- package/plugins/specweave-kafka/terraform/aws-msk/main.tf +362 -0
- package/plugins/specweave-kafka/terraform/aws-msk/outputs.tf +93 -0
- package/plugins/specweave-kafka/terraform/aws-msk/templates/server.properties.tpl +32 -0
- package/plugins/specweave-kafka/terraform/aws-msk/variables.tf +235 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/main.tf +281 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/outputs.tf +118 -0
- package/plugins/specweave-kafka/terraform/azure-event-hubs/variables.tf +148 -0
- package/plugins/specweave-kafka/tsconfig.json +21 -0
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-kafka-streams/README.md +310 -0
- package/plugins/specweave-kafka-streams/skills/kafka-streams-topology/SKILL.md +539 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-n8n/README.md +354 -0
- package/plugins/specweave-n8n/skills/n8n-kafka-workflows/SKILL.md +504 -0
- package/plugins/specweave-release/commands/specweave-release-platform.md +1 -1
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -2
- package/src/templates/AGENTS.md.template +601 -7
- package/src/templates/CLAUDE.md.template +188 -88
- package/plugins/specweave-ado/commands/specweave-ado-sync-spec.md +0 -255
- package/plugins/specweave-github/commands/specweave-github-sync-epic.md +0 -248
- package/plugins/specweave-github/commands/specweave-github-sync-from.md +0 -147
- package/plugins/specweave-github/commands/specweave-github-sync-spec.md +0 -208
- package/plugins/specweave-github/commands/specweave-github-sync-tasks.md +0 -530
- package/plugins/specweave-jira/commands/specweave-jira-sync-epic.md +0 -267
- package/plugins/specweave-jira/commands/specweave-jira-sync-spec.md +0 -240
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as yaml from "yaml";
|
|
5
|
+
class UserStoryContentBuilder {
|
|
6
|
+
constructor(userStoryPath, projectRoot) {
|
|
7
|
+
this.userStoryPath = userStoryPath;
|
|
8
|
+
this.projectRoot = projectRoot;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse user story file and extract all content
|
|
12
|
+
*/
|
|
13
|
+
async parse() {
|
|
14
|
+
const content = await readFile(this.userStoryPath, "utf-8");
|
|
15
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
16
|
+
if (!match) {
|
|
17
|
+
throw new Error("User story file missing YAML frontmatter");
|
|
18
|
+
}
|
|
19
|
+
const frontmatter = yaml.parse(match[1]);
|
|
20
|
+
const bodyContent = content.slice(match[0].length).trim();
|
|
21
|
+
const featureId = this.extractFeatureId(frontmatter.epic, this.userStoryPath);
|
|
22
|
+
const userStoryDescription = this.extractUserStoryDescription(bodyContent);
|
|
23
|
+
const acceptanceCriteria = this.extractAcceptanceCriteria(bodyContent);
|
|
24
|
+
const incrementId = this.extractIncrementId(bodyContent);
|
|
25
|
+
const tasks = incrementId ? await this.extractTasks(bodyContent, incrementId, frontmatter.id) : [];
|
|
26
|
+
return {
|
|
27
|
+
frontmatter,
|
|
28
|
+
userStoryDescription,
|
|
29
|
+
acceptanceCriteria,
|
|
30
|
+
tasks,
|
|
31
|
+
incrementId,
|
|
32
|
+
featureId
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build GitHub issue body from user story content
|
|
37
|
+
*
|
|
38
|
+
* @param githubRepo Optional GitHub repo in format "owner/repo" for generating URLs
|
|
39
|
+
*/
|
|
40
|
+
async buildIssueBody(githubRepo) {
|
|
41
|
+
const content = await this.parse();
|
|
42
|
+
let body = "";
|
|
43
|
+
const priority = this.extractPriorityFromACs(content.acceptanceCriteria);
|
|
44
|
+
const repo = githubRepo || await this.detectGitHubRepo();
|
|
45
|
+
if (repo) {
|
|
46
|
+
body += `**Feature**: [${content.featureId}](https://github.com/${repo}/tree/develop/.specweave/docs/internal/specs/_features/${content.featureId})
|
|
47
|
+
`;
|
|
48
|
+
} else {
|
|
49
|
+
body += `**Feature**: ${content.featureId}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
body += `**Status**: ${content.frontmatter.status}
|
|
53
|
+
`;
|
|
54
|
+
if (priority) {
|
|
55
|
+
body += `**Priority**: ${priority}
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
body += `
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
`;
|
|
62
|
+
body += `## User Story
|
|
63
|
+
|
|
64
|
+
`;
|
|
65
|
+
if (content.userStoryDescription) {
|
|
66
|
+
body += `**As a** ${content.userStoryDescription.asA}
|
|
67
|
+
`;
|
|
68
|
+
body += `**I want** ${content.userStoryDescription.iWant}
|
|
69
|
+
`;
|
|
70
|
+
body += `**So that** ${content.userStoryDescription.soThat}
|
|
71
|
+
|
|
72
|
+
`;
|
|
73
|
+
} else {
|
|
74
|
+
body += `*User story description not found*
|
|
75
|
+
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
const usFilename = path.basename(this.userStoryPath);
|
|
79
|
+
if (repo) {
|
|
80
|
+
const relativePath = this.userStoryPath.replace(this.projectRoot, "").replace(/^\//, "");
|
|
81
|
+
body += `\u{1F4C4} View full story: [\`${usFilename}\`](https://github.com/${repo}/tree/develop/${relativePath})
|
|
82
|
+
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
body += `---
|
|
86
|
+
|
|
87
|
+
`;
|
|
88
|
+
body += `## Acceptance Criteria
|
|
89
|
+
|
|
90
|
+
`;
|
|
91
|
+
if (content.acceptanceCriteria.length > 0) {
|
|
92
|
+
const completed = content.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
93
|
+
const total = content.acceptanceCriteria.length;
|
|
94
|
+
const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
95
|
+
body += `Progress: ${completed}/${total} criteria met (${percentage}%)
|
|
96
|
+
|
|
97
|
+
`;
|
|
98
|
+
for (const ac of content.acceptanceCriteria) {
|
|
99
|
+
const checkbox = ac.completed ? "[x]" : "[ ]";
|
|
100
|
+
const priority2 = ac.priority ? ` (${ac.priority})` : "";
|
|
101
|
+
const testable = ac.testable ? " [testable]" : "";
|
|
102
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}${priority2}${testable}
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
body += "\n";
|
|
106
|
+
} else {
|
|
107
|
+
body += `*No acceptance criteria defined*
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
body += `---
|
|
112
|
+
|
|
113
|
+
`;
|
|
114
|
+
body += `## Implementation Tasks
|
|
115
|
+
|
|
116
|
+
`;
|
|
117
|
+
if (content.tasks.length > 0) {
|
|
118
|
+
const completed = content.tasks.filter((t) => t.status).length;
|
|
119
|
+
const total = content.tasks.length;
|
|
120
|
+
const percentage = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
121
|
+
body += `Progress: ${completed}/${total} tasks complete (${percentage}%)
|
|
122
|
+
|
|
123
|
+
`;
|
|
124
|
+
if (content.incrementId) {
|
|
125
|
+
if (repo) {
|
|
126
|
+
body += `**Increment**: [${content.incrementId}](https://github.com/${repo}/tree/develop/.specweave/increments/${content.incrementId})
|
|
127
|
+
|
|
128
|
+
`;
|
|
129
|
+
} else {
|
|
130
|
+
body += `**Increment**: ${content.incrementId}
|
|
131
|
+
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const task of content.tasks) {
|
|
136
|
+
const checkbox = task.status ? "[x]" : "[ ]";
|
|
137
|
+
let taskLink = task.link;
|
|
138
|
+
if (repo && taskLink.startsWith("../../")) {
|
|
139
|
+
const relativePath = taskLink.replace(/^\.\.\/\.\.\//, ".specweave/");
|
|
140
|
+
taskLink = `https://github.com/${repo}/tree/develop/${relativePath}`;
|
|
141
|
+
}
|
|
142
|
+
body += `- ${checkbox} [${task.id}: ${task.title}](${taskLink})
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
body += "\n";
|
|
146
|
+
} else {
|
|
147
|
+
body += `*No tasks defined yet*
|
|
148
|
+
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
body += `---
|
|
152
|
+
|
|
153
|
+
`;
|
|
154
|
+
body += `\u{1F916} Auto-synced by SpecWeave User Story Sync`;
|
|
155
|
+
return body;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Extract feature ID from epic or path
|
|
159
|
+
*/
|
|
160
|
+
extractFeatureId(epic, filePath) {
|
|
161
|
+
if (epic && epic.startsWith("FS-")) {
|
|
162
|
+
return epic;
|
|
163
|
+
}
|
|
164
|
+
const match = filePath.match(/FS-\d+/);
|
|
165
|
+
return match ? match[0] : "FS-UNKNOWN";
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Extract user story description (As a... I want... So that...)
|
|
169
|
+
*/
|
|
170
|
+
extractUserStoryDescription(content) {
|
|
171
|
+
const asAMatch = content.match(/\*\*As a\*\*\s+([^\n]+)/);
|
|
172
|
+
const iWantMatch = content.match(/\*\*I want\*\*\s+([^\n]+)/);
|
|
173
|
+
const soThatMatch = content.match(/\*\*So that\*\*\s+([^\n]+)/);
|
|
174
|
+
if (asAMatch && iWantMatch && soThatMatch) {
|
|
175
|
+
return {
|
|
176
|
+
asA: asAMatch[1].trim(),
|
|
177
|
+
iWant: iWantMatch[1].trim(),
|
|
178
|
+
soThat: soThatMatch[1].trim()
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Extract acceptance criteria from content
|
|
185
|
+
*/
|
|
186
|
+
extractAcceptanceCriteria(content) {
|
|
187
|
+
const criteria = [];
|
|
188
|
+
const acPattern = /- \[([ x])\] \*\*AC-US(\d+)-(\d+)\*\*:\s*([^(]+)(?:\(([^)]+)\))?/g;
|
|
189
|
+
let match;
|
|
190
|
+
while ((match = acPattern.exec(content)) !== null) {
|
|
191
|
+
const completed = match[1] === "x";
|
|
192
|
+
const usNumber = match[2];
|
|
193
|
+
const acNumber = match[3];
|
|
194
|
+
const description = match[4].trim();
|
|
195
|
+
const metadata = match[5] || "";
|
|
196
|
+
criteria.push({
|
|
197
|
+
id: `AC-US${usNumber}-${acNumber}`,
|
|
198
|
+
description,
|
|
199
|
+
completed,
|
|
200
|
+
priority: metadata.includes("P1") ? "P1" : metadata.includes("P2") ? "P2" : "",
|
|
201
|
+
testable: metadata.includes("testable")
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return criteria;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Extract increment ID from Implementation section
|
|
208
|
+
*/
|
|
209
|
+
extractIncrementId(content) {
|
|
210
|
+
const match = content.match(/\*\*Increment\*\*:\s*\[([^\]]+)\]/);
|
|
211
|
+
return match ? match[1] : null;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Extract tasks for this user story from increment tasks.md
|
|
215
|
+
*/
|
|
216
|
+
async extractTasks(content, incrementId, userStoryId) {
|
|
217
|
+
const tasks = [];
|
|
218
|
+
const incrementFolder = path.join(
|
|
219
|
+
this.projectRoot,
|
|
220
|
+
".specweave",
|
|
221
|
+
"increments",
|
|
222
|
+
incrementId
|
|
223
|
+
);
|
|
224
|
+
if (!existsSync(incrementFolder)) {
|
|
225
|
+
console.warn(` \u26A0\uFE0F Increment folder not found: ${incrementId}`);
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
const tasksPath = path.join(incrementFolder, "tasks.md");
|
|
229
|
+
if (!existsSync(tasksPath)) {
|
|
230
|
+
console.warn(` \u26A0\uFE0F tasks.md not found in ${incrementId}`);
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
const tasksContent = await readFile(tasksPath, "utf-8");
|
|
234
|
+
const taskLinkPattern = /- \[([T-\d]+):\s*([^\]]+)\]/g;
|
|
235
|
+
let match;
|
|
236
|
+
while ((match = taskLinkPattern.exec(content)) !== null) {
|
|
237
|
+
const taskId = match[1];
|
|
238
|
+
const taskTitle = match[2].trim();
|
|
239
|
+
const taskPattern = new RegExp(
|
|
240
|
+
`###\\s+${taskId}:\\s*([^\\n]+)[\\s\\S]*?\\*\\*Status\\*\\*:\\s*\\[([x\\s])\\]`,
|
|
241
|
+
"i"
|
|
242
|
+
);
|
|
243
|
+
const taskMatch = tasksContent.match(taskPattern);
|
|
244
|
+
const isCompleted = taskMatch ? taskMatch[2] === "x" : false;
|
|
245
|
+
const taskAnchor = taskId.toLowerCase() + "-" + taskTitle.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-");
|
|
246
|
+
const link = `../../increments/${incrementId}/tasks.md#${taskAnchor}`;
|
|
247
|
+
tasks.push({
|
|
248
|
+
id: taskId,
|
|
249
|
+
title: taskTitle,
|
|
250
|
+
status: isCompleted,
|
|
251
|
+
link
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return tasks;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Extract highest priority from acceptance criteria
|
|
258
|
+
* Priority order: P1 > P2 > P3
|
|
259
|
+
*/
|
|
260
|
+
extractPriorityFromACs(acceptanceCriteria) {
|
|
261
|
+
if (acceptanceCriteria.length === 0) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
if (acceptanceCriteria.some((ac) => ac.priority === "P1")) {
|
|
265
|
+
return "P1";
|
|
266
|
+
}
|
|
267
|
+
if (acceptanceCriteria.some((ac) => ac.priority === "P2")) {
|
|
268
|
+
return "P2";
|
|
269
|
+
}
|
|
270
|
+
if (acceptanceCriteria.some((ac) => ac.priority === "P3")) {
|
|
271
|
+
return "P3";
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Detect GitHub repository from git remote
|
|
277
|
+
* Returns "owner/repo" format
|
|
278
|
+
*/
|
|
279
|
+
async detectGitHubRepo() {
|
|
280
|
+
try {
|
|
281
|
+
const { execFileNoThrow } = await import("../../../src/utils/execFileNoThrow.js");
|
|
282
|
+
const result = await execFileNoThrow("git", ["remote", "get-url", "origin"]);
|
|
283
|
+
if (result.exitCode !== 0 || !result.stdout) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const remoteUrl = result.stdout.trim();
|
|
287
|
+
const githubMatch = remoteUrl.match(/github\.com[:/](.+\/.+?)(?:\.git)?$/);
|
|
288
|
+
if (githubMatch) {
|
|
289
|
+
return githubMatch[1];
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
} catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
UserStoryContentBuilder
|
|
299
|
+
};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Story Content Builder - GitHub issue content for individual user stories
|
|
3
|
+
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* - Reads individual us-*.md files from living docs
|
|
6
|
+
* - Extracts ACs and converts to GitHub task checkboxes
|
|
7
|
+
* - Finds and links related tasks from increment tasks.md
|
|
8
|
+
* - Generates rich GitHub issue body with proper formatting
|
|
9
|
+
*
|
|
10
|
+
* Key Features:
|
|
11
|
+
* - Checkable ACs (GitHub task checkboxes)
|
|
12
|
+
* - Task connections (links to increment tasks)
|
|
13
|
+
* - User story description (As a... I want... So that...)
|
|
14
|
+
* - Implementation details
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { readFile } from 'fs/promises';
|
|
18
|
+
import { existsSync } from 'fs';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import * as yaml from 'yaml';
|
|
21
|
+
|
|
22
|
+
interface UserStoryFrontmatter {
|
|
23
|
+
id: string;
|
|
24
|
+
epic: string;
|
|
25
|
+
title: string;
|
|
26
|
+
status: 'complete' | 'active' | 'planning' | 'not-started';
|
|
27
|
+
project: string;
|
|
28
|
+
priority: string;
|
|
29
|
+
created: string;
|
|
30
|
+
completed?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface AcceptanceCriterion {
|
|
34
|
+
id: string; // AC-US4-01
|
|
35
|
+
description: string;
|
|
36
|
+
completed: boolean;
|
|
37
|
+
priority: string;
|
|
38
|
+
testable: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Task {
|
|
42
|
+
id: string; // T-008
|
|
43
|
+
title: string;
|
|
44
|
+
status: boolean; // true = completed
|
|
45
|
+
link: string; // Link to tasks.md
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface UserStoryContent {
|
|
49
|
+
frontmatter: UserStoryFrontmatter;
|
|
50
|
+
userStoryDescription: {
|
|
51
|
+
asA: string;
|
|
52
|
+
iWant: string;
|
|
53
|
+
soThat: string;
|
|
54
|
+
} | null;
|
|
55
|
+
acceptanceCriteria: AcceptanceCriterion[];
|
|
56
|
+
tasks: Task[];
|
|
57
|
+
incrementId: string | null;
|
|
58
|
+
featureId: string; // FS-031
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class UserStoryContentBuilder {
|
|
62
|
+
private userStoryPath: string;
|
|
63
|
+
private projectRoot: string;
|
|
64
|
+
|
|
65
|
+
constructor(userStoryPath: string, projectRoot: string) {
|
|
66
|
+
this.userStoryPath = userStoryPath;
|
|
67
|
+
this.projectRoot = projectRoot;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse user story file and extract all content
|
|
72
|
+
*/
|
|
73
|
+
async parse(): Promise<UserStoryContent> {
|
|
74
|
+
const content = await readFile(this.userStoryPath, 'utf-8');
|
|
75
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
76
|
+
|
|
77
|
+
if (!match) {
|
|
78
|
+
throw new Error('User story file missing YAML frontmatter');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const frontmatter = yaml.parse(match[1]) as UserStoryFrontmatter;
|
|
82
|
+
const bodyContent = content.slice(match[0].length).trim();
|
|
83
|
+
|
|
84
|
+
// Extract feature ID from epic or path
|
|
85
|
+
const featureId = this.extractFeatureId(frontmatter.epic, this.userStoryPath);
|
|
86
|
+
|
|
87
|
+
// Extract user story description
|
|
88
|
+
const userStoryDescription = this.extractUserStoryDescription(bodyContent);
|
|
89
|
+
|
|
90
|
+
// Extract acceptance criteria
|
|
91
|
+
const acceptanceCriteria = this.extractAcceptanceCriteria(bodyContent);
|
|
92
|
+
|
|
93
|
+
// Extract increment ID and tasks
|
|
94
|
+
const incrementId = this.extractIncrementId(bodyContent);
|
|
95
|
+
const tasks = incrementId
|
|
96
|
+
? await this.extractTasks(bodyContent, incrementId, frontmatter.id)
|
|
97
|
+
: [];
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
frontmatter,
|
|
101
|
+
userStoryDescription,
|
|
102
|
+
acceptanceCriteria,
|
|
103
|
+
tasks,
|
|
104
|
+
incrementId,
|
|
105
|
+
featureId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build GitHub issue body from user story content
|
|
111
|
+
*
|
|
112
|
+
* @param githubRepo Optional GitHub repo in format "owner/repo" for generating URLs
|
|
113
|
+
*/
|
|
114
|
+
async buildIssueBody(githubRepo?: string): Promise<string> {
|
|
115
|
+
const content = await this.parse();
|
|
116
|
+
|
|
117
|
+
let body = '';
|
|
118
|
+
|
|
119
|
+
// Extract priority from ACs (highest priority wins)
|
|
120
|
+
const priority = this.extractPriorityFromACs(content.acceptanceCriteria);
|
|
121
|
+
|
|
122
|
+
// Detect GitHub repo from git remote if not provided
|
|
123
|
+
const repo = githubRepo || await this.detectGitHubRepo();
|
|
124
|
+
|
|
125
|
+
// Header with metadata
|
|
126
|
+
if (repo) {
|
|
127
|
+
body += `**Feature**: [${content.featureId}](https://github.com/${repo}/tree/develop/.specweave/docs/internal/specs/_features/${content.featureId})\n`;
|
|
128
|
+
} else {
|
|
129
|
+
body += `**Feature**: ${content.featureId}\n`;
|
|
130
|
+
}
|
|
131
|
+
body += `**Status**: ${content.frontmatter.status}\n`;
|
|
132
|
+
if (priority) {
|
|
133
|
+
body += `**Priority**: ${priority}\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
body += `\n---\n\n`;
|
|
137
|
+
|
|
138
|
+
// User Story description
|
|
139
|
+
body += `## User Story\n\n`;
|
|
140
|
+
if (content.userStoryDescription) {
|
|
141
|
+
body += `**As a** ${content.userStoryDescription.asA}\n`;
|
|
142
|
+
body += `**I want** ${content.userStoryDescription.iWant}\n`;
|
|
143
|
+
body += `**So that** ${content.userStoryDescription.soThat}\n\n`;
|
|
144
|
+
} else {
|
|
145
|
+
body += `*User story description not found*\n\n`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Link to full user story file (GitHub URL)
|
|
149
|
+
const usFilename = path.basename(this.userStoryPath);
|
|
150
|
+
if (repo) {
|
|
151
|
+
const relativePath = this.userStoryPath
|
|
152
|
+
.replace(this.projectRoot, '')
|
|
153
|
+
.replace(/^\//, '');
|
|
154
|
+
body += `📄 View full story: [\`${usFilename}\`](https://github.com/${repo}/tree/develop/${relativePath})\n\n`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
body += `---\n\n`;
|
|
158
|
+
|
|
159
|
+
// Acceptance Criteria (checkable!)
|
|
160
|
+
body += `## Acceptance Criteria\n\n`;
|
|
161
|
+
if (content.acceptanceCriteria.length > 0) {
|
|
162
|
+
const completed = content.acceptanceCriteria.filter((ac) => ac.completed).length;
|
|
163
|
+
const total = content.acceptanceCriteria.length;
|
|
164
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
165
|
+
body += `Progress: ${completed}/${total} criteria met (${percentage}%)\n\n`;
|
|
166
|
+
|
|
167
|
+
for (const ac of content.acceptanceCriteria) {
|
|
168
|
+
const checkbox = ac.completed ? '[x]' : '[ ]';
|
|
169
|
+
const priority = ac.priority ? ` (${ac.priority})` : '';
|
|
170
|
+
const testable = ac.testable ? ' [testable]' : '';
|
|
171
|
+
body += `- ${checkbox} **${ac.id}**: ${ac.description}${priority}${testable}\n`;
|
|
172
|
+
}
|
|
173
|
+
body += '\n';
|
|
174
|
+
} else {
|
|
175
|
+
body += `*No acceptance criteria defined*\n\n`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
body += `---\n\n`;
|
|
179
|
+
|
|
180
|
+
// Tasks (linked to increment!)
|
|
181
|
+
body += `## Implementation Tasks\n\n`;
|
|
182
|
+
if (content.tasks.length > 0) {
|
|
183
|
+
const completed = content.tasks.filter((t) => t.status).length;
|
|
184
|
+
const total = content.tasks.length;
|
|
185
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
186
|
+
body += `Progress: ${completed}/${total} tasks complete (${percentage}%)\n\n`;
|
|
187
|
+
|
|
188
|
+
if (content.incrementId) {
|
|
189
|
+
if (repo) {
|
|
190
|
+
body += `**Increment**: [${content.incrementId}](https://github.com/${repo}/tree/develop/.specweave/increments/${content.incrementId})\n\n`;
|
|
191
|
+
} else {
|
|
192
|
+
body += `**Increment**: ${content.incrementId}\n\n`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const task of content.tasks) {
|
|
197
|
+
const checkbox = task.status ? '[x]' : '[ ]';
|
|
198
|
+
// Convert relative link to GitHub URL if repo is known
|
|
199
|
+
let taskLink = task.link;
|
|
200
|
+
if (repo && taskLink.startsWith('../../')) {
|
|
201
|
+
const relativePath = taskLink.replace(/^\.\.\/\.\.\//, '.specweave/');
|
|
202
|
+
taskLink = `https://github.com/${repo}/tree/develop/${relativePath}`;
|
|
203
|
+
}
|
|
204
|
+
body += `- ${checkbox} [${task.id}: ${task.title}](${taskLink})\n`;
|
|
205
|
+
}
|
|
206
|
+
body += '\n';
|
|
207
|
+
} else {
|
|
208
|
+
body += `*No tasks defined yet*\n\n`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
body += `---\n\n`;
|
|
212
|
+
body += `🤖 Auto-synced by SpecWeave User Story Sync`;
|
|
213
|
+
|
|
214
|
+
return body;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extract feature ID from epic or path
|
|
219
|
+
*/
|
|
220
|
+
private extractFeatureId(epic: string, filePath: string): string {
|
|
221
|
+
// Try epic first (e.g., "FS-031")
|
|
222
|
+
if (epic && epic.startsWith('FS-')) {
|
|
223
|
+
return epic;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Extract from path: .../specs/default/FS-031/us-001-...md
|
|
227
|
+
const match = filePath.match(/FS-\d+/);
|
|
228
|
+
return match ? match[0] : 'FS-UNKNOWN';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Extract user story description (As a... I want... So that...)
|
|
233
|
+
*/
|
|
234
|
+
private extractUserStoryDescription(content: string): {
|
|
235
|
+
asA: string;
|
|
236
|
+
iWant: string;
|
|
237
|
+
soThat: string;
|
|
238
|
+
} | null {
|
|
239
|
+
const asAMatch = content.match(/\*\*As a\*\*\s+([^\n]+)/);
|
|
240
|
+
const iWantMatch = content.match(/\*\*I want\*\*\s+([^\n]+)/);
|
|
241
|
+
const soThatMatch = content.match(/\*\*So that\*\*\s+([^\n]+)/);
|
|
242
|
+
|
|
243
|
+
if (asAMatch && iWantMatch && soThatMatch) {
|
|
244
|
+
return {
|
|
245
|
+
asA: asAMatch[1].trim(),
|
|
246
|
+
iWant: iWantMatch[1].trim(),
|
|
247
|
+
soThat: soThatMatch[1].trim(),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Extract acceptance criteria from content
|
|
256
|
+
*/
|
|
257
|
+
private extractAcceptanceCriteria(content: string): AcceptanceCriterion[] {
|
|
258
|
+
const criteria: AcceptanceCriterion[] = [];
|
|
259
|
+
|
|
260
|
+
// Pattern: - [x] **AC-US4-01**: Description (P1, testable)
|
|
261
|
+
const acPattern =
|
|
262
|
+
/- \[([ x])\] \*\*AC-US(\d+)-(\d+)\*\*:\s*([^(]+)(?:\(([^)]+)\))?/g;
|
|
263
|
+
|
|
264
|
+
let match;
|
|
265
|
+
while ((match = acPattern.exec(content)) !== null) {
|
|
266
|
+
const completed = match[1] === 'x';
|
|
267
|
+
const usNumber = match[2];
|
|
268
|
+
const acNumber = match[3];
|
|
269
|
+
const description = match[4].trim();
|
|
270
|
+
const metadata = match[5] || '';
|
|
271
|
+
|
|
272
|
+
criteria.push({
|
|
273
|
+
id: `AC-US${usNumber}-${acNumber}`,
|
|
274
|
+
description,
|
|
275
|
+
completed,
|
|
276
|
+
priority: metadata.includes('P1') ? 'P1' : metadata.includes('P2') ? 'P2' : '',
|
|
277
|
+
testable: metadata.includes('testable'),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return criteria;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Extract increment ID from Implementation section
|
|
286
|
+
*/
|
|
287
|
+
private extractIncrementId(content: string): string | null {
|
|
288
|
+
// Pattern: **Increment**: [0031-external-tool-status-sync](...)
|
|
289
|
+
const match = content.match(/\*\*Increment\*\*:\s*\[([^\]]+)\]/);
|
|
290
|
+
return match ? match[1] : null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Extract tasks for this user story from increment tasks.md
|
|
295
|
+
*/
|
|
296
|
+
private async extractTasks(
|
|
297
|
+
content: string,
|
|
298
|
+
incrementId: string,
|
|
299
|
+
userStoryId: string
|
|
300
|
+
): Promise<Task[]> {
|
|
301
|
+
const tasks: Task[] = [];
|
|
302
|
+
|
|
303
|
+
// Find increment folder
|
|
304
|
+
const incrementFolder = path.join(
|
|
305
|
+
this.projectRoot,
|
|
306
|
+
'.specweave',
|
|
307
|
+
'increments',
|
|
308
|
+
incrementId
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (!existsSync(incrementFolder)) {
|
|
312
|
+
console.warn(` ⚠️ Increment folder not found: ${incrementId}`);
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const tasksPath = path.join(incrementFolder, 'tasks.md');
|
|
317
|
+
if (!existsSync(tasksPath)) {
|
|
318
|
+
console.warn(` ⚠️ tasks.md not found in ${incrementId}`);
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Read tasks.md
|
|
323
|
+
const tasksContent = await readFile(tasksPath, 'utf-8');
|
|
324
|
+
|
|
325
|
+
// Extract task links from user story's Implementation section
|
|
326
|
+
// Format: - [T-008: Create Status Sync Engine](link#t-008-create-status-sync-engine)
|
|
327
|
+
const taskLinkPattern = /- \[([T-\d]+):\s*([^\]]+)\]/g;
|
|
328
|
+
|
|
329
|
+
let match;
|
|
330
|
+
while ((match = taskLinkPattern.exec(content)) !== null) {
|
|
331
|
+
const taskId = match[1]; // e.g., "T-008"
|
|
332
|
+
const taskTitle = match[2].trim();
|
|
333
|
+
|
|
334
|
+
// Find task in tasks.md to get completion status
|
|
335
|
+
// Pattern: ### T-008: Create Status Sync Engine ... **Status**: [x]
|
|
336
|
+
const taskPattern = new RegExp(
|
|
337
|
+
`###\\s+${taskId}:\\s*([^\\n]+)[\\s\\S]*?\\*\\*Status\\*\\*:\\s*\\[([x\\s])\\]`,
|
|
338
|
+
'i'
|
|
339
|
+
);
|
|
340
|
+
const taskMatch = tasksContent.match(taskPattern);
|
|
341
|
+
|
|
342
|
+
const isCompleted = taskMatch ? taskMatch[2] === 'x' : false;
|
|
343
|
+
|
|
344
|
+
// Build link to task in tasks.md
|
|
345
|
+
const taskAnchor = taskId.toLowerCase() + '-' + taskTitle.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-');
|
|
346
|
+
const link = `../../increments/${incrementId}/tasks.md#${taskAnchor}`;
|
|
347
|
+
|
|
348
|
+
tasks.push({
|
|
349
|
+
id: taskId,
|
|
350
|
+
title: taskTitle,
|
|
351
|
+
status: isCompleted,
|
|
352
|
+
link,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return tasks;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Extract highest priority from acceptance criteria
|
|
361
|
+
* Priority order: P1 > P2 > P3
|
|
362
|
+
*/
|
|
363
|
+
private extractPriorityFromACs(acceptanceCriteria: AcceptanceCriterion[]): string | null {
|
|
364
|
+
if (acceptanceCriteria.length === 0) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Check for P1 first (highest priority)
|
|
369
|
+
if (acceptanceCriteria.some(ac => ac.priority === 'P1')) {
|
|
370
|
+
return 'P1';
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Then P2
|
|
374
|
+
if (acceptanceCriteria.some(ac => ac.priority === 'P2')) {
|
|
375
|
+
return 'P2';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Then P3
|
|
379
|
+
if (acceptanceCriteria.some(ac => ac.priority === 'P3')) {
|
|
380
|
+
return 'P3';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return null; // No priority found
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Detect GitHub repository from git remote
|
|
388
|
+
* Returns "owner/repo" format
|
|
389
|
+
*/
|
|
390
|
+
private async detectGitHubRepo(): Promise<string | null> {
|
|
391
|
+
try {
|
|
392
|
+
const { execFileNoThrow } = await import('../../../src/utils/execFileNoThrow.js');
|
|
393
|
+
const result = await execFileNoThrow('git', ['remote', 'get-url', 'origin']);
|
|
394
|
+
|
|
395
|
+
if (result.exitCode !== 0 || !result.stdout) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const remoteUrl = result.stdout.trim();
|
|
400
|
+
|
|
401
|
+
// Parse GitHub URL
|
|
402
|
+
// Formats: git@github.com:owner/repo.git or https://github.com/owner/repo.git
|
|
403
|
+
const githubMatch = remoteUrl.match(/github\.com[:/](.+\/.+?)(?:\.git)?$/);
|
|
404
|
+
if (githubMatch) {
|
|
405
|
+
return githubMatch[1]; // "owner/repo"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return null;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|