@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.
@@ -0,0 +1,141 @@
1
+ import { failure, negative, ok, type ClinkrExit } from "@nseng-ai/clinkr";
2
+ import { optionalEntry } from "@nseng-ai/foundation/primitives";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ provisionFileDecisionSchema,
7
+ provisionFirstPartySkill,
8
+ type ProvisionDecisionSet,
9
+ type ProvisionFirstPartySkillOutcome,
10
+ type ProvisionPlan,
11
+ } from "../api.ts";
12
+ import {
13
+ provisionErrorExit,
14
+ skillsResolvedArtifactLocationSchema,
15
+ skillsTargetRequestSchema,
16
+ unknownSkillExit,
17
+ type SkillsCommandContext,
18
+ } from "./skills-shared.ts";
19
+
20
+ export const skillsInstallRequestSchema = skillsTargetRequestSchema.extend({
21
+ dryRun: z.boolean().default(false),
22
+ force: z.boolean().default(false),
23
+ });
24
+
25
+ export const skillsInstallResultSchema = skillsResolvedArtifactLocationSchema.extend({
26
+ mode: z.enum(["dry-run", "applied"]),
27
+ manifestPath: z.string(),
28
+ isForceRequired: z.boolean(),
29
+ decisions: z.array(provisionFileDecisionSchema),
30
+ writtenFiles: z.array(z.string()),
31
+ });
32
+ export type SkillsInstallResult = z.infer<typeof skillsInstallResultSchema>;
33
+
34
+ export const skillsInstallConflictResultSchema = z.object({
35
+ manifestPath: z.string(),
36
+ conflictingFiles: z.array(z.string()),
37
+ });
38
+ export type SkillsInstallConflictResult = z.infer<typeof skillsInstallConflictResultSchema>;
39
+
40
+ export const skillsInstallCommandResultSchema = z.union([
41
+ skillsInstallResultSchema,
42
+ skillsInstallConflictResultSchema,
43
+ ]);
44
+ export type SkillsInstallCommandResult = z.infer<typeof skillsInstallCommandResultSchema>;
45
+
46
+ export async function runSkillsInstall(
47
+ context: SkillsCommandContext,
48
+ request: z.output<typeof skillsInstallRequestSchema>,
49
+ ): Promise<ClinkrExit<SkillsInstallCommandResult>> {
50
+ const outcome = await provisionFirstPartySkill({
51
+ skill: request.skill,
52
+ harness: request.harness,
53
+ scope: request.scope,
54
+ projectRoot: context.projectRoot,
55
+ ...optionalEntry("homeDir", context.homeDir),
56
+ env: context.env,
57
+ isDryRun: request.dryRun,
58
+ shouldForce: request.force,
59
+ });
60
+ switch (outcome.type) {
61
+ case "catalog-source-unavailable":
62
+ return failure("catalog-source-unavailable", outcome.message);
63
+ case "unknown-skill":
64
+ return unknownSkillExit(outcome.skill);
65
+ case "error":
66
+ return provisionErrorExit(outcome.error);
67
+ case "conflicted":
68
+ return installProvisionConflictExit(outcome);
69
+ case "provisioned":
70
+ return ok(
71
+ installResultFromPlan({
72
+ mode: outcome.mode,
73
+ plan: outcome.plan,
74
+ decisions: outcome.decisions,
75
+ manifestPath: outcome.manifestPath,
76
+ writtenFiles: outcome.writtenFiles,
77
+ }),
78
+ );
79
+ }
80
+ }
81
+
82
+ export function renderSkillsInstallHuman(result: SkillsInstallCommandResult): string {
83
+ if (!("mode" in result)) {
84
+ return `Provision refused: ${result.conflictingFiles.length} locally edited target file(s).\n`;
85
+ }
86
+ const lines = [
87
+ result.mode === "dry-run" ? "Provision preview" : "Provision applied",
88
+ `skill: ${result.skill}`,
89
+ `harness: ${result.harness}`,
90
+ `scope: ${result.scope}`,
91
+ `target path: ${result.targetArtifactPath}`,
92
+ `manifest: ${result.manifestPath}`,
93
+ `requires force: ${result.isForceRequired ? "yes" : "no"}`,
94
+ "decisions:",
95
+ ];
96
+ for (const decision of result.decisions) {
97
+ lines.push(`- ${decision.type}: ${decision.file.relativePath} -> ${decision.file.targetPath}`);
98
+ }
99
+ if (result.mode === "applied") {
100
+ lines.push("written files:");
101
+ for (const file of result.writtenFiles) lines.push(`- ${file}`);
102
+ }
103
+ lines.push("");
104
+ return lines.join("\n");
105
+ }
106
+
107
+ function installResultFromPlan(input: {
108
+ mode: SkillsInstallResult["mode"];
109
+ plan: ProvisionPlan;
110
+ decisions: ProvisionDecisionSet;
111
+ manifestPath: string;
112
+ writtenFiles: readonly string[];
113
+ }): SkillsInstallResult {
114
+ return {
115
+ mode: input.mode,
116
+ skill: input.plan.provisionName,
117
+ artifactId: input.plan.artifactId,
118
+ harness: input.plan.harness,
119
+ scope: input.plan.scope,
120
+ targetRoot: input.plan.targetRoot,
121
+ targetArtifactPath: input.plan.targetArtifactPath,
122
+ manifestPath: input.manifestPath,
123
+ isForceRequired: input.decisions.isForceRequired,
124
+ decisions: [...input.decisions.files],
125
+ writtenFiles: [...input.writtenFiles],
126
+ };
127
+ }
128
+
129
+ function installProvisionConflictExit(
130
+ outcome: Extract<ProvisionFirstPartySkillOutcome, { type: "conflicted" }>,
131
+ ): ClinkrExit<SkillsInstallCommandResult> {
132
+ return negative(
133
+ `Provision refused: ${outcome.conflictingFiles.length} locally edited target file(s). Re-run with --force to overwrite them.`,
134
+ {
135
+ data: {
136
+ manifestPath: outcome.manifestPath,
137
+ conflictingFiles: [...outcome.conflictingFiles],
138
+ },
139
+ },
140
+ );
141
+ }
@@ -0,0 +1,43 @@
1
+ import { ok, type ClinkrExit } from "@nseng-ai/clinkr";
2
+ import { z } from "zod";
3
+
4
+ import { listFirstPartySkillArtifacts } from "../api.ts";
5
+
6
+ export const skillsListRequestSchema = z.object({});
7
+
8
+ export const skillsListResultSchema = z.object({
9
+ catalogId: z.string(),
10
+ skills: z.array(
11
+ z.object({
12
+ id: z.string(),
13
+ name: z.string(),
14
+ description: z.string(),
15
+ skillName: z.string(),
16
+ sourcePackage: z.string(),
17
+ sourceRelativePath: z.string(),
18
+ }),
19
+ ),
20
+ });
21
+ export type SkillsListResult = z.infer<typeof skillsListResultSchema>;
22
+
23
+ export function runSkillsList(): ClinkrExit<SkillsListResult> {
24
+ return ok({
25
+ catalogId: "ns-first-party",
26
+ skills: listFirstPartySkillArtifacts().map((artifact) => ({
27
+ id: artifact.id,
28
+ name: artifact.name,
29
+ description: artifact.description,
30
+ skillName: artifact.skillName,
31
+ sourcePackage: artifact.source.packageName,
32
+ sourceRelativePath: artifact.source.relativePath,
33
+ })),
34
+ });
35
+ }
36
+
37
+ export function renderSkillsListHuman(result: SkillsListResult): string {
38
+ const lines = [`ns first-party skills (${result.catalogId})`];
39
+ for (const skill of result.skills) {
40
+ lines.push(`- ${skill.skillName} (${skill.id}) — ${skill.description}`);
41
+ }
42
+ return `${lines.join("\n")}\n`;
43
+ }
@@ -0,0 +1,53 @@
1
+ import { ok, type ClinkrExit } from "@nseng-ai/clinkr";
2
+ import { z } from "zod";
3
+
4
+ import { findFirstPartySkillArtifact, resolveHarnessArtifactPath } from "../api.ts";
5
+ import {
6
+ harnessPathErrorExit,
7
+ harnessResolutionContext,
8
+ skillsResolvedArtifactLocationSchema,
9
+ skillsTargetRequestSchema,
10
+ unknownSkillExit,
11
+ type SkillsCommandContext,
12
+ } from "./skills-shared.ts";
13
+
14
+ export const skillsPathRequestSchema = skillsTargetRequestSchema;
15
+
16
+ export const skillsPathResultSchema = skillsResolvedArtifactLocationSchema;
17
+ export type SkillsPathResult = z.infer<typeof skillsPathResultSchema>;
18
+
19
+ export function runSkillsPath(
20
+ context: SkillsCommandContext,
21
+ request: z.output<typeof skillsPathRequestSchema>,
22
+ ): ClinkrExit<SkillsPathResult> {
23
+ const artifact = findFirstPartySkillArtifact(request.skill);
24
+ if (artifact === undefined) return unknownSkillExit(request.skill);
25
+ const resolvedPath = resolveHarnessArtifactPath({
26
+ harness: request.harness,
27
+ scope: request.scope,
28
+ kind: artifact.kind,
29
+ artifactName: artifact.skillName,
30
+ context: harnessResolutionContext(context),
31
+ });
32
+ if (!resolvedPath.ok) return harnessPathErrorExit(resolvedPath.error);
33
+ return ok({
34
+ skill: artifact.skillName,
35
+ artifactId: artifact.id,
36
+ harness: resolvedPath.value.harness,
37
+ scope: resolvedPath.value.scope,
38
+ targetRoot: resolvedPath.value.rootPath,
39
+ targetArtifactPath: resolvedPath.value.artifactPath,
40
+ });
41
+ }
42
+
43
+ export function renderSkillsPathHuman(result: SkillsPathResult): string {
44
+ return [
45
+ `skill: ${result.skill}`,
46
+ `artifact: ${result.artifactId}`,
47
+ `harness: ${result.harness}`,
48
+ `scope: ${result.scope}`,
49
+ `target root: ${result.targetRoot}`,
50
+ `target path: ${result.targetArtifactPath}`,
51
+ "",
52
+ ].join("\n");
53
+ }
@@ -0,0 +1,65 @@
1
+ import { failure, negative, usageError, type ClinkrExit } from "@nseng-ai/clinkr";
2
+ import { optionalEntry } from "@nseng-ai/foundation/primitives";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ ALL_HARNESS_IDS,
7
+ firstPartySkillProvisionPathContext,
8
+ HARNESS_SCOPES,
9
+ type HarnessArtifactProvisionErrorInfo,
10
+ type HarnessPathErrorInfo,
11
+ } from "../api.ts";
12
+
13
+ export interface SkillsCommandContext {
14
+ cwd: string;
15
+ projectRoot: string;
16
+ homeDir?: string;
17
+ env: Record<string, string | undefined>;
18
+ }
19
+
20
+ export const skillsTargetRequestSchema = z.object({
21
+ skill: z.string().min(1),
22
+ harness: z.string().min(1),
23
+ scope: z.enum(HARNESS_SCOPES).default("project"),
24
+ });
25
+
26
+ export const skillsResolvedArtifactLocationSchema = z.object({
27
+ skill: z.string(),
28
+ artifactId: z.string(),
29
+ harness: z.enum(ALL_HARNESS_IDS),
30
+ scope: z.enum(HARNESS_SCOPES),
31
+ targetRoot: z.string(),
32
+ targetArtifactPath: z.string(),
33
+ });
34
+
35
+ export function harnessResolutionContext(context: SkillsCommandContext) {
36
+ return firstPartySkillProvisionPathContext({
37
+ projectRoot: context.projectRoot,
38
+ ...optionalEntry("homeDir", context.homeDir),
39
+ env: context.env,
40
+ });
41
+ }
42
+
43
+ export function unknownSkillExit<T>(skill: string): ClinkrExit<T> {
44
+ return negative(`Unknown first-party ns skill ${JSON.stringify(skill)}.`);
45
+ }
46
+
47
+ export function provisionErrorExit<T>(error: HarnessArtifactProvisionErrorInfo): ClinkrExit<T> {
48
+ if (error.code === "unknown_harness") return harnessPathErrorExit(error);
49
+ return genericFailureExit(error);
50
+ }
51
+
52
+ export function harnessPathErrorExit<T>(error: HarnessPathErrorInfo): ClinkrExit<T> {
53
+ if (error.code === "unknown_harness") {
54
+ return usageError(error.message, { field: "harness", ...error.details });
55
+ }
56
+ return genericFailureExit(error);
57
+ }
58
+
59
+ function genericFailureExit<T>(error: {
60
+ code: string;
61
+ message: string;
62
+ details?: unknown;
63
+ }): ClinkrExit<T> {
64
+ return failure(error.code.replaceAll("_", "-"), error.message, error.details);
65
+ }
@@ -0,0 +1,99 @@
1
+ import { failure, negative, ok, type ClinkrExit } from "@nseng-ai/clinkr";
2
+ import { optionalEntry } from "@nseng-ai/foundation/primitives";
3
+ import { z } from "zod";
4
+
5
+ import {
6
+ reconcileReportSchema,
7
+ runHarnessArtifactReconcile,
8
+ type ReconcileErrorInfo,
9
+ } from "../api.ts";
10
+ import type { SkillsCommandContext } from "./skills-shared.ts";
11
+
12
+ export const nsUpdateRequestSchema = z.object({
13
+ dryRun: z.boolean().default(false),
14
+ force: z.boolean().default(false),
15
+ });
16
+
17
+ export const nsUpdateResultSchema = reconcileReportSchema;
18
+ export type NsUpdateRequest = z.output<typeof nsUpdateRequestSchema>;
19
+ export type NsUpdateResult = z.output<typeof nsUpdateResultSchema>;
20
+
21
+ export async function runNsUpdate(
22
+ context: SkillsCommandContext,
23
+ request: NsUpdateRequest,
24
+ ): Promise<ClinkrExit<NsUpdateResult>> {
25
+ const baseRequest = {
26
+ projectRoot: context.projectRoot,
27
+ ...optionalEntry("homeDir", context.homeDir),
28
+ env: context.env,
29
+ shouldForce: request.force,
30
+ };
31
+ if (request.dryRun || !request.force) {
32
+ const preview = await runHarnessArtifactReconcile({ ...baseRequest, isDryRun: true });
33
+ if (!preview.ok) return reconcileFailureExit(preview.error);
34
+ if (request.dryRun) return ok(preview.value);
35
+ if (preview.value.isForceRequired) {
36
+ return negative("Update refused: locally edited target files require --force.", {
37
+ data: preview.value,
38
+ });
39
+ }
40
+ }
41
+ const result = await runHarnessArtifactReconcile({ ...baseRequest, isDryRun: false });
42
+ if (!result.ok) return reconcileFailureExit(result.error);
43
+ if (result.value.skippedCollisions.length > 0) {
44
+ return negative("Update skipped colliding harness artifacts.", { data: result.value });
45
+ }
46
+ return ok(result.value);
47
+ }
48
+
49
+ export function renderNsUpdateHuman(result: NsUpdateResult): string {
50
+ const lines = [result.mode === "dry-run" ? "Update preview" : "Update applied"];
51
+ if (result.harnessSelection.type === "missing") {
52
+ lines.push(
53
+ "",
54
+ "No harness selection found in ns.toml — run `ns init` to select harnesses; refreshed manifest-tracked artifacts only.",
55
+ );
56
+ }
57
+ lines.push("", "artifacts:");
58
+ if (result.artifacts.length === 0) {
59
+ lines.push("- none");
60
+ } else {
61
+ for (const artifact of result.artifacts) {
62
+ lines.push(
63
+ `- ${artifact.action} ${artifact.skillName} (${artifact.harness}) -> ${artifact.targetArtifactPath}`,
64
+ );
65
+ }
66
+ }
67
+ if (result.orphans.length > 0) {
68
+ lines.push("", "orphans:");
69
+ for (const orphan of result.orphans) {
70
+ lines.push(
71
+ `- orphaned — source missing, files left in place: ${orphan.artifactId} (${orphan.harness})`,
72
+ );
73
+ }
74
+ }
75
+ if (result.skippedCollisions.length > 0) {
76
+ lines.push("", "skipped collisions:");
77
+ for (const collision of result.skippedCollisions) {
78
+ lines.push(`- ${collision.kind} ${collision.value}: ${collision.packages.join(", ")}`);
79
+ }
80
+ }
81
+ if (result.isForceRequired) lines.push("", "Conflicts found; re-run with --force to overwrite.");
82
+ lines.push(
83
+ "",
84
+ `summary: ${countArtifacts(result, "installed")} installed, ${countArtifacts(result, "refreshed")} refreshed, ${countArtifacts(result, "unchanged")} unchanged, ${countArtifacts(result, "conflicted")} conflicted, ${countArtifacts(result, "skipped")} skipped, ${result.orphans.length} orphaned, ${result.diagnostics.length} diagnostic(s)`,
85
+ "",
86
+ );
87
+ return lines.join("\n");
88
+ }
89
+
90
+ function reconcileFailureExit<T>(error: ReconcileErrorInfo): ClinkrExit<T> {
91
+ return failure(error.code.replaceAll("_", "-"), error.message, error.details);
92
+ }
93
+
94
+ function countArtifacts(
95
+ result: NsUpdateResult,
96
+ action: NsUpdateResult["artifacts"][number]["action"],
97
+ ): number {
98
+ return result.artifacts.filter((artifact) => artifact.action === action).length;
99
+ }
package/src/ns-toml.ts ADDED
@@ -0,0 +1,195 @@
1
+ import {
2
+ getProjectConfigSetting,
3
+ parseProjectConfigToml,
4
+ projectConfigErrorFromDiagnostics,
5
+ type ProjectConfigDiagnostic,
6
+ type SettingsSchema,
7
+ } from "@nseng-ai/kernel/project-config/points";
8
+ import { z } from "zod";
9
+
10
+ import { ALL_HARNESS_IDS, type HarnessId } from "./harness-paths.ts";
11
+
12
+ export type NsTomlHarnessesParseResult =
13
+ | { type: "ok"; harnesses: readonly HarnessId[] }
14
+ | { type: "missing" }
15
+ | { type: "error"; error: NsTomlErrorInfo };
16
+
17
+ export type NsTomlWritePlanResult =
18
+ | { type: "ok"; content: string; change: NsTomlChange }
19
+ | { type: "error"; error: NsTomlErrorInfo };
20
+
21
+ export type NsTomlChange = "created" | "appended" | "replaced" | "unchanged";
22
+
23
+ export interface NsTomlErrorInfo {
24
+ code: NsTomlErrorCode;
25
+ message: string;
26
+ }
27
+
28
+ export type NsTomlErrorCode =
29
+ | "invalid-toml"
30
+ | "invalid-harnesses"
31
+ | "ambiguous-harnesses-assignment";
32
+
33
+ const nsInitHarnessesSettingsSchema = {
34
+ path: ["harnesses"] as const,
35
+ schema: z.array(z.string()).nonempty(),
36
+ invalidMessage: ({ pathLabel }) =>
37
+ `${pathLabel} top-level harnesses must be a non-empty string array.`,
38
+ } satisfies SettingsSchema<readonly string[]>;
39
+
40
+ const nsInitHarnessesSettingsKey = nsInitHarnessesSettingsSchema.path.join(".");
41
+
42
+ export function parseNsTomlHarnesses(
43
+ content: string,
44
+ pathLabel = "ns.toml",
45
+ ): NsTomlHarnessesParseResult {
46
+ const result = parseProjectConfigToml(content, {
47
+ pathLabel,
48
+ pointsTable: { mode: "skip" },
49
+ settingsSchemas: [nsInitHarnessesSettingsSchema],
50
+ });
51
+ if (!result.ok) return nsTomlErrorFromDiagnostics(result.diagnostics, pathLabel);
52
+ const harnesses = getProjectConfigSetting(result.config, nsInitHarnessesSettingsSchema);
53
+ if (harnesses === undefined) return { type: "missing" };
54
+ return normalizeHarnessSelection(harnesses);
55
+ }
56
+
57
+ export function renderNsTomlHarnesses(harnesses: readonly HarnessId[]): string {
58
+ return `harnesses = ${JSON.stringify([...harnesses])}\n`;
59
+ }
60
+
61
+ export function planNsTomlHarnessesWrite(options: {
62
+ content: string | undefined;
63
+ harnesses: readonly HarnessId[];
64
+ }): NsTomlWritePlanResult {
65
+ const rendered = renderNsTomlHarnesses(options.harnesses);
66
+ if (options.content === undefined) return { type: "ok", content: rendered, change: "created" };
67
+
68
+ const existing = parseNsTomlHarnesses(options.content);
69
+ if (existing.type === "error") return existing;
70
+ if (existing.type === "ok" && sameHarnesses(existing.harnesses, options.harnesses)) {
71
+ return { type: "ok", content: options.content, change: "unchanged" };
72
+ }
73
+
74
+ const replacement = replaceTopLevelHarnessesAssignment(options.content, rendered);
75
+ if (replacement.type === "error") return replacement;
76
+ return {
77
+ type: "ok",
78
+ content: replacement.content,
79
+ change: existing.type === "ok" ? "replaced" : "appended",
80
+ };
81
+ }
82
+
83
+ export function normalizeHarnessSelection(
84
+ values: readonly string[],
85
+ ): { type: "ok"; harnesses: readonly HarnessId[] } | { type: "error"; error: NsTomlErrorInfo } {
86
+ const selected: HarnessId[] = [];
87
+ for (const value of values) {
88
+ if (!isHarnessId(value)) {
89
+ return {
90
+ type: "error",
91
+ error: {
92
+ code: "invalid-harnesses",
93
+ message: `Unknown harness ${JSON.stringify(value)}. Expected one of ${ALL_HARNESS_IDS.join(", ")}.`,
94
+ },
95
+ };
96
+ }
97
+ if (!selected.includes(value)) selected.push(value);
98
+ }
99
+ if (selected.length === 0) {
100
+ return {
101
+ type: "error",
102
+ error: { code: "invalid-harnesses", message: "At least one harness must be selected." },
103
+ };
104
+ }
105
+ return { type: "ok", harnesses: selected };
106
+ }
107
+
108
+ function nsTomlErrorFromDiagnostics(
109
+ diagnostics: readonly ProjectConfigDiagnostic[],
110
+ pathLabel: string,
111
+ ): NsTomlHarnessesParseResult {
112
+ const error = projectConfigErrorFromDiagnostics(diagnostics, {
113
+ invalidToml: "invalid-toml",
114
+ invalidSettingsByPath: { [nsInitHarnessesSettingsKey]: "invalid-harnesses" },
115
+ defaultCode: "invalid-toml",
116
+ defaultMessage: `${pathLabel}: invalid ns.toml`,
117
+ pathLabel,
118
+ });
119
+ return { type: "error", error: { code: error.code, message: error.message } };
120
+ }
121
+
122
+ function replaceTopLevelHarnessesAssignment(
123
+ content: string,
124
+ renderedHarnesses: string,
125
+ ): { type: "ok"; content: string } | { type: "error"; error: NsTomlErrorInfo } {
126
+ const lines = splitLines(content);
127
+ let tableStart = lines.findIndex((line) => tomlTableName(line) !== null);
128
+ if (tableStart < 0) tableStart = lines.length;
129
+
130
+ for (let index = 0; index < tableStart; index += 1) {
131
+ const line = lines[index] ?? "";
132
+ if (!/^\s*harnesses\s*=/u.test(line)) continue;
133
+ if (!line.includes("]")) {
134
+ return {
135
+ type: "error",
136
+ error: {
137
+ code: "ambiguous-harnesses-assignment",
138
+ message:
139
+ "Top-level ns.toml harnesses assignment must be a single-line array before it can be replaced.",
140
+ },
141
+ };
142
+ }
143
+ lines.splice(index, 1, renderedHarnesses);
144
+ return { type: "ok", content: lines.join("") };
145
+ }
146
+
147
+ if (tableStart < lines.length) {
148
+ const beforeTables = lines.slice(0, tableStart).join("");
149
+ const afterTables = lines.slice(tableStart).join("");
150
+ const separator =
151
+ beforeTables.length === 0 || beforeTables.endsWith("\n\n")
152
+ ? ""
153
+ : beforeTables.endsWith("\n")
154
+ ? "\n"
155
+ : "\n\n";
156
+ return {
157
+ type: "ok",
158
+ content: `${beforeTables}${separator}${renderedHarnesses}\n${afterTables}`,
159
+ };
160
+ }
161
+
162
+ return { type: "ok", content: appendTopLevelAssignment(content, renderedHarnesses) };
163
+ }
164
+
165
+ function appendTopLevelAssignment(content: string, assignment: string): string {
166
+ if (content.length === 0) return assignment;
167
+ if (content.endsWith("\n\n")) return `${content}${assignment}`;
168
+ if (content.endsWith("\n")) return `${content}\n${assignment}`;
169
+ return `${content}\n\n${assignment}`;
170
+ }
171
+
172
+ function splitLines(content: string): string[] {
173
+ return content.match(/.*(?:\n|$)/gu)?.filter((line) => line.length > 0) ?? [];
174
+ }
175
+
176
+ function tomlTableName(line: string): string | null {
177
+ const stripped = line.trim();
178
+ if (stripped.startsWith("[[")) {
179
+ const closingIndex = stripped.indexOf("]]", 2);
180
+ if (closingIndex < 0) return null;
181
+ return stripped.slice(2, closingIndex).trim();
182
+ }
183
+ if (!stripped.startsWith("[")) return null;
184
+ const closingIndex = stripped.indexOf("]");
185
+ if (closingIndex < 0) return null;
186
+ return stripped.slice(1, closingIndex).trim();
187
+ }
188
+
189
+ function sameHarnesses(left: readonly HarnessId[], right: readonly HarnessId[]): boolean {
190
+ return left.length === right.length && left.every((value, index) => value === right[index]);
191
+ }
192
+
193
+ function isHarnessId(value: string): value is HarnessId {
194
+ return ALL_HARNESS_IDS.includes(value as HarnessId);
195
+ }