skillspub 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.
@@ -0,0 +1,297 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { explainVisibility, explainVisibilityFromInventory } from "./explain.js";
4
+ import { hashDirectory, scanGlobalInventory, scanProjectInventory, } from "./inventory.js";
5
+ import { normalizeNpxSkillsName, npxSkillsProvenanceLabel, readNpxSkillsLock, sameNpxSkillsSource, } from "./npx-skills.js";
6
+ import { attachUpdateAvailability, projectRows } from "./view.js";
7
+ export function sourceMirrorState(truth) {
8
+ if (/mirror-diverged|diverged mirror/i.test(truth.drift))
9
+ return 'diverged';
10
+ if (/mirror-sync/i.test(truth.drift))
11
+ return 'mirror-sync required';
12
+ return truth.relationships.some((relationship) => /\smirror\//i.test(relationship))
13
+ ? 'current'
14
+ : 'none';
15
+ }
16
+ function sourceRelationshipActual(relationship) {
17
+ if (relationship.info.presence === 'deadlink')
18
+ return 'broken';
19
+ return relationship.info.underOff ? 'off' : 'on';
20
+ }
21
+ export function relationshipStatusText(info) {
22
+ return `${info.presence === 'deadlink' ? 'BROKEN' : info.underOff ? 'OFF' : 'ON'} ${info.form}`;
23
+ }
24
+ export function sourceRelationships(row) {
25
+ return (row?.observedRelationships ?? row?.relationships ?? [])
26
+ .filter(({ target }) => target === 'shared');
27
+ }
28
+ export function sourceInventoryRows(snapshot) {
29
+ return snapshot.rows.filter((row) => sourceRelationships(row).length > 0);
30
+ }
31
+ export function sourceDesiredTruth(home, row, scope, projectPath) {
32
+ if (!row)
33
+ return { desired: 'not applicable', drift: 'not applicable' };
34
+ const relationships = sourceRelationships(row);
35
+ let state = {};
36
+ try {
37
+ const file = scope === 'global'
38
+ ? path.join(home.configDir, 'state.json')
39
+ : path.join(projectPath, '.skillspub', 'state.json');
40
+ if (fs.existsSync(file))
41
+ state = JSON.parse(fs.readFileSync(file, 'utf8'));
42
+ }
43
+ catch {
44
+ return { desired: 'unknown', drift: 'unknown' };
45
+ }
46
+ const baseIntent = state.baseIntent && typeof state.baseIntent === 'object' && !Array.isArray(state.baseIntent)
47
+ ? state.baseIntent
48
+ : {};
49
+ const claims = state.claims && typeof state.claims === 'object' && !Array.isArray(state.claims)
50
+ ? state.claims
51
+ : {};
52
+ let observedDrift = false;
53
+ const desired = relationships.map((relationship) => {
54
+ const actual = relationship.info.underOff ? 'OFF' : 'ON';
55
+ if (relationship.info.presence === 'deadlink' || relationship.info.diverged)
56
+ observedDrift = true;
57
+ if (relationship.readOnly)
58
+ return `read-only ${actual}`;
59
+ const slotId = `${relationship.targetId}\0${relationship.slot}`;
60
+ let activation = actual;
61
+ if (Array.isArray(claims[slotId]) && claims[slotId].length > 0)
62
+ activation = 'ON';
63
+ else if (baseIntent[slotId] === 'off')
64
+ activation = 'OFF';
65
+ else if (baseIntent[slotId] === 'on')
66
+ activation = 'ON';
67
+ if (activation !== actual)
68
+ observedDrift = true;
69
+ return activation;
70
+ });
71
+ return {
72
+ desired: [...new Set(desired)].join('/') || 'unknown',
73
+ drift: observedDrift ? 'observed' : 'none observed',
74
+ };
75
+ }
76
+ function sourceVisibility(home, row, projectPath) {
77
+ if (!row?.realPath)
78
+ return 'unknown';
79
+ try {
80
+ const visibility = explainVisibility(home, `skill:${row.id}`, { projectPath });
81
+ return [...new Set(visibility.harnesses.map(({ effectiveVisibility }) => effectiveVisibility))].join('/');
82
+ }
83
+ catch {
84
+ return 'unknown';
85
+ }
86
+ }
87
+ /** Effective visibility derived from the same inventory evidence — no second scan. */
88
+ function sourceVisibilityFromInventory(home, report, row, projectPath) {
89
+ if (!row?.realPath)
90
+ return 'unknown';
91
+ try {
92
+ const visibility = explainVisibilityFromInventory(home, report, `skill:${row.id}`, { projectPath });
93
+ return [...new Set(visibility.harnesses.map(({ effectiveVisibility }) => effectiveVisibility))].join('/');
94
+ }
95
+ catch {
96
+ return 'unknown';
97
+ }
98
+ }
99
+ function deriveSourceVerification(home, rows, plan, scope, projectPath, visibilityOf) {
100
+ const slot = plan.operation === 'shared.remove'
101
+ ? plan.source.slot
102
+ : plan.candidate?.normalizedSlot ?? plan.slots[0] ?? 'unknown';
103
+ const row = rows.find((candidate) => sourceRelationships(candidate).some((relationship) => relationship.targetId === plan.targetId && relationship.slot === slot));
104
+ const relationships = row?.observedRelationships ?? row?.relationships ?? [];
105
+ const removalLockRemains = plan.operation === 'shared.remove' &&
106
+ readNpxSkillsLock(plan.target.lockFile).some(({ slot: lockSlot }) => lockSlot === slot);
107
+ const removalDependenciesRemain = plan.operation === 'shared.remove'
108
+ ? plan.dependencies.filter(({ path: dependencyPath }) => fs.lstatSync(dependencyPath, { throwIfNoEntry: false }))
109
+ : [];
110
+ const removalSourceRemains = plan.operation === 'shared.remove' && Boolean(row);
111
+ const desiredTruth = plan.operation === 'shared.remove'
112
+ ? {
113
+ desired: 'removed',
114
+ drift: removalSourceRemains || removalLockRemains || removalDependenciesRemain.length > 0
115
+ ? 'observed'
116
+ : 'none',
117
+ }
118
+ : sourceDesiredTruth(home, row, scope, projectPath);
119
+ const actual = relationships.map((relationship) => `${relationship.targetId}/${relationship.slot}=${sourceRelationshipActual(relationship)}/${relationship.info.form}`);
120
+ const driftEvidence = relationships.flatMap(({ targetId, slot: relationshipSlot, info }) => {
121
+ if (info.presence === 'deadlink')
122
+ return [`${targetId}/${relationshipSlot}: broken`];
123
+ if (info.diverged)
124
+ return [`${targetId}/${relationshipSlot}: diverged mirror`];
125
+ if (info.mirrored && row?.realPath) {
126
+ try {
127
+ if (hashDirectory(info.path) !== hashDirectory(row.realPath))
128
+ return [`${targetId}/${relationshipSlot}: mirror-sync required`];
129
+ }
130
+ catch {
131
+ return [`${targetId}/${relationshipSlot}: mirror truth unreadable`];
132
+ }
133
+ }
134
+ return [];
135
+ });
136
+ if (plan.operation === 'shared.remove') {
137
+ if (removalSourceRemains)
138
+ driftEvidence.push('Shared source remains');
139
+ if (removalLockRemains)
140
+ driftEvidence.push('Vercel skills lock entry remains');
141
+ for (const dependency of removalDependenciesRemain)
142
+ driftEvidence.push(`dependent Relationship remains: ${dependency.targetId}/${dependency.slot}`);
143
+ }
144
+ else if (desiredTruth.drift === 'observed')
145
+ driftEvidence.push('Actual differs from Desired');
146
+ const mirrorDrift = driftEvidence.filter((item) => item.includes('mirror')).length;
147
+ return {
148
+ resource: row?.realPath ?? 'missing',
149
+ provenance: row?.sourceLabel ?? (plan.operation === 'shared.remove'
150
+ ? plan.source.provenance
151
+ : 'Source unknown'),
152
+ slot,
153
+ relationships: relationships.map(({ targetId, slot: relationshipSlot, info }) => `${targetId}/${relationshipSlot} ${info.form}/${info.underOff ? 'off' : 'on'} ${info.path}`),
154
+ actual: actual.join(', ') || `${plan.targetId}/${slot}=missing`,
155
+ desired: desiredTruth.desired,
156
+ drift: mirrorDrift > 0
157
+ ? `mirror-sync required (${mirrorDrift}); ${driftEvidence.join(', ')}`
158
+ : driftEvidence.join(', ') || 'none observed',
159
+ updateAvailability: row?.updateAvailability?.status ?? 'unknown',
160
+ effectiveVisibility: visibilityOf(row),
161
+ };
162
+ }
163
+ /**
164
+ * Current scope-local truth for a remote Catalog candidate, matched by its
165
+ * normalized Slot against the latest snapshot. Non-writable (inherited)
166
+ * relationships never count toward installed matching: an installation in one
167
+ * isolated scope must not appear installed in the other.
168
+ */
169
+ export function catalogCandidateTruth(home, snapshot, candidate, scope, projectPath) {
170
+ if (!candidate)
171
+ return undefined;
172
+ const slot = normalizeNpxSkillsName(candidate.name);
173
+ const match = sourceInventoryRows(snapshot)
174
+ .map((row) => ({
175
+ row,
176
+ relationships: sourceRelationships(row).filter((relationship) => !relationship.readOnly && relationship.slot === slot),
177
+ }))
178
+ .find(({ relationships }) => relationships.length > 0);
179
+ if (!match)
180
+ return {
181
+ state: 'not-installed',
182
+ actual: 'not installed',
183
+ desired: 'not applicable',
184
+ drift: 'not applicable',
185
+ updateAvailability: 'unknown',
186
+ relationships: 0,
187
+ effectiveVisibility: 'not applicable',
188
+ };
189
+ const { row, relationships } = match;
190
+ // Desired/Drift stay 'not applicable' for foreign Slots: ownership is not ours to derive.
191
+ const provenanceKnown = npxSkillsProvenanceLabel(row.provenance) !== 'Source unknown';
192
+ if (!provenanceKnown)
193
+ return {
194
+ state: 'occupied-unknown',
195
+ actual: 'Slot occupied — Source unknown; no ownership assumed',
196
+ desired: 'not applicable',
197
+ drift: 'not applicable',
198
+ updateAvailability: 'unknown',
199
+ relationships: sourceRelationships(row).length,
200
+ effectiveVisibility: 'unknown',
201
+ };
202
+ if (!sameNpxSkillsSource(candidate.source, candidate.name, row.provenance))
203
+ return {
204
+ state: 'replace',
205
+ actual: `occupied by ${row.sourceLabel} — Replace required`,
206
+ desired: 'not applicable',
207
+ drift: 'not applicable',
208
+ updateAvailability: row.updateAvailability?.status ?? 'unknown',
209
+ relationships: sourceRelationships(row).length,
210
+ effectiveVisibility: 'unknown',
211
+ };
212
+ const desiredTruth = sourceDesiredTruth(home, row, scope, projectPath);
213
+ return {
214
+ state: 'installed',
215
+ actual: relationships.map(({ info }) => relationshipStatusText(info)).join(', '),
216
+ desired: desiredTruth.desired,
217
+ drift: desiredTruth.drift,
218
+ updateAvailability: row.updateAvailability?.status ?? 'unknown',
219
+ relationships: sourceRelationships(row).length,
220
+ effectiveVisibility: sourceVisibility(home, row, scope === 'project' ? projectPath : undefined),
221
+ };
222
+ }
223
+ function deriveUpdateVerification(sourceRows, plan, result, visibilityOf) {
224
+ const rows = result.items.map((item) => ({
225
+ item,
226
+ row: sourceRows.find((candidate) => sourceRelationships(candidate).some(({ targetId, slot }) => targetId === plan.targetId && slot === item.slot)),
227
+ }));
228
+ const relationshipRows = rows.flatMap(({ row }) => (row?.observedRelationships ?? row?.relationships ?? []).map((relationship) => ({ row, relationship })));
229
+ const relationships = relationshipRows.map(({ relationship }) => relationship);
230
+ const visibility = rows.map(({ item, row }) => `${item.name}=${visibilityOf(row)}`);
231
+ const actual = relationships.map((relationship) => `${relationship.targetId}/${relationship.slot}=${sourceRelationshipActual(relationship)}/${relationship.info.form}`);
232
+ const observedDrift = relationshipRows.flatMap(({ row, relationship: { targetId, slot, info } }) => {
233
+ if (info.presence === 'deadlink')
234
+ return [`${targetId}/${slot}: broken`];
235
+ if (info.diverged)
236
+ return [`${targetId}/${slot}: mirror-diverged`];
237
+ if (info.mirrored && row?.realPath) {
238
+ try {
239
+ if (hashDirectory(info.path) !== hashDirectory(row.realPath))
240
+ return [`${targetId}/${slot}: mirror-sync`];
241
+ }
242
+ catch {
243
+ return [`${targetId}/${slot}: mirror truth unreadable`];
244
+ }
245
+ }
246
+ return [];
247
+ });
248
+ return {
249
+ resource: rows.map(({ row }) => row?.realPath ?? 'missing').join(', '),
250
+ provenance: rows.map(({ item, row }) => `${item.name}=${row?.sourceLabel ?? 'Source unknown'}`).join(', '),
251
+ slot: result.items.map(({ slot }) => slot).join(', '),
252
+ relationships: relationships.map(({ targetId, slot, info }) => `${targetId}/${slot} ${info.form}/${info.underOff ? 'off' : 'on'} ${info.path}`),
253
+ actual: actual.join(', ') || result.actual,
254
+ desired: plan.items.map(({ name, desired }) => `${name}=${desired}`).join(', '),
255
+ drift: [...new Set([...result.drift, ...observedDrift])].join(', ') || 'none',
256
+ updateAvailability: rows.map(({ item, row }) => `${item.name}=${row?.updateAvailability?.status ?? 'unknown'}${row?.updateAvailability?.checkedAt ? ` @ ${row.updateAvailability.checkedAt}` : ''}`).join(', '),
257
+ effectiveVisibility: visibility.join(', '),
258
+ };
259
+ }
260
+ /**
261
+ * Verify a Source mutation (add, replace, update, remove) through exactly one
262
+ * fresh post-mutation inventory scan per invocation. Actual state, Desired
263
+ * state, Drift, Update availability, provenance, Relationships, and Effective
264
+ * visibility are all derived from that same inventory evidence. Fail-soft:
265
+ * unavailable evidence is reported as explicit unknown values, never as
266
+ * success.
267
+ */
268
+ export function verifySourceMutation(home, plan, result, projectPath) {
269
+ const scope = plan.scope?.kind ?? 'global';
270
+ const exactProjectPath = projectPath ?? plan.scope?.path ?? process.cwd();
271
+ try {
272
+ const report = scope === 'project'
273
+ ? scanProjectInventory(home, exactProjectPath, undefined, { persist: false })
274
+ : scanGlobalInventory(home, undefined, { persist: false });
275
+ const rows = attachUpdateAvailability(projectRows(report), home, report)
276
+ .filter((row) => sourceRelationships(row).length > 0);
277
+ const visibilityOf = (row) => sourceVisibilityFromInventory(home, report, row, scope === 'project' ? exactProjectPath : undefined);
278
+ return 'items' in plan
279
+ ? deriveUpdateVerification(rows, plan, result, visibilityOf)
280
+ : deriveSourceVerification(home, rows, plan, scope, exactProjectPath, visibilityOf);
281
+ }
282
+ catch {
283
+ return {
284
+ resource: 'unknown',
285
+ provenance: 'Source unknown',
286
+ slot: 'items' in plan
287
+ ? plan.items.map(({ slot }) => slot).join(', ')
288
+ : plan.slots.join(', '),
289
+ relationships: [],
290
+ actual: result.actual,
291
+ desired: 'unknown',
292
+ drift: result.drift.join(', ') || 'rescan unavailable',
293
+ updateAvailability: 'unknown',
294
+ effectiveVisibility: 'unknown',
295
+ };
296
+ }
297
+ }
@@ -0,0 +1,13 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ export function sharedTargetDefinition() {
4
+ const home = os.homedir();
5
+ return {
6
+ key: 'shared',
7
+ kind: 'shared',
8
+ discoveryRoot: path.join(home, '.agents', 'skills'),
9
+ parkingRoot: path.join(home, '.agents', '.skillspub-off', 'skills'),
10
+ projectPath: '.agents/skills',
11
+ lockFile: path.join(home, '.agents', '.skill-lock.json'),
12
+ };
13
+ }