specweave 0.32.2 → 0.32.5
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 +51 -9
- package/bin/specweave.js +34 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts +100 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +291 -0
- package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts +103 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +310 -0
- package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts +126 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +207 -0
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -0
- package/dist/src/adapters/codex/README.md +1 -1
- package/dist/src/adapters/codex/adapter.js +1 -1
- package/dist/src/cli/commands/archive.d.ts +2 -0
- package/dist/src/cli/commands/archive.d.ts.map +1 -1
- package/dist/src/cli/commands/archive.js +33 -0
- package/dist/src/cli/commands/archive.js.map +1 -1
- package/dist/src/cli/commands/context.d.ts +92 -0
- package/dist/src/cli/commands/context.d.ts.map +1 -0
- package/dist/src/cli/commands/context.js +205 -0
- package/dist/src/cli/commands/context.js.map +1 -0
- package/dist/src/cli/commands/init-multiproject.js +1 -1
- package/dist/src/cli/commands/init-multiproject.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +111 -69
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/migrate-to-multiproject.js +2 -2
- package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
- package/dist/src/cli/helpers/init/external-import.d.ts +3 -0
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/external-import.js +17 -4
- package/dist/src/cli/helpers/init/external-import.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +1 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +2 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts +70 -0
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js +214 -4
- package/dist/src/cli/helpers/init/jira-ado-auto-detect.js.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts +4 -0
- package/dist/src/cli/helpers/init/living-docs-preflight.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/living-docs-preflight.js +34 -3
- package/dist/src/cli/helpers/init/living-docs-preflight.js.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/testing-config.js +9 -2
- package/dist/src/cli/helpers/init/testing-config.js.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.d.ts +3 -0
- package/dist/src/cli/helpers/init/translation-config.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/translation-config.js +21 -4
- package/dist/src/cli/helpers/init/translation-config.js.map +1 -1
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts +45 -0
- package/dist/src/cli/helpers/init/wizard-navigation.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js +97 -0
- package/dist/src/cli/helpers/init/wizard-navigation.js.map +1 -0
- package/dist/src/core/increment/increment-archiver.d.ts +25 -4
- package/dist/src/core/increment/increment-archiver.d.ts.map +1 -1
- package/dist/src/core/increment/increment-archiver.js +64 -20
- package/dist/src/core/increment/increment-archiver.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +65 -0
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +114 -0
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.d.ts +97 -0
- package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -0
- package/dist/src/core/living-docs/cross-project-sync.js +135 -0
- package/dist/src/core/living-docs/cross-project-sync.js.map +1 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +106 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js +146 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts +4 -0
- package/dist/src/core/living-docs/feature-archiver.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-archiver.js +32 -10
- package/dist/src/core/living-docs/feature-archiver.js.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.d.ts.map +1 -1
- package/dist/src/core/living-docs/feature-id-manager.js +7 -3
- package/dist/src/core/living-docs/feature-id-manager.js.map +1 -1
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts +38 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js +325 -0
- package/dist/src/core/living-docs/governance/ecosystem-detector.js.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts +74 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js +366 -0
- package/dist/src/core/living-docs/governance/frontend-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts +64 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js +229 -0
- package/dist/src/core/living-docs/governance/go-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/index.d.ts +50 -0
- package/dist/src/core/living-docs/governance/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/index.js +56 -0
- package/dist/src/core/living-docs/governance/index.js.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts +89 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js +356 -0
- package/dist/src/core/living-docs/governance/java-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts +83 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js +347 -0
- package/dist/src/core/living-docs/governance/python-standards-parser.js.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts +38 -0
- package/dist/src/core/living-docs/governance/standards-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/governance/standards-generator.js +476 -0
- package/dist/src/core/living-docs/governance/standards-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +54 -2
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts +5 -1
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +358 -30
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +44 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts +7 -3
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +94 -10
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.d.ts +22 -0
- package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.js +123 -19
- package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +8 -1
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +18 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +3 -1
- package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/parsers.js +24 -2
- package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -1
- package/dist/src/core/living-docs/types.d.ts +6 -0
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/living-docs/validators/index.d.ts +7 -0
- package/dist/src/core/living-docs/validators/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/validators/index.js +7 -0
- package/dist/src/core/living-docs/validators/index.js.map +1 -0
- package/dist/src/core/living-docs/validators/project-validator.d.ts +92 -0
- package/dist/src/core/living-docs/validators/project-validator.d.ts.map +1 -0
- package/dist/src/core/living-docs/validators/project-validator.js +142 -0
- package/dist/src/core/living-docs/validators/project-validator.js.map +1 -0
- package/dist/src/core/llm/provider-factory.js +2 -2
- package/dist/src/core/llm/provider-factory.js.map +1 -1
- package/dist/src/core/llm/providers/anthropic-provider.js +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.d.ts.map +1 -1
- package/dist/src/core/llm/providers/bedrock-provider.js +8 -4
- package/dist/src/core/llm/providers/bedrock-provider.js.map +1 -1
- package/dist/src/core/project/project-manager.d.ts.map +1 -1
- package/dist/src/core/project/project-manager.js +19 -17
- package/dist/src/core/project/project-manager.js.map +1 -1
- package/dist/src/core/types/config.d.ts +4 -2
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +34 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.d.ts +14 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +75 -0
- package/dist/src/importers/jira-importer.js.map +1 -1
- package/dist/src/integrations/jira/jira-token-provider.d.ts +93 -0
- package/dist/src/integrations/jira/jira-token-provider.d.ts.map +1 -0
- package/dist/src/integrations/jira/jira-token-provider.js +160 -0
- package/dist/src/integrations/jira/jira-token-provider.js.map +1 -0
- package/dist/src/sync/ado-reconciler.d.ts +92 -0
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -0
- package/dist/src/sync/ado-reconciler.js +335 -0
- package/dist/src/sync/ado-reconciler.js.map +1 -0
- package/dist/src/sync/jira-reconciler.d.ts +106 -0
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -0
- package/dist/src/sync/jira-reconciler.js +405 -0
- package/dist/src/sync/jira-reconciler.js.map +1 -0
- package/dist/src/types/model-selection.d.ts +6 -4
- package/dist/src/types/model-selection.d.ts.map +1 -1
- package/dist/src/types/model-selection.js +3 -1
- package/dist/src/types/model-selection.js.map +1 -1
- package/dist/src/utils/cross-cutting-detector.d.ts +66 -0
- package/dist/src/utils/cross-cutting-detector.d.ts.map +1 -0
- package/dist/src/utils/cross-cutting-detector.js +179 -0
- package/dist/src/utils/cross-cutting-detector.js.map +1 -0
- package/dist/src/utils/external-tool-drift-detector.d.ts +1 -1
- package/dist/src/utils/external-tool-drift-detector.d.ts.map +1 -1
- package/dist/src/utils/external-tool-drift-detector.js +5 -4
- package/dist/src/utils/external-tool-drift-detector.js.map +1 -1
- package/dist/src/utils/feature-id-derivation.d.ts +8 -3
- package/dist/src/utils/feature-id-derivation.d.ts.map +1 -1
- package/dist/src/utils/feature-id-derivation.js +14 -6
- package/dist/src/utils/feature-id-derivation.js.map +1 -1
- package/dist/src/utils/model-selection.d.ts +3 -4
- package/dist/src/utils/model-selection.d.ts.map +1 -1
- package/dist/src/utils/model-selection.js +3 -4
- package/dist/src/utils/model-selection.js.map +1 -1
- package/dist/src/utils/project-detection.d.ts +12 -8
- package/dist/src/utils/project-detection.d.ts.map +1 -1
- package/dist/src/utils/project-detection.js +13 -19
- package/dist/src/utils/project-detection.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/agents/code-standards-detective/AGENT.md +48 -0
- package/plugins/specweave/commands/specweave-costs.md +4 -4
- package/plugins/specweave/commands/specweave-do.md +9 -9
- package/plugins/specweave/commands/specweave-done.md +13 -0
- package/plugins/specweave/commands/specweave-status.md +64 -0
- package/plugins/specweave/commands/specweave-validate.md +27 -1
- package/plugins/specweave/hooks/hooks.json +11 -1
- package/plugins/specweave/hooks/spec-project-validator.sh +81 -25
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -0
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +34 -0
- package/plugins/specweave/scripts/read-costs.sh +3 -3
- package/plugins/specweave/skills/code-standards-analyzer/SKILL.md +58 -6
- package/plugins/specweave/skills/increment-planner/SKILL.md +135 -29
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +4 -2
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +2 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-multi-project.md +1 -1
- package/plugins/specweave/skills/increment-planner/templates/tasks-single-project.md +1 -1
- package/plugins/specweave/skills/spec-generator/SKILL.md +78 -7
- package/plugins/specweave-ado/commands/cleanup-duplicates.md +212 -0
- package/plugins/specweave-ado/commands/reconcile.md +120 -0
- package/plugins/specweave-ado/lib/ado-duplicate-detector.js +279 -0
- package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +407 -0
- package/plugins/specweave-github/agents/github-manager/AGENT.md +2 -2
- package/plugins/specweave-infrastructure/skills/hetzner-provisioner/README.md +1 -1
- package/plugins/specweave-jira/agents/jira-manager/AGENT.md +1 -1
- package/plugins/specweave-jira/agents/jira-multi-project-mapper/AGENT.md +530 -0
- package/plugins/specweave-jira/agents/jira-sync-judge/AGENT.md +438 -0
- package/plugins/specweave-jira/commands/cleanup-duplicates.md +219 -0
- package/plugins/specweave-jira/commands/close.md +297 -0
- package/plugins/specweave-jira/commands/create.md +198 -0
- package/plugins/specweave-jira/commands/reconcile.md +123 -0
- package/plugins/specweave-jira/commands/status.md +215 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.js +296 -0
- package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +434 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.js +160 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.ts +276 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.js +222 -0
- package/plugins/specweave-jira/lib/jira-profile-resolver.ts +427 -0
- package/plugins/specweave-jira/reference/jira-specweave-mapping.md +16 -11
- package/plugins/specweave-release/commands/specweave-release-npm.md +140 -14
- package/plugins/specweave/commands/specweave-switch-project.md +0 -168
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specweave-jira:status
|
|
3
|
+
description: Check JIRA sync status for SpecWeave increment
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# JIRA Status Command
|
|
7
|
+
|
|
8
|
+
**Usage**: `/specweave-jira:status <increment-id>`
|
|
9
|
+
|
|
10
|
+
**Purpose**: Display JIRA sync status and issue details for an increment
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Command Behavior
|
|
15
|
+
|
|
16
|
+
When user runs this command, Claude should:
|
|
17
|
+
|
|
18
|
+
### 1. Read Increment Metadata
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Load increment metadata
|
|
22
|
+
const metadataPath = `.specweave/increments/${incrementId}/metadata.json`;
|
|
23
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
24
|
+
|
|
25
|
+
// Check for linked JIRA issue (check both standard and legacy paths)
|
|
26
|
+
const jiraSync = metadata?.external_sync?.jira || metadata?.external_ids?.jira;
|
|
27
|
+
const issueKey = jiraSync?.issueKey || jiraSync?.epic;
|
|
28
|
+
|
|
29
|
+
if (!issueKey) {
|
|
30
|
+
console.log(`
|
|
31
|
+
⚠️ No JIRA issue linked
|
|
32
|
+
|
|
33
|
+
This increment is not linked to a JIRA issue.
|
|
34
|
+
Create one with: /specweave-jira:create ${incrementId}
|
|
35
|
+
`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Resolve JIRA Profile
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Priority: increment profile > global defaultProfile > activeProfile
|
|
44
|
+
let profileName = jiraSync?.profile;
|
|
45
|
+
if (!profileName) {
|
|
46
|
+
const config = JSON.parse(await fs.readFile('.specweave/config.json', 'utf-8'));
|
|
47
|
+
profileName = config?.sync?.defaultProfile ?? config?.sync?.activeProfile;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const profileConfig = config?.sync?.profiles?.[profileName];
|
|
51
|
+
const { domain, projectKey } = profileConfig?.config || {};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Read Local Task Status
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Check tasks.md for completion
|
|
58
|
+
const tasksPath = `.specweave/increments/${incrementId}/tasks.md`;
|
|
59
|
+
const tasksContent = await fs.readFile(tasksPath, 'utf-8');
|
|
60
|
+
|
|
61
|
+
const totalTasks = (tasksContent.match(/### T-\d+/g) || []).length;
|
|
62
|
+
const completedTasks = (tasksContent.match(/\[x\] completed/gi) || []).length;
|
|
63
|
+
const completion = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Invoke JIRA Manager Agent (Optional - For Live Status)
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Use Task tool with subagent_type: "specweave-jira:jira-manager:jira-manager"
|
|
70
|
+
|
|
71
|
+
Prompt: "Check JIRA sync status for increment {increment-id}.
|
|
72
|
+
|
|
73
|
+
Issue Key: {issueKey}
|
|
74
|
+
Profile: {profileName} (domain: {domain})
|
|
75
|
+
|
|
76
|
+
Steps:
|
|
77
|
+
1. GET issue from JIRA API: /rest/api/3/issue/{issueKey}
|
|
78
|
+
2. Extract: status, priority, assignee, sprint, story points
|
|
79
|
+
3. Compare local vs JIRA status
|
|
80
|
+
4. Detect any sync issues or drift
|
|
81
|
+
5. Display comprehensive status"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 5. Display Status
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
JIRA Sync Status
|
|
88
|
+
================
|
|
89
|
+
Increment: {increment-id}
|
|
90
|
+
Issue: {issueKey}
|
|
91
|
+
URL: https://{domain}/browse/{issueKey}
|
|
92
|
+
Status: {jiraStatus}
|
|
93
|
+
Completion: {completion}% ({completedTasks}/{totalTasks} tasks)
|
|
94
|
+
Last Synced: {lastSyncedAt} ({relativeTime})
|
|
95
|
+
Sync Enabled: ✅
|
|
96
|
+
|
|
97
|
+
Profile: {profileName}
|
|
98
|
+
Domain: {domain}
|
|
99
|
+
Project: {projectKey}
|
|
100
|
+
|
|
101
|
+
Next Sync: Automatic on task completion
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Example Output
|
|
107
|
+
|
|
108
|
+
### Issue Linked
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
User: /specweave-jira:status 0005-payment-integration
|
|
112
|
+
|
|
113
|
+
Claude:
|
|
114
|
+
JIRA Sync Status
|
|
115
|
+
================
|
|
116
|
+
Increment: 0005-payment-integration
|
|
117
|
+
Issue: MYPROJ-123
|
|
118
|
+
URL: https://company.atlassian.net/browse/MYPROJ-123
|
|
119
|
+
Status: In Progress
|
|
120
|
+
Completion: 60% (6/10 tasks)
|
|
121
|
+
Last Synced: 2025-12-07 10:30:00 (5 minutes ago)
|
|
122
|
+
Sync Enabled: ✅
|
|
123
|
+
|
|
124
|
+
Profile: jira-my-project
|
|
125
|
+
Domain: company.atlassian.net
|
|
126
|
+
Project: MYPROJ
|
|
127
|
+
|
|
128
|
+
Priority: High
|
|
129
|
+
Assignee: john.doe
|
|
130
|
+
Sprint: Sprint 42
|
|
131
|
+
|
|
132
|
+
Next Sync: Automatic on task completion
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### No Issue Linked
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
User: /specweave-jira:status 0005
|
|
139
|
+
|
|
140
|
+
Claude:
|
|
141
|
+
JIRA Sync Status
|
|
142
|
+
================
|
|
143
|
+
Increment: 0005-payment-integration
|
|
144
|
+
|
|
145
|
+
⚠️ No JIRA issue linked
|
|
146
|
+
|
|
147
|
+
This increment is not linked to a JIRA issue.
|
|
148
|
+
Create one with: /specweave-jira:create 0005-payment-integration
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Sync Issues Detected
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
User: /specweave-jira:status 0005
|
|
155
|
+
|
|
156
|
+
Claude:
|
|
157
|
+
JIRA Sync Status
|
|
158
|
+
================
|
|
159
|
+
Increment: 0005-payment-integration
|
|
160
|
+
Issue: MYPROJ-123
|
|
161
|
+
URL: https://company.atlassian.net/browse/MYPROJ-123
|
|
162
|
+
Status: In Progress
|
|
163
|
+
Completion: 60% (6/10 tasks)
|
|
164
|
+
Last Synced: 2025-12-05 10:30:00 (2 days ago)
|
|
165
|
+
Sync Enabled: ✅
|
|
166
|
+
|
|
167
|
+
⚠️ Sync Issues Detected:
|
|
168
|
+
- Local status: in_progress, JIRA status: Done
|
|
169
|
+
- Status drift detected - run /specweave-jira:pull to sync
|
|
170
|
+
|
|
171
|
+
Profile: jira-my-project
|
|
172
|
+
Domain: company.atlassian.net
|
|
173
|
+
Project: MYPROJ
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Status Information Displayed
|
|
179
|
+
|
|
180
|
+
| Field | Source | Description |
|
|
181
|
+
|-------|--------|-------------|
|
|
182
|
+
| Issue | metadata.json | JIRA issue key (e.g., PROJ-123) |
|
|
183
|
+
| URL | Constructed | Direct link to JIRA issue |
|
|
184
|
+
| Status | JIRA API | Current issue status |
|
|
185
|
+
| Completion | tasks.md | Local completion percentage |
|
|
186
|
+
| Last Synced | metadata.json | Timestamp of last sync |
|
|
187
|
+
| Profile | metadata/config | Which JIRA profile is used |
|
|
188
|
+
| Priority | JIRA API | Issue priority |
|
|
189
|
+
| Assignee | JIRA API | Current assignee |
|
|
190
|
+
| Sprint | JIRA API | Current sprint assignment |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Drift Detection
|
|
195
|
+
|
|
196
|
+
The status command detects common sync issues:
|
|
197
|
+
|
|
198
|
+
| Issue | Detection | Resolution |
|
|
199
|
+
|-------|-----------|------------|
|
|
200
|
+
| Status drift | Local vs JIRA status mismatch | `/specweave-jira:pull` |
|
|
201
|
+
| Stale sync | Last synced > 24 hours ago | `/specweave-jira:sync` |
|
|
202
|
+
| Missing issue | Issue deleted in JIRA | `/specweave-jira:create` |
|
|
203
|
+
| Permission denied | canUpdateExternalItems=false | Read-only mode note |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Related
|
|
208
|
+
|
|
209
|
+
| Command | Purpose |
|
|
210
|
+
|---------|---------|
|
|
211
|
+
| `/specweave-jira:pull` | Pull changes from JIRA |
|
|
212
|
+
| `/specweave-jira:push` | Push progress to JIRA |
|
|
213
|
+
| `/specweave-jira:sync` | Two-way sync |
|
|
214
|
+
| `/specweave-jira:create` | Create JIRA issue |
|
|
215
|
+
| `/specweave-jira:close` | Close issue when complete |
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { consoleLogger } from "../../../src/utils/logger.js";
|
|
2
|
+
class JiraDuplicateDetector {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.domain = options.domain || process.env.JIRA_DOMAIN || "";
|
|
5
|
+
const email = options.email || process.env.JIRA_EMAIL || "";
|
|
6
|
+
const token = options.token || process.env.JIRA_API_TOKEN || "";
|
|
7
|
+
this.auth = Buffer.from(`${email}:${token}`).toString("base64");
|
|
8
|
+
this.logger = options.logger || consoleLogger;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Phase 1: Check if issue exists before creating
|
|
12
|
+
*/
|
|
13
|
+
async checkBeforeCreate(summaryPattern, incrementId) {
|
|
14
|
+
try {
|
|
15
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
16
|
+
if (issues.length > 0) {
|
|
17
|
+
return {
|
|
18
|
+
found: true,
|
|
19
|
+
existingIssue: issues[0],
|
|
20
|
+
count: issues.length
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { found: false, count: 0 };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
this.logger.log(`\u26A0\uFE0F Detection check failed: ${error.message}`);
|
|
26
|
+
return { found: false, count: 0 };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Phase 2: Verify count after creation
|
|
31
|
+
*/
|
|
32
|
+
async verifyAfterCreate(summaryPattern, expectedCount = 1) {
|
|
33
|
+
try {
|
|
34
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
35
|
+
if (issues.length > expectedCount) {
|
|
36
|
+
const sorted = issues.sort(
|
|
37
|
+
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
|
38
|
+
);
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
expectedCount,
|
|
42
|
+
actualCount: issues.length,
|
|
43
|
+
duplicates: sorted.slice(expectedCount)
|
|
44
|
+
// All issues after expected count
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
expectedCount,
|
|
50
|
+
actualCount: issues.length,
|
|
51
|
+
duplicates: []
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
this.logger.log(`\u26A0\uFE0F Verification check failed: ${error.message}`);
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
// Assume success on error
|
|
58
|
+
expectedCount,
|
|
59
|
+
actualCount: expectedCount,
|
|
60
|
+
duplicates: []
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Phase 3: Auto-close duplicates
|
|
66
|
+
*/
|
|
67
|
+
async closeDuplicates(duplicates, keepIssueKey) {
|
|
68
|
+
const result = {
|
|
69
|
+
closedCount: 0,
|
|
70
|
+
keptCount: 1,
|
|
71
|
+
errors: []
|
|
72
|
+
};
|
|
73
|
+
for (const issue of duplicates) {
|
|
74
|
+
try {
|
|
75
|
+
await this.closeIssue(issue.key, keepIssueKey);
|
|
76
|
+
result.closedCount++;
|
|
77
|
+
this.logger.log(` \u2705 Closed ${issue.key} (duplicate of ${keepIssueKey})`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
result.errors.push(`${issue.key}: ${error.message}`);
|
|
80
|
+
this.logger.log(` \u274C Failed to close ${issue.key}: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Full cleanup: Find and close all duplicates for a feature
|
|
87
|
+
*/
|
|
88
|
+
async cleanupFeatureDuplicates(featureId, dryRun = false) {
|
|
89
|
+
const searchPattern = `[${featureId}]`;
|
|
90
|
+
const issues = await this.searchIssues(searchPattern);
|
|
91
|
+
this.logger.log(`
|
|
92
|
+
\u{1F50D} Scanning for duplicates in Feature ${featureId}...`);
|
|
93
|
+
this.logger.log(` Found ${issues.length} total issues`);
|
|
94
|
+
const groups = this.groupBySummary(issues);
|
|
95
|
+
const duplicateGroups = groups.filter((g) => g.duplicates.length > 0);
|
|
96
|
+
if (duplicateGroups.length === 0) {
|
|
97
|
+
this.logger.log(` \u2705 No duplicates found!`);
|
|
98
|
+
return {
|
|
99
|
+
groups: [],
|
|
100
|
+
totalIssues: issues.length,
|
|
101
|
+
duplicateCount: 0,
|
|
102
|
+
closedCount: 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
this.logger.log(` Detected ${duplicateGroups.length} duplicate groups:
|
|
106
|
+
`);
|
|
107
|
+
for (let i = 0; i < duplicateGroups.length; i++) {
|
|
108
|
+
const group = duplicateGroups[i];
|
|
109
|
+
this.logger.log(` \u{1F4CB} Group ${i + 1}: "${group.summary.substring(0, 50)}..."`);
|
|
110
|
+
this.logger.log(` - ${group.keepIssue.key} (KEEP) - Created ${group.keepIssue.created.split("T")[0]}`);
|
|
111
|
+
for (const dup of group.duplicates) {
|
|
112
|
+
this.logger.log(` - ${dup.key} (CLOSE) - Created ${dup.created.split("T")[0]} - DUPLICATE`);
|
|
113
|
+
}
|
|
114
|
+
this.logger.log("");
|
|
115
|
+
}
|
|
116
|
+
const totalDuplicates = duplicateGroups.reduce((sum, g) => sum + g.duplicates.length, 0);
|
|
117
|
+
if (dryRun) {
|
|
118
|
+
this.logger.log(`
|
|
119
|
+
\u2705 Dry run complete!`);
|
|
120
|
+
this.logger.log(` Total issues: ${issues.length}`);
|
|
121
|
+
this.logger.log(` Duplicate groups: ${duplicateGroups.length}`);
|
|
122
|
+
this.logger.log(` Issues to close: ${totalDuplicates}`);
|
|
123
|
+
this.logger.log(`
|
|
124
|
+
\u26A0\uFE0F This was a DRY RUN - no changes made.`);
|
|
125
|
+
return {
|
|
126
|
+
groups: duplicateGroups,
|
|
127
|
+
totalIssues: issues.length,
|
|
128
|
+
duplicateCount: totalDuplicates,
|
|
129
|
+
closedCount: 0
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
let closedCount = 0;
|
|
133
|
+
this.logger.log(`\u{1F5D1}\uFE0F Closing duplicates...`);
|
|
134
|
+
for (const group of duplicateGroups) {
|
|
135
|
+
const result = await this.closeDuplicates(group.duplicates, group.keepIssue.key);
|
|
136
|
+
closedCount += result.closedCount;
|
|
137
|
+
}
|
|
138
|
+
this.logger.log(`
|
|
139
|
+
\u2705 Cleanup complete!`);
|
|
140
|
+
this.logger.log(` Closed: ${closedCount} duplicates`);
|
|
141
|
+
this.logger.log(` Kept: ${duplicateGroups.length} original issues`);
|
|
142
|
+
return {
|
|
143
|
+
groups: duplicateGroups,
|
|
144
|
+
totalIssues: issues.length,
|
|
145
|
+
duplicateCount: totalDuplicates,
|
|
146
|
+
closedCount
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Group issues by summary
|
|
151
|
+
*/
|
|
152
|
+
groupBySummary(issues) {
|
|
153
|
+
const summaryMap = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const issue of issues) {
|
|
155
|
+
const existing = summaryMap.get(issue.summary) || [];
|
|
156
|
+
existing.push(issue);
|
|
157
|
+
summaryMap.set(issue.summary, existing);
|
|
158
|
+
}
|
|
159
|
+
const groups = [];
|
|
160
|
+
for (const [summary, groupIssues] of summaryMap) {
|
|
161
|
+
const sorted = groupIssues.sort(
|
|
162
|
+
(a, b) => new Date(a.created).getTime() - new Date(b.created).getTime()
|
|
163
|
+
);
|
|
164
|
+
groups.push({
|
|
165
|
+
summary,
|
|
166
|
+
issues: sorted,
|
|
167
|
+
keepIssue: sorted[0],
|
|
168
|
+
duplicates: sorted.slice(1)
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return groups;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Search for issues using JQL
|
|
175
|
+
*/
|
|
176
|
+
async searchIssues(summaryPattern) {
|
|
177
|
+
if (!this.domain || !this.auth) {
|
|
178
|
+
throw new Error("JIRA credentials not configured");
|
|
179
|
+
}
|
|
180
|
+
const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
|
|
181
|
+
const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
|
|
182
|
+
const response = await fetch(url, {
|
|
183
|
+
headers: {
|
|
184
|
+
Authorization: `Basic ${this.auth}`,
|
|
185
|
+
Accept: "application/json"
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
throw new Error(`JQL search failed: ${response.status}`);
|
|
190
|
+
}
|
|
191
|
+
const data = await response.json();
|
|
192
|
+
return (data.issues || []).map((issue) => ({
|
|
193
|
+
key: issue.key,
|
|
194
|
+
summary: issue.fields.summary,
|
|
195
|
+
status: issue.fields.status?.name,
|
|
196
|
+
created: issue.fields.created,
|
|
197
|
+
url: `https://${this.domain}/browse/${issue.key}`
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Close an issue with duplicate comment
|
|
202
|
+
*/
|
|
203
|
+
async closeIssue(issueKey, originalKey) {
|
|
204
|
+
await this.addComment(issueKey, originalKey);
|
|
205
|
+
const transitions = await this.getTransitions(issueKey);
|
|
206
|
+
const closeTransition = transitions.find(
|
|
207
|
+
(t) => t.name === "Won't Do" || t.name === "Done" || t.name === "Closed" || t.to?.name === "Won't Do" || t.to?.name === "Done"
|
|
208
|
+
);
|
|
209
|
+
if (!closeTransition) {
|
|
210
|
+
throw new Error(`No close transition found. Available: ${transitions.map((t) => t.name).join(", ")}`);
|
|
211
|
+
}
|
|
212
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
213
|
+
const response = await fetch(url, {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: {
|
|
216
|
+
Authorization: `Basic ${this.auth}`,
|
|
217
|
+
"Content-Type": "application/json"
|
|
218
|
+
},
|
|
219
|
+
body: JSON.stringify({
|
|
220
|
+
transition: { id: closeTransition.id }
|
|
221
|
+
})
|
|
222
|
+
});
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
const error = await response.text();
|
|
225
|
+
throw new Error(`Failed to transition issue: ${response.status} - ${error}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get available transitions for an issue
|
|
230
|
+
*/
|
|
231
|
+
async getTransitions(issueKey) {
|
|
232
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
233
|
+
const response = await fetch(url, {
|
|
234
|
+
headers: {
|
|
235
|
+
Authorization: `Basic ${this.auth}`,
|
|
236
|
+
Accept: "application/json"
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`Failed to get transitions: ${response.status}`);
|
|
241
|
+
}
|
|
242
|
+
const data = await response.json();
|
|
243
|
+
return data.transitions || [];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Add duplicate comment to issue
|
|
247
|
+
*/
|
|
248
|
+
async addComment(issueKey, originalKey) {
|
|
249
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
|
|
250
|
+
const comment = `h2. Duplicate of ${originalKey}
|
|
251
|
+
|
|
252
|
+
This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
|
|
253
|
+
|
|
254
|
+
The original issue (${originalKey}) contains the same content and should be used for tracking instead.
|
|
255
|
+
|
|
256
|
+
----
|
|
257
|
+
\u{1F916} Auto-closed by SpecWeave Duplicate Cleanup`;
|
|
258
|
+
const response = await fetch(url, {
|
|
259
|
+
method: "POST",
|
|
260
|
+
headers: {
|
|
261
|
+
Authorization: `Basic ${this.auth}`,
|
|
262
|
+
"Content-Type": "application/json"
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
body: {
|
|
266
|
+
type: "doc",
|
|
267
|
+
version: 1,
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "paragraph",
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: comment
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
});
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
this.logger.log(` \u26A0\uFE0F Failed to add comment to ${issueKey}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function cleanupJiraDuplicates(featureId, dryRun = false) {
|
|
288
|
+
const detector = new JiraDuplicateDetector();
|
|
289
|
+
return detector.cleanupFeatureDuplicates(featureId, dryRun);
|
|
290
|
+
}
|
|
291
|
+
var jira_duplicate_detector_default = JiraDuplicateDetector;
|
|
292
|
+
export {
|
|
293
|
+
JiraDuplicateDetector,
|
|
294
|
+
cleanupJiraDuplicates,
|
|
295
|
+
jira_duplicate_detector_default as default
|
|
296
|
+
};
|