kspec 1.0.16 → 1.0.17
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/package.json +1 -1
- package/src/index.js +321 -6
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const STEERING_DIR = '.kiro/steering';
|
|
|
9
9
|
const AGENTS_DIR = '.kiro/agents';
|
|
10
10
|
const CONFIG_FILE = path.join(KSPEC_DIR, 'config.json');
|
|
11
11
|
const UPDATE_CHECK_FILE = path.join(os.homedir(), '.kspec-update-check');
|
|
12
|
+
const KIRO_MCP_CONFIG = path.join(os.homedir(), '.kiro', 'mcp.json');
|
|
12
13
|
|
|
13
14
|
// Default config
|
|
14
15
|
const defaultConfig = {
|
|
@@ -92,6 +93,67 @@ async function checkForUpdates() {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
// MCP Integration Detection
|
|
97
|
+
function getMcpConfig() {
|
|
98
|
+
try {
|
|
99
|
+
if (fs.existsSync(KIRO_MCP_CONFIG)) {
|
|
100
|
+
return JSON.parse(fs.readFileSync(KIRO_MCP_CONFIG, 'utf8'));
|
|
101
|
+
}
|
|
102
|
+
} catch {}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hasAtlassianMcp() {
|
|
107
|
+
const mcpConfig = getMcpConfig();
|
|
108
|
+
if (!mcpConfig || !mcpConfig.mcpServers) return false;
|
|
109
|
+
|
|
110
|
+
// Check for atlassian or jira MCP server
|
|
111
|
+
const serverNames = Object.keys(mcpConfig.mcpServers);
|
|
112
|
+
return serverNames.some(name =>
|
|
113
|
+
name.toLowerCase().includes('atlassian') ||
|
|
114
|
+
name.toLowerCase().includes('jira')
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getAtlassianMcpName() {
|
|
119
|
+
const mcpConfig = getMcpConfig();
|
|
120
|
+
if (!mcpConfig || !mcpConfig.mcpServers) return null;
|
|
121
|
+
|
|
122
|
+
const serverNames = Object.keys(mcpConfig.mcpServers);
|
|
123
|
+
return serverNames.find(name =>
|
|
124
|
+
name.toLowerCase().includes('atlassian') ||
|
|
125
|
+
name.toLowerCase().includes('jira')
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function requireAtlassianMcp() {
|
|
130
|
+
if (!hasAtlassianMcp()) {
|
|
131
|
+
die(`Atlassian MCP not configured.
|
|
132
|
+
|
|
133
|
+
To use Jira integration, you need to:
|
|
134
|
+
1. Install the Atlassian MCP server
|
|
135
|
+
2. Configure it in ~/.kiro/mcp.json
|
|
136
|
+
|
|
137
|
+
Example ~/.kiro/mcp.json:
|
|
138
|
+
{
|
|
139
|
+
"mcpServers": {
|
|
140
|
+
"atlassian": {
|
|
141
|
+
"command": "npx",
|
|
142
|
+
"args": ["-y", "@anthropic/mcp-atlassian"],
|
|
143
|
+
"env": {
|
|
144
|
+
"ATLASSIAN_HOST": "https://your-domain.atlassian.net",
|
|
145
|
+
"ATLASSIAN_EMAIL": "your-email@example.com",
|
|
146
|
+
"ATLASSIAN_API_TOKEN": "your-api-token"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Get your API token: https://id.atlassian.com/manage-profile/security/api-tokens`);
|
|
153
|
+
}
|
|
154
|
+
return getAtlassianMcpName();
|
|
155
|
+
}
|
|
156
|
+
|
|
95
157
|
// Helpers
|
|
96
158
|
function log(msg) { console.log(`[kspec] ${msg}`); }
|
|
97
159
|
function die(msg) { console.error(`Error: ${msg}`); process.exit(1); }
|
|
@@ -283,6 +345,15 @@ No active spec. Run: \`kspec spec "Feature Name"\`
|
|
|
283
345
|
memory = fs.readFileSync(memoryFile, 'utf8');
|
|
284
346
|
}
|
|
285
347
|
|
|
348
|
+
// Read Jira links if exists
|
|
349
|
+
const jiraLinksFile = path.join(current, 'jira-links.json');
|
|
350
|
+
let jiraLinks = null;
|
|
351
|
+
if (fs.existsSync(jiraLinksFile)) {
|
|
352
|
+
try {
|
|
353
|
+
jiraLinks = JSON.parse(fs.readFileSync(jiraLinksFile, 'utf8'));
|
|
354
|
+
} catch {}
|
|
355
|
+
}
|
|
356
|
+
|
|
286
357
|
// Build context
|
|
287
358
|
let content = `# kspec Context
|
|
288
359
|
> Auto-generated. Always read this first after context compression.
|
|
@@ -319,6 +390,24 @@ ${memory}
|
|
|
319
390
|
`;
|
|
320
391
|
}
|
|
321
392
|
|
|
393
|
+
if (jiraLinks) {
|
|
394
|
+
content += `## Jira Links
|
|
395
|
+
`;
|
|
396
|
+
if (jiraLinks.sourceIssues && jiraLinks.sourceIssues.length > 0) {
|
|
397
|
+
content += `- Source Issues: ${jiraLinks.sourceIssues.join(', ')}
|
|
398
|
+
`;
|
|
399
|
+
}
|
|
400
|
+
if (jiraLinks.specIssue) {
|
|
401
|
+
content += `- Spec Issue: ${jiraLinks.specIssue}
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
if (jiraLinks.subtasks && jiraLinks.subtasks.length > 0) {
|
|
405
|
+
content += `- Subtasks: ${jiraLinks.subtasks.join(', ')}
|
|
406
|
+
`;
|
|
407
|
+
}
|
|
408
|
+
content += '\n';
|
|
409
|
+
}
|
|
410
|
+
|
|
322
411
|
content += `## Quick Commands
|
|
323
412
|
- \`kspec build\` - Continue building tasks
|
|
324
413
|
- \`kspec verify\` - Verify implementation
|
|
@@ -567,6 +656,59 @@ Your job:
|
|
|
567
656
|
Output: APPROVE / REQUEST_CHANGES with specific issues.`,
|
|
568
657
|
keyboardShortcut: 'ctrl+shift+r',
|
|
569
658
|
welcomeMessage: 'Ready to review. What should I look at?'
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
'kspec-jira.json': {
|
|
662
|
+
name: 'kspec-jira',
|
|
663
|
+
description: 'Jira integration for specs',
|
|
664
|
+
model: 'claude-sonnet-4',
|
|
665
|
+
tools: ['read', 'write', 'mcp'],
|
|
666
|
+
allowedTools: ['read', 'write', 'mcp'],
|
|
667
|
+
resources: [
|
|
668
|
+
'file://.kspec/CONTEXT.md',
|
|
669
|
+
'file://.kiro/steering/**/*.md',
|
|
670
|
+
'file://.kspec/**/*.md'
|
|
671
|
+
],
|
|
672
|
+
prompt: `You are the kspec Jira integration agent.
|
|
673
|
+
|
|
674
|
+
PREREQUISITE: This agent requires Atlassian MCP to be configured.
|
|
675
|
+
If MCP calls fail, inform the user to configure Atlassian MCP first.
|
|
676
|
+
|
|
677
|
+
CAPABILITIES:
|
|
678
|
+
|
|
679
|
+
1. PULL FROM JIRA (when user provides issue keys):
|
|
680
|
+
- Use MCP to fetch Jira issue details
|
|
681
|
+
- Extract: summary, description, acceptance criteria, comments
|
|
682
|
+
- For multiple issues, consolidate into unified requirements
|
|
683
|
+
- Create spec.md with proper attribution to source issues
|
|
684
|
+
- Include Jira links in spec for traceability
|
|
685
|
+
|
|
686
|
+
2. SYNC TO JIRA (when user asks to sync/push):
|
|
687
|
+
- Create new "Technical Specification" issue in Jira
|
|
688
|
+
- Or update existing issue with spec content
|
|
689
|
+
- Link to source stories
|
|
690
|
+
- Add comment requesting BA review
|
|
691
|
+
- Set appropriate labels (kspec, technical-spec)
|
|
692
|
+
|
|
693
|
+
3. CREATE SUBTASKS (when user asks after tasks.md exists):
|
|
694
|
+
- Read tasks.md from current spec
|
|
695
|
+
- Create Jira sub-tasks for each task
|
|
696
|
+
- Link to parent spec issue
|
|
697
|
+
- Include task details and acceptance criteria
|
|
698
|
+
|
|
699
|
+
WORKFLOW:
|
|
700
|
+
1. Read .kspec/CONTEXT.md for current spec state
|
|
701
|
+
2. Identify what user wants (pull/sync/subtasks)
|
|
702
|
+
3. Use Atlassian MCP for Jira operations
|
|
703
|
+
4. Update .kspec/CONTEXT.md with Jira links
|
|
704
|
+
5. Report what was created/updated
|
|
705
|
+
|
|
706
|
+
IMPORTANT:
|
|
707
|
+
- Always include Jira issue links in spec.md
|
|
708
|
+
- Add "Source: JIRA-XXX" attribution for pulled requirements
|
|
709
|
+
- Update CONTEXT.md with linked Jira issues`,
|
|
710
|
+
keyboardShortcut: 'ctrl+shift+j',
|
|
711
|
+
welcomeMessage: 'Jira integration ready. Provide issue keys to pull, or say "sync" to push spec to Jira.'
|
|
570
712
|
}
|
|
571
713
|
};
|
|
572
714
|
|
|
@@ -635,16 +777,62 @@ Update steering docs as needed.`, 'kspec-analyse');
|
|
|
635
777
|
},
|
|
636
778
|
|
|
637
779
|
async spec(args) {
|
|
780
|
+
// Parse --jira flag
|
|
781
|
+
const jiraIndex = args.findIndex(a => a === '--jira' || a.startsWith('--jira='));
|
|
782
|
+
let jiraIssues = null;
|
|
783
|
+
|
|
784
|
+
if (jiraIndex !== -1) {
|
|
785
|
+
// Check prerequisite
|
|
786
|
+
requireAtlassianMcp();
|
|
787
|
+
|
|
788
|
+
if (args[jiraIndex].startsWith('--jira=')) {
|
|
789
|
+
jiraIssues = args[jiraIndex].split('=')[1];
|
|
790
|
+
args.splice(jiraIndex, 1);
|
|
791
|
+
} else if (args[jiraIndex + 1] && !args[jiraIndex + 1].startsWith('-')) {
|
|
792
|
+
jiraIssues = args[jiraIndex + 1];
|
|
793
|
+
args.splice(jiraIndex, 2);
|
|
794
|
+
} else {
|
|
795
|
+
die('Usage: kspec spec --jira PROJ-123,PROJ-124 "Feature Name"');
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
638
799
|
const feature = args.join(' ');
|
|
639
|
-
if (!feature) die('Usage: kspec spec "Feature Name"');
|
|
800
|
+
if (!feature && !jiraIssues) die('Usage: kspec spec "Feature Name" [--jira ISSUE-123,ISSUE-456]');
|
|
640
801
|
|
|
641
802
|
const date = formatDate(config.dateFormat || 'YYYY-MM-DD');
|
|
642
|
-
const
|
|
803
|
+
const featureName = feature || `jira-${jiraIssues.split(',')[0].toLowerCase()}`;
|
|
804
|
+
const folder = path.join(getSpecsDir(), `${date}-${slugify(featureName)}`);
|
|
643
805
|
ensureDir(folder);
|
|
644
806
|
setCurrentSpec(folder);
|
|
645
807
|
|
|
646
808
|
log(`Spec folder: ${folder}`);
|
|
647
|
-
|
|
809
|
+
|
|
810
|
+
if (jiraIssues) {
|
|
811
|
+
// Jira-driven spec creation
|
|
812
|
+
log(`Pulling requirements from Jira: ${jiraIssues}`);
|
|
813
|
+
await chat(`Create specification from Jira issues: ${jiraIssues}
|
|
814
|
+
|
|
815
|
+
Folder: ${folder}
|
|
816
|
+
|
|
817
|
+
WORKFLOW:
|
|
818
|
+
1. Use Atlassian MCP to fetch each Jira issue: ${jiraIssues}
|
|
819
|
+
2. Extract from each issue:
|
|
820
|
+
- Summary and description
|
|
821
|
+
- Acceptance criteria
|
|
822
|
+
- Comments (for context)
|
|
823
|
+
- Linked issues
|
|
824
|
+
3. Consolidate into unified spec.md with:
|
|
825
|
+
- Problem/Context (from issue descriptions)
|
|
826
|
+
- Requirements (from acceptance criteria)
|
|
827
|
+
- Source attribution: "Source: JIRA-XXX" for each requirement
|
|
828
|
+
- Links to original issues
|
|
829
|
+
4. Create spec-lite.md (<500 words, key requirements only)
|
|
830
|
+
5. Save Jira issue keys to ${folder}/jira-links.json
|
|
831
|
+
|
|
832
|
+
IMPORTANT: Include Jira links for traceability.`, 'kspec-jira');
|
|
833
|
+
} else {
|
|
834
|
+
// Standard spec creation
|
|
835
|
+
await chat(`Create specification for: ${feature}
|
|
648
836
|
|
|
649
837
|
Folder: ${folder}
|
|
650
838
|
|
|
@@ -653,6 +841,7 @@ Folder: ${folder}
|
|
|
653
841
|
3. IMMEDIATELY create ${folder}/spec-lite.md (concise version, <500 words)
|
|
654
842
|
|
|
655
843
|
spec-lite.md is critical - it's loaded after context compression.`, 'kspec-spec');
|
|
844
|
+
}
|
|
656
845
|
},
|
|
657
846
|
|
|
658
847
|
async 'verify-spec'(args) {
|
|
@@ -670,6 +859,120 @@ Read the codebase to check implementability.
|
|
|
670
859
|
Report: PASS/FAIL with specific issues.`, 'kspec-verify');
|
|
671
860
|
},
|
|
672
861
|
|
|
862
|
+
async 'sync-jira'(args) {
|
|
863
|
+
// Check prerequisite
|
|
864
|
+
requireAtlassianMcp();
|
|
865
|
+
|
|
866
|
+
const folder = getOrSelectSpec();
|
|
867
|
+
|
|
868
|
+
// Parse flags
|
|
869
|
+
const createFlag = args.includes('--create');
|
|
870
|
+
const updateIndex = args.findIndex(a => a === '--update' || a.startsWith('--update='));
|
|
871
|
+
let updateIssue = null;
|
|
872
|
+
|
|
873
|
+
if (updateIndex !== -1) {
|
|
874
|
+
if (args[updateIndex].startsWith('--update=')) {
|
|
875
|
+
updateIssue = args[updateIndex].split('=')[1];
|
|
876
|
+
} else if (args[updateIndex + 1] && !args[updateIndex + 1].startsWith('-')) {
|
|
877
|
+
updateIssue = args[updateIndex + 1];
|
|
878
|
+
} else {
|
|
879
|
+
die('Usage: kspec sync-jira --update PROJ-123');
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (!createFlag && !updateIssue) {
|
|
884
|
+
// Default to create
|
|
885
|
+
log('No flag specified, will create new Jira issue');
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
log(`Syncing spec to Jira: ${folder}`);
|
|
889
|
+
|
|
890
|
+
if (updateIssue) {
|
|
891
|
+
await chat(`Update existing Jira issue with specification.
|
|
892
|
+
|
|
893
|
+
Spec folder: ${folder}
|
|
894
|
+
Target issue: ${updateIssue}
|
|
895
|
+
|
|
896
|
+
WORKFLOW:
|
|
897
|
+
1. Read ${folder}/spec.md
|
|
898
|
+
2. Use Atlassian MCP to update ${updateIssue}:
|
|
899
|
+
- Update description with spec content (or add as comment)
|
|
900
|
+
- Add label: kspec-spec
|
|
901
|
+
- Add comment: "Technical specification updated via kspec"
|
|
902
|
+
3. Update ${folder}/jira-links.json with the issue key
|
|
903
|
+
4. Update .kspec/CONTEXT.md with Jira link
|
|
904
|
+
|
|
905
|
+
Report the updated issue URL.`, 'kspec-jira');
|
|
906
|
+
} else {
|
|
907
|
+
await chat(`Create new Jira issue from specification.
|
|
908
|
+
|
|
909
|
+
Spec folder: ${folder}
|
|
910
|
+
|
|
911
|
+
WORKFLOW:
|
|
912
|
+
1. Read ${folder}/spec.md and ${folder}/spec-lite.md
|
|
913
|
+
2. Check ${folder}/jira-links.json for source issues to link
|
|
914
|
+
3. Use Atlassian MCP to create new issue:
|
|
915
|
+
- Type: Task or Story (based on project settings)
|
|
916
|
+
- Summary: Extract from spec title
|
|
917
|
+
- Description: Include spec-lite.md content
|
|
918
|
+
- Labels: kspec-spec, technical-specification
|
|
919
|
+
- Link to source issues if any
|
|
920
|
+
4. Add comment requesting BA/PM review
|
|
921
|
+
5. Save new issue key to ${folder}/jira-links.json
|
|
922
|
+
6. Update .kspec/CONTEXT.md with new Jira link
|
|
923
|
+
|
|
924
|
+
Report the created issue URL.`, 'kspec-jira');
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
|
|
928
|
+
async 'jira-subtasks'(args) {
|
|
929
|
+
// Check prerequisite
|
|
930
|
+
requireAtlassianMcp();
|
|
931
|
+
|
|
932
|
+
const folder = getOrSelectSpec();
|
|
933
|
+
const tasksFile = path.join(folder, 'tasks.md');
|
|
934
|
+
|
|
935
|
+
if (!fs.existsSync(tasksFile)) {
|
|
936
|
+
die(`No tasks.md found in ${folder}. Run 'kspec tasks' first.`);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Check for parent issue
|
|
940
|
+
const jiraLinksFile = path.join(folder, 'jira-links.json');
|
|
941
|
+
let parentIssue = args[0];
|
|
942
|
+
|
|
943
|
+
if (!parentIssue && fs.existsSync(jiraLinksFile)) {
|
|
944
|
+
try {
|
|
945
|
+
const links = JSON.parse(fs.readFileSync(jiraLinksFile, 'utf8'));
|
|
946
|
+
parentIssue = links.specIssue || links.sourceIssues?.[0];
|
|
947
|
+
} catch {}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (!parentIssue) {
|
|
951
|
+
die(`No parent issue specified. Usage: kspec jira-subtasks PROJ-123
|
|
952
|
+
Or run 'kspec sync-jira' first to create a spec issue.`);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
log(`Creating Jira subtasks from: ${folder}`);
|
|
956
|
+
|
|
957
|
+
await chat(`Create Jira subtasks from tasks.md.
|
|
958
|
+
|
|
959
|
+
Spec folder: ${folder}
|
|
960
|
+
Parent issue: ${parentIssue}
|
|
961
|
+
Tasks file: ${tasksFile}
|
|
962
|
+
|
|
963
|
+
WORKFLOW:
|
|
964
|
+
1. Read ${tasksFile}
|
|
965
|
+
2. For each uncompleted task (- [ ]):
|
|
966
|
+
- Use Atlassian MCP to create subtask under ${parentIssue}
|
|
967
|
+
- Summary: Task description
|
|
968
|
+
- Description: Include any details, file paths mentioned
|
|
969
|
+
- Labels: kspec-task
|
|
970
|
+
3. Save created subtask keys to ${folder}/jira-links.json
|
|
971
|
+
4. Update .kspec/CONTEXT.md with subtask links
|
|
972
|
+
|
|
973
|
+
Report created subtasks with their URLs.`, 'kspec-jira');
|
|
974
|
+
},
|
|
975
|
+
|
|
673
976
|
async tasks(args) {
|
|
674
977
|
const folder = getOrSelectSpec(args.join(' '));
|
|
675
978
|
log(`Generating tasks: ${folder}`);
|
|
@@ -863,14 +1166,15 @@ Output: APPROVE or REQUEST_CHANGES with specifics.`, 'kspec-review');
|
|
|
863
1166
|
console.log(`
|
|
864
1167
|
kspec Agents
|
|
865
1168
|
|
|
866
|
-
Agent Shortcut
|
|
867
|
-
|
|
1169
|
+
Agent Shortcut Purpose
|
|
1170
|
+
─────────────────────────────────────────────────────────
|
|
868
1171
|
kspec-analyse Ctrl+Shift+A Analyse codebase, update steering
|
|
869
1172
|
kspec-spec Ctrl+Shift+S Create specifications
|
|
870
1173
|
kspec-tasks Ctrl+Shift+T Generate tasks from spec
|
|
871
1174
|
kspec-build Ctrl+Shift+B Execute tasks with TDD
|
|
872
1175
|
kspec-verify Ctrl+Shift+V Verify spec/tasks/implementation
|
|
873
1176
|
kspec-review Ctrl+Shift+R Code review
|
|
1177
|
+
kspec-jira Ctrl+Shift+J Jira integration (requires Atlassian MCP)
|
|
874
1178
|
|
|
875
1179
|
Switch: /agent swap or use keyboard shortcuts
|
|
876
1180
|
`);
|
|
@@ -930,6 +1234,16 @@ Inside kiro-cli (recommended):
|
|
|
930
1234
|
|
|
931
1235
|
Agents read .kspec/CONTEXT.md automatically for state.
|
|
932
1236
|
|
|
1237
|
+
Jira Integration (requires Atlassian MCP):
|
|
1238
|
+
kspec spec --jira PROJ-123,PROJ-456 "Feature"
|
|
1239
|
+
Create spec from Jira issues
|
|
1240
|
+
kspec sync-jira Create/update Jira issue from spec
|
|
1241
|
+
kspec sync-jira --update PROJ-123
|
|
1242
|
+
Update existing Jira issue
|
|
1243
|
+
kspec jira-subtasks Create Jira subtasks from tasks.md
|
|
1244
|
+
kspec jira-subtasks PROJ-123
|
|
1245
|
+
Create subtasks under specific issue
|
|
1246
|
+
|
|
933
1247
|
Other:
|
|
934
1248
|
kspec context Refresh/view context file
|
|
935
1249
|
kspec review [target] Code review
|
|
@@ -942,6 +1256,7 @@ Other:
|
|
|
942
1256
|
Examples:
|
|
943
1257
|
kspec init # First time setup
|
|
944
1258
|
kspec spec "User Auth" # CLI mode
|
|
1259
|
+
kspec spec --jira PROJ-123 "Auth" # From Jira story
|
|
945
1260
|
kiro-cli --agent kspec-spec # Direct agent mode
|
|
946
1261
|
`);
|
|
947
1262
|
}
|
|
@@ -972,4 +1287,4 @@ async function run(args) {
|
|
|
972
1287
|
}
|
|
973
1288
|
}
|
|
974
1289
|
|
|
975
|
-
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentTask, checkForUpdates, compareVersions };
|
|
1290
|
+
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig };
|