specweave 0.1.9 → 0.3.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 +600 -0
- package/README.md +245 -81
- package/bin/install-all.sh +1 -1
- package/bin/install-commands.sh +3 -3
- package/bin/specweave.js +39 -9
- package/dist/adapters/adapter-base.d.ts +1 -1
- package/dist/adapters/adapter-base.d.ts.map +1 -1
- package/dist/adapters/adapter-base.js +6 -41
- package/dist/adapters/adapter-base.js.map +1 -1
- package/dist/adapters/adapter-interface.js +1 -2
- package/dist/adapters/adapter-interface.js.map +1 -1
- package/dist/adapters/adapter-loader.d.ts +86 -0
- package/dist/adapters/adapter-loader.d.ts.map +1 -0
- package/dist/adapters/adapter-loader.js +216 -0
- package/dist/adapters/adapter-loader.js.map +1 -0
- package/dist/adapters/agents-md-generator.d.ts +48 -0
- package/dist/adapters/agents-md-generator.d.ts.map +1 -0
- package/dist/adapters/agents-md-generator.js +132 -0
- package/dist/adapters/agents-md-generator.js.map +1 -0
- package/dist/adapters/claude/adapter.d.ts +2 -2
- package/dist/adapters/claude/adapter.d.ts.map +1 -1
- package/dist/adapters/claude/adapter.js +5 -42
- package/dist/adapters/claude/adapter.js.map +1 -1
- package/dist/adapters/claude-md-generator.d.ts +78 -0
- package/dist/adapters/claude-md-generator.d.ts.map +1 -0
- package/dist/adapters/claude-md-generator.js +246 -0
- package/dist/adapters/claude-md-generator.js.map +1 -0
- package/dist/adapters/codex/adapter.d.ts +50 -0
- package/dist/adapters/codex/adapter.d.ts.map +1 -0
- package/dist/adapters/codex/adapter.js +316 -0
- package/dist/adapters/codex/adapter.js.map +1 -0
- package/dist/adapters/copilot/adapter.d.ts +10 -9
- package/dist/adapters/copilot/adapter.d.ts.map +1 -1
- package/dist/adapters/copilot/adapter.js +35 -100
- package/dist/adapters/copilot/adapter.js.map +1 -1
- package/dist/adapters/cursor/adapter.d.ts +8 -6
- package/dist/adapters/cursor/adapter.d.ts.map +1 -1
- package/dist/adapters/cursor/adapter.js +47 -130
- package/dist/adapters/cursor/adapter.js.map +1 -1
- package/dist/adapters/doc-generator.d.ts +69 -0
- package/dist/adapters/doc-generator.d.ts.map +1 -0
- package/dist/adapters/doc-generator.js +247 -0
- package/dist/adapters/doc-generator.js.map +1 -0
- package/dist/adapters/gemini/adapter.d.ts +50 -0
- package/dist/adapters/gemini/adapter.d.ts.map +1 -0
- package/dist/adapters/gemini/adapter.js +281 -0
- package/dist/adapters/gemini/adapter.js.map +1 -0
- package/dist/adapters/generic/adapter.d.ts +7 -4
- package/dist/adapters/generic/adapter.d.ts.map +1 -1
- package/dist/adapters/generic/adapter.js +60 -59
- package/dist/adapters/generic/adapter.js.map +1 -1
- package/dist/cli/commands/init.d.ts +3 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +272 -170
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +22 -58
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/list.d.ts.map +1 -1
- package/dist/cli/commands/list.js +27 -64
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/core/credentials-manager.d.ts +90 -0
- package/dist/core/credentials-manager.d.ts.map +1 -0
- package/dist/core/credentials-manager.js +271 -0
- package/dist/core/credentials-manager.js.map +1 -0
- package/dist/core/project-structure-detector.d.ts +92 -0
- package/dist/core/project-structure-detector.d.ts.map +1 -0
- package/dist/core/project-structure-detector.js +289 -0
- package/dist/core/project-structure-detector.js.map +1 -0
- package/dist/core/rfc-generator-v2.d.ts +149 -0
- package/dist/core/rfc-generator-v2.d.ts.map +1 -0
- package/dist/core/rfc-generator-v2.js +399 -0
- package/dist/core/rfc-generator-v2.js.map +1 -0
- package/dist/core/rfc-generator.d.ts +147 -0
- package/dist/core/rfc-generator.d.ts.map +1 -0
- package/dist/core/rfc-generator.js +434 -0
- package/dist/core/rfc-generator.js.map +1 -0
- package/dist/integrations/ado/ado-client.d.ts +123 -0
- package/dist/integrations/ado/ado-client.d.ts.map +1 -0
- package/dist/integrations/ado/ado-client.js +398 -0
- package/dist/integrations/ado/ado-client.js.map +1 -0
- package/dist/integrations/jira/jira-client.d.ts +139 -0
- package/dist/integrations/jira/jira-client.d.ts.map +1 -0
- package/dist/integrations/jira/jira-client.js +386 -0
- package/dist/integrations/jira/jira-client.js.map +1 -0
- package/dist/integrations/jira/jira-incremental-mapper.d.ts +75 -0
- package/dist/integrations/jira/jira-incremental-mapper.d.ts.map +1 -0
- package/dist/integrations/jira/jira-incremental-mapper.js +474 -0
- package/dist/integrations/jira/jira-incremental-mapper.js.map +1 -0
- package/dist/integrations/jira/jira-mapper.d.ts +105 -0
- package/dist/integrations/jira/jira-mapper.d.ts.map +1 -0
- package/dist/integrations/jira/jira-mapper.js +494 -0
- package/dist/integrations/jira/jira-mapper.js.map +1 -0
- package/dist/testing/test-generator.d.ts +117 -0
- package/dist/testing/test-generator.d.ts.map +1 -0
- package/dist/testing/test-generator.js +370 -0
- package/dist/testing/test-generator.js.map +1 -0
- package/dist/utils/auto-install.d.ts +3 -0
- package/dist/utils/auto-install.d.ts.map +1 -1
- package/dist/utils/auto-install.js +16 -82
- package/dist/utils/auto-install.js.map +1 -1
- package/dist/utils/esm-helpers.d.ts +50 -0
- package/dist/utils/esm-helpers.d.ts.map +1 -0
- package/dist/utils/esm-helpers.js +57 -0
- package/dist/utils/esm-helpers.js.map +1 -0
- package/package.json +16 -7
- package/src/adapters/README.md +1 -2
- package/src/adapters/adapter-base.ts +6 -3
- package/src/adapters/adapter-loader.ts +261 -0
- package/src/adapters/agents-md-generator.ts +162 -0
- package/src/adapters/claude/README.md +6 -14
- package/src/adapters/claude/adapter.ts +4 -4
- package/src/adapters/claude-md-generator.ts +311 -0
- package/src/adapters/codex/README.md +105 -0
- package/src/adapters/codex/adapter.ts +333 -0
- package/src/adapters/copilot/adapter.ts +36 -65
- package/src/adapters/cursor/README.md +0 -2
- package/src/adapters/cursor/adapter.ts +46 -92
- package/src/adapters/doc-generator.ts +331 -0
- package/src/adapters/gemini/README.md +97 -0
- package/src/adapters/gemini/adapter.ts +298 -0
- package/src/adapters/generic/adapter.ts +61 -57
- package/src/adapters/registry.yaml +86 -25
- package/src/agents/devops/AGENT.md +16 -18
- package/src/agents/docs-writer/AGENT.md +2 -2
- package/src/agents/pm/AGENT.md +1 -50
- package/src/commands/README.md +134 -111
- package/src/commands/{build.md → specweave.do.md} +141 -69
- package/src/commands/{done.md → specweave.done.md} +3 -3
- package/src/commands/{inc.md → specweave.inc.md} +4 -4
- package/src/commands/{increment.md → specweave.increment.md} +143 -76
- package/src/commands/specweave.md +430 -0
- package/src/commands/specweave.next.md +495 -0
- package/src/commands/{progress.md → specweave.progress.md} +12 -12
- package/src/commands/specweave.sync-docs.md +665 -0
- package/src/commands/specweave.sync-github.md +269 -0
- package/src/commands/specweave.sync-jira.md +197 -0
- package/src/commands/{validate.md → specweave.validate.md} +4 -4
- package/src/hooks/README.md +19 -29
- package/src/hooks/post-task-completion.sh +25 -30
- package/src/skills/ado-sync/README.md +1 -36
- package/src/skills/bmad-method-expert/SKILL.md +1 -3
- package/src/skills/brownfield-analyzer/SKILL.md +429 -23
- package/src/skills/brownfield-onboarder/SKILL.md +221 -8
- package/src/skills/context-loader/SKILL.md +239 -617
- package/src/skills/context-optimizer/SKILL.md +0 -30
- package/src/skills/github-sync/SKILL.md +1 -19
- package/src/skills/increment-planner/SKILL.md +64 -18
- package/src/skills/increment-quality-judge/SKILL.md +1 -36
- package/src/skills/jira-sync/README.md +1 -38
- package/src/skills/role-orchestrator/README.md +1 -22
- package/src/skills/role-orchestrator/SKILL.md +1 -59
- package/src/skills/skill-router/SKILL.md +0 -18
- package/src/skills/spec-kit-expert/SKILL.md +1 -3
- package/src/skills/specweave-detector/SKILL.md +225 -275
- package/src/skills/task-builder/README.md +1 -7
- package/src/templates/AGENTS.md.template +334 -0
- package/src/templates/CLAUDE.md.template +131 -298
- package/src/templates/README.md.template +115 -23
- package/src/templates/environments/minimal/README.md +0 -1
- package/INSTALL.md +0 -848
- package/SPECWEAVE.md +0 -743
- package/src/adapters/copilot/.github/copilot/instructions.md +0 -376
- package/src/adapters/cursor/.cursorrules +0 -325
- package/src/adapters/generic/SPECWEAVE-MANUAL.md +0 -676
- package/src/commands/create-project.md +0 -528
- package/src/commands/generate-docs.md +0 -623
- package/src/commands/review-docs.md +0 -331
- package/src/commands/sync-github.md +0 -115
- package/src/skills/ado-sync/test-cases/test-1.yaml +0 -9
- package/src/skills/ado-sync/test-cases/test-2.yaml +0 -8
- package/src/skills/ado-sync/test-cases/test-3.yaml +0 -9
- package/src/skills/bmad-method-expert/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/bmad-method-expert/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/bmad-method-expert/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/brownfield-analyzer/test-cases/test-1-basic-analysis.yaml +0 -48
- package/src/skills/brownfield-analyzer/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/brownfield-analyzer/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/brownfield-onboarder/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/brownfield-onboarder/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/brownfield-onboarder/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/calendar-system/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/calendar-system/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/calendar-system/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/context-loader/test-cases/test-1-basic-loading.yaml +0 -39
- package/src/skills/context-loader/test-cases/test-2-token-budget-exceeded.yaml +0 -44
- package/src/skills/context-loader/test-cases/test-3-section-anchors.yaml +0 -45
- package/src/skills/context-optimizer/test-cases/test-1-bug-fix-narrow.yaml +0 -97
- package/src/skills/context-optimizer/test-cases/test-2-feature-focused.yaml +0 -109
- package/src/skills/context-optimizer/test-cases/test-3-architecture-broad.yaml +0 -98
- package/src/skills/cost-optimizer/test-cases/test-1-basic-comparison.yaml +0 -75
- package/src/skills/cost-optimizer/test-cases/test-2-budget-constraint.yaml +0 -52
- package/src/skills/cost-optimizer/test-cases/test-3-scale-requirement.yaml +0 -63
- package/src/skills/cost-optimizer/test-results/README.md +0 -46
- package/src/skills/design-system-architect/test-cases/test-1-token-structure.yaml +0 -23
- package/src/skills/design-system-architect/test-cases/test-2-component-hierarchy.yaml +0 -24
- package/src/skills/design-system-architect/test-cases/test-3-accessibility-checklist.yaml +0 -23
- package/src/skills/diagrams-architect/test-cases/test-1-c4-context.yaml +0 -13
- package/src/skills/diagrams-architect/test-cases/test-2-sequence-diagram.yaml +0 -13
- package/src/skills/diagrams-architect/test-cases/test-3-er-diagram.yaml +0 -13
- package/src/skills/diagrams-generator/test-cases/test-1.yaml +0 -9
- package/src/skills/diagrams-generator/test-cases/test-2.yaml +0 -9
- package/src/skills/diagrams-generator/test-cases/test-3.yaml +0 -8
- package/src/skills/docs-updater/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/docs-updater/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/docs-updater/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/dotnet-backend/test-cases/test-1-rest-api.yaml +0 -14
- package/src/skills/dotnet-backend/test-cases/test-2-authentication.yaml +0 -13
- package/src/skills/dotnet-backend/test-cases/test-3-minimal-api.yaml +0 -13
- package/src/skills/e2e-playwright/test-cases/TC-001-basic-navigation.yaml +0 -54
- package/src/skills/e2e-playwright/test-cases/TC-002-form-interaction.yaml +0 -64
- package/src/skills/e2e-playwright/test-cases/TC-003-specweave-integration.yaml +0 -74
- package/src/skills/e2e-playwright/test-cases/TC-004-accessibility-check.yaml +0 -98
- package/src/skills/figma-designer/test-cases/test-1-design-system.yaml +0 -13
- package/src/skills/figma-designer/test-cases/test-2-component-library.yaml +0 -13
- package/src/skills/figma-designer/test-cases/test-3-responsive-layout.yaml +0 -13
- package/src/skills/figma-implementer/test-cases/test-1-design-to-react.yaml +0 -13
- package/src/skills/figma-implementer/test-cases/test-2-storybook.yaml +0 -13
- package/src/skills/figma-implementer/test-cases/test-3-design-tokens.yaml +0 -13
- package/src/skills/figma-mcp-connector/test-cases/test-1-read-file-desktop.yaml +0 -22
- package/src/skills/figma-mcp-connector/test-cases/test-2-read-file-framelink.yaml +0 -21
- package/src/skills/figma-mcp-connector/test-cases/test-3-error-handling.yaml +0 -18
- package/src/skills/figma-to-code/test-cases/test-1-token-generation.yaml +0 -29
- package/src/skills/figma-to-code/test-cases/test-2-component-generation.yaml +0 -27
- package/src/skills/figma-to-code/test-cases/test-3-typescript-generation.yaml +0 -28
- package/src/skills/frontend/test-cases/test-1-react-component.yaml +0 -13
- package/src/skills/frontend/test-cases/test-2-form-validation.yaml +0 -13
- package/src/skills/frontend/test-cases/test-3-state-management.yaml +0 -13
- package/src/skills/github-sync/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/github-sync/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/github-sync/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/hetzner-provisioner/test-cases/test-1-basic-provision.yaml +0 -71
- package/src/skills/hetzner-provisioner/test-cases/test-2-postgres-provision.yaml +0 -85
- package/src/skills/hetzner-provisioner/test-cases/test-3-ssl-config.yaml +0 -126
- package/src/skills/hetzner-provisioner/test-results/README.md +0 -259
- package/src/skills/increment-planner/test-cases/test-1-basic-feature.yaml +0 -27
- package/src/skills/increment-planner/test-cases/test-2-complex-feature.yaml +0 -30
- package/src/skills/increment-planner/test-cases/test-3-auto-numbering.yaml +0 -24
- package/src/skills/increment-quality-judge/test-cases/test-1-good-spec.yaml +0 -95
- package/src/skills/increment-quality-judge/test-cases/test-2-poor-spec.yaml +0 -108
- package/src/skills/increment-quality-judge/test-cases/test-3-export-suggestions.yaml +0 -87
- package/src/skills/jira-sync/test-cases/test-1.yaml +0 -9
- package/src/skills/jira-sync/test-cases/test-2.yaml +0 -9
- package/src/skills/jira-sync/test-cases/test-3.yaml +0 -10
- package/src/skills/nextjs/test-cases/test-1-app-router.yaml +0 -13
- package/src/skills/nextjs/test-cases/test-2-server-actions.yaml +0 -13
- package/src/skills/nextjs/test-cases/test-3-api-routes.yaml +0 -13
- package/src/skills/nodejs-backend/test-cases/test-1-express-api.yaml +0 -13
- package/src/skills/nodejs-backend/test-cases/test-2-prisma-orm.yaml +0 -13
- package/src/skills/nodejs-backend/test-cases/test-3-authentication.yaml +0 -13
- package/src/skills/notification-system/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/notification-system/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/notification-system/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/python-backend/test-cases/test-1-fastapi-crud.yaml +0 -13
- package/src/skills/python-backend/test-cases/test-2-sqlalchemy.yaml +0 -13
- package/src/skills/python-backend/test-cases/test-3-authentication.yaml +0 -13
- package/src/skills/role-orchestrator/test-cases/test-1-simple-product.yaml +0 -98
- package/src/skills/role-orchestrator/test-cases/test-2-quality-gate-failure.yaml +0 -73
- package/src/skills/role-orchestrator/test-cases/test-3-security-workflow.yaml +0 -121
- package/src/skills/role-orchestrator/test-cases/test-4-parallel-execution.yaml +0 -145
- package/src/skills/role-orchestrator/test-cases/test-5-feedback-loops.yaml +0 -149
- package/src/skills/skill-creator/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/skill-creator/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/skill-creator/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/skill-router/test-cases/test-1-basic-routing.yaml +0 -33
- package/src/skills/skill-router/test-cases/test-2-ambiguous-request.yaml +0 -42
- package/src/skills/skill-router/test-cases/test-3-nested-orchestration.yaml +0 -50
- package/src/skills/spec-driven-brainstorming/test-cases/TC-001-simple-idea-to-design.yaml +0 -148
- package/src/skills/spec-driven-brainstorming/test-cases/TC-002-complex-ultrathink-design.yaml +0 -190
- package/src/skills/spec-driven-brainstorming/test-cases/TC-003-unclear-requirements-socratic.yaml +0 -233
- package/src/skills/spec-driven-debugging/test-cases/TC-001-simple-auth-bug.yaml +0 -212
- package/src/skills/spec-driven-debugging/test-cases/TC-002-race-condition-ultrathink.yaml +0 -461
- package/src/skills/spec-driven-debugging/test-cases/TC-003-brownfield-missing-spec.yaml +0 -366
- package/src/skills/spec-kit-expert/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/spec-kit-expert/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/spec-kit-expert/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/specweave-ado-mapper/test-cases/test-1-export-to-ado.yaml +0 -13
- package/src/skills/specweave-ado-mapper/test-cases/test-2-import-from-ado.yaml +0 -13
- package/src/skills/specweave-ado-mapper/test-cases/test-3-bidirectional-sync.yaml +0 -13
- package/src/skills/specweave-detector/test-cases/test-1-basic-detection.yaml +0 -37
- package/src/skills/specweave-detector/test-cases/test-2-missing-config.yaml +0 -37
- package/src/skills/specweave-detector/test-cases/test-3-non-specweave-project.yaml +0 -34
- package/src/skills/specweave-jira-mapper/test-cases/test-1-export-to-jira.yaml +0 -13
- package/src/skills/specweave-jira-mapper/test-cases/test-2-import-from-jira.yaml +0 -13
- package/src/skills/specweave-jira-mapper/test-cases/test-3-sync-status.yaml +0 -13
- package/src/skills/stripe-integrator/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/stripe-integrator/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/stripe-integrator/test-cases/test-3-placeholder.yaml +0 -12
- package/src/skills/task-builder/test-cases/test-1-placeholder.yaml +0 -12
- package/src/skills/task-builder/test-cases/test-2-placeholder.yaml +0 -12
- package/src/skills/task-builder/test-cases/test-3-placeholder.yaml +0 -12
- package/src/templates/config.yaml +0 -351
- /package/src/commands/{list-increments.md → specweave.list-increments.md} +0 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure Credentials Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles API keys and credentials for external integrations (Jira, ADO, etc.)
|
|
5
|
+
* - Loads from .env file or environment variables
|
|
6
|
+
* - Validates credential format
|
|
7
|
+
* - Never logs secrets
|
|
8
|
+
* - Provides masked logging for debugging
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
export class CredentialsManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.credentials = {};
|
|
15
|
+
this.envLoaded = false;
|
|
16
|
+
this.loadFromEnv();
|
|
17
|
+
}
|
|
18
|
+
static getInstance() {
|
|
19
|
+
if (!CredentialsManager.instance) {
|
|
20
|
+
CredentialsManager.instance = new CredentialsManager();
|
|
21
|
+
}
|
|
22
|
+
return CredentialsManager.instance;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Load credentials from .env file or environment variables
|
|
26
|
+
*/
|
|
27
|
+
loadFromEnv() {
|
|
28
|
+
// First, try to load from .env file
|
|
29
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
30
|
+
if (fs.existsSync(envPath)) {
|
|
31
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
32
|
+
const envVars = this.parseEnvFile(envContent);
|
|
33
|
+
// Merge with process.env (process.env takes precedence)
|
|
34
|
+
Object.keys(envVars).forEach(key => {
|
|
35
|
+
if (!process.env[key]) {
|
|
36
|
+
process.env[key] = envVars[key];
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Load ADO credentials
|
|
41
|
+
if (process.env.AZURE_DEVOPS_PAT) {
|
|
42
|
+
this.credentials.ado = {
|
|
43
|
+
pat: process.env.AZURE_DEVOPS_PAT,
|
|
44
|
+
organization: process.env.AZURE_DEVOPS_ORG || '',
|
|
45
|
+
project: process.env.AZURE_DEVOPS_PROJECT || ''
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Load Jira credentials
|
|
49
|
+
if (process.env.JIRA_API_TOKEN) {
|
|
50
|
+
this.credentials.jira = {
|
|
51
|
+
apiToken: process.env.JIRA_API_TOKEN,
|
|
52
|
+
email: process.env.JIRA_EMAIL || '',
|
|
53
|
+
domain: process.env.JIRA_DOMAIN || ''
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
this.envLoaded = true;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Parse .env file content into key-value pairs
|
|
60
|
+
*/
|
|
61
|
+
parseEnvFile(content) {
|
|
62
|
+
const result = {};
|
|
63
|
+
const lines = content.split('\n');
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const trimmed = line.trim();
|
|
66
|
+
// Skip empty lines and comments
|
|
67
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Parse KEY=VALUE
|
|
71
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
72
|
+
if (match) {
|
|
73
|
+
const key = match[1].trim();
|
|
74
|
+
let value = match[2].trim();
|
|
75
|
+
// Remove quotes if present
|
|
76
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
77
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
78
|
+
value = value.slice(1, -1);
|
|
79
|
+
}
|
|
80
|
+
result[key] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get ADO credentials
|
|
87
|
+
* @throws Error if credentials not found
|
|
88
|
+
*/
|
|
89
|
+
getAdoCredentials() {
|
|
90
|
+
if (!this.credentials.ado) {
|
|
91
|
+
throw new Error('Azure DevOps credentials not found. Please set AZURE_DEVOPS_PAT, ' +
|
|
92
|
+
'AZURE_DEVOPS_ORG, and AZURE_DEVOPS_PROJECT in .env file or environment variables.');
|
|
93
|
+
}
|
|
94
|
+
this.validateAdoCredentials(this.credentials.ado);
|
|
95
|
+
return this.credentials.ado;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get Jira credentials
|
|
99
|
+
* @throws Error if credentials not found
|
|
100
|
+
*/
|
|
101
|
+
getJiraCredentials() {
|
|
102
|
+
if (!this.credentials.jira) {
|
|
103
|
+
throw new Error('Jira credentials not found. Please set JIRA_API_TOKEN, JIRA_EMAIL, ' +
|
|
104
|
+
'and JIRA_DOMAIN in .env file or environment variables.');
|
|
105
|
+
}
|
|
106
|
+
this.validateJiraCredentials(this.credentials.jira);
|
|
107
|
+
return this.credentials.jira;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if ADO credentials are available
|
|
111
|
+
*/
|
|
112
|
+
hasAdoCredentials() {
|
|
113
|
+
return !!this.credentials.ado?.pat;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Check if Jira credentials are available
|
|
117
|
+
*/
|
|
118
|
+
hasJiraCredentials() {
|
|
119
|
+
return !!this.credentials.jira?.apiToken;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate ADO credentials format
|
|
123
|
+
*/
|
|
124
|
+
validateAdoCredentials(creds) {
|
|
125
|
+
if (!creds.pat || creds.pat.length !== 52) {
|
|
126
|
+
console.warn(`⚠️ Azure DevOps PAT length unexpected. Expected: 52 characters, Got: ${creds.pat.length}`);
|
|
127
|
+
}
|
|
128
|
+
if (!creds.organization || !/^[a-zA-Z0-9-]+$/.test(creds.organization)) {
|
|
129
|
+
throw new Error(`Invalid Azure DevOps organization: "${creds.organization}". ` +
|
|
130
|
+
'Expected: alphanumeric and hyphens only.');
|
|
131
|
+
}
|
|
132
|
+
if (!creds.project) {
|
|
133
|
+
throw new Error('Azure DevOps project name is required.');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Validate Jira credentials format
|
|
138
|
+
*/
|
|
139
|
+
validateJiraCredentials(creds) {
|
|
140
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
141
|
+
if (!emailRegex.test(creds.email)) {
|
|
142
|
+
throw new Error(`Invalid Jira email: "${creds.email}". Expected: valid email address.`);
|
|
143
|
+
}
|
|
144
|
+
if (!creds.domain.includes('.atlassian.net') && !creds.domain.includes('://')) {
|
|
145
|
+
console.warn(`⚠️ Jira domain format unexpected: "${creds.domain}". ` +
|
|
146
|
+
'Expected: *.atlassian.net or custom domain with protocol.');
|
|
147
|
+
}
|
|
148
|
+
if (!creds.apiToken) {
|
|
149
|
+
throw new Error('Jira API token is required.');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get masked version of credentials for logging (safe to display)
|
|
154
|
+
*/
|
|
155
|
+
getMaskedAdoInfo() {
|
|
156
|
+
if (!this.credentials.ado) {
|
|
157
|
+
return 'ADO credentials: Not configured';
|
|
158
|
+
}
|
|
159
|
+
return [
|
|
160
|
+
'ADO credentials:',
|
|
161
|
+
` PAT: ${this.maskSecret(this.credentials.ado.pat)}`,
|
|
162
|
+
` Organization: ${this.credentials.ado.organization}`,
|
|
163
|
+
` Project: ${this.credentials.ado.project}`
|
|
164
|
+
].join('\n');
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get masked version of credentials for logging (safe to display)
|
|
168
|
+
*/
|
|
169
|
+
getMaskedJiraInfo() {
|
|
170
|
+
if (!this.credentials.jira) {
|
|
171
|
+
return 'Jira credentials: Not configured';
|
|
172
|
+
}
|
|
173
|
+
return [
|
|
174
|
+
'Jira credentials:',
|
|
175
|
+
` API Token: ${this.maskSecret(this.credentials.jira.apiToken)}`,
|
|
176
|
+
` Email: ${this.credentials.jira.email}`,
|
|
177
|
+
` Domain: ${this.credentials.jira.domain}`
|
|
178
|
+
].join('\n');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Mask a secret for safe logging
|
|
182
|
+
*/
|
|
183
|
+
maskSecret(secret) {
|
|
184
|
+
if (!secret)
|
|
185
|
+
return '[EMPTY]';
|
|
186
|
+
if (secret.length <= 8)
|
|
187
|
+
return '****';
|
|
188
|
+
const visibleChars = 4;
|
|
189
|
+
const start = secret.slice(0, visibleChars);
|
|
190
|
+
const end = secret.slice(-visibleChars);
|
|
191
|
+
return `${start}...${end} (${secret.length} chars)`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Save credentials to .env file (with backup)
|
|
195
|
+
*/
|
|
196
|
+
saveToEnvFile(config) {
|
|
197
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
198
|
+
// Backup existing .env
|
|
199
|
+
if (fs.existsSync(envPath)) {
|
|
200
|
+
const backup = `${envPath}.backup.${Date.now()}`;
|
|
201
|
+
fs.copyFileSync(envPath, backup);
|
|
202
|
+
console.log(`📦 Backed up existing .env to: ${backup}`);
|
|
203
|
+
}
|
|
204
|
+
// Read existing content
|
|
205
|
+
let existingContent = '';
|
|
206
|
+
let existingVars = {};
|
|
207
|
+
if (fs.existsSync(envPath)) {
|
|
208
|
+
existingContent = fs.readFileSync(envPath, 'utf-8');
|
|
209
|
+
existingVars = this.parseEnvFile(existingContent);
|
|
210
|
+
}
|
|
211
|
+
// Update with new credentials
|
|
212
|
+
if (config.ado) {
|
|
213
|
+
existingVars.AZURE_DEVOPS_PAT = config.ado.pat;
|
|
214
|
+
existingVars.AZURE_DEVOPS_ORG = config.ado.organization;
|
|
215
|
+
existingVars.AZURE_DEVOPS_PROJECT = config.ado.project;
|
|
216
|
+
}
|
|
217
|
+
if (config.jira) {
|
|
218
|
+
existingVars.JIRA_API_TOKEN = config.jira.apiToken;
|
|
219
|
+
existingVars.JIRA_EMAIL = config.jira.email;
|
|
220
|
+
existingVars.JIRA_DOMAIN = config.jira.domain;
|
|
221
|
+
}
|
|
222
|
+
// Write back to .env
|
|
223
|
+
const newContent = Object.entries(existingVars)
|
|
224
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
225
|
+
.join('\n');
|
|
226
|
+
fs.writeFileSync(envPath, newContent + '\n', 'utf-8');
|
|
227
|
+
console.log('✅ Credentials saved to .env');
|
|
228
|
+
// Ensure .env is in .gitignore
|
|
229
|
+
this.ensureGitignore();
|
|
230
|
+
// Reload credentials
|
|
231
|
+
this.loadFromEnv();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Ensure .env is in .gitignore
|
|
235
|
+
*/
|
|
236
|
+
ensureGitignore() {
|
|
237
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
238
|
+
let gitignoreContent = '';
|
|
239
|
+
if (fs.existsSync(gitignorePath)) {
|
|
240
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
241
|
+
}
|
|
242
|
+
if (!gitignoreContent.includes('.env')) {
|
|
243
|
+
fs.appendFileSync(gitignorePath, '\n# Environment variables\n.env\n.env.*.backup\n');
|
|
244
|
+
console.log('✅ Added .env to .gitignore');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Create .env.example file for team
|
|
249
|
+
*/
|
|
250
|
+
createEnvExample() {
|
|
251
|
+
const examplePath = path.join(process.cwd(), '.env.example');
|
|
252
|
+
const exampleContent = `# Azure DevOps Personal Access Token
|
|
253
|
+
# Get from: https://dev.azure.com/{org}/_usersSettings/tokens
|
|
254
|
+
# Scopes: Work Items (Read, Write, Manage), Code (Read), Project (Read)
|
|
255
|
+
AZURE_DEVOPS_PAT=your-ado-pat-52-chars-base64
|
|
256
|
+
AZURE_DEVOPS_ORG=your-organization-name
|
|
257
|
+
AZURE_DEVOPS_PROJECT=your-project-name
|
|
258
|
+
|
|
259
|
+
# Jira API Token
|
|
260
|
+
# Get from: https://id.atlassian.com/manage-profile/security/api-tokens
|
|
261
|
+
JIRA_API_TOKEN=your-jira-api-token
|
|
262
|
+
JIRA_EMAIL=your-email@example.com
|
|
263
|
+
JIRA_DOMAIN=your-domain.atlassian.net
|
|
264
|
+
`;
|
|
265
|
+
fs.writeFileSync(examplePath, exampleContent, 'utf-8');
|
|
266
|
+
console.log('✅ Created .env.example (safe to commit)');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Export singleton instance
|
|
270
|
+
export const credentialsManager = CredentialsManager.getInstance();
|
|
271
|
+
//# sourceMappingURL=credentials-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-manager.js","sourceRoot":"","sources":["../../src/core/credentials-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Structure Detector
|
|
3
|
+
*
|
|
4
|
+
* Automatically detects project management structure from:
|
|
5
|
+
* - Existing increments and work items
|
|
6
|
+
* - Source system metadata (Jira, ADO, GitHub)
|
|
7
|
+
* - Environment variables
|
|
8
|
+
*
|
|
9
|
+
* Supports various hierarchies:
|
|
10
|
+
* - Jira: Epic → Story → Sub-task
|
|
11
|
+
* - ADO: Epic → Feature → User Story → Task
|
|
12
|
+
* - GitHub: Milestone → Issue (or flat Issues)
|
|
13
|
+
* - Custom: Detected from patterns
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Hierarchy levels:
|
|
17
|
+
* - FLAT: No hierarchy (GitHub issues, flat ADO)
|
|
18
|
+
* - SINGLE_PARENT: Milestone/Feature → Items
|
|
19
|
+
* - TWO_LEVEL: Epic → Stories → Tasks
|
|
20
|
+
* - THREE_LEVEL: Initiative → Epic → Feature → Stories
|
|
21
|
+
*/
|
|
22
|
+
export type HierarchyLevel = 'flat' | 'single_parent' | 'two_level' | 'three_level';
|
|
23
|
+
/**
|
|
24
|
+
* Work item types vary by system
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkItemTypes {
|
|
27
|
+
topLevel?: string;
|
|
28
|
+
parentLevel?: string;
|
|
29
|
+
itemLevel: string;
|
|
30
|
+
subItemLevel?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Grouping strategy for RFC organization
|
|
34
|
+
*/
|
|
35
|
+
export type GroupingStrategy = 'by_type' | 'by_parent' | 'by_priority' | 'by_label' | 'flat' | 'custom';
|
|
36
|
+
/**
|
|
37
|
+
* Project structure configuration
|
|
38
|
+
*/
|
|
39
|
+
export interface ProjectStructure {
|
|
40
|
+
source: 'jira' | 'ado' | 'github' | 'manual';
|
|
41
|
+
hierarchyLevel: HierarchyLevel;
|
|
42
|
+
workItemTypes: WorkItemTypes;
|
|
43
|
+
groupingStrategy: GroupingStrategy;
|
|
44
|
+
customGrouping?: (items: any[]) => Record<string, any[]>;
|
|
45
|
+
epicFieldName?: string;
|
|
46
|
+
featureFieldName?: string;
|
|
47
|
+
milestoneFieldName?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Detected structure from analysis
|
|
51
|
+
*/
|
|
52
|
+
export interface DetectedStructure {
|
|
53
|
+
structure: ProjectStructure;
|
|
54
|
+
confidence: number;
|
|
55
|
+
evidence: string[];
|
|
56
|
+
sampleIncrements: string[];
|
|
57
|
+
}
|
|
58
|
+
export declare class ProjectStructureDetector {
|
|
59
|
+
private projectRoot;
|
|
60
|
+
constructor(projectRoot?: string);
|
|
61
|
+
/**
|
|
62
|
+
* Detect project structure with pure auto-detection
|
|
63
|
+
*/
|
|
64
|
+
detectStructure(): Promise<ProjectStructure>;
|
|
65
|
+
/**
|
|
66
|
+
* Auto-detect structure from existing increments
|
|
67
|
+
*/
|
|
68
|
+
private autoDetectStructure;
|
|
69
|
+
/**
|
|
70
|
+
* Analyze increments to detect structure
|
|
71
|
+
*/
|
|
72
|
+
private analyzeIncrements;
|
|
73
|
+
/**
|
|
74
|
+
* Infer work item types from source and hierarchy
|
|
75
|
+
*/
|
|
76
|
+
private inferWorkItemTypes;
|
|
77
|
+
/**
|
|
78
|
+
* Infer grouping strategy from source and hierarchy
|
|
79
|
+
*/
|
|
80
|
+
private inferGroupingStrategy;
|
|
81
|
+
/**
|
|
82
|
+
* Get default structure when no increments exist
|
|
83
|
+
*/
|
|
84
|
+
private getDefaultStructure;
|
|
85
|
+
/**
|
|
86
|
+
* Get structure summary for display
|
|
87
|
+
*/
|
|
88
|
+
getStructureSummary(structure: ProjectStructure): string;
|
|
89
|
+
private getHierarchyDescription;
|
|
90
|
+
private getGroupingDescription;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=project-structure-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-structure-detector.d.ts","sourceRoot":"","sources":["../../src/core/project-structure-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,eAAe,GAAG,WAAW,GAAG,aAAa,CAAC;AAEpF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,aAAa,GACb,UAAU,GACV,MAAM,GACN,QAAQ,CAAC;AAEb;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAGzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,GAAE,MAAsB;IAI/C;;OAEG;IACU,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAMzD;;OAEG;YACW,mBAAmB;IA2BjC;;OAEG;YACW,iBAAiB;IA2G/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAqC1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAwB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwD3B;;OAEG;IACI,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,GAAG,MAAM;IAO/D,OAAO,CAAC,uBAAuB;IAY/B,OAAO,CAAC,sBAAsB;CAY/B"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Structure Detector
|
|
3
|
+
*
|
|
4
|
+
* Automatically detects project management structure from:
|
|
5
|
+
* - Existing increments and work items
|
|
6
|
+
* - Source system metadata (Jira, ADO, GitHub)
|
|
7
|
+
* - Environment variables
|
|
8
|
+
*
|
|
9
|
+
* Supports various hierarchies:
|
|
10
|
+
* - Jira: Epic → Story → Sub-task
|
|
11
|
+
* - ADO: Epic → Feature → User Story → Task
|
|
12
|
+
* - GitHub: Milestone → Issue (or flat Issues)
|
|
13
|
+
* - Custom: Detected from patterns
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as yaml from 'js-yaml';
|
|
18
|
+
export class ProjectStructureDetector {
|
|
19
|
+
constructor(projectRoot = process.cwd()) {
|
|
20
|
+
this.projectRoot = projectRoot;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect project structure with pure auto-detection
|
|
24
|
+
*/
|
|
25
|
+
async detectStructure() {
|
|
26
|
+
// Auto-detect from existing increments
|
|
27
|
+
const detected = await this.autoDetectStructure();
|
|
28
|
+
return detected.structure;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Auto-detect structure from existing increments
|
|
32
|
+
*/
|
|
33
|
+
async autoDetectStructure() {
|
|
34
|
+
const incrementsDir = path.join(this.projectRoot, '.specweave', 'increments');
|
|
35
|
+
if (!fs.existsSync(incrementsDir)) {
|
|
36
|
+
// No increments yet, use defaults based on source
|
|
37
|
+
return this.getDefaultStructure();
|
|
38
|
+
}
|
|
39
|
+
const increments = fs.readdirSync(incrementsDir)
|
|
40
|
+
.filter(name => /^\d{4}/.test(name))
|
|
41
|
+
.slice(0, 5); // Analyze first 5 increments
|
|
42
|
+
if (increments.length === 0) {
|
|
43
|
+
return this.getDefaultStructure();
|
|
44
|
+
}
|
|
45
|
+
// Analyze increments for patterns
|
|
46
|
+
const analysis = await this.analyzeIncrements(increments);
|
|
47
|
+
return {
|
|
48
|
+
structure: analysis.structure,
|
|
49
|
+
confidence: analysis.confidence,
|
|
50
|
+
evidence: analysis.evidence,
|
|
51
|
+
sampleIncrements: increments
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Analyze increments to detect structure
|
|
56
|
+
*/
|
|
57
|
+
async analyzeIncrements(increments) {
|
|
58
|
+
const evidence = [];
|
|
59
|
+
let hasEpics = false;
|
|
60
|
+
let hasFeatures = false;
|
|
61
|
+
let hasMilestones = false;
|
|
62
|
+
let hasInitiatives = false;
|
|
63
|
+
let source = 'manual';
|
|
64
|
+
const workItemTypesSeen = new Set();
|
|
65
|
+
for (const increment of increments) {
|
|
66
|
+
const specPath = path.join(this.projectRoot, '.specweave', 'increments', increment, 'spec.md');
|
|
67
|
+
if (!fs.existsSync(specPath))
|
|
68
|
+
continue;
|
|
69
|
+
const content = fs.readFileSync(specPath, 'utf-8');
|
|
70
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
71
|
+
if (!frontmatterMatch)
|
|
72
|
+
continue;
|
|
73
|
+
const frontmatter = yaml.load(frontmatterMatch[1]);
|
|
74
|
+
// Detect source
|
|
75
|
+
if (frontmatter.jira) {
|
|
76
|
+
source = 'jira';
|
|
77
|
+
evidence.push(`Found Jira metadata in ${increment}`);
|
|
78
|
+
if (frontmatter.jira.epic_key) {
|
|
79
|
+
hasEpics = true;
|
|
80
|
+
evidence.push(`Found Epic link: ${frontmatter.jira.epic_key}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (frontmatter.ado) {
|
|
84
|
+
source = 'ado';
|
|
85
|
+
evidence.push(`Found ADO metadata in ${increment}`);
|
|
86
|
+
if (frontmatter.ado.feature_id) {
|
|
87
|
+
hasFeatures = true;
|
|
88
|
+
evidence.push(`Found Feature link: ${frontmatter.ado.feature_id}`);
|
|
89
|
+
}
|
|
90
|
+
if (frontmatter.ado.epic_id) {
|
|
91
|
+
hasEpics = true;
|
|
92
|
+
evidence.push(`Found Epic link in ADO`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (frontmatter.github) {
|
|
96
|
+
source = 'github';
|
|
97
|
+
evidence.push(`Found GitHub metadata in ${increment}`);
|
|
98
|
+
if (frontmatter.github.milestone) {
|
|
99
|
+
hasMilestones = true;
|
|
100
|
+
evidence.push(`Found Milestone: ${frontmatter.github.milestone}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Analyze work items
|
|
104
|
+
if (frontmatter.work_items && Array.isArray(frontmatter.work_items)) {
|
|
105
|
+
frontmatter.work_items.forEach((item) => {
|
|
106
|
+
if (item.type) {
|
|
107
|
+
workItemTypesSeen.add(item.type);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Determine hierarchy level
|
|
113
|
+
let hierarchyLevel;
|
|
114
|
+
if (hasInitiatives && hasEpics && hasFeatures) {
|
|
115
|
+
hierarchyLevel = 'three_level';
|
|
116
|
+
evidence.push('Detected 3-level hierarchy (Initiative → Epic → Feature → Stories)');
|
|
117
|
+
}
|
|
118
|
+
else if (hasEpics || hasFeatures || hasMilestones) {
|
|
119
|
+
hierarchyLevel = 'two_level';
|
|
120
|
+
evidence.push(`Detected 2-level hierarchy with ${hasEpics ? 'Epics' : hasFeatures ? 'Features' : 'Milestones'}`);
|
|
121
|
+
}
|
|
122
|
+
else if (workItemTypesSeen.size > 0) {
|
|
123
|
+
hierarchyLevel = 'single_parent';
|
|
124
|
+
evidence.push('Detected single-level grouping by type');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
hierarchyLevel = 'flat';
|
|
128
|
+
evidence.push('Detected flat structure (no hierarchy)');
|
|
129
|
+
}
|
|
130
|
+
// Determine work item types
|
|
131
|
+
const workItemTypes = this.inferWorkItemTypes(source, hierarchyLevel, workItemTypesSeen);
|
|
132
|
+
// Determine grouping strategy
|
|
133
|
+
const groupingStrategy = this.inferGroupingStrategy(source, hierarchyLevel, workItemTypesSeen);
|
|
134
|
+
return {
|
|
135
|
+
structure: {
|
|
136
|
+
source,
|
|
137
|
+
hierarchyLevel,
|
|
138
|
+
workItemTypes,
|
|
139
|
+
groupingStrategy
|
|
140
|
+
},
|
|
141
|
+
confidence: Math.min(increments.length / 5, 1), // More samples = higher confidence
|
|
142
|
+
evidence,
|
|
143
|
+
sampleIncrements: increments
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Infer work item types from source and hierarchy
|
|
148
|
+
*/
|
|
149
|
+
inferWorkItemTypes(source, hierarchyLevel, typesSeen) {
|
|
150
|
+
switch (source) {
|
|
151
|
+
case 'jira':
|
|
152
|
+
return hierarchyLevel === 'three_level'
|
|
153
|
+
? { topLevel: 'Initiative', parentLevel: 'Epic', itemLevel: 'Story', subItemLevel: 'Sub-task' }
|
|
154
|
+
: { parentLevel: 'Epic', itemLevel: 'Story', subItemLevel: 'Sub-task' };
|
|
155
|
+
case 'ado':
|
|
156
|
+
if (hierarchyLevel === 'three_level') {
|
|
157
|
+
return { topLevel: 'Epic', parentLevel: 'Feature', itemLevel: 'User Story', subItemLevel: 'Task' };
|
|
158
|
+
}
|
|
159
|
+
else if (hierarchyLevel === 'two_level') {
|
|
160
|
+
return { parentLevel: 'Feature', itemLevel: 'User Story', subItemLevel: 'Task' };
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return { itemLevel: 'User Story', subItemLevel: 'Task' };
|
|
164
|
+
}
|
|
165
|
+
case 'github':
|
|
166
|
+
return hierarchyLevel === 'two_level'
|
|
167
|
+
? { parentLevel: 'Milestone', itemLevel: 'Issue' }
|
|
168
|
+
: { itemLevel: 'Issue' };
|
|
169
|
+
default:
|
|
170
|
+
// Infer from types seen
|
|
171
|
+
if (typesSeen.has('story')) {
|
|
172
|
+
return { parentLevel: 'Epic', itemLevel: 'Story', subItemLevel: 'Task' };
|
|
173
|
+
}
|
|
174
|
+
else if (typesSeen.has('issue')) {
|
|
175
|
+
return { itemLevel: 'Issue' };
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
return { itemLevel: 'Item' };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Infer grouping strategy from source and hierarchy
|
|
184
|
+
*/
|
|
185
|
+
inferGroupingStrategy(source, hierarchyLevel, typesSeen) {
|
|
186
|
+
// If we see story/bug/task types, group by type
|
|
187
|
+
if (typesSeen.has('story') || typesSeen.has('bug') || typesSeen.has('task')) {
|
|
188
|
+
return 'by_type';
|
|
189
|
+
}
|
|
190
|
+
// If we have parent level (epic, feature, milestone), group by parent
|
|
191
|
+
if (hierarchyLevel === 'two_level' || hierarchyLevel === 'three_level') {
|
|
192
|
+
return 'by_parent';
|
|
193
|
+
}
|
|
194
|
+
// GitHub typically uses flat or by_label
|
|
195
|
+
if (source === 'github') {
|
|
196
|
+
return 'by_label';
|
|
197
|
+
}
|
|
198
|
+
// Default to by_type
|
|
199
|
+
return 'by_type';
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get default structure when no increments exist
|
|
203
|
+
*/
|
|
204
|
+
getDefaultStructure() {
|
|
205
|
+
// Try to infer from environment or use sensible defaults
|
|
206
|
+
const envSource = (process.env.JIRA_DOMAIN ? 'jira' :
|
|
207
|
+
process.env.AZURE_DEVOPS_ORG ? 'ado' :
|
|
208
|
+
process.env.GITHUB_REPOSITORY ? 'github' : 'manual');
|
|
209
|
+
const defaultStructures = {
|
|
210
|
+
jira: {
|
|
211
|
+
source: 'jira',
|
|
212
|
+
hierarchyLevel: 'two_level',
|
|
213
|
+
workItemTypes: {
|
|
214
|
+
parentLevel: 'Epic',
|
|
215
|
+
itemLevel: 'Story',
|
|
216
|
+
subItemLevel: 'Sub-task'
|
|
217
|
+
},
|
|
218
|
+
groupingStrategy: 'by_type'
|
|
219
|
+
},
|
|
220
|
+
ado: {
|
|
221
|
+
source: 'ado',
|
|
222
|
+
hierarchyLevel: 'two_level',
|
|
223
|
+
workItemTypes: {
|
|
224
|
+
parentLevel: 'Feature',
|
|
225
|
+
itemLevel: 'User Story',
|
|
226
|
+
subItemLevel: 'Task'
|
|
227
|
+
},
|
|
228
|
+
groupingStrategy: 'by_type'
|
|
229
|
+
},
|
|
230
|
+
github: {
|
|
231
|
+
source: 'github',
|
|
232
|
+
hierarchyLevel: 'two_level',
|
|
233
|
+
workItemTypes: {
|
|
234
|
+
parentLevel: 'Milestone',
|
|
235
|
+
itemLevel: 'Issue'
|
|
236
|
+
},
|
|
237
|
+
groupingStrategy: 'by_label'
|
|
238
|
+
},
|
|
239
|
+
manual: {
|
|
240
|
+
source: 'manual',
|
|
241
|
+
hierarchyLevel: 'two_level',
|
|
242
|
+
workItemTypes: {
|
|
243
|
+
parentLevel: 'Feature',
|
|
244
|
+
itemLevel: 'Story',
|
|
245
|
+
subItemLevel: 'Task'
|
|
246
|
+
},
|
|
247
|
+
groupingStrategy: 'by_type'
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
return {
|
|
251
|
+
structure: defaultStructures[envSource],
|
|
252
|
+
confidence: 0.5,
|
|
253
|
+
evidence: ['Using default structure based on environment'],
|
|
254
|
+
sampleIncrements: []
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get structure summary for display
|
|
259
|
+
*/
|
|
260
|
+
getStructureSummary(structure) {
|
|
261
|
+
const hierarchy = this.getHierarchyDescription(structure);
|
|
262
|
+
const grouping = this.getGroupingDescription(structure.groupingStrategy);
|
|
263
|
+
return `Source: ${structure.source.toUpperCase()}\nHierarchy: ${hierarchy}\nGrouping: ${grouping}`;
|
|
264
|
+
}
|
|
265
|
+
getHierarchyDescription(structure) {
|
|
266
|
+
const types = structure.workItemTypes;
|
|
267
|
+
const parts = [];
|
|
268
|
+
if (types.topLevel)
|
|
269
|
+
parts.push(types.topLevel);
|
|
270
|
+
if (types.parentLevel)
|
|
271
|
+
parts.push(types.parentLevel);
|
|
272
|
+
parts.push(types.itemLevel);
|
|
273
|
+
if (types.subItemLevel)
|
|
274
|
+
parts.push(types.subItemLevel);
|
|
275
|
+
return parts.join(' → ');
|
|
276
|
+
}
|
|
277
|
+
getGroupingDescription(strategy) {
|
|
278
|
+
const descriptions = {
|
|
279
|
+
by_type: 'Group by Type (Story, Bug, Task)',
|
|
280
|
+
by_parent: 'Group by Parent (Epic, Feature, Milestone)',
|
|
281
|
+
by_priority: 'Group by Priority (P1, P2, P3)',
|
|
282
|
+
by_label: 'Group by Label/Tag',
|
|
283
|
+
flat: 'Flat List (No Grouping)',
|
|
284
|
+
custom: 'Custom Grouping'
|
|
285
|
+
};
|
|
286
|
+
return descriptions[strategy];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=project-structure-detector.js.map
|