@nbrem108/cai-mcp-server 0.1.0
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/.env.example +4 -0
- package/README.md +177 -0
- package/bin/cai-mcp.mjs +13 -0
- package/dist/config/defaults.d.ts +8 -0
- package/dist/config/defaults.js +16 -0
- package/dist/config/vocab.json +32 -0
- package/dist/formatters/jiraMarkdown.d.ts +37 -0
- package/dist/formatters/jiraMarkdown.js +75 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/policies/guardrails.d.ts +32 -0
- package/dist/policies/guardrails.js +88 -0
- package/dist/prompts/guidelines.d.ts +10 -0
- package/dist/prompts/guidelines.js +52 -0
- package/dist/prompts/templates.d.ts +14 -0
- package/dist/prompts/templates.js +158 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +44 -0
- package/dist/tools/acceptanceCriteria.d.ts +29 -0
- package/dist/tools/acceptanceCriteria.js +15 -0
- package/dist/tools/jiraBugFromReport.d.ts +37 -0
- package/dist/tools/jiraBugFromReport.js +17 -0
- package/dist/tools/jiraCreateIssue.d.ts +61 -0
- package/dist/tools/jiraCreateIssue.js +130 -0
- package/dist/tools/jiraIssueDraft.d.ts +45 -0
- package/dist/tools/jiraIssueDraft.js +19 -0
- package/dist/tools/releaseNotes.d.ts +77 -0
- package/dist/tools/releaseNotes.js +22 -0
- package/dist/tools/spikePlan.d.ts +37 -0
- package/dist/tools/spikePlan.js +17 -0
- package/dist/types/index.d.ts +88 -0
- package/dist/types/index.js +4 -0
- package/package.json +56 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Command Alkon Teams MCP Server
|
|
2
|
+
|
|
3
|
+
**One place, official prompt context for writing Jira issues.** This MCP does not call any Jira APIs. It gives Cursor (and your team) a single source of truth: tools that return structured, copy-pasteable drafts and a prompt that encodes your standard description template, domain vocabulary, and quality rules.
|
|
4
|
+
|
|
5
|
+
Use it so everyone writes Jira issues the same way — consistent format, shared vocabulary, no guesswork.
|
|
6
|
+
|
|
7
|
+
## What it provides
|
|
8
|
+
|
|
9
|
+
- **Prompt: `jira_issue_guidelines`** – Official Jira issue writing guidelines (description template, App Areas, entities, label patterns, quality rules). Use this so the model follows your standards when writing or reviewing issues.
|
|
10
|
+
- **jira_issue_draft** – Draft Story, Task, Spike, or Bug from a short request
|
|
11
|
+
- **spike_plan** – Time-boxed spike plan with tasks, deliverables, and risks
|
|
12
|
+
- **jira_bug_from_report** – Turn messy bug reports into structured Jira bugs
|
|
13
|
+
- **acceptance_criteria** – Generate acceptance criteria and test notes for stories
|
|
14
|
+
- **release_notes** – Produce release notes from a list of issues
|
|
15
|
+
|
|
16
|
+
All output is for copy-paste into Jira. No API keys or Jira integration required.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
**Option A: Run via npx (no install)**
|
|
21
|
+
|
|
22
|
+
If the package is published to npm, use:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @nbrem108/cai-mcp-server
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then in Cursor MCP config, point to this command (see below).
|
|
29
|
+
|
|
30
|
+
**Option B: Clone and build**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone <repo-url>
|
|
34
|
+
cd cai-mcp-server
|
|
35
|
+
npm install
|
|
36
|
+
npm run build
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then run with `npm start` or `node dist/index.js`, and point Cursor at `dist/index.js`.
|
|
40
|
+
|
|
41
|
+
## Environment variables
|
|
42
|
+
|
|
43
|
+
Optional. Copy `.env.example` to `.env` if you want to override defaults:
|
|
44
|
+
|
|
45
|
+
| Variable | Description | Default |
|
|
46
|
+
|----------|-------------|---------|
|
|
47
|
+
| `JIRA_DEFAULT_PROJECT_KEY` | Default project key used in drafts when none is provided | `PROJ` |
|
|
48
|
+
|
|
49
|
+
No Jira API keys or secrets are used. The MCP is prompt context and structured output only.
|
|
50
|
+
|
|
51
|
+
## Register MCP in Cursor
|
|
52
|
+
|
|
53
|
+
1. Open Cursor Settings → MCP (or edit your MCP config file).
|
|
54
|
+
2. Add the server using either **npx** (if the package is on npm) or a **path** to your build:
|
|
55
|
+
|
|
56
|
+
**Using npx:**
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"command-alkon": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["-y", "@nbrem108/cai-mcp-server"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Using a local path (Windows):**
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"command-alkon": {
|
|
75
|
+
"command": "node",
|
|
76
|
+
"args": ["C:\\Users\\<you>\\cai-mcp-server\\dist\\index.js"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Using a local path (macOS/Linux):**
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"command-alkon": {
|
|
88
|
+
"command": "node",
|
|
89
|
+
"args": ["/path/to/cai-mcp-server/dist/index.js"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
3. Restart Cursor if needed. The **prompt** (`jira_issue_guidelines`) and **tools** will appear.
|
|
96
|
+
|
|
97
|
+
## Cursor skill (how the agent uses this MCP)
|
|
98
|
+
|
|
99
|
+
This repo includes a **Cursor Agent Skill** so the agent knows when and how to use the Jira MCP:
|
|
100
|
+
|
|
101
|
+
- **Location:** `.cursor/skills/jira-issue-writer/SKILL.md`
|
|
102
|
+
- **When it applies:** Creating or formatting Jira stories, bugs, tasks, spikes; acceptance criteria; bug-from-report; release notes; or when the user asks how issues “should” be written.
|
|
103
|
+
- **What it does:** Tells the agent to load the `jira_issue_guidelines` prompt first, then call the appropriate tool and present copy-pasteable output.
|
|
104
|
+
|
|
105
|
+
If you cloned the repo, the skill is already in the project. For team-wide use, ensure the MCP is configured in Cursor and the agent will follow the skill when users ask for Jira content.
|
|
106
|
+
|
|
107
|
+
## Testing
|
|
108
|
+
|
|
109
|
+
### 1. Smoke test (no Cursor)
|
|
110
|
+
|
|
111
|
+
After `npm run build`, run the tool handlers with minimal input to confirm they return valid JSON:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This runs `scripts/smoke-test-tools.mjs`, which calls each of the five tools once and checks that the response has parseable JSON. Use this to verify the server build and tool logic.
|
|
118
|
+
|
|
119
|
+
### 2. Run the server and keep it alive
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run build
|
|
123
|
+
npm start
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The process will sit on stdio waiting for MCP messages. Do not type into the terminal; that would send input to the server. Stop with Ctrl+C.
|
|
127
|
+
|
|
128
|
+
### 3. Test inside Cursor (full flow)
|
|
129
|
+
|
|
130
|
+
1. Add the MCP server to Cursor (see “Register MCP in Cursor” above) and restart if needed.
|
|
131
|
+
2. In a chat, ask the model to use a tool, for example:
|
|
132
|
+
- “Use the jira_issue_draft tool to create a Story with context: allow dispatchers to filter loads by carrier.”
|
|
133
|
+
- “Call spike_plan with goal: evaluate OAuth providers, timebox 2 days.”
|
|
134
|
+
3. Confirm the model calls the tool and you get back structured JSON (summary, descriptionMarkdown, etc.).
|
|
135
|
+
|
|
136
|
+
If the tools don’t appear, check Cursor’s MCP settings and that the `args` path points to your built `dist/index.js`.
|
|
137
|
+
|
|
138
|
+
## Example prompts (for Cursor)
|
|
139
|
+
|
|
140
|
+
- **Load official guidelines:** Use the **jira_issue_guidelines** prompt so the model has the standard template and vocabulary, then ask it to write an issue.
|
|
141
|
+
- **Create a Jira story:** “Create a Jira story for: allow dispatchers to filter loads by carrier.”
|
|
142
|
+
- **Spike plan:** “Make a spike plan for evaluating OAuth providers, timebox 2 days.”
|
|
143
|
+
- **Bug from report:** “Convert this bug report into a Jira bug: [paste text]”
|
|
144
|
+
- **Acceptance criteria:** “Generate acceptance criteria for this story: [paste summary and description]”
|
|
145
|
+
- **Release notes:** “Write release notes for these issues: [paste list] with audience Customer, tone Neutral, group by Type.”
|
|
146
|
+
|
|
147
|
+
## Project structure
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
bin/
|
|
151
|
+
cai-mcp.mjs # Runnable entrypoint for npx
|
|
152
|
+
src/
|
|
153
|
+
index.ts # Entry point
|
|
154
|
+
server.ts # MCP server and tool registration
|
|
155
|
+
config/ # Defaults and vocab
|
|
156
|
+
tools/ # Tool handlers
|
|
157
|
+
prompts/ # Template helpers + guidelines
|
|
158
|
+
formatters/ # Jira markdown
|
|
159
|
+
policies/ # Guardrails
|
|
160
|
+
.cursor/skills/
|
|
161
|
+
jira-issue-writer/SKILL.md # Cursor skill for when to use this MCP
|
|
162
|
+
server.json # MCP Registry metadata (optional publish)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Publishing
|
|
166
|
+
|
|
167
|
+
See **[PUBLISHING.md](PUBLISHING.md)** for step-by-step instructions to publish to npm and (optionally) the MCP Registry.
|
|
168
|
+
|
|
169
|
+
## Design
|
|
170
|
+
|
|
171
|
+
- **No Jira API** – This MCP never calls Jira. It is prompt context and structured drafts only; users copy-paste into Jira themselves.
|
|
172
|
+
- **Single source of truth** – The `jira_issue_guidelines` prompt and tools define how your team writes issues (template, vocabulary, guardrails).
|
|
173
|
+
- **Open questions** – When info is missing (e.g. project key, expected vs actual for bugs), the tools add open questions instead of guessing.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
MIT
|
package/bin/cai-mcp.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runnable entrypoint for npx / global install.
|
|
4
|
+
* Runs the MCP server over stdio (for Cursor and other MCP clients).
|
|
5
|
+
*/
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const distIndex = join(__dirname, "..", "dist", "index.js");
|
|
12
|
+
|
|
13
|
+
await import(pathToFileURL(distIndex).href);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
let _vocab = null;
|
|
6
|
+
export function loadVocab() {
|
|
7
|
+
if (!_vocab) {
|
|
8
|
+
const path = join(__dirname, "vocab.json");
|
|
9
|
+
const raw = readFileSync(path, "utf-8");
|
|
10
|
+
_vocab = JSON.parse(raw);
|
|
11
|
+
}
|
|
12
|
+
return _vocab;
|
|
13
|
+
}
|
|
14
|
+
export function getDefaultProjectKey() {
|
|
15
|
+
return process.env.JIRA_DEFAULT_PROJECT_KEY ?? "PROJ";
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"appAreas": [
|
|
3
|
+
"Company Admin",
|
|
4
|
+
"Dispatch",
|
|
5
|
+
"Deliveries",
|
|
6
|
+
"Billing",
|
|
7
|
+
"Sales & Quoting",
|
|
8
|
+
"Accounting"
|
|
9
|
+
],
|
|
10
|
+
"entities": [
|
|
11
|
+
"Customer",
|
|
12
|
+
"Carrier",
|
|
13
|
+
"Location",
|
|
14
|
+
"Legal Entity",
|
|
15
|
+
"Role",
|
|
16
|
+
"Permission",
|
|
17
|
+
"ABAC filter",
|
|
18
|
+
"Team"
|
|
19
|
+
],
|
|
20
|
+
"labelPatterns": [
|
|
21
|
+
"app-company-admin",
|
|
22
|
+
"feature-permissions",
|
|
23
|
+
"ux",
|
|
24
|
+
"bug",
|
|
25
|
+
"spike"
|
|
26
|
+
],
|
|
27
|
+
"personas": [
|
|
28
|
+
"default",
|
|
29
|
+
"readyMixSupport",
|
|
30
|
+
"billingSupport"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jira-ready markdown formatting utilities.
|
|
3
|
+
* Uses a consistent description template for copy-paste into Jira.
|
|
4
|
+
*/
|
|
5
|
+
export interface DescriptionSections {
|
|
6
|
+
overview?: string;
|
|
7
|
+
userProblem?: string;
|
|
8
|
+
scope?: string;
|
|
9
|
+
outOfScope?: string;
|
|
10
|
+
workflowNotes?: string;
|
|
11
|
+
permissionsRoles?: string;
|
|
12
|
+
dataApiNotes?: string;
|
|
13
|
+
acceptanceCriteria?: string[];
|
|
14
|
+
testNotes?: string[];
|
|
15
|
+
openQuestions?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Build a standard Jira description with the canonical section order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildDescription(sections: DescriptionSections): string;
|
|
21
|
+
/**
|
|
22
|
+
* Build a bug description with: Problem, Steps to Reproduce, Expected, Actual, Environment, Impact, Notes/Logs.
|
|
23
|
+
*/
|
|
24
|
+
export interface BugDescriptionSections {
|
|
25
|
+
problem: string;
|
|
26
|
+
stepsToReproduce?: string;
|
|
27
|
+
expected?: string;
|
|
28
|
+
actual?: string;
|
|
29
|
+
environment?: string;
|
|
30
|
+
impact?: string;
|
|
31
|
+
notesLogs?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function buildBugDescription(sections: BugDescriptionSections): string;
|
|
34
|
+
/**
|
|
35
|
+
* Escape or normalize text for Jira markdown (e.g. avoid breaking list items).
|
|
36
|
+
*/
|
|
37
|
+
export declare function escapeJiraMarkdown(text: string): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jira-ready markdown formatting utilities.
|
|
3
|
+
* Uses a consistent description template for copy-paste into Jira.
|
|
4
|
+
*/
|
|
5
|
+
const H2 = (title) => `\n## ${title}\n`;
|
|
6
|
+
const bullets = (items) => items.length ? items.map((s) => `* ${s}`).join("\n") : "";
|
|
7
|
+
/**
|
|
8
|
+
* Build a standard Jira description with the canonical section order.
|
|
9
|
+
*/
|
|
10
|
+
export function buildDescription(sections) {
|
|
11
|
+
const parts = [];
|
|
12
|
+
if (sections.overview) {
|
|
13
|
+
parts.push(H2("Overview"), sections.overview);
|
|
14
|
+
}
|
|
15
|
+
if (sections.userProblem) {
|
|
16
|
+
parts.push(H2("User Problem / Value"), sections.userProblem);
|
|
17
|
+
}
|
|
18
|
+
if (sections.scope) {
|
|
19
|
+
parts.push(H2("Scope"), sections.scope);
|
|
20
|
+
}
|
|
21
|
+
if (sections.outOfScope) {
|
|
22
|
+
parts.push(H2("Out of Scope"), sections.outOfScope);
|
|
23
|
+
}
|
|
24
|
+
if (sections.workflowNotes) {
|
|
25
|
+
parts.push(H2("Workflow / UI Notes"), sections.workflowNotes);
|
|
26
|
+
}
|
|
27
|
+
if (sections.permissionsRoles) {
|
|
28
|
+
parts.push(H2("Permissions / Roles"), sections.permissionsRoles);
|
|
29
|
+
}
|
|
30
|
+
if (sections.dataApiNotes) {
|
|
31
|
+
parts.push(H2("Data / API Notes"), sections.dataApiNotes);
|
|
32
|
+
}
|
|
33
|
+
if (sections.acceptanceCriteria?.length) {
|
|
34
|
+
parts.push(H2("Acceptance Criteria"), bullets(sections.acceptanceCriteria));
|
|
35
|
+
}
|
|
36
|
+
if (sections.testNotes?.length) {
|
|
37
|
+
parts.push(H2("Test Notes"), bullets(sections.testNotes));
|
|
38
|
+
}
|
|
39
|
+
if (sections.openQuestions?.length) {
|
|
40
|
+
parts.push(H2("Open Questions"), bullets(sections.openQuestions));
|
|
41
|
+
}
|
|
42
|
+
return parts.join("\n").trim() || "";
|
|
43
|
+
}
|
|
44
|
+
export function buildBugDescription(sections) {
|
|
45
|
+
const parts = [];
|
|
46
|
+
parts.push(H2("Problem"), sections.problem);
|
|
47
|
+
if (sections.stepsToReproduce) {
|
|
48
|
+
parts.push(H2("Steps to Reproduce"), sections.stepsToReproduce);
|
|
49
|
+
}
|
|
50
|
+
if (sections.expected) {
|
|
51
|
+
parts.push(H2("Expected"), sections.expected);
|
|
52
|
+
}
|
|
53
|
+
if (sections.actual) {
|
|
54
|
+
parts.push(H2("Actual"), sections.actual);
|
|
55
|
+
}
|
|
56
|
+
if (sections.environment) {
|
|
57
|
+
parts.push(H2("Environment"), sections.environment);
|
|
58
|
+
}
|
|
59
|
+
if (sections.impact) {
|
|
60
|
+
parts.push(H2("Impact"), sections.impact);
|
|
61
|
+
}
|
|
62
|
+
if (sections.notesLogs) {
|
|
63
|
+
parts.push(H2("Notes / Logs"), sections.notesLogs);
|
|
64
|
+
}
|
|
65
|
+
return parts.join("\n").trim();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Escape or normalize text for Jira markdown (e.g. avoid breaking list items).
|
|
69
|
+
*/
|
|
70
|
+
export function escapeJiraMarkdown(text) {
|
|
71
|
+
return text
|
|
72
|
+
.replace(/\r\n/g, "\n")
|
|
73
|
+
.replace(/\r/g, "\n")
|
|
74
|
+
.trim();
|
|
75
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrails: no hallucinated fields, open questions for missing/ambiguous info.
|
|
3
|
+
*/
|
|
4
|
+
import type { Vocab } from "../config/defaults.js";
|
|
5
|
+
/**
|
|
6
|
+
* Ensure labels only use known patterns or safe defaults; otherwise suggest open question.
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateLabels(labels: string[], vocab: Vocab): {
|
|
9
|
+
labels: string[];
|
|
10
|
+
openQuestions: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* If project key is missing or empty, add open question.
|
|
14
|
+
*/
|
|
15
|
+
export declare function ensureProjectKey(projectKey: string | undefined, openQuestions: string[]): string[];
|
|
16
|
+
/**
|
|
17
|
+
* For bugs: if expected/actual behavior is missing, add open question.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureBugExpectedActual(expected: string | undefined, actual: string | undefined, openQuestions: string[]): string[];
|
|
20
|
+
/**
|
|
21
|
+
* If app area is provided but not in vocab, add open question.
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateAppArea(appArea: string | undefined, vocab: Vocab): string[];
|
|
24
|
+
export declare function capDescription(markdown: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Collect all open questions and deduplicate.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeOpenQuestions(...questionLists: string[][]): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Get vocab for use in tools (prefer vocab terms, fall back to open questions).
|
|
31
|
+
*/
|
|
32
|
+
export declare function getVocab(): Vocab;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { loadVocab } from "../config/defaults.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ensure labels only use known patterns or safe defaults; otherwise suggest open question.
|
|
4
|
+
*/
|
|
5
|
+
export function validateLabels(labels, vocab) {
|
|
6
|
+
const known = new Set(vocab.labelPatterns);
|
|
7
|
+
const valid = [];
|
|
8
|
+
const questions = [];
|
|
9
|
+
for (const l of labels) {
|
|
10
|
+
const normalized = l.toLowerCase().replace(/\s+/g, "-");
|
|
11
|
+
if (known.has(normalized) || normalized === "bug" || normalized === "spike") {
|
|
12
|
+
valid.push(l);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
valid.push(l);
|
|
16
|
+
if (!questions.includes("Confirm label usage for: " + l)) {
|
|
17
|
+
questions.push("Confirm label usage for: " + l);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { labels: valid, openQuestions: questions };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* If project key is missing or empty, add open question.
|
|
25
|
+
*/
|
|
26
|
+
export function ensureProjectKey(projectKey, openQuestions) {
|
|
27
|
+
if (!projectKey || projectKey.trim() === "") {
|
|
28
|
+
return [...openQuestions, "Which Jira project key should this issue use?"];
|
|
29
|
+
}
|
|
30
|
+
return openQuestions;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* For bugs: if expected/actual behavior is missing, add open question.
|
|
34
|
+
*/
|
|
35
|
+
export function ensureBugExpectedActual(expected, actual, openQuestions) {
|
|
36
|
+
const out = [...openQuestions];
|
|
37
|
+
if (!expected?.trim()) {
|
|
38
|
+
out.push("What was the expected behavior?");
|
|
39
|
+
}
|
|
40
|
+
if (!actual?.trim()) {
|
|
41
|
+
out.push("What was the actual behavior?");
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* If app area is provided but not in vocab, add open question.
|
|
47
|
+
*/
|
|
48
|
+
export function validateAppArea(appArea, vocab) {
|
|
49
|
+
if (!appArea?.trim())
|
|
50
|
+
return [];
|
|
51
|
+
const known = new Set(vocab.appAreas.map((a) => a.toLowerCase()));
|
|
52
|
+
if (known.has(appArea.toLowerCase()))
|
|
53
|
+
return [];
|
|
54
|
+
return ["Is the app area correct? Known areas: " + vocab.appAreas.join(", ")];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Cap description length for "ready to paste" (avoid massive blocks).
|
|
58
|
+
*/
|
|
59
|
+
const MAX_DESCRIPTION_LENGTH = 32000;
|
|
60
|
+
export function capDescription(markdown) {
|
|
61
|
+
if (markdown.length <= MAX_DESCRIPTION_LENGTH)
|
|
62
|
+
return markdown;
|
|
63
|
+
return (markdown.slice(0, MAX_DESCRIPTION_LENGTH) +
|
|
64
|
+
"\n\n... (truncated for length; consider splitting into child issues)");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Collect all open questions and deduplicate.
|
|
68
|
+
*/
|
|
69
|
+
export function mergeOpenQuestions(...questionLists) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const list of questionLists) {
|
|
73
|
+
for (const q of list) {
|
|
74
|
+
const t = q.trim();
|
|
75
|
+
if (t && !seen.has(t)) {
|
|
76
|
+
seen.add(t);
|
|
77
|
+
out.push(q);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get vocab for use in tools (prefer vocab terms, fall back to open questions).
|
|
85
|
+
*/
|
|
86
|
+
export function getVocab() {
|
|
87
|
+
return loadVocab();
|
|
88
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official Jira issue writing guidelines — single source of truth for prompt context.
|
|
3
|
+
* Exposed as MCP prompt "jira_issue_guidelines" so the model can use it when writing issues.
|
|
4
|
+
*/
|
|
5
|
+
import { loadVocab } from "../config/defaults.js";
|
|
6
|
+
export function getJiraIssueGuidelines() {
|
|
7
|
+
const vocab = loadVocab();
|
|
8
|
+
const text = `You are following Command Alkon's official Jira issue writing standards. Use this as the single source of truth whenever you write or review Jira issues.
|
|
9
|
+
|
|
10
|
+
## Standard description template
|
|
11
|
+
|
|
12
|
+
Use this section order for issue descriptions (markdown). Prefer structured bullets over long paragraphs.
|
|
13
|
+
|
|
14
|
+
1. **Overview** – What this is and why it matters.
|
|
15
|
+
2. **User Problem / Value** – Who is affected and what value they get.
|
|
16
|
+
3. **Scope** – What is in scope.
|
|
17
|
+
4. **Out of Scope** – What is explicitly not in scope.
|
|
18
|
+
5. **Workflow / UI Notes** – How it should behave in the product.
|
|
19
|
+
6. **Permissions / Roles** – If applicable (roles, permissions, ABAC).
|
|
20
|
+
7. **Data / API Notes** – If applicable (APIs, data model).
|
|
21
|
+
8. **Acceptance Criteria** – Bullet list of testable criteria.
|
|
22
|
+
9. **Test Notes** – Manual test checklist.
|
|
23
|
+
10. **Open Questions** – Unresolved questions; do not guess — list them.
|
|
24
|
+
|
|
25
|
+
Consistency is more important than being verbose.
|
|
26
|
+
|
|
27
|
+
## Domain vocabulary (Command Alkon / Command Cloud)
|
|
28
|
+
|
|
29
|
+
**App areas:** ${vocab.appAreas.join(", ")}
|
|
30
|
+
|
|
31
|
+
**Entities:** ${vocab.entities.join(", ")}
|
|
32
|
+
|
|
33
|
+
**Label patterns:** Prefer these when relevant: ${vocab.labelPatterns.join(", ")}. Do not invent custom field IDs or project-specific fields unless the user provides them.
|
|
34
|
+
|
|
35
|
+
**Personas:** ${vocab.personas.join(", ")}
|
|
36
|
+
|
|
37
|
+
## Quality rules
|
|
38
|
+
|
|
39
|
+
- Never hallucinate Jira custom fields (e.g. story points field IDs) unless the user or config provides them.
|
|
40
|
+
- When information is missing (e.g. project key, expected vs actual for bugs, permissions scope), add **Open Questions** instead of guessing.
|
|
41
|
+
- Output must be copy-pasteable into Jira (clear headings, bullets, no broken markdown).
|
|
42
|
+
- For bugs, always include: Problem, Steps to Reproduce, Expected, Actual, Environment, Impact (and Notes/Logs if applicable).`;
|
|
43
|
+
return {
|
|
44
|
+
description: "Official Jira issue writing guidelines (template, vocabulary, quality rules)",
|
|
45
|
+
messages: [
|
|
46
|
+
{
|
|
47
|
+
role: "user",
|
|
48
|
+
content: { type: "text", text },
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic prompt/template helpers for tool output structure.
|
|
3
|
+
* Tools use these to build consistent, copy-pasteable content.
|
|
4
|
+
*/
|
|
5
|
+
import type { JiraIssueDraftInput, JiraIssueDraftOutput } from "../types/index.js";
|
|
6
|
+
import type { JiraBugFromReportInput, JiraBugFromReportOutput } from "../types/index.js";
|
|
7
|
+
import type { SpikePlanInput, SpikePlanOutput } from "../types/index.js";
|
|
8
|
+
import type { AcceptanceCriteriaInput, AcceptanceCriteriaOutput } from "../types/index.js";
|
|
9
|
+
import type { ReleaseNotesInput, ReleaseNotesOutput } from "../types/index.js";
|
|
10
|
+
export declare function applyJiraIssueDraftTemplate(input: JiraIssueDraftInput): JiraIssueDraftOutput;
|
|
11
|
+
export declare function applySpikePlanTemplate(input: SpikePlanInput): SpikePlanOutput;
|
|
12
|
+
export declare function applyJiraBugFromReportTemplate(input: JiraBugFromReportInput): JiraBugFromReportOutput;
|
|
13
|
+
export declare function applyAcceptanceCriteriaTemplate(input: AcceptanceCriteriaInput): AcceptanceCriteriaOutput;
|
|
14
|
+
export declare function applyReleaseNotesTemplate(input: ReleaseNotesInput): ReleaseNotesOutput;
|