@aholbreich/agent-skills 0.8.0 → 1.0.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,283 @@
1
+ 'use strict';
2
+
3
+ function adfDoc(content) {
4
+ return { type: 'doc', version: 1, content: content || [] };
5
+ }
6
+
7
+ function inlineNodes(text) {
8
+ const nodes = [];
9
+ let i = 0;
10
+ let plain = '';
11
+
12
+ function pushPlain() {
13
+ if (plain) {
14
+ nodes.push({ type: 'text', text: plain });
15
+ plain = '';
16
+ }
17
+ }
18
+
19
+ function pushMarked(t, marks) {
20
+ if (!t) return;
21
+ nodes.push({ type: 'text', text: t, marks });
22
+ }
23
+
24
+ while (i < text.length) {
25
+ const ch = text[i];
26
+
27
+ if (ch === '`') {
28
+ const end = text.indexOf('`', i + 1);
29
+ if (end !== -1) {
30
+ pushPlain();
31
+ pushMarked(text.slice(i + 1, end), [{ type: 'code' }]);
32
+ i = end + 1;
33
+ continue;
34
+ }
35
+ }
36
+
37
+ if (ch === '[') {
38
+ const close = text.indexOf(']', i + 1);
39
+ if (close !== -1 && text[close + 1] === '(') {
40
+ const urlEnd = text.indexOf(')', close + 2);
41
+ if (urlEnd !== -1) {
42
+ pushPlain();
43
+ pushMarked(text.slice(i + 1, close), [{ type: 'link', attrs: { href: text.slice(close + 2, urlEnd) } }]);
44
+ i = urlEnd + 1;
45
+ continue;
46
+ }
47
+ }
48
+ }
49
+
50
+ if (ch === '*' && text[i + 1] === '*') {
51
+ const end = text.indexOf('**', i + 2);
52
+ if (end !== -1) {
53
+ pushPlain();
54
+ pushMarked(text.slice(i + 2, end), [{ type: 'strong' }]);
55
+ i = end + 2;
56
+ continue;
57
+ }
58
+ }
59
+
60
+ if (ch === '*' && text[i + 1] !== '*') {
61
+ const end = text.indexOf('*', i + 1);
62
+ if (end !== -1) {
63
+ pushPlain();
64
+ pushMarked(text.slice(i + 1, end), [{ type: 'em' }]);
65
+ i = end + 1;
66
+ continue;
67
+ }
68
+ }
69
+
70
+ plain += ch;
71
+ i++;
72
+ }
73
+ pushPlain();
74
+ return nodes;
75
+ }
76
+
77
+ function paragraph(text) {
78
+ return { type: 'paragraph', content: inlineNodes(text) };
79
+ }
80
+
81
+ function listItem(text) {
82
+ return { type: 'listItem', content: [paragraph(text)] };
83
+ }
84
+
85
+ function markdownToAdf(input) {
86
+ const lines = String(input || '').replace(/\r\n/g, '\n').split('\n');
87
+ const blocks = [];
88
+ let paragraphLines = [];
89
+ let list = null;
90
+ let inCode = false;
91
+ let codeLanguage = '';
92
+ let codeLines = [];
93
+
94
+ function flushParagraph() {
95
+ if (!paragraphLines.length) return;
96
+ blocks.push(paragraph(paragraphLines.join(' ')));
97
+ paragraphLines = [];
98
+ }
99
+
100
+ function closeList() {
101
+ if (!list) return;
102
+ blocks.push({ type: list.type, content: list.items });
103
+ list = null;
104
+ }
105
+
106
+ function flushCode() {
107
+ blocks.push({
108
+ type: 'codeBlock',
109
+ attrs: codeLanguage ? { language: codeLanguage } : {},
110
+ content: codeLines.length ? [{ type: 'text', text: codeLines.join('\n') }] : [],
111
+ });
112
+ codeLines = [];
113
+ codeLanguage = '';
114
+ }
115
+
116
+ for (const rawLine of lines) {
117
+ const line = rawLine.replace(/\s+$/, '');
118
+
119
+ const fence = line.match(/^```(\w*)\s*$/);
120
+ if (fence) {
121
+ if (inCode) { inCode = false; flushCode(); }
122
+ else { flushParagraph(); closeList(); inCode = true; codeLanguage = fence[1] || ''; codeLines = []; }
123
+ continue;
124
+ }
125
+ if (inCode) { codeLines.push(rawLine); continue; }
126
+
127
+ if (!line.trim()) { flushParagraph(); closeList(); continue; }
128
+
129
+ const heading = line.match(/^(#{1,6})\s+(.+)$/);
130
+ if (heading) {
131
+ flushParagraph(); closeList();
132
+ blocks.push({ type: 'heading', attrs: { level: heading[1].length }, content: inlineNodes(heading[2].trim()) });
133
+ continue;
134
+ }
135
+
136
+ const bullet = line.match(/^[-*]\s+(.+)$/);
137
+ if (bullet) {
138
+ flushParagraph();
139
+ if (!list || list.type !== 'bulletList') { closeList(); list = { type: 'bulletList', items: [] }; }
140
+ list.items.push(listItem(bullet[1].trim()));
141
+ continue;
142
+ }
143
+
144
+ const ordered = line.match(/^\d+[.)]\s+(.+)$/);
145
+ if (ordered) {
146
+ flushParagraph();
147
+ if (!list || list.type !== 'orderedList') { closeList(); list = { type: 'orderedList', items: [] }; }
148
+ list.items.push(listItem(ordered[1].trim()));
149
+ continue;
150
+ }
151
+
152
+ closeList();
153
+ paragraphLines.push(line.trim());
154
+ }
155
+
156
+ if (inCode) flushCode();
157
+ flushParagraph();
158
+ closeList();
159
+ return adfDoc(blocks);
160
+ }
161
+
162
+ function renderDescription(input, representation) {
163
+ const rep = String(representation || 'markdown').toLowerCase();
164
+ if (rep === 'adf') {
165
+ if (!input || typeof input !== 'object') throw new Error('descriptionRepresentation: adf requires an ADF object');
166
+ return input;
167
+ }
168
+ if (rep === 'markdown' || rep === 'md') return markdownToAdf(String(input ?? ''));
169
+ throw new Error(`Unsupported representation: ${representation}`);
170
+ }
171
+
172
+ function parseAssignee(value) {
173
+ if (value === null || value === undefined || value === '') return null;
174
+ if (typeof value === 'object') return value;
175
+ const s = String(value);
176
+ if (s.startsWith('accountId:')) return { accountId: s.slice('accountId:'.length) };
177
+ return { name: s };
178
+ }
179
+
180
+ function buildCreatePayload(manifest) {
181
+ if (!manifest || typeof manifest !== 'object') throw new Error('create manifest must be an object');
182
+ if (!manifest.project) throw new Error('create manifest requires project (key)');
183
+ if (!manifest.issueType) throw new Error('create manifest requires issueType (name)');
184
+ if (!manifest.summary) throw new Error('create manifest requires summary');
185
+
186
+ const fields = {
187
+ project: { key: String(manifest.project) },
188
+ issuetype: { name: String(manifest.issueType) },
189
+ summary: String(manifest.summary),
190
+ };
191
+
192
+ if (manifest.description !== undefined && manifest.description !== null) {
193
+ fields.description = renderDescription(manifest.description, manifest.descriptionRepresentation);
194
+ }
195
+
196
+ if (Array.isArray(manifest.labels)) fields.labels = manifest.labels.map(String);
197
+ const assignee = parseAssignee(manifest.assignee);
198
+ if (assignee) fields.assignee = assignee;
199
+ if (manifest.priority) fields.priority = typeof manifest.priority === 'string' ? { name: manifest.priority } : manifest.priority;
200
+ if (manifest.parent) fields.parent = typeof manifest.parent === 'string' ? { key: manifest.parent } : manifest.parent;
201
+
202
+ if (manifest.fields && typeof manifest.fields === 'object') {
203
+ Object.assign(fields, manifest.fields);
204
+ }
205
+
206
+ return { fields };
207
+ }
208
+
209
+ function resolveTransition(transitionsResponse, query) {
210
+ const list = (transitionsResponse && transitionsResponse.transitions) || [];
211
+ if (!list.length) throw new Error('No transitions available');
212
+ if (query.id) {
213
+ const match = list.find(t => String(t.id) === String(query.id));
214
+ if (!match) throw new Error(`Transition not found: id=${query.id}. Available: ${list.map(t => `${t.id}:${t.name}`).join(', ')}`);
215
+ return match;
216
+ }
217
+ if (query.name) {
218
+ const want = String(query.name).toLowerCase();
219
+ const match = list.find(t => String(t.name).toLowerCase() === want);
220
+ if (!match) throw new Error(`Transition not found: "${query.name}". Available: ${list.map(t => t.name).join(', ')}`);
221
+ return match;
222
+ }
223
+ throw new Error('resolveTransition requires {name} or {id}');
224
+ }
225
+
226
+ function fieldValueFromCli(key, value) {
227
+ if (['resolution', 'priority', 'status'].includes(key)) return { name: value };
228
+ if (['labels', 'components', 'fixVersions'].includes(key)) {
229
+ const parts = String(value).split(',').map(s => s.trim()).filter(Boolean);
230
+ if (key === 'labels') return parts;
231
+ return parts.map(name => ({ name }));
232
+ }
233
+ return value;
234
+ }
235
+
236
+ function buildTransitionPayload({ transitionId, commentBody, fields }) {
237
+ if (!transitionId) throw new Error('buildTransitionPayload requires transitionId');
238
+ const payload = { transition: { id: String(transitionId) } };
239
+ if (commentBody) {
240
+ payload.update = { comment: [{ add: { body: commentBody } }] };
241
+ }
242
+ if (fields && Object.keys(fields).length) {
243
+ payload.fields = {};
244
+ for (const [k, v] of Object.entries(fields)) payload.fields[k] = fieldValueFromCli(k, v);
245
+ }
246
+ return payload;
247
+ }
248
+
249
+ function resolveLinkType(typesResponse, query) {
250
+ const list = (typesResponse && typesResponse.issueLinkTypes) || [];
251
+ if (!list.length) throw new Error('No issue link types available');
252
+ const want = String(query || '').toLowerCase();
253
+ const match = list.find(t =>
254
+ String(t.name).toLowerCase() === want
255
+ || String(t.inward).toLowerCase() === want
256
+ || String(t.outward).toLowerCase() === want
257
+ );
258
+ if (!match) throw new Error(`Link type not found: "${query}". Available: ${list.map(t => t.name).join(', ')}`);
259
+ return match;
260
+ }
261
+
262
+ function buildLinkPayload({ from, to, linkType }) {
263
+ if (!from || !to) throw new Error('buildLinkPayload requires from and to');
264
+ if (!linkType || !linkType.name) throw new Error('buildLinkPayload requires linkType.name');
265
+ return {
266
+ type: { name: linkType.name },
267
+ inwardIssue: { key: to },
268
+ outwardIssue: { key: from },
269
+ };
270
+ }
271
+
272
+ module.exports = {
273
+ adfDoc,
274
+ markdownToAdf,
275
+ renderDescription,
276
+ parseAssignee,
277
+ buildCreatePayload,
278
+ resolveTransition,
279
+ fieldValueFromCli,
280
+ buildTransitionPayload,
281
+ resolveLinkType,
282
+ buildLinkPayload,
283
+ };