@nseng-ai/harness-artifacts 0.1.2
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/package.json +32 -0
- package/src/api.ts +190 -0
- package/src/artifact-catalog.ts +65 -0
- package/src/diagnostic-sort.ts +14 -0
- package/src/filesystem.ts +181 -0
- package/src/first-party-catalog.ts +44 -0
- package/src/first-party-skill-provisioning.ts +162 -0
- package/src/fs-state.ts +22 -0
- package/src/harness-paths.ts +217 -0
- package/src/index.ts +1 -0
- package/src/module-artifact-declaration.ts +273 -0
- package/src/module-artifact-discovery.ts +416 -0
- package/src/ns/command.ts +32 -0
- package/src/ns/commands/install.ts +31 -0
- package/src/ns/commands/list.ts +23 -0
- package/src/ns/commands/path.ts +26 -0
- package/src/ns/commands/update.ts +28 -0
- package/src/ns/preinstalled-catalog.ts +26 -0
- package/src/ns/repo-local-ns-extension.ts +21 -0
- package/src/ns/skills-install.ts +141 -0
- package/src/ns/skills-list.ts +43 -0
- package/src/ns/skills-path.ts +53 -0
- package/src/ns/skills-shared.ts +65 -0
- package/src/ns/update.ts +99 -0
- package/src/ns-toml.ts +195 -0
- package/src/provision-apply.ts +350 -0
- package/src/provision-plan.ts +389 -0
- package/src/reconcile.ts +583 -0
- package/src/skill-frontmatter.ts +151 -0
- package/src/skill-mirror-conventions.ts +128 -0
- package/src/skills-lockfile.ts +107 -0
- package/src/sort.ts +3 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { optionalEntry, sha256Digest } from "@nseng-ai/foundation/primitives";
|
|
4
|
+
import { resultErr, resultOk, type Result } from "@nseng-ai/foundation/result";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
artifactProvisionName,
|
|
9
|
+
type HarnessArtifactEntry,
|
|
10
|
+
type HarnessArtifactSource,
|
|
11
|
+
} from "./artifact-catalog.ts";
|
|
12
|
+
import {
|
|
13
|
+
resolveHarnessArtifactPath,
|
|
14
|
+
type HarnessId,
|
|
15
|
+
type HarnessPathContext,
|
|
16
|
+
type HarnessScope,
|
|
17
|
+
} from "./harness-paths.ts";
|
|
18
|
+
import { sortStrings } from "./sort.ts";
|
|
19
|
+
|
|
20
|
+
export type ProvisionableHarnessArtifactEntry = Extract<HarnessArtifactEntry, { kind: "skill" }>;
|
|
21
|
+
|
|
22
|
+
export interface ProvisionSourceFile {
|
|
23
|
+
relativePath: string;
|
|
24
|
+
contentHash: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BuildProvisionPlanInput {
|
|
28
|
+
artifact: HarnessArtifactEntry;
|
|
29
|
+
harness: string;
|
|
30
|
+
scope: HarnessScope;
|
|
31
|
+
context: HarnessPathContext;
|
|
32
|
+
sourceVersion: string;
|
|
33
|
+
sourceFiles: readonly ProvisionSourceFile[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ProvisionSourceProvenance {
|
|
37
|
+
type: HarnessArtifactSource["type"];
|
|
38
|
+
packageName: string;
|
|
39
|
+
relativePath: string;
|
|
40
|
+
version: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProvisionPlanFile {
|
|
44
|
+
relativePath: string;
|
|
45
|
+
sourcePath: string;
|
|
46
|
+
targetPath: string;
|
|
47
|
+
contentHash: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProvisionPlan {
|
|
51
|
+
artifactId: string;
|
|
52
|
+
kind: "skill";
|
|
53
|
+
provisionName: string;
|
|
54
|
+
harness: HarnessId;
|
|
55
|
+
scope: HarnessScope;
|
|
56
|
+
targetRoot: string;
|
|
57
|
+
targetArtifactPath: string;
|
|
58
|
+
source: ProvisionSourceProvenance;
|
|
59
|
+
files: readonly ProvisionPlanFile[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type ProvisionPlanErrorInfo =
|
|
63
|
+
| {
|
|
64
|
+
code: "unsupported_artifact_kind";
|
|
65
|
+
message: string;
|
|
66
|
+
details: { kind: HarnessArtifactEntry["kind"]; artifactId: string };
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
code: "duplicate_source_file";
|
|
70
|
+
message: string;
|
|
71
|
+
details: { relativePath: string };
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
code: "unknown_harness";
|
|
75
|
+
message: string;
|
|
76
|
+
details: { input: string };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export interface InstallManifestFileData {
|
|
80
|
+
sourcePath: string;
|
|
81
|
+
targetPath: string;
|
|
82
|
+
contentHash: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type InstallManifestSourceData = ProvisionSourceProvenance;
|
|
86
|
+
|
|
87
|
+
export interface InstallManifestEntryData {
|
|
88
|
+
artifactId: string;
|
|
89
|
+
kind: "skill";
|
|
90
|
+
provisionName: string;
|
|
91
|
+
harness: HarnessId;
|
|
92
|
+
scope: HarnessScope;
|
|
93
|
+
targetRoot: string;
|
|
94
|
+
targetArtifactPath: string;
|
|
95
|
+
source: InstallManifestSourceData;
|
|
96
|
+
files: Record<string, InstallManifestFileData>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface InstallManifestData {
|
|
100
|
+
version: 1;
|
|
101
|
+
artifacts: Record<string, InstallManifestEntryData>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type TargetFileHashFact =
|
|
105
|
+
| { type: "missing"; targetPath: string }
|
|
106
|
+
| { type: "file"; targetPath: string; contentHash: string };
|
|
107
|
+
|
|
108
|
+
export type ProvisionFileDecisionType = "fresh-write" | "unchanged" | "locally-edited-conflict";
|
|
109
|
+
|
|
110
|
+
export interface ProvisionFileDecision {
|
|
111
|
+
type: ProvisionFileDecisionType;
|
|
112
|
+
file: ProvisionPlanFile;
|
|
113
|
+
currentHash?: string;
|
|
114
|
+
manifestHash?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ProvisionDecisionSet {
|
|
118
|
+
files: readonly ProvisionFileDecision[];
|
|
119
|
+
isForceRequired: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const provisionPlanFileSchema = z.object({
|
|
123
|
+
relativePath: z.string(),
|
|
124
|
+
sourcePath: z.string(),
|
|
125
|
+
targetPath: z.string(),
|
|
126
|
+
contentHash: z.string(),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export const provisionFileDecisionSchema = z.object({
|
|
130
|
+
type: z.enum(["fresh-write", "unchanged", "locally-edited-conflict"]),
|
|
131
|
+
file: provisionPlanFileSchema,
|
|
132
|
+
currentHash: z.string().optional(),
|
|
133
|
+
manifestHash: z.string().optional(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export type ProvisionDecisionErrorInfo =
|
|
137
|
+
| {
|
|
138
|
+
code: "duplicate_target_hash_fact";
|
|
139
|
+
message: string;
|
|
140
|
+
details: { targetPath: string };
|
|
141
|
+
}
|
|
142
|
+
| {
|
|
143
|
+
code: "target_hash_fact_missing";
|
|
144
|
+
message: string;
|
|
145
|
+
details: { targetPath: string };
|
|
146
|
+
}
|
|
147
|
+
| {
|
|
148
|
+
code: "manifest_entry_mismatch";
|
|
149
|
+
message: string;
|
|
150
|
+
details: { installKey: string };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export function contentHashForText(text: string): string {
|
|
154
|
+
return sha256Digest(text);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function contentHashForBytes(bytes: Uint8Array): string {
|
|
158
|
+
return sha256Digest(bytes);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function buildProvisionPlan(
|
|
162
|
+
input: BuildProvisionPlanInput,
|
|
163
|
+
): Result<ProvisionPlan, ProvisionPlanErrorInfo> {
|
|
164
|
+
if (input.artifact.kind !== "skill") {
|
|
165
|
+
return resultErr({
|
|
166
|
+
code: "unsupported_artifact_kind",
|
|
167
|
+
message: `Only skill harness artifacts can be provisioned by this steelthread planner; ${input.artifact.kind} is model-only for now.`,
|
|
168
|
+
details: { kind: input.artifact.kind, artifactId: input.artifact.id },
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const resolvedPath = resolveHarnessArtifactPath({
|
|
173
|
+
harness: input.harness,
|
|
174
|
+
scope: input.scope,
|
|
175
|
+
kind: input.artifact.kind,
|
|
176
|
+
artifactName: artifactProvisionName(input.artifact),
|
|
177
|
+
context: input.context,
|
|
178
|
+
});
|
|
179
|
+
if (!resolvedPath.ok) {
|
|
180
|
+
if (resolvedPath.error.code === "unknown_harness") return resultErr(resolvedPath.error);
|
|
181
|
+
return resultErr({
|
|
182
|
+
code: "unsupported_artifact_kind",
|
|
183
|
+
message: resolvedPath.error.message,
|
|
184
|
+
details: { kind: input.artifact.kind, artifactId: input.artifact.id },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const sourceFilesByPath = new Map<string, ProvisionSourceFile>();
|
|
189
|
+
for (const sourceFile of input.sourceFiles) {
|
|
190
|
+
if (sourceFilesByPath.has(sourceFile.relativePath)) {
|
|
191
|
+
return resultErr({
|
|
192
|
+
code: "duplicate_source_file",
|
|
193
|
+
message: `Provision source file ${JSON.stringify(sourceFile.relativePath)} appears more than once.`,
|
|
194
|
+
details: { relativePath: sourceFile.relativePath },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
sourceFilesByPath.set(sourceFile.relativePath, sourceFile);
|
|
198
|
+
}
|
|
199
|
+
const files: ProvisionPlanFile[] = [];
|
|
200
|
+
for (const relativePath of sortStrings([...sourceFilesByPath.keys()])) {
|
|
201
|
+
const sourceFile = sourceFilesByPath.get(relativePath);
|
|
202
|
+
if (sourceFile === undefined) {
|
|
203
|
+
throw new Error(`Provision source file vanished for ${relativePath}.`);
|
|
204
|
+
}
|
|
205
|
+
files.push({
|
|
206
|
+
relativePath,
|
|
207
|
+
sourcePath: join(input.artifact.source.relativePath, relativePath),
|
|
208
|
+
targetPath: join(resolvedPath.value.artifactPath, relativePath),
|
|
209
|
+
contentHash: sourceFile.contentHash,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return resultOk({
|
|
214
|
+
artifactId: input.artifact.id,
|
|
215
|
+
kind: input.artifact.kind,
|
|
216
|
+
provisionName: input.artifact.skillName,
|
|
217
|
+
harness: resolvedPath.value.harness,
|
|
218
|
+
scope: input.scope,
|
|
219
|
+
targetRoot: resolvedPath.value.rootPath,
|
|
220
|
+
targetArtifactPath: resolvedPath.value.artifactPath,
|
|
221
|
+
source: sourceProvenance(input.artifact.source, input.sourceVersion),
|
|
222
|
+
files,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function installManifestKey(plan: ProvisionPlan): string {
|
|
227
|
+
return provisionIdentityKey(plan);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function buildInstallManifestEntry(plan: ProvisionPlan): InstallManifestEntryData {
|
|
231
|
+
const files: Record<string, InstallManifestFileData> = {};
|
|
232
|
+
for (const file of plan.files) {
|
|
233
|
+
files[file.relativePath] = {
|
|
234
|
+
sourcePath: file.sourcePath,
|
|
235
|
+
targetPath: file.targetPath,
|
|
236
|
+
contentHash: file.contentHash,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
artifactId: plan.artifactId,
|
|
241
|
+
kind: plan.kind,
|
|
242
|
+
provisionName: plan.provisionName,
|
|
243
|
+
harness: plan.harness,
|
|
244
|
+
scope: plan.scope,
|
|
245
|
+
targetRoot: plan.targetRoot,
|
|
246
|
+
targetArtifactPath: plan.targetArtifactPath,
|
|
247
|
+
source: plan.source,
|
|
248
|
+
files,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function buildInstallManifestData(
|
|
253
|
+
entries: readonly InstallManifestEntryData[],
|
|
254
|
+
): InstallManifestData {
|
|
255
|
+
const artifacts: Record<string, InstallManifestEntryData> = {};
|
|
256
|
+
const sortedEntries = [...entries].sort((left, right) =>
|
|
257
|
+
manifestEntryKey(left).localeCompare(manifestEntryKey(right)),
|
|
258
|
+
);
|
|
259
|
+
for (const entry of sortedEntries) {
|
|
260
|
+
artifacts[manifestEntryKey(entry)] = entry;
|
|
261
|
+
}
|
|
262
|
+
return { version: 1, artifacts };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function classifyProvisionDecisions(input: {
|
|
266
|
+
plan: ProvisionPlan;
|
|
267
|
+
existingManifestEntry?: InstallManifestEntryData;
|
|
268
|
+
targetFacts: readonly TargetFileHashFact[];
|
|
269
|
+
}): Result<ProvisionDecisionSet, ProvisionDecisionErrorInfo> {
|
|
270
|
+
if (
|
|
271
|
+
input.existingManifestEntry !== undefined &&
|
|
272
|
+
!manifestEntryMatchesPlan(input.existingManifestEntry, input.plan)
|
|
273
|
+
) {
|
|
274
|
+
return resultErr({
|
|
275
|
+
code: "manifest_entry_mismatch",
|
|
276
|
+
message: `Install manifest entry does not match plan ${installManifestKey(input.plan)}.`,
|
|
277
|
+
details: { installKey: installManifestKey(input.plan) },
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const facts = new Map<string, TargetFileHashFact>();
|
|
282
|
+
for (const fact of input.targetFacts) {
|
|
283
|
+
if (facts.has(fact.targetPath)) {
|
|
284
|
+
return resultErr({
|
|
285
|
+
code: "duplicate_target_hash_fact",
|
|
286
|
+
message: `Target hash fact for ${JSON.stringify(fact.targetPath)} appears more than once.`,
|
|
287
|
+
details: { targetPath: fact.targetPath },
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
facts.set(fact.targetPath, fact);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const decisions: ProvisionFileDecision[] = [];
|
|
294
|
+
for (const file of input.plan.files) {
|
|
295
|
+
const fact = facts.get(file.targetPath);
|
|
296
|
+
if (fact === undefined) {
|
|
297
|
+
return resultErr({
|
|
298
|
+
code: "target_hash_fact_missing",
|
|
299
|
+
message: `No target hash fact was supplied for ${JSON.stringify(file.targetPath)}.`,
|
|
300
|
+
details: { targetPath: file.targetPath },
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
const manifestHash = input.existingManifestEntry?.files[file.relativePath]?.contentHash;
|
|
304
|
+
decisions.push(classifyProvisionFile({ file, fact, manifestHash }));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return resultOk({
|
|
308
|
+
files: decisions,
|
|
309
|
+
isForceRequired: decisions.some((decision) => decision.type === "locally-edited-conflict"),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function sourceProvenance(
|
|
314
|
+
source: HarnessArtifactSource,
|
|
315
|
+
version: string,
|
|
316
|
+
): ProvisionSourceProvenance {
|
|
317
|
+
return {
|
|
318
|
+
type: source.type,
|
|
319
|
+
packageName: source.packageName,
|
|
320
|
+
relativePath: source.relativePath,
|
|
321
|
+
version,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function manifestEntryKey(entry: InstallManifestEntryData): string {
|
|
326
|
+
return provisionIdentityKey(entry);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function provisionIdentityKey(
|
|
330
|
+
identity: Pick<ProvisionPlan, "artifactId" | "harness" | "kind" | "scope">,
|
|
331
|
+
): string {
|
|
332
|
+
return `${identity.harness}:${identity.scope}:${identity.kind}:${identity.artifactId}`;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function manifestEntryMatchesPlan(entry: InstallManifestEntryData, plan: ProvisionPlan): boolean {
|
|
336
|
+
return (
|
|
337
|
+
entry.artifactId === plan.artifactId &&
|
|
338
|
+
entry.kind === plan.kind &&
|
|
339
|
+
entry.provisionName === plan.provisionName &&
|
|
340
|
+
entry.harness === plan.harness &&
|
|
341
|
+
entry.scope === plan.scope &&
|
|
342
|
+
entry.targetRoot === plan.targetRoot &&
|
|
343
|
+
entry.targetArtifactPath === plan.targetArtifactPath
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function classifyProvisionFile(input: {
|
|
348
|
+
file: ProvisionPlanFile;
|
|
349
|
+
fact: TargetFileHashFact;
|
|
350
|
+
manifestHash: string | undefined;
|
|
351
|
+
}): ProvisionFileDecision {
|
|
352
|
+
if (input.fact.type === "missing") return { type: "fresh-write", file: input.file };
|
|
353
|
+
if (input.fact.contentHash === input.file.contentHash) {
|
|
354
|
+
return provisionFileDecisionWithHash({
|
|
355
|
+
type: "unchanged",
|
|
356
|
+
file: input.file,
|
|
357
|
+
currentHash: input.fact.contentHash,
|
|
358
|
+
manifestHash: input.manifestHash,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
if (input.manifestHash !== undefined && input.fact.contentHash === input.manifestHash) {
|
|
362
|
+
return {
|
|
363
|
+
type: "fresh-write",
|
|
364
|
+
file: input.file,
|
|
365
|
+
currentHash: input.fact.contentHash,
|
|
366
|
+
manifestHash: input.manifestHash,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return provisionFileDecisionWithHash({
|
|
370
|
+
type: "locally-edited-conflict",
|
|
371
|
+
file: input.file,
|
|
372
|
+
currentHash: input.fact.contentHash,
|
|
373
|
+
manifestHash: input.manifestHash,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function provisionFileDecisionWithHash(input: {
|
|
378
|
+
type: Extract<ProvisionFileDecisionType, "unchanged" | "locally-edited-conflict">;
|
|
379
|
+
file: ProvisionPlanFile;
|
|
380
|
+
currentHash: string;
|
|
381
|
+
manifestHash: string | undefined;
|
|
382
|
+
}): ProvisionFileDecision {
|
|
383
|
+
return {
|
|
384
|
+
type: input.type,
|
|
385
|
+
file: input.file,
|
|
386
|
+
currentHash: input.currentHash,
|
|
387
|
+
...optionalEntry("manifestHash", input.manifestHash),
|
|
388
|
+
};
|
|
389
|
+
}
|