specweave 0.18.1 → 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/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-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 +7 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/epic-content-builder.js +42 -0
- package/dist/plugins/specweave-github/lib/epic-content-builder.js.map +1 -1
- 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.js +1 -1
- 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 +8 -6
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js +78 -117
- package/dist/plugins/specweave-jira/lib/enhanced-jira-sync.js.map +1 -1
- 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/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/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.map +1 -1
- package/dist/src/core/sync/enhanced-content-builder.js +2 -1
- 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/types/config.d.ts +94 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js +16 -0
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +6 -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/package.json +1 -1
- package/plugins/specweave/COMMANDS.md +13 -4
- 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/hooks/hooks.json +4 -0
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +2 -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-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 +10 -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 +38 -0
- package/plugins/specweave-github/lib/epic-content-builder.ts +59 -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.ts +1 -1
- 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
|
@@ -1,238 +1,515 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SpecWeave Hierarchy Mapper
|
|
2
|
+
* SpecWeave Hierarchy Mapper (v4.0.0 - Universal Hierarchy with FS-XXX)
|
|
3
3
|
*
|
|
4
|
-
* Maps increments to
|
|
4
|
+
* Maps increments to universal hierarchy following work item type matrix:
|
|
5
|
+
* - Epic (EPIC-{id}) -> Cross-project strategic themes (_epics/)
|
|
6
|
+
* - Feature (FS-XXX) -> Cross-project features (_features/ + project folders)
|
|
7
|
+
* - User Story (us-{id}) -> Project-specific requirements (project/FS-XXX/us-{id}.md)
|
|
8
|
+
* - Task (T-{id}) -> Increment-specific implementation (increments/{id}/tasks.md)
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* Key Principles:
|
|
11
|
+
* - NO HARDCODED PROJECT NAMES (backend, frontend are examples)
|
|
12
|
+
* - Projects are DYNAMIC from config.json -> multiProject.projects
|
|
13
|
+
* - Single-project mode: ['default'] (always)
|
|
14
|
+
* - Multi-project mode: User-configured project names
|
|
15
|
+
* - Feature IDs assigned by creation date (FS-001, FS-002, etc.)
|
|
16
|
+
* - NO DUPLICATE FEATURE IDS (enforced by FeatureIDManager)
|
|
10
17
|
*
|
|
11
18
|
* @author SpecWeave Team
|
|
12
|
-
* @version
|
|
19
|
+
* @version 4.0.0 (Universal Hierarchy with FS-XXX)
|
|
13
20
|
*/
|
|
14
21
|
import fs from 'fs-extra';
|
|
15
22
|
import path from 'path';
|
|
23
|
+
import { ConfigManager } from '../config-manager.js';
|
|
24
|
+
import { FeatureIDManager } from './feature-id-manager.js';
|
|
16
25
|
/**
|
|
17
|
-
* HierarchyMapper - Maps increments to
|
|
26
|
+
* HierarchyMapper - Maps increments to universal hierarchy
|
|
18
27
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
28
|
+
* Universal Hierarchy (v4.0.0):
|
|
29
|
+
* 1. Epic (EPIC-YYYY-QN-{name}) -> _epics/EPIC-{id}/EPIC.md
|
|
30
|
+
* 2. Feature (FS-XXX) -> _features/FS-XXX/FEATURE.md + {project}/FS-XXX/
|
|
31
|
+
* 3. User Story (us-NNN-{name}) -> {project}/FS-XXX/us-{id}.md
|
|
32
|
+
* 4. Task (T-NNN) -> increments/{id}/tasks.md
|
|
24
33
|
*/
|
|
25
34
|
export class HierarchyMapper {
|
|
26
35
|
constructor(projectRoot, config) {
|
|
36
|
+
this.specweaveConfig = null;
|
|
27
37
|
this.projectRoot = projectRoot;
|
|
28
|
-
|
|
38
|
+
this.configManager = new ConfigManager(projectRoot);
|
|
39
|
+
this.featureIdManager = new FeatureIDManager(projectRoot);
|
|
29
40
|
this.config = {
|
|
30
|
-
level: 'standard',
|
|
31
|
-
specsBaseDir: path.join(projectRoot, '.specweave', 'docs', 'internal', 'specs'
|
|
32
|
-
|
|
33
|
-
featureFolderPattern: '{name}', // Feature-based naming (no FS- prefix)
|
|
34
|
-
userStoriesSubdir: '', // User stories go directly in feature folder, not subfolder
|
|
41
|
+
level: 'standard',
|
|
42
|
+
specsBaseDir: path.join(projectRoot, '.specweave', 'docs', 'internal', 'specs'),
|
|
43
|
+
detectEpicFrom: ['frontmatter', 'config'],
|
|
35
44
|
detectFeatureFrom: ['frontmatter', 'increment-name', 'config'],
|
|
36
|
-
|
|
45
|
+
detectProjectFrom: ['frontmatter', 'increment-name', 'config'],
|
|
37
46
|
...config,
|
|
38
47
|
};
|
|
39
48
|
}
|
|
40
49
|
/**
|
|
41
|
-
*
|
|
50
|
+
* Load SpecWeave config (cached)
|
|
51
|
+
*/
|
|
52
|
+
async getSpecweaveConfig() {
|
|
53
|
+
if (!this.specweaveConfig) {
|
|
54
|
+
this.specweaveConfig = await this.configManager.loadAsync();
|
|
55
|
+
}
|
|
56
|
+
return this.specweaveConfig;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get list of configured projects
|
|
42
60
|
*
|
|
43
|
-
*
|
|
61
|
+
* Returns:
|
|
62
|
+
* - Single-project mode: ['default']
|
|
63
|
+
* - Multi-project mode: User-configured project names (dynamic, no hardcodes)
|
|
64
|
+
*/
|
|
65
|
+
async getConfiguredProjects() {
|
|
66
|
+
const config = await this.getSpecweaveConfig();
|
|
67
|
+
// Check if multi-project mode is enabled
|
|
68
|
+
if (config.multiProject?.enabled && config.multiProject.projects) {
|
|
69
|
+
const projects = Object.keys(config.multiProject.projects);
|
|
70
|
+
// If projects object is empty, fallback to default
|
|
71
|
+
if (projects.length > 0) {
|
|
72
|
+
return projects;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Default: single-project mode
|
|
76
|
+
return ['default'];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a feature is archived
|
|
80
|
+
*/
|
|
81
|
+
async isFeatureArchived(featureId) {
|
|
82
|
+
const archivePaths = [
|
|
83
|
+
path.join(this.config.specsBaseDir, '_features', '_archive', featureId),
|
|
84
|
+
// Check project-specific archives
|
|
85
|
+
...(await this.getConfiguredProjects()).map(project => path.join(this.config.specsBaseDir, project, '_archive', featureId))
|
|
86
|
+
];
|
|
87
|
+
for (const archivePath of archivePaths) {
|
|
88
|
+
if (await fs.pathExists(archivePath)) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if an epic is archived
|
|
96
|
+
*/
|
|
97
|
+
async isEpicArchived(epicId) {
|
|
98
|
+
const archivePath = path.join(this.config.specsBaseDir, '_epics', '_archive', epicId);
|
|
99
|
+
return await fs.pathExists(archivePath);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Filter out archived items from mappings
|
|
103
|
+
*/
|
|
104
|
+
async filterArchivedItems(items, type) {
|
|
105
|
+
const filtered = [];
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
const isArchived = type === 'feature'
|
|
108
|
+
? await this.isFeatureArchived(item.id)
|
|
109
|
+
: await this.isEpicArchived(item.id);
|
|
110
|
+
if (!isArchived) {
|
|
111
|
+
filtered.push(item);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return filtered;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get project context for a specific project ID
|
|
118
|
+
*/
|
|
119
|
+
async getProjectContext(projectId) {
|
|
120
|
+
const config = await this.getSpecweaveConfig();
|
|
121
|
+
// Single-project mode
|
|
122
|
+
if (projectId === 'default') {
|
|
123
|
+
return {
|
|
124
|
+
projectId: 'default',
|
|
125
|
+
projectName: config.project?.name || 'Default Project',
|
|
126
|
+
projectPath: path.join(this.config.specsBaseDir, 'default'),
|
|
127
|
+
keywords: [],
|
|
128
|
+
techStack: config.project?.techStack || [],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Multi-project mode
|
|
132
|
+
const projectConfig = config.multiProject?.projects?.[projectId];
|
|
133
|
+
if (!projectConfig) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
projectId,
|
|
138
|
+
projectName: projectConfig.name,
|
|
139
|
+
projectPath: path.join(this.config.specsBaseDir, projectId),
|
|
140
|
+
keywords: projectConfig.keywords || [],
|
|
141
|
+
techStack: projectConfig.techStack || [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Detect which epic this increment belongs to (OPTIONAL)
|
|
146
|
+
*
|
|
147
|
+
* Epic Format: EPIC-YYYY-QN-{name}
|
|
148
|
+
* Example: EPIC-2025-Q4-platform
|
|
149
|
+
*
|
|
150
|
+
* Detection Methods:
|
|
151
|
+
* 1. Frontmatter: epic: EPIC-2025-Q4-platform
|
|
152
|
+
* 2. Config: livingDocs.hierarchyMapping.incrementToEpic
|
|
153
|
+
* 3. Fallback: null (no epic required)
|
|
154
|
+
*/
|
|
155
|
+
async detectEpicMapping(incrementId) {
|
|
156
|
+
const specPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'spec.md');
|
|
157
|
+
if (!fs.existsSync(specPath)) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
161
|
+
// Try each detection method
|
|
162
|
+
for (const method of this.config.detectEpicFrom) {
|
|
163
|
+
let mapping = null;
|
|
164
|
+
switch (method) {
|
|
165
|
+
case 'frontmatter':
|
|
166
|
+
mapping = await this.detectEpicFromFrontmatter(content, incrementId);
|
|
167
|
+
break;
|
|
168
|
+
case 'config':
|
|
169
|
+
mapping = await this.detectEpicFromConfig(incrementId);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
if (mapping && mapping.confidence >= 80) {
|
|
173
|
+
return mapping;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// No epic found (OK - epics are optional)
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Detect which feature this increment belongs to (REQUIRED)
|
|
181
|
+
*
|
|
182
|
+
* Feature Format (Greenfield): FS-XXX (matches increment number)
|
|
183
|
+
* Feature Format (Brownfield): FS-YY-MM-DD-{feature-name} (date-based)
|
|
184
|
+
* Examples:
|
|
185
|
+
* - Greenfield: 0031-external-tool-sync → FS-031
|
|
186
|
+
* - Brownfield: Imported from JIRA → FS-25-11-14-external-tool-sync
|
|
187
|
+
*
|
|
188
|
+
* Detection Methods:
|
|
189
|
+
* 1. Frontmatter: feature: FS-031 (greenfield) or feature: FS-25-11-14-name (brownfield)
|
|
190
|
+
* 2. Increment Name: 0031-external-tool-status-sync → FS-031 (auto-extract number)
|
|
191
|
+
* 3. Config: livingDocs.hierarchyMapping.incrementToFeature
|
|
192
|
+
* 4. Fallback: Auto-create feature from increment number (FS-XXX format)
|
|
44
193
|
*/
|
|
45
194
|
async detectFeatureMapping(incrementId) {
|
|
195
|
+
// Load feature registry first
|
|
196
|
+
await this.featureIdManager.loadRegistry();
|
|
46
197
|
const specPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'spec.md');
|
|
47
198
|
if (!fs.existsSync(specPath)) {
|
|
48
199
|
throw new Error(`Increment spec not found: ${specPath}`);
|
|
49
200
|
}
|
|
50
201
|
const content = await fs.readFile(specPath, 'utf-8');
|
|
51
|
-
// Try each detection method
|
|
202
|
+
// Try each detection method
|
|
52
203
|
for (const method of this.config.detectFeatureFrom) {
|
|
53
204
|
let mapping = null;
|
|
54
205
|
switch (method) {
|
|
55
206
|
case 'frontmatter':
|
|
56
|
-
mapping = await this.
|
|
207
|
+
mapping = await this.detectFeatureFromFrontmatter(content, incrementId);
|
|
57
208
|
break;
|
|
58
209
|
case 'increment-name':
|
|
59
|
-
mapping = await this.
|
|
210
|
+
mapping = await this.detectFeatureFromIncrementName(incrementId);
|
|
60
211
|
break;
|
|
61
212
|
case 'config':
|
|
62
|
-
mapping = await this.
|
|
213
|
+
mapping = await this.detectFeatureFromConfig(incrementId);
|
|
63
214
|
break;
|
|
64
215
|
}
|
|
65
216
|
if (mapping && mapping.confidence >= 80) {
|
|
66
217
|
return mapping;
|
|
67
218
|
}
|
|
68
219
|
}
|
|
69
|
-
// Fallback:
|
|
70
|
-
return await this.
|
|
220
|
+
// Fallback: Create feature from increment name + date
|
|
221
|
+
return await this.createFallbackFeatureMapping(incrementId);
|
|
71
222
|
}
|
|
72
223
|
/**
|
|
73
|
-
*
|
|
224
|
+
* Detect which projects this increment/feature affects (REQUIRED)
|
|
225
|
+
*
|
|
226
|
+
* Returns array of project IDs (dynamic, from config)
|
|
227
|
+
*
|
|
228
|
+
* Detection Methods:
|
|
229
|
+
* 1. Frontmatter: project: backend OR projects: [backend, frontend]
|
|
230
|
+
* 2. Increment Name: Contains project keyword (0031-backend-api-sync)
|
|
231
|
+
* 3. Config: livingDocs.hierarchyMapping.incrementToProjects
|
|
232
|
+
* 4. Fallback: ['default'] (single-project mode)
|
|
74
233
|
*/
|
|
75
|
-
async
|
|
76
|
-
|
|
234
|
+
async detectProjects(incrementId) {
|
|
235
|
+
const specPath = path.join(this.projectRoot, '.specweave', 'increments', incrementId, 'spec.md');
|
|
236
|
+
if (!fs.existsSync(specPath)) {
|
|
237
|
+
// Fallback to configured projects when spec missing
|
|
238
|
+
return await this.getConfiguredProjects();
|
|
239
|
+
}
|
|
240
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
241
|
+
const config = await this.getSpecweaveConfig();
|
|
242
|
+
const configuredProjects = await this.getConfiguredProjects();
|
|
243
|
+
// Method 1: Frontmatter (explicit)
|
|
244
|
+
const frontmatterProjects = await this.detectProjectsFromFrontmatter(content);
|
|
245
|
+
if (frontmatterProjects.length > 0) {
|
|
246
|
+
// Validate projects exist in config
|
|
247
|
+
const validProjects = frontmatterProjects.filter(p => configuredProjects.includes(p));
|
|
248
|
+
if (validProjects.length > 0) {
|
|
249
|
+
return validProjects;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Method 2: Increment name (contains project keyword)
|
|
253
|
+
const nameProjects = this.detectProjectsFromIncrementName(incrementId, configuredProjects, config);
|
|
254
|
+
if (nameProjects.length > 0) {
|
|
255
|
+
return nameProjects;
|
|
256
|
+
}
|
|
257
|
+
// Method 3: Config mapping
|
|
258
|
+
const configProjects = await this.detectProjectsFromConfig(incrementId);
|
|
259
|
+
if (configProjects.length > 0) {
|
|
260
|
+
return configProjects;
|
|
261
|
+
}
|
|
262
|
+
// Fallback: Use configured projects (single-project mode uses repo name, not 'default')
|
|
263
|
+
return configuredProjects;
|
|
77
264
|
}
|
|
78
265
|
/**
|
|
79
|
-
* Detect
|
|
80
|
-
*
|
|
81
|
-
* FORMAT (v0.18.1+): Accepts simple feature names (external-tool-status-sync)
|
|
82
|
-
* LEGACY: Also accepts old FS-### format (FS-001) - extracts from increment name instead
|
|
266
|
+
* Detect epic from frontmatter
|
|
83
267
|
*/
|
|
84
|
-
async
|
|
268
|
+
async detectEpicFromFrontmatter(content, incrementId) {
|
|
85
269
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
86
270
|
if (!frontmatterMatch)
|
|
87
271
|
return null;
|
|
88
272
|
try {
|
|
89
273
|
const yaml = await import('yaml');
|
|
90
274
|
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
91
|
-
// Check for explicit epic field
|
|
92
275
|
if (frontmatter.epic && typeof frontmatter.epic === 'string') {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
featureFolder,
|
|
105
|
-
featurePath: path.join(this.config.specsBaseDir, featureFolder),
|
|
106
|
-
userStoriesPath: path.join(this.config.specsBaseDir, featureFolder, this.config.userStoriesSubdir),
|
|
107
|
-
confidence: 100,
|
|
108
|
-
detectionMethod: 'frontmatter',
|
|
109
|
-
};
|
|
276
|
+
const epicId = frontmatter.epic; // EPIC-2025-Q4-platform
|
|
277
|
+
const epicFolder = epicId;
|
|
278
|
+
const epicPath = path.join(this.config.specsBaseDir, '_epics', epicFolder);
|
|
279
|
+
// Try to detect which features belong to this epic (from existing EPIC.md)
|
|
280
|
+
const epicFilePath = path.join(epicPath, 'EPIC.md');
|
|
281
|
+
let features = [];
|
|
282
|
+
if (fs.existsSync(epicFilePath)) {
|
|
283
|
+
const epicContent = await fs.readFile(epicFilePath, 'utf-8');
|
|
284
|
+
// Extract feature IDs from epic file (simple pattern matching)
|
|
285
|
+
const featureMatches = epicContent.matchAll(/FS-\d{2}-\d{2}-\d{2}-[a-z0-9-]+/g);
|
|
286
|
+
features = Array.from(featureMatches, m => m[0]);
|
|
110
287
|
}
|
|
288
|
+
return {
|
|
289
|
+
epicId,
|
|
290
|
+
epicFolder,
|
|
291
|
+
epicPath,
|
|
292
|
+
features,
|
|
293
|
+
confidence: 100,
|
|
294
|
+
detectionMethod: 'frontmatter',
|
|
295
|
+
};
|
|
111
296
|
}
|
|
112
297
|
}
|
|
113
298
|
catch (error) {
|
|
114
|
-
console.warn(` ⚠️ Failed to parse frontmatter: ${error}`);
|
|
299
|
+
console.warn(` ⚠️ Failed to parse frontmatter for epic detection: ${error}`);
|
|
115
300
|
}
|
|
116
301
|
return null;
|
|
117
302
|
}
|
|
118
303
|
/**
|
|
119
|
-
* Detect
|
|
304
|
+
* Detect epic from config
|
|
305
|
+
*/
|
|
306
|
+
async detectEpicFromConfig(incrementId) {
|
|
307
|
+
const config = await this.getSpecweaveConfig();
|
|
308
|
+
const epicMapping = config.livingDocs?.hierarchyMapping?.incrementToEpic?.[incrementId];
|
|
309
|
+
if (epicMapping && typeof epicMapping === 'string') {
|
|
310
|
+
const epicId = epicMapping;
|
|
311
|
+
const epicFolder = epicId;
|
|
312
|
+
const epicPath = path.join(this.config.specsBaseDir, '_epics', epicFolder);
|
|
313
|
+
return {
|
|
314
|
+
epicId,
|
|
315
|
+
epicFolder,
|
|
316
|
+
epicPath,
|
|
317
|
+
features: [],
|
|
318
|
+
confidence: 100,
|
|
319
|
+
detectionMethod: 'config',
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Detect feature from frontmatter
|
|
120
326
|
*
|
|
121
|
-
*
|
|
327
|
+
* CRITICAL: For greenfield projects, ALWAYS use increment number (FS-XXX)
|
|
328
|
+
* even if frontmatter says FS-YY-MM-DD-name (date-based format)
|
|
329
|
+
*/
|
|
330
|
+
async detectFeatureFromFrontmatter(content, incrementId) {
|
|
331
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
332
|
+
if (!frontmatterMatch)
|
|
333
|
+
return null;
|
|
334
|
+
try {
|
|
335
|
+
const yaml = await import('yaml');
|
|
336
|
+
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
337
|
+
if (frontmatter.feature && typeof frontmatter.feature === 'string') {
|
|
338
|
+
let featureId = frontmatter.feature;
|
|
339
|
+
// Check if this is a brownfield project (imported from external tool)
|
|
340
|
+
const isBrownfield = frontmatter.source === 'external' || frontmatter.imported === true;
|
|
341
|
+
if (!isBrownfield) {
|
|
342
|
+
// Greenfield: ALWAYS use increment number, ignore frontmatter's date-based ID
|
|
343
|
+
const numMatch = incrementId.match(/^(\d{4})-/);
|
|
344
|
+
if (numMatch) {
|
|
345
|
+
const num = parseInt(numMatch[1], 10);
|
|
346
|
+
featureId = `FS-${String(num).padStart(3, '0')}`;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// For brownfield, keep the date-based ID from frontmatter
|
|
350
|
+
const projects = await this.detectProjects(incrementId);
|
|
351
|
+
const epic = frontmatter.epic || undefined;
|
|
352
|
+
return this.buildFeatureMapping(featureId, projects, epic, 100, 'frontmatter');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
console.warn(` ⚠️ Failed to parse frontmatter for feature detection: ${error}`);
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Detect feature from increment name
|
|
122
362
|
*
|
|
123
|
-
* Extracts the descriptive part of increment ID and normalizes it to feature name
|
|
124
363
|
* Examples:
|
|
125
|
-
*
|
|
126
|
-
*
|
|
364
|
+
* - 0031-external-tool-status-sync → FS-031
|
|
365
|
+
* - 0032-user-authentication → FS-032
|
|
366
|
+
* - 0001-core-framework → FS-001
|
|
127
367
|
*/
|
|
128
|
-
async
|
|
129
|
-
// Extract
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
368
|
+
async detectFeatureFromIncrementName(incrementId) {
|
|
369
|
+
// Extract increment number (first 4 digits)
|
|
370
|
+
const numMatch = incrementId.match(/^(\d{4})-/);
|
|
371
|
+
if (!numMatch)
|
|
132
372
|
return null;
|
|
133
|
-
const
|
|
134
|
-
//
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
373
|
+
const num = parseInt(numMatch[1], 10);
|
|
374
|
+
// Build feature ID: FS-XXX (using last 3 digits, zero-padded)
|
|
375
|
+
const featureId = `FS-${String(num).padStart(3, '0')}`;
|
|
376
|
+
// Detect projects
|
|
377
|
+
const projects = await this.detectProjects(incrementId);
|
|
378
|
+
// Check if feature folder already exists
|
|
379
|
+
const existingFeature = await this.findExistingFeatureFolder(featureId);
|
|
380
|
+
if (existingFeature) {
|
|
381
|
+
return this.buildFeatureMapping(existingFeature, projects, undefined, 90, 'increment-name');
|
|
142
382
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
383
|
+
return this.buildFeatureMapping(featureId, projects, undefined, 90, 'increment-name');
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Detect feature from config
|
|
387
|
+
*/
|
|
388
|
+
async detectFeatureFromConfig(incrementId) {
|
|
389
|
+
const config = await this.getSpecweaveConfig();
|
|
390
|
+
const featureMapping = config.livingDocs?.hierarchyMapping?.incrementToFeature?.[incrementId];
|
|
391
|
+
if (featureMapping && typeof featureMapping === 'string') {
|
|
392
|
+
const featureId = featureMapping;
|
|
393
|
+
const projects = await this.detectProjects(incrementId);
|
|
394
|
+
return this.buildFeatureMapping(featureId, projects, undefined, 100, 'config');
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Create fallback feature mapping
|
|
400
|
+
*/
|
|
401
|
+
async createFallbackFeatureMapping(incrementId) {
|
|
402
|
+
// Extract increment number (first 4 digits)
|
|
403
|
+
const numMatch = incrementId.match(/^(\d{4})-/);
|
|
404
|
+
if (!numMatch) {
|
|
405
|
+
throw new Error(`Invalid increment ID format: ${incrementId}`);
|
|
148
406
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
407
|
+
const num = parseInt(numMatch[1], 10);
|
|
408
|
+
// Build feature ID: FS-XXX (using last 3 digits, zero-padded)
|
|
409
|
+
const featureId = `FS-${String(num).padStart(3, '0')}`;
|
|
410
|
+
// Detect projects
|
|
411
|
+
const projects = await this.detectProjects(incrementId);
|
|
412
|
+
console.log(` 📁 Creating new feature: ${featureId}`);
|
|
413
|
+
return this.buildFeatureMapping(featureId, projects, undefined, 80, 'fallback');
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Build FeatureMapping object
|
|
417
|
+
*
|
|
418
|
+
* CRITICAL: For greenfield (FS-XXX format), use the feature ID directly.
|
|
419
|
+
* The feature ID manager is only used for brownfield (date-based) IDs.
|
|
420
|
+
*/
|
|
421
|
+
buildFeatureMapping(featureId, projects, epic, confidence, detectionMethod) {
|
|
422
|
+
// Check if this is greenfield (FS-XXX) or brownfield (FS-YY-MM-DD-name)
|
|
423
|
+
const isGreenfield = /^FS-\d{3}$/.test(featureId);
|
|
424
|
+
// For greenfield, use the feature ID directly (no registry lookup)
|
|
425
|
+
// For brownfield, get assigned ID from registry (for deduplication)
|
|
426
|
+
const finalFeatureId = isGreenfield
|
|
427
|
+
? featureId
|
|
428
|
+
: this.featureIdManager.getAssignedId(featureId);
|
|
429
|
+
// Use final ID for folders
|
|
430
|
+
const featureFolder = finalFeatureId;
|
|
431
|
+
const featurePath = path.join(this.config.specsBaseDir, '_features', featureFolder);
|
|
432
|
+
// Build project paths map using final ID
|
|
433
|
+
const projectPaths = new Map();
|
|
434
|
+
for (const project of projects) {
|
|
435
|
+
projectPaths.set(project, path.join(this.config.specsBaseDir, project, finalFeatureId));
|
|
153
436
|
}
|
|
154
437
|
return {
|
|
155
|
-
featureId:
|
|
438
|
+
featureId: finalFeatureId,
|
|
156
439
|
featureFolder,
|
|
157
|
-
featurePath
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
440
|
+
featurePath,
|
|
441
|
+
projects,
|
|
442
|
+
projectPaths,
|
|
443
|
+
epic,
|
|
444
|
+
confidence,
|
|
445
|
+
detectionMethod,
|
|
161
446
|
};
|
|
162
447
|
}
|
|
163
448
|
/**
|
|
164
|
-
* Detect
|
|
449
|
+
* Detect projects from frontmatter
|
|
165
450
|
*/
|
|
166
|
-
async
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return null;
|
|
451
|
+
async detectProjectsFromFrontmatter(content) {
|
|
452
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
453
|
+
if (!frontmatterMatch)
|
|
454
|
+
return [];
|
|
171
455
|
try {
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
userStoriesPath: path.join(this.config.specsBaseDir, featureFolder, this.config.userStoriesSubdir),
|
|
182
|
-
confidence: 100,
|
|
183
|
-
detectionMethod: 'config',
|
|
184
|
-
};
|
|
185
|
-
}
|
|
456
|
+
const yaml = await import('yaml');
|
|
457
|
+
const frontmatter = yaml.parse(frontmatterMatch[1]);
|
|
458
|
+
// Single project: project: backend
|
|
459
|
+
if (frontmatter.project && typeof frontmatter.project === 'string') {
|
|
460
|
+
return [frontmatter.project];
|
|
461
|
+
}
|
|
462
|
+
// Multiple projects: projects: [backend, frontend]
|
|
463
|
+
if (frontmatter.projects && Array.isArray(frontmatter.projects)) {
|
|
464
|
+
return frontmatter.projects.filter((p) => typeof p === 'string');
|
|
186
465
|
}
|
|
187
466
|
}
|
|
188
467
|
catch (error) {
|
|
189
|
-
console.warn(` ⚠️ Failed to
|
|
468
|
+
console.warn(` ⚠️ Failed to parse frontmatter for project detection: ${error}`);
|
|
190
469
|
}
|
|
191
|
-
return
|
|
470
|
+
return [];
|
|
192
471
|
}
|
|
193
472
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* FORMAT (v0.18.1+): Simple descriptive names (no prefixes)
|
|
197
|
-
* - Uses core feature name extracted from increment ID
|
|
198
|
-
* - No date prefixes (removed to prevent duplicates)
|
|
199
|
-
* - No FS- prefixes (features are not numbered)
|
|
200
|
-
*
|
|
201
|
-
* Example: 0023-release-management-enhancements → release-management
|
|
473
|
+
* Detect projects from increment name (keyword matching)
|
|
202
474
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if
|
|
212
|
-
|
|
213
|
-
|
|
475
|
+
detectProjectsFromIncrementName(incrementId, configuredProjects, config) {
|
|
476
|
+
const detectedProjects = [];
|
|
477
|
+
for (const projectId of configuredProjects) {
|
|
478
|
+
if (projectId === 'default')
|
|
479
|
+
continue;
|
|
480
|
+
const projectConfig = config.multiProject?.projects?.[projectId];
|
|
481
|
+
if (!projectConfig)
|
|
482
|
+
continue;
|
|
483
|
+
// Check if increment name contains project keywords
|
|
484
|
+
const keywords = projectConfig.keywords || [projectId];
|
|
485
|
+
for (const keyword of keywords) {
|
|
486
|
+
if (incrementId.toLowerCase().includes(keyword.toLowerCase())) {
|
|
487
|
+
detectedProjects.push(projectId);
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
214
490
|
}
|
|
215
491
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
492
|
+
return detectedProjects;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Detect projects from config
|
|
496
|
+
*/
|
|
497
|
+
async detectProjectsFromConfig(incrementId) {
|
|
498
|
+
const config = await this.getSpecweaveConfig();
|
|
499
|
+
const projectMapping = config.livingDocs?.hierarchyMapping?.incrementToProjects?.[incrementId];
|
|
500
|
+
if (projectMapping) {
|
|
501
|
+
if (typeof projectMapping === 'string') {
|
|
502
|
+
return [projectMapping];
|
|
503
|
+
}
|
|
504
|
+
if (Array.isArray(projectMapping)) {
|
|
505
|
+
return projectMapping.filter((p) => typeof p === 'string');
|
|
506
|
+
}
|
|
224
507
|
}
|
|
225
|
-
return
|
|
226
|
-
featureId: featureFolder, // ID = folder name (external-tool-status-sync)
|
|
227
|
-
featureFolder,
|
|
228
|
-
featurePath,
|
|
229
|
-
userStoriesPath,
|
|
230
|
-
confidence: 80,
|
|
231
|
-
detectionMethod: 'fallback',
|
|
232
|
-
};
|
|
508
|
+
return [];
|
|
233
509
|
}
|
|
234
510
|
/**
|
|
235
|
-
* Get increment creation date in
|
|
511
|
+
* Get increment creation date in YY-MM-DD format
|
|
512
|
+
*
|
|
236
513
|
* Tries: metadata.json → spec.md frontmatter → current date
|
|
237
514
|
*/
|
|
238
515
|
async getIncrementCreationDate(incrementId) {
|
|
@@ -246,7 +523,7 @@ export class HierarchyMapper {
|
|
|
246
523
|
}
|
|
247
524
|
}
|
|
248
525
|
catch (error) {
|
|
249
|
-
// Fall through
|
|
526
|
+
// Fall through
|
|
250
527
|
}
|
|
251
528
|
}
|
|
252
529
|
// Try spec.md frontmatter
|
|
@@ -264,14 +541,14 @@ export class HierarchyMapper {
|
|
|
264
541
|
}
|
|
265
542
|
}
|
|
266
543
|
catch (error) {
|
|
267
|
-
// Fall through
|
|
544
|
+
// Fall through
|
|
268
545
|
}
|
|
269
546
|
}
|
|
270
547
|
// Fallback: current date
|
|
271
548
|
return this.formatDateShort(new Date().toISOString());
|
|
272
549
|
}
|
|
273
550
|
/**
|
|
274
|
-
* Format date as
|
|
551
|
+
* Format date as YY-MM-DD
|
|
275
552
|
* Input: "2025-11-14" or "2025-11-14T12:00:00Z"
|
|
276
553
|
* Output: "25-11-14"
|
|
277
554
|
*/
|
|
@@ -283,58 +560,66 @@ export class HierarchyMapper {
|
|
|
283
560
|
return `${yy}-${mm}-${dd}`;
|
|
284
561
|
}
|
|
285
562
|
/**
|
|
286
|
-
*
|
|
563
|
+
* Find existing feature folder (exact or fuzzy match)
|
|
287
564
|
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* external-tool-sync → external-tool-status-sync/ (fuzzy match)
|
|
565
|
+
* CRITICAL: For greenfield (FS-XXX), only exact match.
|
|
566
|
+
* For brownfield (FS-YY-MM-DD-name), fuzzy match allowed.
|
|
291
567
|
*/
|
|
292
|
-
async
|
|
568
|
+
async findExistingFeatureFolder(featureId) {
|
|
569
|
+
const featuresDir = path.join(this.config.specsBaseDir, '_features');
|
|
570
|
+
if (!fs.existsSync(featuresDir)) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
293
573
|
try {
|
|
294
|
-
const folders = await fs.readdir(
|
|
295
|
-
//
|
|
296
|
-
if (folders.includes(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
574
|
+
const folders = await fs.readdir(featuresDir);
|
|
575
|
+
// Exact match (always try this first)
|
|
576
|
+
if (folders.includes(featureId)) {
|
|
577
|
+
return featureId;
|
|
578
|
+
}
|
|
579
|
+
// Fuzzy match ONLY for brownfield (date-based) IDs
|
|
580
|
+
// Greenfield IDs (FS-XXX) should NEVER fuzzy match
|
|
581
|
+
const isGreenfield = /^FS-\d{3}$/.test(featureId);
|
|
582
|
+
if (isGreenfield) {
|
|
583
|
+
return null; // No fuzzy match for greenfield
|
|
584
|
+
}
|
|
585
|
+
// Fuzzy match for brownfield (feature name is substring)
|
|
586
|
+
// Example: FS-25-11-14-external-tool-sync matches FS-25-11-14-external-tool-status-sync
|
|
587
|
+
const featureNamePart = featureId.split('-').slice(3).join('-'); // external-tool-sync
|
|
588
|
+
if (!featureNamePart) {
|
|
589
|
+
return null; // No feature name part, skip fuzzy match
|
|
302
590
|
}
|
|
303
|
-
// Second: Try fuzzy match (feature name is substring of folder name)
|
|
304
591
|
for (const folder of folders) {
|
|
305
|
-
if (folder.
|
|
306
|
-
const folderPath = path.join(
|
|
592
|
+
if (folder.includes(featureNamePart)) {
|
|
593
|
+
const folderPath = path.join(featuresDir, folder);
|
|
307
594
|
const stats = await fs.stat(folderPath);
|
|
308
|
-
if (stats.isDirectory()
|
|
595
|
+
if (stats.isDirectory()) {
|
|
309
596
|
return folder;
|
|
310
597
|
}
|
|
311
598
|
}
|
|
312
599
|
}
|
|
313
600
|
}
|
|
314
601
|
catch (error) {
|
|
315
|
-
console.warn(` ⚠️ Failed to find feature folder
|
|
602
|
+
console.warn(` ⚠️ Failed to find feature folder: ${error}`);
|
|
316
603
|
}
|
|
317
604
|
return null;
|
|
318
605
|
}
|
|
319
606
|
/**
|
|
320
|
-
*
|
|
321
|
-
*/
|
|
322
|
-
async findEpicFolder(epicId) {
|
|
323
|
-
return this.findFeatureFolder(epicId);
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Get all feature folders (NEW: no FS- prefix filtering)
|
|
607
|
+
* Get all feature folders
|
|
327
608
|
*/
|
|
328
609
|
async getAllFeatureFolders() {
|
|
610
|
+
const featuresDir = path.join(this.config.specsBaseDir, '_features');
|
|
611
|
+
if (!fs.existsSync(featuresDir)) {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
329
614
|
try {
|
|
330
|
-
const folders = await fs.readdir(
|
|
615
|
+
const folders = await fs.readdir(featuresDir);
|
|
331
616
|
const featureFolders = [];
|
|
332
617
|
for (const folder of folders) {
|
|
333
618
|
// Skip special files/folders
|
|
334
|
-
if (folder
|
|
619
|
+
if (folder.startsWith('.') || folder.startsWith('_')) {
|
|
335
620
|
continue;
|
|
336
621
|
}
|
|
337
|
-
const folderPath = path.join(
|
|
622
|
+
const folderPath = path.join(featuresDir, folder);
|
|
338
623
|
const stats = await fs.stat(folderPath);
|
|
339
624
|
if (stats.isDirectory()) {
|
|
340
625
|
featureFolders.push(folder);
|
|
@@ -348,106 +633,39 @@ export class HierarchyMapper {
|
|
|
348
633
|
}
|
|
349
634
|
}
|
|
350
635
|
/**
|
|
351
|
-
*
|
|
636
|
+
* Get all epic folders
|
|
352
637
|
*/
|
|
353
638
|
async getAllEpicFolders() {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Create feature folder structure if missing (NEW: creates FEATURE.md)
|
|
380
|
-
*/
|
|
381
|
-
async createFeatureFolderStructure(featureFolder, title) {
|
|
382
|
-
const featurePath = path.join(this.config.specsBaseDir, featureFolder);
|
|
383
|
-
// Create feature directory (user stories go directly here, no subfolder)
|
|
384
|
-
await fs.ensureDir(featurePath);
|
|
385
|
-
// Create FEATURE.md if missing (feature overview - high-level summary)
|
|
386
|
-
const featureFilePath = path.join(featurePath, 'FEATURE.md');
|
|
387
|
-
if (!fs.existsSync(featureFilePath)) {
|
|
388
|
-
// ID = folder name (e.g., FS-25-11-14-release-management)
|
|
389
|
-
const featureContent = `---
|
|
390
|
-
id: ${featureFolder}
|
|
391
|
-
title: "${title}"
|
|
392
|
-
type: feature
|
|
393
|
-
status: in-progress
|
|
394
|
-
priority: P1
|
|
395
|
-
created: ${new Date().toISOString().split('T')[0]}
|
|
396
|
-
last_updated: ${new Date().toISOString().split('T')[0]}
|
|
397
|
-
# External Tool Mapping
|
|
398
|
-
external_tools:
|
|
399
|
-
github:
|
|
400
|
-
type: project
|
|
401
|
-
id: null
|
|
402
|
-
url: null
|
|
403
|
-
jira:
|
|
404
|
-
type: epic
|
|
405
|
-
key: null
|
|
406
|
-
url: null
|
|
407
|
-
ado:
|
|
408
|
-
type: epic
|
|
409
|
-
id: null
|
|
410
|
-
url: null
|
|
411
|
-
---
|
|
412
|
-
|
|
413
|
-
# ${featureFolder}: ${title}
|
|
414
|
-
|
|
415
|
-
## Overview
|
|
416
|
-
|
|
417
|
-
[High-level description of this feature - what it does and why it matters]
|
|
418
|
-
|
|
419
|
-
## Business Value
|
|
420
|
-
|
|
421
|
-
[Add business value and impact]
|
|
422
|
-
|
|
423
|
-
## User Stories
|
|
424
|
-
|
|
425
|
-
User stories are in this folder as \`us-*.md\` files:
|
|
426
|
-
- us-001-*.md
|
|
427
|
-
- us-002-*.md
|
|
428
|
-
- ...
|
|
429
|
-
|
|
430
|
-
## Implementation History
|
|
431
|
-
|
|
432
|
-
| Increment | Stories Implemented | Status | Completion Date |
|
|
433
|
-
|-----------|-------------------|--------|----------------|
|
|
434
|
-
| [TBD]() | TBD | 🚧 In Progress | TBD |
|
|
435
|
-
|
|
436
|
-
## External Tool Integration
|
|
437
|
-
|
|
438
|
-
**GitHub Project**: Not yet synced
|
|
439
|
-
**Jira Epic**: Not yet synced
|
|
440
|
-
**Azure DevOps Epic**: Not yet synced
|
|
441
|
-
`;
|
|
442
|
-
await fs.writeFile(featureFilePath, featureContent, 'utf-8');
|
|
443
|
-
console.log(` ✅ Created FEATURE.md for ${featureFolder}`);
|
|
639
|
+
const epicsDir = path.join(this.config.specsBaseDir, '_epics');
|
|
640
|
+
if (!fs.existsSync(epicsDir)) {
|
|
641
|
+
return [];
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
const folders = await fs.readdir(epicsDir);
|
|
645
|
+
const epicFolders = [];
|
|
646
|
+
for (const folder of folders) {
|
|
647
|
+
// Skip special files/folders
|
|
648
|
+
if (folder.startsWith('.') || folder.startsWith('_')) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const folderPath = path.join(epicsDir, folder);
|
|
652
|
+
const stats = await fs.stat(folderPath);
|
|
653
|
+
if (stats.isDirectory()) {
|
|
654
|
+
epicFolders.push(folder);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return epicFolders.sort();
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
console.warn(` ⚠️ Failed to get epic folders: ${error}`);
|
|
661
|
+
return [];
|
|
444
662
|
}
|
|
445
663
|
}
|
|
446
664
|
/**
|
|
447
|
-
* LEGACY:
|
|
665
|
+
* LEGACY: Backward compatibility alias for detectFeatureMapping
|
|
448
666
|
*/
|
|
449
|
-
async
|
|
450
|
-
return this.
|
|
667
|
+
async detectEpicMapping_LEGACY(incrementId) {
|
|
668
|
+
return this.detectFeatureMapping(incrementId);
|
|
451
669
|
}
|
|
452
670
|
}
|
|
453
671
|
//# sourceMappingURL=hierarchy-mapper.js.map
|