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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +321 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kspec",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Spec-driven development workflow for Kiro CLI",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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 folder = path.join(getSpecsDir(), `${date}-${slugify(feature)}`);
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
- await chat(`Create specification for: ${feature}
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 Purpose
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 };