specweave 0.32.2 → 0.32.3
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 +39 -0
- 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.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/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/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 +6 -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 +17 -8
- 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/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/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/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/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-validate.md +27 -1
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +80 -25
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +135 -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 +56 -25
- 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-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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Duplicate Detector (v0.33.0)
|
|
3
|
+
*
|
|
4
|
+
* Implements 3-phase duplicate protection for JIRA issues:
|
|
5
|
+
* 1. Detection: Check before create
|
|
6
|
+
* 2. Verification: Count check after create
|
|
7
|
+
* 3. Reflection: Auto-close duplicates
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the GitHub DuplicateDetector pattern for consistency.
|
|
10
|
+
*/
|
|
11
|
+
import { consoleLogger } from '../../../src/utils/logger.js';
|
|
12
|
+
export class JiraDuplicateDetector {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.domain = options.domain || process.env.JIRA_DOMAIN || '';
|
|
15
|
+
const email = options.email || process.env.JIRA_EMAIL || '';
|
|
16
|
+
const token = options.token || process.env.JIRA_API_TOKEN || '';
|
|
17
|
+
this.auth = Buffer.from(`${email}:${token}`).toString('base64');
|
|
18
|
+
this.logger = options.logger || consoleLogger;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Phase 1: Check if issue exists before creating
|
|
22
|
+
*/
|
|
23
|
+
async checkBeforeCreate(summaryPattern, incrementId) {
|
|
24
|
+
try {
|
|
25
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
26
|
+
if (issues.length > 0) {
|
|
27
|
+
return {
|
|
28
|
+
found: true,
|
|
29
|
+
existingIssue: issues[0],
|
|
30
|
+
count: issues.length,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return { found: false, count: 0 };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
this.logger.log(`⚠️ Detection check failed: ${error.message}`);
|
|
37
|
+
// Graceful degradation - continue anyway
|
|
38
|
+
return { found: false, count: 0 };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Phase 2: Verify count after creation
|
|
43
|
+
*/
|
|
44
|
+
async verifyAfterCreate(summaryPattern, expectedCount = 1) {
|
|
45
|
+
try {
|
|
46
|
+
const issues = await this.searchIssues(summaryPattern);
|
|
47
|
+
if (issues.length > expectedCount) {
|
|
48
|
+
// Duplicates detected!
|
|
49
|
+
const sorted = issues.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
expectedCount,
|
|
53
|
+
actualCount: issues.length,
|
|
54
|
+
duplicates: sorted.slice(expectedCount), // All issues after expected count
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
expectedCount,
|
|
60
|
+
actualCount: issues.length,
|
|
61
|
+
duplicates: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.logger.log(`⚠️ Verification check failed: ${error.message}`);
|
|
66
|
+
return {
|
|
67
|
+
success: true, // Assume success on error
|
|
68
|
+
expectedCount,
|
|
69
|
+
actualCount: expectedCount,
|
|
70
|
+
duplicates: [],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Phase 3: Auto-close duplicates
|
|
76
|
+
*/
|
|
77
|
+
async closeDuplicates(duplicates, keepIssueKey) {
|
|
78
|
+
const result = {
|
|
79
|
+
closedCount: 0,
|
|
80
|
+
keptCount: 1,
|
|
81
|
+
errors: [],
|
|
82
|
+
};
|
|
83
|
+
for (const issue of duplicates) {
|
|
84
|
+
try {
|
|
85
|
+
await this.closeIssue(issue.key, keepIssueKey);
|
|
86
|
+
result.closedCount++;
|
|
87
|
+
this.logger.log(` ✅ Closed ${issue.key} (duplicate of ${keepIssueKey})`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
result.errors.push(`${issue.key}: ${error.message}`);
|
|
91
|
+
this.logger.log(` ❌ Failed to close ${issue.key}: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Full cleanup: Find and close all duplicates for a feature
|
|
98
|
+
*/
|
|
99
|
+
async cleanupFeatureDuplicates(featureId, dryRun = false) {
|
|
100
|
+
// 1. Search for all issues with feature ID
|
|
101
|
+
const searchPattern = `[${featureId}]`;
|
|
102
|
+
const issues = await this.searchIssues(searchPattern);
|
|
103
|
+
this.logger.log(`\n🔍 Scanning for duplicates in Feature ${featureId}...`);
|
|
104
|
+
this.logger.log(` Found ${issues.length} total issues`);
|
|
105
|
+
// 2. Group by summary
|
|
106
|
+
const groups = this.groupBySummary(issues);
|
|
107
|
+
const duplicateGroups = groups.filter(g => g.duplicates.length > 0);
|
|
108
|
+
if (duplicateGroups.length === 0) {
|
|
109
|
+
this.logger.log(` ✅ No duplicates found!`);
|
|
110
|
+
return {
|
|
111
|
+
groups: [],
|
|
112
|
+
totalIssues: issues.length,
|
|
113
|
+
duplicateCount: 0,
|
|
114
|
+
closedCount: 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
this.logger.log(` Detected ${duplicateGroups.length} duplicate groups:\n`);
|
|
118
|
+
// 3. Display groups
|
|
119
|
+
for (let i = 0; i < duplicateGroups.length; i++) {
|
|
120
|
+
const group = duplicateGroups[i];
|
|
121
|
+
this.logger.log(` 📋 Group ${i + 1}: "${group.summary.substring(0, 50)}..."`);
|
|
122
|
+
this.logger.log(` - ${group.keepIssue.key} (KEEP) - Created ${group.keepIssue.created.split('T')[0]}`);
|
|
123
|
+
for (const dup of group.duplicates) {
|
|
124
|
+
this.logger.log(` - ${dup.key} (CLOSE) - Created ${dup.created.split('T')[0]} - DUPLICATE`);
|
|
125
|
+
}
|
|
126
|
+
this.logger.log('');
|
|
127
|
+
}
|
|
128
|
+
const totalDuplicates = duplicateGroups.reduce((sum, g) => sum + g.duplicates.length, 0);
|
|
129
|
+
if (dryRun) {
|
|
130
|
+
this.logger.log(`\n✅ Dry run complete!`);
|
|
131
|
+
this.logger.log(` Total issues: ${issues.length}`);
|
|
132
|
+
this.logger.log(` Duplicate groups: ${duplicateGroups.length}`);
|
|
133
|
+
this.logger.log(` Issues to close: ${totalDuplicates}`);
|
|
134
|
+
this.logger.log(`\n⚠️ This was a DRY RUN - no changes made.`);
|
|
135
|
+
return {
|
|
136
|
+
groups: duplicateGroups,
|
|
137
|
+
totalIssues: issues.length,
|
|
138
|
+
duplicateCount: totalDuplicates,
|
|
139
|
+
closedCount: 0,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// 4. Close duplicates
|
|
143
|
+
let closedCount = 0;
|
|
144
|
+
this.logger.log(`🗑️ Closing duplicates...`);
|
|
145
|
+
for (const group of duplicateGroups) {
|
|
146
|
+
const result = await this.closeDuplicates(group.duplicates, group.keepIssue.key);
|
|
147
|
+
closedCount += result.closedCount;
|
|
148
|
+
}
|
|
149
|
+
this.logger.log(`\n✅ Cleanup complete!`);
|
|
150
|
+
this.logger.log(` Closed: ${closedCount} duplicates`);
|
|
151
|
+
this.logger.log(` Kept: ${duplicateGroups.length} original issues`);
|
|
152
|
+
return {
|
|
153
|
+
groups: duplicateGroups,
|
|
154
|
+
totalIssues: issues.length,
|
|
155
|
+
duplicateCount: totalDuplicates,
|
|
156
|
+
closedCount,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Group issues by summary
|
|
161
|
+
*/
|
|
162
|
+
groupBySummary(issues) {
|
|
163
|
+
const summaryMap = new Map();
|
|
164
|
+
for (const issue of issues) {
|
|
165
|
+
const existing = summaryMap.get(issue.summary) || [];
|
|
166
|
+
existing.push(issue);
|
|
167
|
+
summaryMap.set(issue.summary, existing);
|
|
168
|
+
}
|
|
169
|
+
const groups = [];
|
|
170
|
+
for (const [summary, groupIssues] of summaryMap) {
|
|
171
|
+
// Sort by created date (oldest first)
|
|
172
|
+
const sorted = groupIssues.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
|
|
173
|
+
groups.push({
|
|
174
|
+
summary,
|
|
175
|
+
issues: sorted,
|
|
176
|
+
keepIssue: sorted[0],
|
|
177
|
+
duplicates: sorted.slice(1),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return groups;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Search for issues using JQL
|
|
184
|
+
*/
|
|
185
|
+
async searchIssues(summaryPattern) {
|
|
186
|
+
if (!this.domain || !this.auth) {
|
|
187
|
+
throw new Error('JIRA credentials not configured');
|
|
188
|
+
}
|
|
189
|
+
const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
|
|
190
|
+
const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
|
|
191
|
+
const response = await fetch(url, {
|
|
192
|
+
headers: {
|
|
193
|
+
Authorization: `Basic ${this.auth}`,
|
|
194
|
+
Accept: 'application/json',
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new Error(`JQL search failed: ${response.status}`);
|
|
199
|
+
}
|
|
200
|
+
const data = await response.json();
|
|
201
|
+
return (data.issues || []).map((issue) => ({
|
|
202
|
+
key: issue.key,
|
|
203
|
+
summary: issue.fields.summary,
|
|
204
|
+
status: issue.fields.status?.name,
|
|
205
|
+
created: issue.fields.created,
|
|
206
|
+
url: `https://${this.domain}/browse/${issue.key}`,
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Close an issue with duplicate comment
|
|
211
|
+
*/
|
|
212
|
+
async closeIssue(issueKey, originalKey) {
|
|
213
|
+
// First, add comment
|
|
214
|
+
await this.addComment(issueKey, originalKey);
|
|
215
|
+
// Then, transition to closed
|
|
216
|
+
const transitions = await this.getTransitions(issueKey);
|
|
217
|
+
// Find "Won't Do" or "Done" transition
|
|
218
|
+
const closeTransition = transitions.find((t) => t.name === "Won't Do" ||
|
|
219
|
+
t.name === 'Done' ||
|
|
220
|
+
t.name === 'Closed' ||
|
|
221
|
+
t.to?.name === "Won't Do" ||
|
|
222
|
+
t.to?.name === 'Done');
|
|
223
|
+
if (!closeTransition) {
|
|
224
|
+
throw new Error(`No close transition found. Available: ${transitions.map((t) => t.name).join(', ')}`);
|
|
225
|
+
}
|
|
226
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
227
|
+
const response = await fetch(url, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: {
|
|
230
|
+
Authorization: `Basic ${this.auth}`,
|
|
231
|
+
'Content-Type': 'application/json',
|
|
232
|
+
},
|
|
233
|
+
body: JSON.stringify({
|
|
234
|
+
transition: { id: closeTransition.id },
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const error = await response.text();
|
|
239
|
+
throw new Error(`Failed to transition issue: ${response.status} - ${error}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get available transitions for an issue
|
|
244
|
+
*/
|
|
245
|
+
async getTransitions(issueKey) {
|
|
246
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
|
|
247
|
+
const response = await fetch(url, {
|
|
248
|
+
headers: {
|
|
249
|
+
Authorization: `Basic ${this.auth}`,
|
|
250
|
+
Accept: 'application/json',
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
throw new Error(`Failed to get transitions: ${response.status}`);
|
|
255
|
+
}
|
|
256
|
+
const data = await response.json();
|
|
257
|
+
return data.transitions || [];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Add duplicate comment to issue
|
|
261
|
+
*/
|
|
262
|
+
async addComment(issueKey, originalKey) {
|
|
263
|
+
const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
|
|
264
|
+
const comment = `h2. Duplicate of ${originalKey}
|
|
265
|
+
|
|
266
|
+
This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
|
|
267
|
+
|
|
268
|
+
The original issue (${originalKey}) contains the same content and should be used for tracking instead.
|
|
269
|
+
|
|
270
|
+
----
|
|
271
|
+
🤖 Auto-closed by SpecWeave Duplicate Cleanup`;
|
|
272
|
+
const response = await fetch(url, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
Authorization: `Basic ${this.auth}`,
|
|
276
|
+
'Content-Type': 'application/json',
|
|
277
|
+
},
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
body: {
|
|
280
|
+
type: 'doc',
|
|
281
|
+
version: 1,
|
|
282
|
+
content: [
|
|
283
|
+
{
|
|
284
|
+
type: 'paragraph',
|
|
285
|
+
content: [
|
|
286
|
+
{
|
|
287
|
+
type: 'text',
|
|
288
|
+
text: comment,
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
});
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
// Non-fatal, just log warning
|
|
298
|
+
this.logger.log(` ⚠️ Failed to add comment to ${issueKey}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Convenience function for quick duplicate cleanup
|
|
304
|
+
*/
|
|
305
|
+
export async function cleanupJiraDuplicates(featureId, dryRun = false) {
|
|
306
|
+
const detector = new JiraDuplicateDetector();
|
|
307
|
+
return detector.cleanupFeatureDuplicates(featureId, dryRun);
|
|
308
|
+
}
|
|
309
|
+
export default JiraDuplicateDetector;
|
|
310
|
+
//# sourceMappingURL=jira-duplicate-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira-duplicate-detector.js","sourceRoot":"","sources":["../../../../plugins/specweave-jira/lib/jira-duplicate-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Permission Gate
|
|
3
|
+
*
|
|
4
|
+
* Validates that sync permissions are enabled before allowing JIRA write operations.
|
|
5
|
+
* This ensures manual JIRA commands respect the same permission settings as the
|
|
6
|
+
* sync-coordinator.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const gate = await createJiraPermissionGate();
|
|
11
|
+
* const result = gate.checkWritePermission();
|
|
12
|
+
* if (!result.allowed) {
|
|
13
|
+
* console.log(result.reason);
|
|
14
|
+
* return;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @module jira-permission-gate
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Permission check result
|
|
22
|
+
*/
|
|
23
|
+
export interface PermissionCheckResult {
|
|
24
|
+
/**
|
|
25
|
+
* Whether the operation is allowed
|
|
26
|
+
*/
|
|
27
|
+
allowed: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Human-readable reason for the decision
|
|
30
|
+
*/
|
|
31
|
+
reason: string;
|
|
32
|
+
/**
|
|
33
|
+
* Suggested action if permission denied
|
|
34
|
+
*/
|
|
35
|
+
suggestedAction?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Which setting controls this permission
|
|
38
|
+
*/
|
|
39
|
+
settingPath?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sync settings from config.json
|
|
43
|
+
*/
|
|
44
|
+
export interface SyncSettings {
|
|
45
|
+
canUpsertInternalItems: boolean;
|
|
46
|
+
canUpdateExternalItems: boolean;
|
|
47
|
+
canUpdateStatus: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Default settings (all disabled for safety)
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEFAULT_SYNC_SETTINGS: SyncSettings;
|
|
53
|
+
/**
|
|
54
|
+
* JIRA Permission Gate
|
|
55
|
+
*
|
|
56
|
+
* Checks permission settings before allowing JIRA write operations.
|
|
57
|
+
*/
|
|
58
|
+
export declare class JiraPermissionGate {
|
|
59
|
+
private settings;
|
|
60
|
+
private configPath;
|
|
61
|
+
constructor(settings: SyncSettings, configPath: string);
|
|
62
|
+
/**
|
|
63
|
+
* Check if write operations (create/update issues) are allowed
|
|
64
|
+
*
|
|
65
|
+
* Requires: canUpdateExternalItems = true
|
|
66
|
+
*/
|
|
67
|
+
checkWritePermission(): PermissionCheckResult;
|
|
68
|
+
/**
|
|
69
|
+
* Check if status updates (transitions) are allowed
|
|
70
|
+
*
|
|
71
|
+
* Requires: canUpdateStatus = true
|
|
72
|
+
*/
|
|
73
|
+
checkStatusPermission(): PermissionCheckResult;
|
|
74
|
+
/**
|
|
75
|
+
* Check if internal item creation is allowed
|
|
76
|
+
*
|
|
77
|
+
* Requires: canUpsertInternalItems = true
|
|
78
|
+
*/
|
|
79
|
+
checkCreateInternalPermission(): PermissionCheckResult;
|
|
80
|
+
/**
|
|
81
|
+
* Check if close operation is allowed (requires both write AND status)
|
|
82
|
+
*
|
|
83
|
+
* Requires: canUpdateExternalItems = true AND canUpdateStatus = true
|
|
84
|
+
*/
|
|
85
|
+
checkClosePermission(): PermissionCheckResult;
|
|
86
|
+
/**
|
|
87
|
+
* Get current settings
|
|
88
|
+
*/
|
|
89
|
+
getSettings(): SyncSettings;
|
|
90
|
+
/**
|
|
91
|
+
* Get human-readable permission summary
|
|
92
|
+
*/
|
|
93
|
+
getPermissionSummary(): string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a JiraPermissionGate from config.json
|
|
97
|
+
*
|
|
98
|
+
* @param projectRoot - Project root directory (defaults to cwd)
|
|
99
|
+
* @returns JiraPermissionGate instance
|
|
100
|
+
*/
|
|
101
|
+
export declare function createJiraPermissionGate(projectRoot?: string): Promise<JiraPermissionGate>;
|
|
102
|
+
/**
|
|
103
|
+
* Quick check: Are JIRA write operations allowed?
|
|
104
|
+
*
|
|
105
|
+
* Convenience function for simple permission checks.
|
|
106
|
+
*
|
|
107
|
+
* @param projectRoot - Project root directory
|
|
108
|
+
* @returns Permission check result
|
|
109
|
+
*/
|
|
110
|
+
export declare function canWriteToJira(projectRoot?: string): Promise<PermissionCheckResult>;
|
|
111
|
+
/**
|
|
112
|
+
* Quick check: Are JIRA status updates allowed?
|
|
113
|
+
*
|
|
114
|
+
* @param projectRoot - Project root directory
|
|
115
|
+
* @returns Permission check result
|
|
116
|
+
*/
|
|
117
|
+
export declare function canUpdateJiraStatus(projectRoot?: string): Promise<PermissionCheckResult>;
|
|
118
|
+
/**
|
|
119
|
+
* Quick check: Can JIRA issues be closed?
|
|
120
|
+
*
|
|
121
|
+
* @param projectRoot - Project root directory
|
|
122
|
+
* @returns Permission check result
|
|
123
|
+
*/
|
|
124
|
+
export declare function canCloseJiraIssue(projectRoot?: string): Promise<PermissionCheckResult>;
|
|
125
|
+
export default JiraPermissionGate;
|
|
126
|
+
//# sourceMappingURL=jira-permission-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira-permission-gate.d.ts","sourceRoot":"","sources":["../../../../plugins/specweave-jira/lib/jira-permission-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sBAAsB,EAAE,OAAO,CAAC;IAChC,sBAAsB,EAAE,OAAO,CAAC;IAChC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAC;AAEF;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,UAAU,CAAS;gBAEf,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM;IAKtD;;;;OAIG;IACH,oBAAoB,IAAI,qBAAqB;IAgB7C;;;;OAIG;IACH,qBAAqB,IAAI,qBAAqB;IAgB9C;;;;OAIG;IACH,6BAA6B,IAAI,qBAAqB;IAgBtD;;;;OAIG;IACH,oBAAoB,IAAI,qBAAqB;IA2B7C;;OAEG;IACH,WAAW,IAAI,YAAY;IAI3B;;OAEG;IACH,oBAAoB,IAAI,MAAM;CAmB/B;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,GAAE,MAAsB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAkB7B;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,WAAW,GAAE,MAAsB,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAGhC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,GAAE,MAAsB,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAGhC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,GAAE,MAAsB,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAGhC;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JIRA Permission Gate
|
|
3
|
+
*
|
|
4
|
+
* Validates that sync permissions are enabled before allowing JIRA write operations.
|
|
5
|
+
* This ensures manual JIRA commands respect the same permission settings as the
|
|
6
|
+
* sync-coordinator.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const gate = await createJiraPermissionGate();
|
|
11
|
+
* const result = gate.checkWritePermission();
|
|
12
|
+
* if (!result.allowed) {
|
|
13
|
+
* console.log(result.reason);
|
|
14
|
+
* return;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @module jira-permission-gate
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
/**
|
|
23
|
+
* Default settings (all disabled for safety)
|
|
24
|
+
*/
|
|
25
|
+
export const DEFAULT_SYNC_SETTINGS = {
|
|
26
|
+
canUpsertInternalItems: false,
|
|
27
|
+
canUpdateExternalItems: false,
|
|
28
|
+
canUpdateStatus: false,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* JIRA Permission Gate
|
|
32
|
+
*
|
|
33
|
+
* Checks permission settings before allowing JIRA write operations.
|
|
34
|
+
*/
|
|
35
|
+
export class JiraPermissionGate {
|
|
36
|
+
constructor(settings, configPath) {
|
|
37
|
+
this.settings = settings;
|
|
38
|
+
this.configPath = configPath;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if write operations (create/update issues) are allowed
|
|
42
|
+
*
|
|
43
|
+
* Requires: canUpdateExternalItems = true
|
|
44
|
+
*/
|
|
45
|
+
checkWritePermission() {
|
|
46
|
+
if (this.settings.canUpdateExternalItems) {
|
|
47
|
+
return {
|
|
48
|
+
allowed: true,
|
|
49
|
+
reason: 'Write operations permitted (canUpdateExternalItems=true)',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
allowed: false,
|
|
54
|
+
reason: 'Permission denied: JIRA updates are disabled.',
|
|
55
|
+
suggestedAction: `Enable sync.settings.canUpdateExternalItems in ${this.configPath}`,
|
|
56
|
+
settingPath: 'sync.settings.canUpdateExternalItems',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if status updates (transitions) are allowed
|
|
61
|
+
*
|
|
62
|
+
* Requires: canUpdateStatus = true
|
|
63
|
+
*/
|
|
64
|
+
checkStatusPermission() {
|
|
65
|
+
if (this.settings.canUpdateStatus) {
|
|
66
|
+
return {
|
|
67
|
+
allowed: true,
|
|
68
|
+
reason: 'Status updates permitted (canUpdateStatus=true)',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
allowed: false,
|
|
73
|
+
reason: 'Permission denied: JIRA status transitions are disabled.',
|
|
74
|
+
suggestedAction: `Enable sync.settings.canUpdateStatus in ${this.configPath}`,
|
|
75
|
+
settingPath: 'sync.settings.canUpdateStatus',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if internal item creation is allowed
|
|
80
|
+
*
|
|
81
|
+
* Requires: canUpsertInternalItems = true
|
|
82
|
+
*/
|
|
83
|
+
checkCreateInternalPermission() {
|
|
84
|
+
if (this.settings.canUpsertInternalItems) {
|
|
85
|
+
return {
|
|
86
|
+
allowed: true,
|
|
87
|
+
reason: 'Internal item creation permitted (canUpsertInternalItems=true)',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
allowed: false,
|
|
92
|
+
reason: 'Permission denied: Creating internal items is disabled.',
|
|
93
|
+
suggestedAction: `Enable sync.settings.canUpsertInternalItems in ${this.configPath}`,
|
|
94
|
+
settingPath: 'sync.settings.canUpsertInternalItems',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if close operation is allowed (requires both write AND status)
|
|
99
|
+
*
|
|
100
|
+
* Requires: canUpdateExternalItems = true AND canUpdateStatus = true
|
|
101
|
+
*/
|
|
102
|
+
checkClosePermission() {
|
|
103
|
+
const writeCheck = this.checkWritePermission();
|
|
104
|
+
const statusCheck = this.checkStatusPermission();
|
|
105
|
+
if (writeCheck.allowed && statusCheck.allowed) {
|
|
106
|
+
return {
|
|
107
|
+
allowed: true,
|
|
108
|
+
reason: 'Close operations permitted (canUpdateExternalItems=true, canUpdateStatus=true)',
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const missingPermissions = [];
|
|
112
|
+
if (!writeCheck.allowed) {
|
|
113
|
+
missingPermissions.push('canUpdateExternalItems');
|
|
114
|
+
}
|
|
115
|
+
if (!statusCheck.allowed) {
|
|
116
|
+
missingPermissions.push('canUpdateStatus');
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
allowed: false,
|
|
120
|
+
reason: `Permission denied: Closing JIRA issues requires ${missingPermissions.join(' and ')}.`,
|
|
121
|
+
suggestedAction: `Enable ${missingPermissions.map(p => `sync.settings.${p}`).join(' and ')} in ${this.configPath}`,
|
|
122
|
+
settingPath: missingPermissions.map(p => `sync.settings.${p}`).join(', '),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get current settings
|
|
127
|
+
*/
|
|
128
|
+
getSettings() {
|
|
129
|
+
return { ...this.settings };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get human-readable permission summary
|
|
133
|
+
*/
|
|
134
|
+
getPermissionSummary() {
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (this.settings.canUpdateExternalItems) {
|
|
137
|
+
parts.push('create/update JIRA issues');
|
|
138
|
+
}
|
|
139
|
+
if (this.settings.canUpdateStatus) {
|
|
140
|
+
parts.push('transition issue status');
|
|
141
|
+
}
|
|
142
|
+
if (this.settings.canUpsertInternalItems) {
|
|
143
|
+
parts.push('create internal items');
|
|
144
|
+
}
|
|
145
|
+
if (parts.length === 0) {
|
|
146
|
+
return 'All JIRA write operations disabled (read-only mode)';
|
|
147
|
+
}
|
|
148
|
+
return `Allowed: ${parts.join(', ')}`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create a JiraPermissionGate from config.json
|
|
153
|
+
*
|
|
154
|
+
* @param projectRoot - Project root directory (defaults to cwd)
|
|
155
|
+
* @returns JiraPermissionGate instance
|
|
156
|
+
*/
|
|
157
|
+
export async function createJiraPermissionGate(projectRoot = process.cwd()) {
|
|
158
|
+
const configPath = path.join(projectRoot, '.specweave', 'config.json');
|
|
159
|
+
try {
|
|
160
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
161
|
+
const config = JSON.parse(content);
|
|
162
|
+
const settings = {
|
|
163
|
+
canUpsertInternalItems: config?.sync?.settings?.canUpsertInternalItems ?? false,
|
|
164
|
+
canUpdateExternalItems: config?.sync?.settings?.canUpdateExternalItems ?? false,
|
|
165
|
+
canUpdateStatus: config?.sync?.settings?.canUpdateStatus ?? false,
|
|
166
|
+
};
|
|
167
|
+
return new JiraPermissionGate(settings, configPath);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Return gate with default (disabled) settings if config not found
|
|
171
|
+
return new JiraPermissionGate(DEFAULT_SYNC_SETTINGS, configPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Quick check: Are JIRA write operations allowed?
|
|
176
|
+
*
|
|
177
|
+
* Convenience function for simple permission checks.
|
|
178
|
+
*
|
|
179
|
+
* @param projectRoot - Project root directory
|
|
180
|
+
* @returns Permission check result
|
|
181
|
+
*/
|
|
182
|
+
export async function canWriteToJira(projectRoot = process.cwd()) {
|
|
183
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
184
|
+
return gate.checkWritePermission();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Quick check: Are JIRA status updates allowed?
|
|
188
|
+
*
|
|
189
|
+
* @param projectRoot - Project root directory
|
|
190
|
+
* @returns Permission check result
|
|
191
|
+
*/
|
|
192
|
+
export async function canUpdateJiraStatus(projectRoot = process.cwd()) {
|
|
193
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
194
|
+
return gate.checkStatusPermission();
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Quick check: Can JIRA issues be closed?
|
|
198
|
+
*
|
|
199
|
+
* @param projectRoot - Project root directory
|
|
200
|
+
* @returns Permission check result
|
|
201
|
+
*/
|
|
202
|
+
export async function canCloseJiraIssue(projectRoot = process.cwd()) {
|
|
203
|
+
const gate = await createJiraPermissionGate(projectRoot);
|
|
204
|
+
return gate.checkClosePermission();
|
|
205
|
+
}
|
|
206
|
+
export default JiraPermissionGate;
|
|
207
|
+
//# sourceMappingURL=jira-permission-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira-permission-gate.js","sourceRoot":"","sources":["../../../../plugins/specweave-jira/lib/jira-permission-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -82,7 +82,7 @@ codex "Read AGENTS.md and increment 0001. Fix auth bug. Run tests."
|
|
|
82
82
|
| **Skills** | Native | Via AGENTS.md |
|
|
83
83
|
| **Agents** | Native | Via AGENTS.md |
|
|
84
84
|
| **Access Points** | CLI only | CLI + Web + IDE + GitHub + iOS |
|
|
85
|
-
| **Model** |
|
|
85
|
+
| **Model** | Opus 4.5 | GPT-5-Codex |
|
|
86
86
|
| **Task Isolation** | No | Yes (isolated per task) |
|
|
87
87
|
| **Hooks** | Yes | No |
|
|
88
88
|
|