@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.
- package/CHANGELOG.md +77 -0
- package/LICENSE +21 -0
- package/README.md +342 -0
- package/bin/sigmaskills.js +7 -0
- package/manifest.json +28 -0
- package/package.json +42 -0
- package/registry/agent-hosts.json +2404 -0
- package/registry/schema.json +110 -0
- package/registry/skill-baselines.json +4 -0
- package/registry/source.json +6 -0
- package/sigmabrief/SKILL.md +56 -0
- package/sigmabrief/agents/openai.yaml +12 -0
- package/sigmabrief/references/brief-method.md +73 -0
- package/sigmabrief/references/prompt-contract.md +174 -0
- package/sigmaperformance/SKILL.md +118 -0
- package/sigmaperformance/agents/openai.yaml +12 -0
- package/sigmaperformance/references/audit-method.md +112 -0
- package/sigmaperformance/references/calibration.md +45 -0
- package/sigmaperformance/references/report-contract.md +103 -0
- package/sigmareview/SKILL.md +133 -0
- package/sigmareview/agents/openai.yaml +12 -0
- package/sigmareview/references/report-contract.md +217 -0
- package/sigmareview/references/review-method.md +233 -0
- package/sigmawrite/SKILL.md +45 -0
- package/sigmawrite/agents/openai.yaml +12 -0
- package/src/adoption.js +370 -0
- package/src/backup.js +398 -0
- package/src/catalog.js +211 -0
- package/src/cli.js +657 -0
- package/src/customization.js +344 -0
- package/src/destinations.js +491 -0
- package/src/interactive.js +959 -0
- package/src/links.js +157 -0
- package/src/plan.js +429 -0
- package/src/prepack.js +10 -0
- package/src/project-lock.js +169 -0
- package/src/purge.js +477 -0
- package/src/registry/automation-ci.js +411 -0
- package/src/registry/automation.js +554 -0
- package/src/registry/diff.js +149 -0
- package/src/registry/normalize.js +67 -0
- package/src/registry/parse.js +184 -0
- package/src/registry/sync.js +230 -0
- package/src/registry/validate.js +223 -0
- package/src/release-ci.js +12 -0
- package/src/release.js +837 -0
- package/src/restore.js +518 -0
- package/src/revision.js +82 -0
- package/src/state.js +480 -0
- package/src/status.js +469 -0
- package/src/transaction.js +636 -0
- package/src/uninstall.js +647 -0
- package/src/update.js +815 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* validate.js — validate an attributed host registry against the schema
|
|
5
|
+
* contracts and safety rules. Rejects unsafe traversal, roots, control
|
|
6
|
+
* characters, shell syntax, duplicate/case-colliding ids, invalid platform
|
|
7
|
+
* data, and intra-host destination overlap (a host whose project and global
|
|
8
|
+
* destinations resolve to the same path). Cross-host sharing of a skills
|
|
9
|
+
* directory is legitimate and is not rejected. Returns { valid, errors }.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/;
|
|
13
|
+
const SHELL_SYNTAX = /[`$;|&<>()"'\\]/;
|
|
14
|
+
const TRAVERSAL_SEGMENT = /^(\.\.|\.\.\.)$/;
|
|
15
|
+
const ABSOLUTE_PREFIX = /^([/\\~%]|[A-Za-z]:)/;
|
|
16
|
+
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
17
|
+
const BASE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
18
|
+
const ENV_VAR_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
19
|
+
|
|
20
|
+
/** The platforms this registry supports; anything else is invalid data. */
|
|
21
|
+
export const SUPPORTED_PLATFORMS = ['darwin', 'linux', 'win32'];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Assert a relative path fragment is safe. Fragments may contain '/' path
|
|
25
|
+
* separators (they join onto a base home dir), but must not be absolute,
|
|
26
|
+
* must not escape via traversal segments, and must not contain control
|
|
27
|
+
* characters or shell syntax.
|
|
28
|
+
*/
|
|
29
|
+
export function assertSafePath(path, where) {
|
|
30
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
31
|
+
return `${where}: empty destination path`;
|
|
32
|
+
}
|
|
33
|
+
if (CONTROL_CHARS.test(path)) {
|
|
34
|
+
return `${where}: control character in destination path ${JSON.stringify(path)}`;
|
|
35
|
+
}
|
|
36
|
+
if (SHELL_SYNTAX.test(path)) {
|
|
37
|
+
return `${where}: shell syntax in destination path ${JSON.stringify(path)}`;
|
|
38
|
+
}
|
|
39
|
+
if (ABSOLUTE_PREFIX.test(path)) {
|
|
40
|
+
return `${where}: absolute/root destination path ${JSON.stringify(path)}`;
|
|
41
|
+
}
|
|
42
|
+
for (const segment of path.split('/')) {
|
|
43
|
+
if (TRAVERSAL_SEGMENT.test(segment)) {
|
|
44
|
+
return `${where}: traversal segment in destination path ${JSON.stringify(path)}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Validate one host record. Returns an error string or null. */
|
|
51
|
+
export function validateHost(host) {
|
|
52
|
+
if (!host || typeof host !== 'object') {
|
|
53
|
+
return 'host entry is missing or not an object';
|
|
54
|
+
}
|
|
55
|
+
if (typeof host.id !== 'string' || host.id.length === 0) {
|
|
56
|
+
return 'host id is missing or empty';
|
|
57
|
+
}
|
|
58
|
+
if (!ID_PATTERN.test(host.id)) {
|
|
59
|
+
return `host id is not a safe lowercase id (${JSON.stringify(host.id)})`;
|
|
60
|
+
}
|
|
61
|
+
if (CONTROL_CHARS.test(host.id)) {
|
|
62
|
+
return `host id contains control characters (${JSON.stringify(host.id)})`;
|
|
63
|
+
}
|
|
64
|
+
if (typeof host.name !== 'string' || host.name.length === 0) {
|
|
65
|
+
return `${host.id}: name is missing or empty`;
|
|
66
|
+
}
|
|
67
|
+
if (typeof host.displayName !== 'string' || host.displayName.length === 0) {
|
|
68
|
+
return `${host.id}: displayName is missing or empty`;
|
|
69
|
+
}
|
|
70
|
+
if (CONTROL_CHARS.test(host.displayName)) {
|
|
71
|
+
return `${host.id}: displayName contains control characters`;
|
|
72
|
+
}
|
|
73
|
+
if (typeof host.universal !== 'boolean' || typeof host.universalPrompt !== 'boolean') {
|
|
74
|
+
return `${host.id}: universal membership flags must be booleans`;
|
|
75
|
+
}
|
|
76
|
+
const d = host.destinations;
|
|
77
|
+
if (!d || typeof d !== 'object') {
|
|
78
|
+
return `${host.id}: destinations are missing`;
|
|
79
|
+
}
|
|
80
|
+
const projectErr = validateDestination(d.project, `${host.id}.destinations.project`, true);
|
|
81
|
+
if (projectErr) return projectErr;
|
|
82
|
+
const globalErr = validateDestination(d.global, `${host.id}.destinations.global`, false);
|
|
83
|
+
if (globalErr) return globalErr;
|
|
84
|
+
const projectKey = destinationKey(d.project);
|
|
85
|
+
const globalKey = destinationKey(d.global);
|
|
86
|
+
if (projectKey && projectKey === globalKey) {
|
|
87
|
+
return `${host.id}: project and global destinations overlap (both resolve to ${projectKey})`;
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(host.aliases)) return `${host.id}: aliases must be an array`;
|
|
90
|
+
for (const alias of host.aliases) {
|
|
91
|
+
if (typeof alias !== 'string' || alias.length === 0 || CONTROL_CHARS.test(alias)) {
|
|
92
|
+
return `${host.id}: invalid alias declaration ${JSON.stringify(alias)}`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!Array.isArray(host.platforms)) return `${host.id}: platforms must be an array`;
|
|
96
|
+
for (const platform of host.platforms) {
|
|
97
|
+
if (typeof platform !== 'string' || !SUPPORTED_PLATFORMS.includes(platform)) {
|
|
98
|
+
return `${host.id}: unsupported platform ${JSON.stringify(platform)} (expected one of ${SUPPORTED_PLATFORMS.join(', ')})`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const envVars = host.detection && host.detection.envVars;
|
|
102
|
+
if (!Array.isArray(envVars)) return `${host.id}: detection.envVars must be an array`;
|
|
103
|
+
for (const envVar of envVars) {
|
|
104
|
+
if (typeof envVar !== 'string' || !ENV_VAR_PATTERN.test(envVar)) {
|
|
105
|
+
return `${host.id}: invalid detection env var ${JSON.stringify(envVar)}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const attribution = host.attribution;
|
|
109
|
+
if (!attribution || typeof attribution !== 'object') {
|
|
110
|
+
return `${host.id}: attribution is missing`;
|
|
111
|
+
}
|
|
112
|
+
if (typeof attribution.upstreamFile !== 'string' || attribution.upstreamFile.length === 0) {
|
|
113
|
+
return `${host.id}: attribution.upstreamFile is missing`;
|
|
114
|
+
}
|
|
115
|
+
if (typeof attribution.upstreamLine !== 'number' || attribution.upstreamLine < 1) {
|
|
116
|
+
return `${host.id}: attribution.upstreamLine is invalid`;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Validate one destination formula record. */
|
|
122
|
+
export function validateDestination(dest, where, requirePresent) {
|
|
123
|
+
if (!dest) {
|
|
124
|
+
return requirePresent ? `${where}: destination is missing` : null;
|
|
125
|
+
}
|
|
126
|
+
switch (dest.kind) {
|
|
127
|
+
case 'none':
|
|
128
|
+
return null;
|
|
129
|
+
case 'literal':
|
|
130
|
+
return assertSafePath(dest.path, where);
|
|
131
|
+
case 'join':
|
|
132
|
+
if (typeof dest.base !== 'string' || !BASE_IDENTIFIER.test(dest.base)) {
|
|
133
|
+
return `${where}: join base is not an env-var identifier (${JSON.stringify(dest.base)})`;
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(dest.segments) || dest.segments.length === 0) {
|
|
136
|
+
return `${where}: join has no path segments`;
|
|
137
|
+
}
|
|
138
|
+
for (const segment of dest.segments) {
|
|
139
|
+
const err = assertSafePath(segment, where);
|
|
140
|
+
if (err) return err;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
case 'conditional':
|
|
144
|
+
if (!Array.isArray(dest.cases) || dest.cases.length === 0) {
|
|
145
|
+
return `${where}: conditional has no cases`;
|
|
146
|
+
}
|
|
147
|
+
for (const entry of dest.cases) {
|
|
148
|
+
if (!entry || typeof entry !== 'object') {
|
|
149
|
+
return `${where}: conditional case is not an object`;
|
|
150
|
+
}
|
|
151
|
+
const err = validateDestination(entry.formula, `${where}.case(${JSON.stringify(entry.probe)})`, true);
|
|
152
|
+
if (err) return err;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
case 'function':
|
|
156
|
+
return `${where}: destination requires evaluation (function ${JSON.stringify(dest.name)})`;
|
|
157
|
+
case 'unknown':
|
|
158
|
+
return `${where}: destination formula is not parseable (${JSON.stringify(dest.raw)})`;
|
|
159
|
+
default:
|
|
160
|
+
return `${where}: unknown destination kind ${JSON.stringify(dest.kind)}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Resolve a destination to a canonical path key for overlap checks. */
|
|
165
|
+
export function destinationKey(dest) {
|
|
166
|
+
if (!dest) return '';
|
|
167
|
+
switch (dest.kind) {
|
|
168
|
+
case 'literal':
|
|
169
|
+
return 'project:' + dest.path;
|
|
170
|
+
case 'join':
|
|
171
|
+
return 'global:' + dest.base + ':' + dest.segments.join('/');
|
|
172
|
+
case 'conditional':
|
|
173
|
+
return 'conditional:' + JSON.stringify(dest.cases);
|
|
174
|
+
case 'none':
|
|
175
|
+
return '';
|
|
176
|
+
default:
|
|
177
|
+
return `${dest.kind}:${JSON.stringify(dest)}`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validate a full snapshot. Returns { valid, errors } where errors are
|
|
183
|
+
* schema-level and cross-host problems; host-level errors are collected
|
|
184
|
+
* under hostErrors.
|
|
185
|
+
*/
|
|
186
|
+
export function validateSnapshot(snapshot) {
|
|
187
|
+
const errors = [];
|
|
188
|
+
const hostErrors = {};
|
|
189
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
190
|
+
return { valid: false, errors: ['snapshot is missing'] };
|
|
191
|
+
}
|
|
192
|
+
if (snapshot.schemaVersion !== 1) {
|
|
193
|
+
errors.push(`unsupported schemaVersion ${JSON.stringify(snapshot.schemaVersion)}`);
|
|
194
|
+
}
|
|
195
|
+
if (!Array.isArray(snapshot.hosts)) {
|
|
196
|
+
errors.push('snapshot.hosts must be an array');
|
|
197
|
+
return { valid: errors.length === 0, errors, hostErrors };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const byId = new Map();
|
|
201
|
+
const byLower = new Map();
|
|
202
|
+
|
|
203
|
+
for (const host of snapshot.hosts) {
|
|
204
|
+
const err = validateHost(host);
|
|
205
|
+
if (err) {
|
|
206
|
+
hostErrors[host && host.id ? host.id : '<malformed>'] = err;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (byId.has(host.id)) {
|
|
210
|
+
errors.push(`duplicate host id ${JSON.stringify(host.id)}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const lower = host.id.toLowerCase();
|
|
214
|
+
if (byLower.has(lower) && byLower.get(lower) !== host.id) {
|
|
215
|
+
errors.push(`case-colliding host ids ${JSON.stringify(byLower.get(lower))} and ${JSON.stringify(host.id)}`);
|
|
216
|
+
}
|
|
217
|
+
byId.set(host.id, host);
|
|
218
|
+
byLower.set(lower, host.id);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const valid = errors.length === 0 && Object.keys(hostErrors).length === 0;
|
|
222
|
+
return { valid, errors, hostErrors };
|
|
223
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { runTrustedPublish, runTrustedValidate } from './release.js';
|
|
2
|
+
|
|
3
|
+
const mode = process.argv[2];
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
if (mode === 'validate') await runTrustedValidate(process.env);
|
|
7
|
+
else if (mode === 'publish') await runTrustedPublish(process.env);
|
|
8
|
+
else throw new Error('usage: node src/release-ci.js validate|publish');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.error(err.message || err);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|