@sysvv/ai-skill 1.2.0 → 1.4.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.
@@ -0,0 +1,190 @@
1
+ import fs from "fs-extra";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import * as TOML from "smol-toml";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ // ── Targets por agent ───────────────────────────────────────────────────
7
+ const AGENT_MCP_TARGETS = {
8
+ // ── Claude ──────────────────────────────────────────────────────────
9
+ // .mcp.json → { mcpServers: { name: { command, args, env } } }
10
+ claude: {
11
+ format: "json",
12
+ file: ".mcp.json",
13
+ serversKey: "mcpServers",
14
+ buildServerConfig: (mcp) => {
15
+ if (mcp.transport === "stdio") {
16
+ const config = { command: mcp.command };
17
+ if (mcp.args?.length)
18
+ config.args = mcp.args;
19
+ if (mcp.env && Object.keys(mcp.env).length)
20
+ config.env = mcp.env;
21
+ return config;
22
+ }
23
+ const config = { url: mcp.url };
24
+ if (mcp.headers && Object.keys(mcp.headers).length)
25
+ config.headers = mcp.headers;
26
+ if (mcp.env && Object.keys(mcp.env).length)
27
+ config.env = mcp.env;
28
+ return config;
29
+ }
30
+ },
31
+ // ── Gemini ──────────────────────────────────────────────────────────
32
+ // .gemini/settings.json → { mcpServers: { name: { command, args, env, cwd, timeout } } }
33
+ // Gemini usa "httpUrl" para streamable-http
34
+ gemini: {
35
+ format: "json",
36
+ file: ".gemini/settings.json",
37
+ serversKey: "mcpServers",
38
+ buildServerConfig: (mcp) => {
39
+ const config = {};
40
+ if (mcp.transport === "stdio") {
41
+ config.command = mcp.command;
42
+ if (mcp.args?.length)
43
+ config.args = mcp.args;
44
+ if (mcp.cwd)
45
+ config.cwd = mcp.cwd;
46
+ }
47
+ else if (mcp.transport === "sse") {
48
+ config.url = mcp.url;
49
+ if (mcp.headers && Object.keys(mcp.headers).length)
50
+ config.headers = mcp.headers;
51
+ }
52
+ else if (mcp.transport === "streamable-http") {
53
+ config.httpUrl = mcp.url;
54
+ if (mcp.headers && Object.keys(mcp.headers).length)
55
+ config.headers = mcp.headers;
56
+ }
57
+ if (mcp.env && Object.keys(mcp.env).length)
58
+ config.env = mcp.env;
59
+ if (mcp.timeout)
60
+ config.timeout = mcp.timeout;
61
+ return config;
62
+ }
63
+ },
64
+ // ── Codex ───────────────────────────────────────────────────────────
65
+ // .codex/config.toml → [mcp_servers.<name>]
66
+ // stdio: command, args, env, cwd
67
+ // streamable-http: url, bearer_token_env_var, http_headers
68
+ // extras: startup_timeout_sec, tool_timeout_sec, enabled
69
+ codex: {
70
+ format: "toml",
71
+ file: ".codex/config.toml",
72
+ buildServerEntry: (mcp) => {
73
+ const entry = {};
74
+ if (mcp.transport === "stdio") {
75
+ entry.command = mcp.command;
76
+ if (mcp.args?.length)
77
+ entry.args = mcp.args;
78
+ if (mcp.cwd)
79
+ entry.cwd = mcp.cwd;
80
+ }
81
+ else {
82
+ // sse e streamable-http → Codex usa "url" para ambos
83
+ entry.url = mcp.url;
84
+ if (mcp.headers && Object.keys(mcp.headers).length) {
85
+ entry.http_headers = mcp.headers;
86
+ }
87
+ }
88
+ if (mcp.env && Object.keys(mcp.env).length)
89
+ entry.env = mcp.env;
90
+ if (mcp.timeout)
91
+ entry.tool_timeout_sec = Math.ceil(mcp.timeout / 1000);
92
+ return entry;
93
+ }
94
+ },
95
+ // ── Copilot ─────────────────────────────────────────────────────────
96
+ // .vscode/mcp.json → { inputs: [], servers: { name: { command, args, env } } }
97
+ copilot: {
98
+ format: "json",
99
+ file: ".vscode/mcp.json",
100
+ serversKey: "servers",
101
+ buildServerConfig: (mcp) => {
102
+ if (mcp.transport === "stdio") {
103
+ const config = { command: mcp.command };
104
+ if (mcp.args?.length)
105
+ config.args = mcp.args;
106
+ if (mcp.env && Object.keys(mcp.env).length)
107
+ config.env = mcp.env;
108
+ return config;
109
+ }
110
+ const config = { url: mcp.url };
111
+ if (mcp.headers && Object.keys(mcp.headers).length)
112
+ config.headers = mcp.headers;
113
+ if (mcp.env && Object.keys(mcp.env).length)
114
+ config.env = mcp.env;
115
+ return config;
116
+ }
117
+ },
118
+ };
119
+ // ── Writers ─────────────────────────────────────────────────────────────
120
+ async function writeJsonTarget(target, targetPath, mcp) {
121
+ let existing = {};
122
+ if (await fs.pathExists(targetPath)) {
123
+ existing = await fs.readJson(targetPath);
124
+ }
125
+ // Copilot: garante que "inputs" exista
126
+ if (target.serversKey === "servers" && !existing.inputs) {
127
+ existing.inputs = [];
128
+ }
129
+ const servers = existing[target.serversKey] ?? {};
130
+ servers[mcp.name] = target.buildServerConfig(mcp);
131
+ existing[target.serversKey] = servers;
132
+ await fs.ensureDir(path.dirname(targetPath));
133
+ await fs.writeJson(targetPath, existing, { spaces: 2 });
134
+ }
135
+ async function writeTomlTarget(target, targetPath, mcp) {
136
+ let existing = {};
137
+ if (await fs.pathExists(targetPath)) {
138
+ const content = await fs.readFile(targetPath, "utf8");
139
+ if (content.trim()) {
140
+ existing = TOML.parse(content);
141
+ }
142
+ }
143
+ const mcpServers = existing.mcp_servers ?? {};
144
+ mcpServers[mcp.name] = target.buildServerEntry(mcp);
145
+ existing.mcp_servers = mcpServers;
146
+ await fs.ensureDir(path.dirname(targetPath));
147
+ await fs.writeFile(targetPath, TOML.stringify(existing), "utf8");
148
+ }
149
+ // ── API pública ─────────────────────────────────────────────────────────
150
+ /** Carrega a definição de um MCP pelo nome */
151
+ export async function loadMcp(name) {
152
+ const mcpFile = path.resolve(__dirname, "../../mcps", `${name}.mcp.json`);
153
+ if (!(await fs.pathExists(mcpFile))) {
154
+ throw new Error(`MCP "${name}" não encontrado em mcps/${name}.mcp.json`);
155
+ }
156
+ return fs.readJson(mcpFile);
157
+ }
158
+ /** Escreve o MCP no arquivo alvo do agent, fazendo merge com configs existentes */
159
+ export async function writeMcp(agent, mcp) {
160
+ const target = AGENT_MCP_TARGETS[agent];
161
+ if (!target)
162
+ throw new Error(`Agent "${agent}" não tem target de MCP configurado.`);
163
+ const targetPath = path.resolve(target.file);
164
+ if (target.format === "json") {
165
+ await writeJsonTarget(target, targetPath, mcp);
166
+ }
167
+ else {
168
+ await writeTomlTarget(target, targetPath, mcp);
169
+ }
170
+ return targetPath;
171
+ }
172
+ /** Retorna o path do arquivo MCP de um agent */
173
+ export function getMcpTargetPath(agent) {
174
+ return AGENT_MCP_TARGETS[agent]?.file;
175
+ }
176
+ /** Lista todos os MCPs disponíveis */
177
+ export async function listMcps() {
178
+ const mcpsDir = path.resolve(__dirname, "../../mcps");
179
+ if (!(await fs.pathExists(mcpsDir)))
180
+ return [];
181
+ const files = await fs.readdir(mcpsDir);
182
+ const results = [];
183
+ for (const file of files) {
184
+ if (file.endsWith(".mcp.json")) {
185
+ const mcp = await fs.readJson(path.join(mcpsDir, file));
186
+ results.push(mcp);
187
+ }
188
+ }
189
+ return results;
190
+ }
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  import { findSkillFiles } from "./core/registry.js";
8
8
  import { parseSkillFile } from "./core/frontmatter.js";
