@djordje-stojanovic/sigmaskills 0.2.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +21 -0
  3. package/README.md +342 -0
  4. package/bin/sigmaskills.js +7 -0
  5. package/manifest.json +28 -0
  6. package/package.json +42 -0
  7. package/registry/agent-hosts.json +2404 -0
  8. package/registry/schema.json +110 -0
  9. package/registry/skill-baselines.json +4 -0
  10. package/registry/source.json +6 -0
  11. package/sigmabrief/SKILL.md +56 -0
  12. package/sigmabrief/agents/openai.yaml +12 -0
  13. package/sigmabrief/references/brief-method.md +73 -0
  14. package/sigmabrief/references/prompt-contract.md +174 -0
  15. package/sigmaperformance/SKILL.md +118 -0
  16. package/sigmaperformance/agents/openai.yaml +12 -0
  17. package/sigmaperformance/references/audit-method.md +112 -0
  18. package/sigmaperformance/references/calibration.md +45 -0
  19. package/sigmaperformance/references/report-contract.md +103 -0
  20. package/sigmareview/SKILL.md +133 -0
  21. package/sigmareview/agents/openai.yaml +12 -0
  22. package/sigmareview/references/report-contract.md +217 -0
  23. package/sigmareview/references/review-method.md +233 -0
  24. package/sigmawrite/SKILL.md +45 -0
  25. package/sigmawrite/agents/openai.yaml +12 -0
  26. package/src/adoption.js +370 -0
  27. package/src/backup.js +398 -0
  28. package/src/catalog.js +211 -0
  29. package/src/cli.js +657 -0
  30. package/src/customization.js +344 -0
  31. package/src/destinations.js +491 -0
  32. package/src/interactive.js +959 -0
  33. package/src/links.js +157 -0
  34. package/src/plan.js +429 -0
  35. package/src/prepack.js +10 -0
  36. package/src/project-lock.js +169 -0
  37. package/src/purge.js +477 -0
  38. package/src/registry/automation-ci.js +411 -0
  39. package/src/registry/automation.js +554 -0
  40. package/src/registry/diff.js +149 -0
  41. package/src/registry/normalize.js +67 -0
  42. package/src/registry/parse.js +184 -0
  43. package/src/registry/sync.js +230 -0
  44. package/src/registry/validate.js +223 -0
  45. package/src/release-ci.js +12 -0
  46. package/src/release.js +837 -0
  47. package/src/restore.js +518 -0
  48. package/src/revision.js +82 -0
  49. package/src/state.js +480 -0
  50. package/src/status.js +469 -0
  51. package/src/transaction.js +636 -0
  52. package/src/uninstall.js +647 -0
  53. package/src/update.js +815 -0
