kspec 1.0.15 → 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 +417 -9
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
3
4
|
const { execSync, spawn } = require('child_process');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
@@ -7,6 +8,8 @@ const KSPEC_DIR = '.kspec';
|
|
|
7
8
|
const STEERING_DIR = '.kiro/steering';
|
|
8
9
|
const AGENTS_DIR = '.kiro/agents';
|
|
9
10
|
const CONFIG_FILE = path.join(KSPEC_DIR, 'config.json');
|
|
11
|
+
const UPDATE_CHECK_FILE = path.join(os.homedir(), '.kspec-update-check');
|
|
12
|
+
const KIRO_MCP_CONFIG = path.join(os.homedir(), '.kiro', 'mcp.json');
|
|
10
13
|
|
|
11
14
|
// Default config
|
|
12
15
|
const defaultConfig = {
|
|
@@ -33,6 +36,123 @@ function saveConfig(cfg) {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
const config = loadConfig();
|
|
39
|
+
const pkg = require('../package.json');
|
|
40
|
+
|
|
41
|
+
// Update check (non-blocking, cached for 24h)
|
|
42
|
+
function shouldCheckUpdate() {
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(UPDATE_CHECK_FILE)) {
|
|
45
|
+
const lastCheck = parseInt(fs.readFileSync(UPDATE_CHECK_FILE, 'utf8'), 10);
|
|
46
|
+
const hoursSinceCheck = (Date.now() - lastCheck) / (1000 * 60 * 60);
|
|
47
|
+
return hoursSinceCheck >= 24;
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function saveUpdateCheck() {
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(UPDATE_CHECK_FILE, Date.now().toString());
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compareVersions(v1, v2) {
|
|
60
|
+
const parts1 = v1.split('.').map(Number);
|
|
61
|
+
const parts2 = v2.split('.').map(Number);
|
|
62
|
+
for (let i = 0; i < 3; i++) {
|
|
63
|
+
if ((parts1[i] || 0) < (parts2[i] || 0)) return -1;
|
|
64
|
+
if ((parts1[i] || 0) > (parts2[i] || 0)) return 1;
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function checkForUpdates() {
|
|
70
|
+
if (!shouldCheckUpdate()) return;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const https = require('https');
|
|
74
|
+
const data = await new Promise((resolve, reject) => {
|
|
75
|
+
const req = https.get('https://registry.npmjs.org/kspec/latest', { timeout: 3000 }, res => {
|
|
76
|
+
let body = '';
|
|
77
|
+
res.on('data', chunk => body += chunk);
|
|
78
|
+
res.on('end', () => resolve(body));
|
|
79
|
+
});
|
|
80
|
+
req.on('error', reject);
|
|
81
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const latest = JSON.parse(data).version;
|
|
85
|
+
saveUpdateCheck();
|
|
86
|
+
|
|
87
|
+
if (compareVersions(pkg.version, latest) < 0) {
|
|
88
|
+
console.log(`\n Update available: ${pkg.version} → ${latest}`);
|
|
89
|
+
console.log(` Run: npm install -g kspec\n`);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Silently fail - don't block user workflow
|
|
93
|
+
}
|
|
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
|
+
}
|
|
36
156
|
|
|
37
157
|
// Helpers
|
|
38
158
|
function log(msg) { console.log(`[kspec] ${msg}`); }
|
|
@@ -225,6 +345,15 @@ No active spec. Run: \`kspec spec "Feature Name"\`
|
|
|
225
345
|
memory = fs.readFileSync(memoryFile, 'utf8');
|
|
226
346
|
}
|
|
227
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
|
+
|
|
228
357
|
// Build context
|
|
229
358
|
let content = `# kspec Context
|
|
230
359
|
> Auto-generated. Always read this first after context compression.
|
|
@@ -261,6 +390,24 @@ ${memory}
|
|
|
261
390
|
`;
|
|
262
391
|
}
|
|
263
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
|
+
|
|
264
411
|
content += `## Quick Commands
|
|
265
412
|
- \`kspec build\` - Continue building tasks
|
|
266
413
|
- \`kspec verify\` - Verify implementation
|
|
@@ -509,6 +656,59 @@ Your job:
|
|
|
509
656
|
Output: APPROVE / REQUEST_CHANGES with specific issues.`,
|
|
510
657
|
keyboardShortcut: 'ctrl+shift+r',
|
|
511
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.'
|
|
512
712
|
}
|
|
513
713
|
};
|
|
514
714
|
|
|
@@ -577,16 +777,62 @@ Update steering docs as needed.`, 'kspec-analyse');
|
|
|
577
777
|
},
|
|
578
778
|
|
|
579
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
|
+
|
|
580
799
|
const feature = args.join(' ');
|
|
581
|
-
if (!feature) die('Usage: kspec spec "Feature Name"');
|
|
800
|
+
if (!feature && !jiraIssues) die('Usage: kspec spec "Feature Name" [--jira ISSUE-123,ISSUE-456]');
|
|
582
801
|
|
|
583
802
|
const date = formatDate(config.dateFormat || 'YYYY-MM-DD');
|
|
584
|
-
const
|
|
803
|
+
const featureName = feature || `jira-${jiraIssues.split(',')[0].toLowerCase()}`;
|
|
804
|
+
const folder = path.join(getSpecsDir(), `${date}-${slugify(featureName)}`);
|
|
585
805
|
ensureDir(folder);
|
|
586
806
|
setCurrentSpec(folder);
|
|
587
807
|
|
|
588
808
|
log(`Spec folder: ${folder}`);
|
|
589
|
-
|
|
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}
|
|
590
836
|
|
|
591
837
|
Folder: ${folder}
|
|
592
838
|
|
|
@@ -595,6 +841,7 @@ Folder: ${folder}
|
|
|
595
841
|
3. IMMEDIATELY create ${folder}/spec-lite.md (concise version, <500 words)
|
|
596
842
|
|
|
597
843
|
spec-lite.md is critical - it's loaded after context compression.`, 'kspec-spec');
|
|
844
|
+
}
|
|
598
845
|
},
|
|
599
846
|
|
|
600
847
|
async 'verify-spec'(args) {
|
|
@@ -612,6 +859,120 @@ Read the codebase to check implementability.
|
|
|
612
859
|
Report: PASS/FAIL with specific issues.`, 'kspec-verify');
|
|
613
860
|
},
|
|
614
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
|
+
|
|
615
976
|
async tasks(args) {
|
|
616
977
|
const folder = getOrSelectSpec(args.join(' '));
|
|
617
978
|
log(`Generating tasks: ${folder}`);
|
|
@@ -805,19 +1166,51 @@ Output: APPROVE or REQUEST_CHANGES with specifics.`, 'kspec-review');
|
|
|
805
1166
|
console.log(`
|
|
806
1167
|
kspec Agents
|
|
807
1168
|
|
|
808
|
-
Agent Shortcut
|
|
809
|
-
|
|
1169
|
+
Agent Shortcut Purpose
|
|
1170
|
+
─────────────────────────────────────────────────────────
|
|
810
1171
|
kspec-analyse Ctrl+Shift+A Analyse codebase, update steering
|
|
811
1172
|
kspec-spec Ctrl+Shift+S Create specifications
|
|
812
1173
|
kspec-tasks Ctrl+Shift+T Generate tasks from spec
|
|
813
1174
|
kspec-build Ctrl+Shift+B Execute tasks with TDD
|
|
814
1175
|
kspec-verify Ctrl+Shift+V Verify spec/tasks/implementation
|
|
815
1176
|
kspec-review Ctrl+Shift+R Code review
|
|
1177
|
+
kspec-jira Ctrl+Shift+J Jira integration (requires Atlassian MCP)
|
|
816
1178
|
|
|
817
1179
|
Switch: /agent swap or use keyboard shortcuts
|
|
818
1180
|
`);
|
|
819
1181
|
},
|
|
820
1182
|
|
|
1183
|
+
async update() {
|
|
1184
|
+
console.log(`\nkspec v${pkg.version}\n`);
|
|
1185
|
+
console.log('Checking for updates...');
|
|
1186
|
+
|
|
1187
|
+
try {
|
|
1188
|
+
const https = require('https');
|
|
1189
|
+
const data = await new Promise((resolve, reject) => {
|
|
1190
|
+
const req = https.get('https://registry.npmjs.org/kspec/latest', { timeout: 5000 }, res => {
|
|
1191
|
+
let body = '';
|
|
1192
|
+
res.on('data', chunk => body += chunk);
|
|
1193
|
+
res.on('end', () => resolve(body));
|
|
1194
|
+
});
|
|
1195
|
+
req.on('error', reject);
|
|
1196
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
const latest = JSON.parse(data).version;
|
|
1200
|
+
saveUpdateCheck();
|
|
1201
|
+
|
|
1202
|
+
if (compareVersions(pkg.version, latest) < 0) {
|
|
1203
|
+
console.log(`\nUpdate available: ${pkg.version} → ${latest}`);
|
|
1204
|
+
console.log('\nTo update, run:');
|
|
1205
|
+
console.log(' npm install -g kspec\n');
|
|
1206
|
+
} else {
|
|
1207
|
+
console.log(`\nYou're on the latest version!\n`);
|
|
1208
|
+
}
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
console.error('\nCould not check for updates. Check your internet connection.\n');
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
|
|
821
1214
|
help() {
|
|
822
1215
|
console.log(`
|
|
823
1216
|
kspec - Spec-driven development for Kiro CLI
|
|
@@ -841,31 +1234,46 @@ Inside kiro-cli (recommended):
|
|
|
841
1234
|
|
|
842
1235
|
Agents read .kspec/CONTEXT.md automatically for state.
|
|
843
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
|
+
|
|
844
1247
|
Other:
|
|
845
1248
|
kspec context Refresh/view context file
|
|
846
1249
|
kspec review [target] Code review
|
|
847
1250
|
kspec list List all specs
|
|
848
1251
|
kspec status Current status
|
|
849
1252
|
kspec agents List agents
|
|
1253
|
+
kspec update Check for updates
|
|
850
1254
|
kspec help Show this help
|
|
851
1255
|
|
|
852
1256
|
Examples:
|
|
853
1257
|
kspec init # First time setup
|
|
854
1258
|
kspec spec "User Auth" # CLI mode
|
|
1259
|
+
kspec spec --jira PROJ-123 "Auth" # From Jira story
|
|
855
1260
|
kiro-cli --agent kspec-spec # Direct agent mode
|
|
856
1261
|
`);
|
|
857
1262
|
}
|
|
858
1263
|
};
|
|
859
1264
|
|
|
860
1265
|
async function run(args) {
|
|
1266
|
+
// Check for updates (non-blocking, cached for 24h)
|
|
1267
|
+
checkForUpdates();
|
|
1268
|
+
|
|
861
1269
|
// Handle standard CLI flags first
|
|
862
1270
|
if (args.includes('--help') || args.includes('-h')) {
|
|
863
1271
|
return commands.help();
|
|
864
1272
|
}
|
|
865
|
-
|
|
1273
|
+
|
|
866
1274
|
if (args.includes('--version') || args.includes('-v')) {
|
|
867
|
-
|
|
868
|
-
|
|
1275
|
+
// Show version and check for updates
|
|
1276
|
+
await commands.update();
|
|
869
1277
|
return;
|
|
870
1278
|
}
|
|
871
1279
|
|
|
@@ -879,4 +1287,4 @@ async function run(args) {
|
|
|
879
1287
|
}
|
|
880
1288
|
}
|
|
881
1289
|
|
|
882
|
-
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentTask };
|
|
1290
|
+
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig };
|