9
9
  import { loadSkill } from "./core/skill.js";
10
+ import { loadMcp, writeMcp, getMcpTargetPath } from "./core/mcp.js";
10
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
12
  // ── ANSI ────────────────────────────────────────────────────────────────
12
13
  const ANSI = {
@@ -18,6 +19,7 @@ const PINK = '\x1b[38;2;238;54;141m';
18
19
  const BLUE = '\x1b[38;2;14;93;171m';
19
20
  const RED = '\x1b[38;2;255;70;70m';
20
21
  const GREEN = '\x1b[38;2;80;200;120m';
22
+ const YELLOW = '\x1b[38;2;255;200;50m';
21
23
  const WHITE = '\x1b[97m';
22
24
  // ── Inquirer Theme ──────────────────────────────────────────────────────
23
25
  const sysTheme = {
@@ -87,7 +89,7 @@ function showBanner() {
87
89
  console.log(indent + ANSI.dim + centerPad('│ ' + WHITE + desc + ANSI.reset + ANSI.dim + ' │', maxWidth) + ANSI.reset);
88
90
  console.log(indent + ANSI.dim + centerPad('└' + '─'.repeat(boxInner) + '┘', maxWidth) + ANSI.reset);
89
91
  console.log('');
90
- console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --clear', maxWidth) + ANSI.reset);
92
+ console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --copilot | --clear', maxWidth) + ANSI.reset);
91
93
  console.log('');
92
94
  console.log(separator);
93
95
  console.log('');
@@ -97,6 +99,7 @@ const AGENT_DIRS = {
97
99
  claude: ".claude/skills",
98
100
  codex: ".codex/skills",
99
101
  gemini: ".gemini/skills",
102
+ copilot: ".github/skills",
100
103
  };
101
104
  // ── Parse args ──────────────────────────────────────────────────────────
102
105
  function parseArgs() {
@@ -109,7 +112,7 @@ function parseArgs() {
109
112
  }
110
113
  return {};
111
114
  }
112
- // ── Install skills for agent ────────────────────────────────────────────
115
+ // ── Install skills + MCPs for agent ─────────────────────────────────────
113
116
  async function installSkills(agent) {
114
117
  const skillFiles = await findSkillFiles();
115
118
  const available = [];
@@ -120,7 +123,8 @@ async function installSkills(agent) {
120
123
  file,
121
124
  name: metadata.name,
122
125
  title: metadata.title ?? metadata.name,
123
- description: metadata.description
126
+ description: metadata.description,
127
+ mcps: metadata.mcps ?? []
124
128
  });
125
129
  }
126
130
  if (available.length === 0) {
@@ -133,7 +137,7 @@ async function installSkills(agent) {
133
137
  name: "selected",
134
138
  message: "Escolha as skills:",
135
139
  choices: available.map((s) => ({
136
- name: `${s.title} ${ANSI.dim}— ${s.description}${ANSI.reset}`,
140
+ name: `${s.title} ${ANSI.dim}— ${s.description}${s.mcps.length > 0 ? ` ${YELLOW}[MCP]${ANSI.reset}${ANSI.dim}` : ''}${ANSI.reset}`,
137
141
  value: s.file
138
142
  })),
139
143
  validate: (answer) => answer.length > 0 ? true : "Selecione pelo menos uma skill.",
@@ -141,6 +145,8 @@ async function installSkills(agent) {
141
145
  }
142
146
  ]);
143
147
  const destBase = path.resolve(AGENT_DIRS[agent]);
148
+ const mcpsToInstall = new Set();
149
+ // Instala skills
144
150
  console.log('');
145
151
  for (const file of selected) {
146
152
  const skill = await loadSkill(file, agent);
@@ -148,32 +154,58 @@ async function installSkills(agent) {
148
154
  await fs.ensureDir(skillDir);
149
155
  await fs.writeFile(path.join(skillDir, "SKILL.md"), skill.renderedBody, "utf8");
150
156
  console.log(` ${GREEN}✔${ANSI.reset} ${skill.metadata.name}`);
157
+ // Coleta MCPs necessários
158
+ for (const mcpName of skill.metadata.mcps ?? []) {
159
+ mcpsToInstall.add(mcpName);
160
+ }
161
+ }
162
+ // Instala MCPs automaticamente
163
+ if (mcpsToInstall.size > 0) {
164
+ console.log('');
165
+ let mcpTarget = '';
166
+ for (const mcpName of mcpsToInstall) {
167
+ try {
168
+ const mcp = await loadMcp(mcpName);
169
+ mcpTarget = await writeMcp(agent, mcp);
170
+ console.log(` ${GREEN}✔${ANSI.reset} MCP ${ANSI.bold}${mcpName}${ANSI.reset} ${ANSI.dim}→ ${mcpTarget}${ANSI.reset}`);
171
+ }
172
+ catch (err) {
173
+ console.log(` ${RED}✖${ANSI.reset} MCP ${mcpName}: ${err.message}`);
174
+ }
175
+ }
151
176
  }
152
177
  console.log(`\n Skills instaladas em ${ANSI.bold}${destBase}/${ANSI.reset}\n`);
153
178
  }
154
- // ── Clear all skills ────────────────────────────────────────────────────
179
+ // ── Clear all skills + MCPs ─────────────────────────────────────────────
155
180
  async function clearSkills() {
156
- const existing = [];
181
+ const found = [];
157
182
  for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
158
- const full = path.resolve(dir);
159
- if (await fs.pathExists(full)) {
160
- existing.push(agent);
183
+ const skillsFull = path.resolve(dir);
184
+ const mcpTarget = getMcpTargetPath(agent);
185
+ const mcpFull = mcpTarget ? path.resolve(mcpTarget) : "";
186
+ const hasSkills = await fs.pathExists(skillsFull);
187
+ const hasMcp = mcpFull ? await fs.pathExists(mcpFull) : false;
188
+ if (hasSkills || hasMcp) {
189
+ found.push({ agent, skillsDir: skillsFull, mcpFile: mcpFull, hasSkills, hasMcp });
161
190
  }
162
191
  }
163
- if (existing.length === 0) {
164
- console.log(" Nenhuma pasta de skills encontrada.\n");
192
+ if (found.length === 0) {
193
+ console.log(" Nenhuma pasta de skills ou MCP encontrada.\n");
165
194
  return;
166
195
  }
167
- console.log(` ${RED}⚠${ANSI.reset} Pastas encontradas:\n`);
168
- for (const agent of existing) {
169
- console.log(` ${ANSI.dim}${path.resolve(AGENT_DIRS[agent])}${ANSI.reset}`);
196
+ console.log(` ${RED}⚠${ANSI.reset} Encontrado:\n`);
197
+ for (const f of found) {
198
+ if (f.hasSkills)
199
+ console.log(` ${ANSI.dim}${f.skillsDir}${ANSI.reset}`);
200
+ if (f.hasMcp)
201
+ console.log(` ${ANSI.dim}${f.mcpFile}${ANSI.reset}`);
170
202
  }
171
203
  console.log('');
172
204
  const { confirm } = await inquirer.prompt([
173
205
  {
174
206
  type: "confirm",
175
207
  name: "confirm",
176
- message: "Tem certeza que deseja apagar TODAS as pastas de skills?",
208
+ message: "Tem certeza que deseja apagar TUDO (skills + MCPs)?",
177
209
  default: false,
178
210
  theme: sysTheme
179
211
  }
@@ -182,12 +214,17 @@ async function clearSkills() {
182
214
  console.log("\n Operação cancelada.\n");
183
215
  return;
184
216
  }
185
- for (const agent of existing) {
186
- const full = path.resolve(AGENT_DIRS[agent]);
187
- await fs.remove(full);
188
- console.log(` ${RED}✖${ANSI.reset} ${full} removida`);
217
+ for (const f of found) {
218
+ if (f.hasSkills) {
219
+ await fs.remove(f.skillsDir);
220
+ console.log(` ${RED}✖${ANSI.reset} ${f.skillsDir}`);
221
+ }
222
+ if (f.hasMcp) {
223
+ await fs.remove(f.mcpFile);
224
+ console.log(` ${RED}✖${ANSI.reset} ${f.mcpFile}`);
225
+ }
189
226
  }
190
- console.log("\n Todas as skills foram removidas.\n");
227
+ console.log("\n Tudo removido.\n");
191
228
  }
192
229
  // ── Main ────────────────────────────────────────────────────────────────
193
230
  async function main() {
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "../schemas/mcp.schema.json",
3
+ "name": "azure-devops",
4
+ "description": "Integração com Azure DevOps para work items, repos e pipelines.",
5
+ "transport": "stdio",
6
+ "command": "npx",
7
+ "args": [
8
+ "-y",
9
+ "@azure-devops/mcp",
10
+ "sysmanagerdevops",
11
+ "-d", "core", "work", "work-items"
12
+ ],
13
+ "env": {
14
+ "AZURE_DEVOPS_PAT": "${AZURE_DEVOPS_PAT}"
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sysvv/ai-skill",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Instale skills de IA direto no seu projeto. Escolha o agent, escolha as skills.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,9 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "skills"
11
+ "skills",
12
+ "mcps",
13
+ "schemas"
12
14
  ],
13
15
  "scripts": {
14
16
  "build": "tsc",
@@ -28,7 +30,8 @@
28
30
  "dependencies": {
29
31
  "fs-extra": "^11.3.0",
30
32
  "gray-matter": "^4.0.3",
31
- "inquirer": "^12.5.2"
33
+ "inquirer": "^12.5.2",
34
+ "smol-toml": "^1.6.0"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@types/fs-extra": "^11.0.4",
@@ -0,0 +1,89 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://sysvv.dev/schemas/mcp.schema.json",
4
+ "title": "MCP Server Configuration",
5
+ "description": "Schema para configuração de servidores MCP (Model Context Protocol). Suporta os transportes stdio, sse e streamable-http. Campos opcionais como timeout e cwd são aplicados conforme o suporte de cada agent.",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "Referência ao JSON Schema para validação e autocomplete."
11
+ },
12
+ "name": {
13
+ "type": "string",
14
+ "pattern": "^[a-z0-9][a-z0-9-]*$",
15
+ "description": "Identificador único do MCP. Deve ser slug (lowercase, hífens). Ex: azure-devops"
16
+ },
17
+ "description": {
18
+ "type": "string",
19
+ "description": "Descrição curta do que o MCP faz."
20
+ },
21
+ "transport": {
22
+ "type": "string",
23
+ "enum": ["stdio", "sse", "streamable-http"],
24
+ "description": "Tipo de transporte do MCP."
25
+ },
26
+ "command": {
27
+ "type": "string",
28
+ "description": "[stdio] Comando para iniciar o servidor MCP."
29
+ },
30
+ "args": {
31
+ "type": "array",
32
+ "items": { "type": "string" },
33
+ "description": "[stdio] Argumentos passados ao comando."
34
+ },
35
+ "cwd": {
36
+ "type": "string",
37
+ "description": "[stdio] Diretório de trabalho para o processo do servidor."
38
+ },
39
+ "url": {
40
+ "type": "string",
41
+ "format": "uri",
42
+ "description": "[sse | streamable-http] URL do servidor MCP."
43
+ },
44
+ "headers": {
45
+ "type": "object",
46
+ "additionalProperties": { "type": "string" },
47
+ "description": "[sse | streamable-http] Headers HTTP enviados nas requisições."
48
+ },
49
+ "env": {
50
+ "type": "object",
51
+ "additionalProperties": { "type": "string" },
52
+ "description": "Variáveis de ambiente. Use ${VAR} para referências dinâmicas."
53
+ },
54
+ "timeout": {
55
+ "type": "number",
56
+ "description": "Timeout da requisição em milissegundos. Padrão varia por agent."
57
+ }
58
+ },
59
+ "required": ["name", "description", "transport"],
60
+ "allOf": [
61
+ {
62
+ "if": {
63
+ "properties": { "transport": { "const": "stdio" } },
64
+ "required": ["transport"]
65
+ },
66
+ "then": {
67
+ "required": ["command"]
68
+ }
69
+ },
70
+ {
71
+ "if": {
72
+ "properties": { "transport": { "const": "sse" } },
73
+ "required": ["transport"]
74
+ },
75
+ "then": {
76
+ "required": ["url"]
77
+ }
78
+ },
79
+ {
80
+ "if": {
81
+ "properties": { "transport": { "const": "streamable-http" } },
82
+ "required": ["transport"]
83
+ },
84
+ "then": {
85
+ "required": ["url"]
86
+ }
87
+ }
88
+ ]
89
+ }
@@ -0,0 +1,163 @@
1
+ ---
2
+ name: ado-workflow
3
+ title: ADO Workflow
4
+ description: gerenciar work items do Azure DevOps integrado com Git local. Sincroniza estados, vincula commits e mantém ADO atualizado.
5
+ version: 1.0.0
6
+ providers:
7
+ - claude
8
+ - codex
9
+ - gemini
10
+ - copilot
11
+ mcps:
12
+ - azure-devops
13
+ variables:
14
+ language: pt-BR
15
+ ---
16
+
17
+ # ADO Workflow Skill
18
+
19
+ Reusable skill that integrates Azure DevOps work items with local Git operations. Manages work item states, links commits to tasks, and keeps ADO in sync with development progress. Uses MCP tools as primary interface with CLI fallback.
20
+
21
+ ## Setup
22
+
23
+ 1. Verify MCP server "azure-devops" is available (check `mcp__azure-devops__*` tools)
24
+ 2. If MCP is NOT available: fall back to `az` CLI commands for all operations
25
+ 3. Verify `git` is installed and repository is initialized
26
+ 4. **NEVER** print or display the value of `AZURE_DEVOPS_PAT`
27
+
28
+ ## Rules
29
+
30
+ 1. ALWAYS reference ADO work item IDs in commit messages (e.g., `feat: implement login #123`)
31
+ 2. NEVER update work item state without verifying current state first
32
+ 3. NEVER print or display PAT/token values
33
+ 4. Make atomic commits — one logical change per commit
34
+ 5. Use Conventional Commits format (`feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, etc.)
35
+ 6. Keep ADO work item state in sync with actual progress
36
+ 7. ALWAYS confirm with the user before changing work item states
37
+ 8. Organization, project, area paths, and states are project-specific — do not hardcode them
38
+ 9. Work item states may use custom/localized names — always verify before updating
39
+ 10. When creating Tasks: do NOT assign, ALWAYS ask Area Path, activity requires user selection
40
+
41
+ ## Operations
42
+
43
+ ### list
44
+
45
+ List work items assigned to the current user.
46
+
47
+ **MCP:** `mcp__azure-devops__wit_my_work_items` with `project: {project}`, `type: "assignedtome"`, `includeCompleted: false`
48
+
49
+ **Fallback:**
50
+ ```bash
51
+ AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards query \
52
+ --wiql "SELECT [System.Id], [System.Title], [System.State], [System.WorkItemType] FROM WorkItems WHERE [System.AssignedTo] = @Me AND [System.State] <> 'Closed'" \
53
+ --project {project} --organization {organization} --output table
54
+ ```
55
+
56
+ **Output:** Display ID, Title, State, Assigned To, Work Item Type as formatted table.
57
+
58
+ ### show
59
+
60
+ Show full details of a work item by ID.
61
+
62
+ **Params:** `id` (required)
63
+
64
+ **MCP:** `mcp__azure-devops__wit_get_work_item` with `id: {id}`, `project: {project}`, `expand: "relations"`
65
+
66
+ **Fallback:**
67
+ ```bash
68
+ AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item show --id {id} --organization {organization} --output json
69
+ ```
70
+
71
+ **Output:** Parse and display id, type, title, state, description, acceptance criteria, assignedTo, iterationPath, areaPath, priority, storyPoints, relations.
72
+
73
+ ### start
74
+
75
+ Start working on a task — set state to active and begin implementation.
76
+
77
+ **Params:** `id` (required)
78
+
79
+ **Process:**
80
+ 1. Fetch work item details using "show" operation
81
+ 2. Display title, description, and acceptance criteria to user
82
+ 3. Update state to the active state in ADO
83
+ 4. Create implementation plan based on requirements
84
+ 5. Ask user for confirmation before proceeding
85
+
86
+ **MCP:** `mcp__azure-devops__wit_update_work_item` with `id: {id}`, `updates: [{"path": "/fields/System.State", "value": "{active_state}"}]`
87
+
88
+ **Fallback:**
89
+ ```bash
90
+ AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item update --id {id} --state "{active_state}" --organization {organization} --output json
91
+ ```
92
+
93
+ **Output:** Confirm state change. Show implementation plan. Wait for user approval.
94
+
95
+ ### complete
96
+
97
+ Complete a task — verify criteria, final commit, set state to closed.
98
+
99
+ **Params:** `id` (required)
100
+
101
+ **Process:**
102
+ 1. Fetch work item details — verify acceptance criteria are met
103
+ 2. Run final tests if applicable
104
+ 3. Make final commit referencing work item ID
105
+ 4. Update state to "Closed" in ADO
106
+ 5. Add completion comment with commit SHA
107
+
108
+ **MCP:** `mcp__azure-devops__wit_update_work_item` with `id: {id}`, `updates: [{"path": "/fields/System.State", "value": "Closed"}]`
109
+
110
+ **Fallback:**
111
+ ```bash
112
+ AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item update --id {id} --state "Closed" --organization {organization} --output json
113
+ ```
114
+
115
+ **Output:** Confirm completion. Show final commit SHA, updated state, summary.
116
+
117
+ ### commit
118
+
119
+ Make an incremental commit linked to a work item.
120
+
121
+ **Params:** `message` (optional), `work-item-id` (optional)
122
+
123
+ **Process:**
124
+ 1. Delegate to git-cli skill `/git-commit`
125
+ 2. Append work item reference to commit message (e.g., `#123`)
126
+
127
+ **Output:** Commit hash, message, files changed. Tip: use `/git-push` to push.
128
+
129
+ **Notes:** This operation delegates to the git-cli skill for actual Git operations.
130
+
131
+ ### create
132
+
133
+ Create a new work item in ADO.
134
+
135
+ **Params:** `type` (required), `title` (required), `description` (optional), `area-path` (optional — ALWAYS ask user), `activity` (optional — present types to user)
136
+
137
+ **MCP:** `mcp__azure-devops__wit_create_work_item` with `project: {project}`, `type: "{type}"`, `title: "{title}"`, `additionalFields: {"System.Description": "{description}", "System.AreaPath": "{area-path}", "Microsoft.VSTS.Common.Activity": "{activity}", "Microsoft.VSTS.Scheduling.OriginalEstimate": 0, "Microsoft.VSTS.Scheduling.RemainingWork": 0, "Microsoft.VSTS.Scheduling.CompletedWork": 0}`
138
+
139
+ **Fallback:**
140
+ ```bash
141
+ AZURE_DEVOPS_EXT_PAT=$AZURE_DEVOPS_PAT az boards work-item create \
142
+ --type "{type}" --title "{title}" --project {project} \
143
+ --organization {organization} --area "{area-path}" \
144
+ --fields "Microsoft.VSTS.Common.Activity={activity}" \
145
+ --output json
146
+ ```
147
+
148
+ **Output:** Parse response: extract id, title, state, url.
149
+
150
+ **Notes:**
151
+ - Omit additionalFields entries for params not provided
152
+ - Do NOT assign unless explicitly requested
153
+ - ALWAYS ask user for Area Path before executing
154
+
155
+ ## Error Handling
156
+
157
+ | Error | Cause | Resolution |
158
+ |-------|-------|------------|
159
+ | MCP server not available | Not configured or not running | Check `.mcp.json`, verify PAT in `.env`, restart AI tool |
160
+ | 401 / 403 | Invalid or expired PAT | Check PAT scopes: Work Items (Read, Write, Manage), Project (Read) |
161
+ | Work item type not found | Process template mismatch | Agile = "User Story", Scrum = "Product Backlog Item" |
162
+ | State transition not allowed | Invalid state change | Verify allowed transitions for the work item type |
163
+ | MCP tool call fails | Various | Fall back to `az` CLI equivalent command |