@@ -0,0 +1,344 @@
1
+ export const CUSTOM_BLOCK_START = '<sigmaskills-custom>';
2
+ export const CUSTOM_BLOCK_END = '</sigmaskills-custom>';
3
+ export const SECTION_HEADING = '## Personal instructions';
4
+
5
+ /**
6
+ * Validate that markdown content contains exactly one well-formed customization block
7
+ * under the '## Personal instructions' section.
8
+ *
9
+ * @param {string} markdownContent
10
+ * @param {string} [skillId]
11
+ * @returns {{ valid: boolean, customContent: string }}
12
+ */
13
+ export function validateCustomizationBlock(markdownContent, skillId = 'skill') {
14
+ if (typeof markdownContent !== 'string') {
15
+ throw new TypeError(`${skillId}: markdown content must be a string`);
16
+ }
17
+
18
+ const headingIndex = markdownContent.indexOf(SECTION_HEADING);
19
+ if (headingIndex === -1) {
20
+ throw new Error(`${skillId}: missing required section: '${SECTION_HEADING}'`);
21
+ }
22
+
23
+ // Count occurrences of tags in the entire file
24
+ const startMatches = markdownContent.match(new RegExp(escapeRegExp(CUSTOM_BLOCK_START), 'g')) || [];
25
+ const endMatches = markdownContent.match(new RegExp(escapeRegExp(CUSTOM_BLOCK_END), 'g')) || [];
26
+
27
+ if (startMatches.length === 0) {
28
+ throw new Error(`${skillId}: missing opening '${CUSTOM_BLOCK_START}' marker`);
29
+ }
30
+ if (startMatches.length > 1) {
31
+ throw new Error(`${skillId}: multiple '${CUSTOM_BLOCK_START}' markers found`);
32
+ }
33
+ if (endMatches.length === 0) {
34
+ throw new Error(`${skillId}: missing closing '${CUSTOM_BLOCK_END}' marker`);
35
+ }
36
+ if (endMatches.length > 1) {
37
+ throw new Error(`${skillId}: multiple '${CUSTOM_BLOCK_END}' markers found`);
38
+ }
39
+
40
+ const startIndex = markdownContent.indexOf(CUSTOM_BLOCK_START);
41
+ const endIndex = markdownContent.indexOf(CUSTOM_BLOCK_END);
42
+
43
+ if (startIndex < headingIndex || endIndex < headingIndex) {
44
+ throw new Error(`${skillId}: markers must appear inside the '${SECTION_HEADING}' section`);
45
+ }
46
+
47
+ if (endIndex < startIndex) {
48
+ throw new Error(`${skillId}: closing marker appears before opening marker`);
49
+ }
50
+
51
+ const customContent = markdownContent.slice(
52
+ startIndex + CUSTOM_BLOCK_START.length,
53
+ endIndex,
54
+ );
55
+
56
+ // If there is a leading newline after <sigmaskills-custom>, strip it if it's the standard container newline,
57
+ // but keep custom content as extracted
58
+ let normalizedCustomContent = customContent;
59
+ if (normalizedCustomContent.startsWith('\r\n')) {
60
+ normalizedCustomContent = normalizedCustomContent.slice(2);
61
+ } else if (normalizedCustomContent.startsWith('\n')) {
62
+ normalizedCustomContent = normalizedCustomContent.slice(1);
63
+ }
64
+
65
+ return {
66
+ valid: true,
67
+ customContent: normalizedCustomContent,
68
+ rawCustomContent: customContent,
69
+ };
70
+ }
71
+
72
+ function countOccurrences(haystack, needle) {
73
+ let count = 0;
74
+ let index = 0;
75
+ while (needle && (index = haystack.indexOf(needle, index)) !== -1) {
76
+ count += 1;
77
+ index += needle.length;
78
+ }
79
+ return count;
80
+ }
81
+
82
+ function dominantEol(markdown) {
83
+ return markdown.includes('\r\n') ? '\r\n' : '\n';
84
+ }
85
+
86
+ function isWhitespaceOnly(text) {
87
+ return text.length === 0 || /^[\r\n \t]*$/.test(text);
88
+ }
89
+
90
+ function stripLeadingEols(text) {
91
+ let index = 0;
92
+ while (index < text.length) {
93
+ if (text.startsWith('\r\n', index)) index += 2;
94
+ else if (text[index] === '\n') index += 1;
95
+ else break;
96
+ }
97
+ return text.slice(index);
98
+ }
99
+
100
+ function insertEmptyBlockAfterHeading(markdown) {
101
+ const eol = dominantEol(markdown);
102
+ const headingIndex = markdown.indexOf(SECTION_HEADING);
103
+ const headingEnd = headingIndex + SECTION_HEADING.length;
104
+ const rest = stripLeadingEols(markdown.slice(headingEnd));
105
+ return `${markdown.slice(0, headingEnd)}${eol}${eol}${CUSTOM_BLOCK_START}${eol}${CUSTOM_BLOCK_END}${eol}${rest}`;
106
+ }
107
+
108
+ function insertHeadingBeforeStart(markdown) {
109
+ const eol = dominantEol(markdown);
110
+ const startIndex = markdown.indexOf(CUSTOM_BLOCK_START);
111
+ return `${markdown.slice(0, startIndex)}${SECTION_HEADING}${eol}${eol}${markdown.slice(startIndex)}`;
112
+ }
113
+
114
+ function swapReversedWhitespacePair(markdown) {
115
+ const endIndex = markdown.indexOf(CUSTOM_BLOCK_END);
116
+ const startIndex = markdown.indexOf(CUSTOM_BLOCK_START);
117
+ const mid = markdown.slice(endIndex + CUSTOM_BLOCK_END.length, startIndex);
118
+ return `${markdown.slice(0, endIndex)}${CUSTOM_BLOCK_START}${mid}${CUSTOM_BLOCK_END}${markdown.slice(startIndex + CUSTOM_BLOCK_START.length)}`;
119
+ }
120
+
121
+ /**
122
+ * Diagnose marker shapes without guessing user-text boundaries.
123
+ * Unique mechanical repairs never wrap existing user bytes as Skill Customization.
124
+ *
125
+ * @param {string} markdownContent
126
+ * @param {string} [skillId]
127
+ * @returns {{
128
+ * status: 'valid' | 'empty' | 'absent' | 'malformed',
129
+ * shape: string,
130
+ * error?: string,
131
+ * repairable: boolean,
132
+ * proposedRepair?: string,
133
+ * customContent?: string,
134
+ * }}
135
+ */
136
+ export function diagnoseCustomizationMarkers(markdownContent, skillId = 'skill') {
137
+ if (typeof markdownContent !== 'string') {
138
+ return {
139
+ status: 'malformed',
140
+ shape: 'not-string',
141
+ error: `${skillId}: markdown content must be a string`,
142
+ repairable: false,
143
+ };
144
+ }
145
+
146
+ const startCount = countOccurrences(markdownContent, CUSTOM_BLOCK_START);
147
+ const endCount = countOccurrences(markdownContent, CUSTOM_BLOCK_END);
148
+ const headingIndex = markdownContent.indexOf(SECTION_HEADING);
149
+
150
+ try {
151
+ const result = validateCustomizationBlock(markdownContent, skillId);
152
+ return {
153
+ status: result.customContent ? 'valid' : 'empty',
154
+ shape: 'well-formed',
155
+ repairable: false,
156
+ customContent: result.customContent,
157
+ };
158
+ } catch (err) {
159
+ const error = err.message;
160
+ if (startCount === 0 && endCount === 0 && headingIndex !== -1) {
161
+ return {
162
+ status: 'malformed',
163
+ shape: 'missing-markers',
164
+ error,
165
+ repairable: true,
166
+ proposedRepair: insertEmptyBlockAfterHeading(markdownContent),
167
+ };
168
+ }
169
+ if (startCount === 0 && endCount === 0 && headingIndex === -1) {
170
+ return {
171
+ status: 'malformed',
172
+ shape: 'missing-markers',
173
+ error,
174
+ repairable: false,
175
+ };
176
+ }
177
+ if (headingIndex === -1 && startCount === 1 && endCount === 1) {
178
+ const startIndex = markdownContent.indexOf(CUSTOM_BLOCK_START);
179
+ const endIndex = markdownContent.indexOf(CUSTOM_BLOCK_END);
180
+ if (startIndex < endIndex) {
181
+ return {
182
+ status: 'malformed',
183
+ shape: 'missing-heading',
184
+ error,
185
+ repairable: true,
186
+ proposedRepair: insertHeadingBeforeStart(markdownContent),
187
+ };
188
+ }
189
+ }
190
+ if (startCount === 1 && endCount === 1) {
191
+ const startIndex = markdownContent.indexOf(CUSTOM_BLOCK_START);
192
+ const endIndex = markdownContent.indexOf(CUSTOM_BLOCK_END);
193
+ if (endIndex < startIndex) {
194
+ const mid = markdownContent.slice(endIndex + CUSTOM_BLOCK_END.length, startIndex);
195
+ const repairable = isWhitespaceOnly(mid) && headingIndex !== -1 && endIndex > headingIndex;
196
+ return {
197
+ status: 'malformed',
198
+ shape: 'reversed',
199
+ error,
200
+ repairable,
201
+ proposedRepair: repairable ? swapReversedWhitespacePair(markdownContent) : undefined,
202
+ };
203
+ }
204
+ }
205
+ let shape = 'malformed';
206
+ if (startCount > 1 && endCount > 1) shape = 'nested';
207
+ else if (startCount > 1) shape = 'duplicate-start';
208
+ else if (endCount > 1) shape = 'duplicate-end';
209
+ else if (startCount === 0) shape = 'missing-start';
210
+ else if (endCount === 0) shape = 'missing-end';
211
+ else if (headingIndex === -1) shape = 'missing-heading';
212
+ else if (markdownContent.indexOf(CUSTOM_BLOCK_START) < headingIndex) shape = 'outside-section';
213
+ return {
214
+ status: 'malformed',
215
+ shape,
216
+ error,
217
+ repairable: false,
218
+ };
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Return exact approved repair bytes. Never infers marker boundaries.
224
+ *
225
+ * @param {string} markdownContent
226
+ * @param {string} [skillId]
227
+ * @param {{ editor?: (bytes: string) => string }} [options]
228
+ * @returns {string}
229
+ */
230
+ export function applyProposedRepair(markdownContent, skillId = 'skill', options = {}) {
231
+ const diagnosis = diagnoseCustomizationMarkers(markdownContent, skillId);
232
+ if (!diagnosis.repairable || typeof diagnosis.proposedRepair !== 'string') {
233
+ throw new Error(`${skillId}: invalid repair; marker boundaries cannot be inferred`);
234
+ }
235
+ let bytes = diagnosis.proposedRepair;
236
+ if (typeof options.editor === 'function') {
237
+ bytes = options.editor(bytes);
238
+ }
239
+ if (typeof bytes !== 'string') {
240
+ throw new Error(`${skillId}: editor produced invalid repair bytes`);
241
+ }
242
+ const after = diagnoseCustomizationMarkers(bytes, skillId);
243
+ if (after.status !== 'valid' && after.status !== 'empty') {
244
+ throw new Error(`${skillId}: editor produced invalid repair`);
245
+ }
246
+ return bytes;
247
+ }
248
+
249
+ /**
250
+ * Inspect customization markers without guessing. Absent markers stay absent;
251
+ * malformed markers are reported and never repaired.
252
+ *
253
+ * @param {string} markdownContent
254
+ * @param {string} [skillId]
255
+ * @returns {{ status: 'absent' | 'empty' | 'valid' | 'malformed', customContent?: string, error?: string }}
256
+ */
257
+ export function inspectCustomizationBlock(markdownContent, skillId = 'skill') {
258
+ if (typeof markdownContent !== 'string') {
259
+ return { status: 'malformed', error: `${skillId}: markdown content must be a string` };
260
+ }
261
+
262
+ const hasStart = markdownContent.includes(CUSTOM_BLOCK_START);
263
+ const hasEnd = markdownContent.includes(CUSTOM_BLOCK_END);
264
+ if (!hasStart && !hasEnd) {
265
+ return { status: 'absent' };
266
+ }
267
+
268
+ try {
269
+ const result = validateCustomizationBlock(markdownContent, skillId);
270
+ return {
271
+ status: result.customContent ? 'valid' : 'empty',
272
+ customContent: result.customContent,
273
+ };
274
+ } catch (err) {
275
+ return { status: 'malformed', error: err.message };
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Extract the user-customized instructions from a skill markdown string.
281
+ *
282
+ * @param {string} markdownContent
283
+ * @param {string} [skillId]
284
+ * @returns {string}
285
+ */
286
+ export function extractCustomContent(markdownContent, skillId = 'skill') {
287
+ const result = validateCustomizationBlock(markdownContent, skillId);
288
+ return result.customContent;
289
+ }
290
+
291
+ /**
292
+ * Exact bytes between the customization markers, including leading newlines.
293
+ *
294
+ * @param {string} markdownContent
295
+ * @param {string} [skillId]
296
+ * @returns {string}
297
+ */
298
+ export function extractRawCustomContent(markdownContent, skillId = 'skill') {
299
+ validateCustomizationBlock(markdownContent, skillId);
300
+ const startIndex = markdownContent.indexOf(CUSTOM_BLOCK_START);
301
+ const endIndex = markdownContent.indexOf(CUSTOM_BLOCK_END);
302
+ return markdownContent.slice(startIndex + CUSTOM_BLOCK_START.length, endIndex);
303
+ }
304
+
305
+ /**
306
+ * Replace bytes between customization markers without normalizing newlines.
307
+ *
308
+ * @param {string} baseMarkdownContent
309
+ * @param {string} rawCustomContent
310
+ * @param {string} [skillId]
311
+ * @returns {string}
312
+ */
313
+ export function injectRawCustomContent(baseMarkdownContent, rawCustomContent, skillId = 'skill') {
314
+ validateCustomizationBlock(baseMarkdownContent, skillId);
315
+ const startIndex = baseMarkdownContent.indexOf(CUSTOM_BLOCK_START);
316
+ const endIndex = baseMarkdownContent.indexOf(CUSTOM_BLOCK_END);
317
+ return `${baseMarkdownContent.slice(0, startIndex + CUSTOM_BLOCK_START.length)}${rawCustomContent}${baseMarkdownContent.slice(endIndex)}`;
318
+ }
319
+
320
+ /**
321
+ * Inject user-customized instructions into a base skill markdown string.
322
+ *
323
+ * @param {string} baseMarkdownContent
324
+ * @param {string} customContent
325
+ * @param {string} [skillId]
326
+ * @returns {string}
327
+ */
328
+ export function injectCustomContent(baseMarkdownContent, customContent, skillId = 'skill') {
329
+ validateCustomizationBlock(baseMarkdownContent, skillId);
330
+
331
+ const startIndex = baseMarkdownContent.indexOf(CUSTOM_BLOCK_START);
332
+ const endIndex = baseMarkdownContent.indexOf(CUSTOM_BLOCK_END);
333
+
334
+ const before = baseMarkdownContent.slice(0, startIndex + CUSTOM_BLOCK_START.length);
335
+ const after = baseMarkdownContent.slice(endIndex);
336
+
337
+ const formattedCustom = customContent ? `\n${customContent}` : '\n';
338
+ return `${before}${formattedCustom}${after}`;
339
+ }
340
+
341
+ export function escapeRegExp(string) {
342
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
343
+ }
344
+