@open-domain-specification/skill 0.1.11 → 0.3.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.
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, join } from "node:path";
4
- import { Workspace } from "@open-domain-specification/core";
4
+ import { PATTERNS, Workspace } from "@open-domain-specification/core";
5
5
  import { describe, expect, it } from "vitest";
6
6
  import {
7
7
  generateReferences,
@@ -11,6 +11,41 @@ import {
11
11
 
12
12
  const file = (path: string) => readFileSync(join(skillRoot, path), "utf8");
13
13
 
14
+ const require_ = createRequire(import.meta.url);
15
+ const corePkgRoot = dirname(
16
+ require_.resolve("@open-domain-specification/core/package.json"),
17
+ );
18
+
19
+ /** The values an enum in core's generated JSON Schema allows. */
20
+ function schemaEnum(definition: string): string[] {
21
+ const schema = require_(join(corePkgRoot, "dist/workspace.schema.json")) as {
22
+ definitions: Record<string, { enum?: string[] }>;
23
+ };
24
+ const values = schema.definitions[definition]?.enum;
25
+ if (!values) throw new Error(`no enum ${definition} in the workspace schema`);
26
+ return values;
27
+ }
28
+
29
+ /** The fenced blocks of one language in a markdown file, in order. */
30
+ function fencedBlocks(markdown: string, language: string): string[] {
31
+ return [
32
+ ...markdown.matchAll(
33
+ new RegExp(`\`\`\`${language}\\n([\\s\\S]*?)\`\`\``, "g"),
34
+ ),
35
+ ].map((m) => m[1]);
36
+ }
37
+
38
+ /**
39
+ * The ```json blocks of a markdown file, parsed. A block that shows fields in
40
+ * place rather than a whole document is a fragment (`"provides": { ... }`), so
41
+ * wrap it to parse as the object it would sit in.
42
+ */
43
+ function jsonBlocks(markdown: string): Array<Record<string, unknown>> {
44
+ return fencedBlocks(markdown, "json").map((block) =>
45
+ JSON.parse(block.trimStart().startsWith("{") ? block : `{${block}}`),
46
+ );
47
+ }
48
+
14
49
  describe("SKILL.md", () => {
15
50
  const skill = file("SKILL.md");
16
51
  const frontmatter = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(skill);
@@ -42,13 +77,10 @@ describe("generated references", () => {
42
77
  });
43
78
 
44
79
  describe("dsl-api.md", () => {
80
+ const doc = file("references/dsl-api.md");
81
+
45
82
  it("names only methods that exist on the core classes", () => {
46
- const require = createRequire(import.meta.url);
47
- const corePkg = dirname(
48
- require.resolve("@open-domain-specification/core/package.json"),
49
- );
50
- const source = readFileSync(join(corePkg, "src/workspace.ts"), "utf8");
51
- const doc = file("references/dsl-api.md");
83
+ const source = readFileSync(join(corePkgRoot, "src/workspace.ts"), "utf8");
52
84
  const methods = [...doc.matchAll(/\| `\.?(?:new )?(\w+)\(/g)].map(
53
85
  (m) => m[1],
54
86
  );
@@ -58,6 +90,142 @@ describe("dsl-api.md", () => {
58
90
  expect(source, method).toMatch(new RegExp(`\\b${method}\\(`));
59
91
  }
60
92
  });
93
+
94
+ it("offers the evidence pair on a relationship, a consumable and a consumption", () => {
95
+ const rows = doc.split("\n").filter((line) => line.startsWith("| `"));
96
+ const evidenced = (method: string) =>
97
+ rows.find(
98
+ (row) =>
99
+ row.includes(`\`${method}(`) &&
100
+ row.includes("comments?") &&
101
+ row.includes("disposition?"),
102
+ );
103
+ for (const method of ["upstreamOf", "partnerOf", "provides", "consumes"])
104
+ expect(evidenced(method), method).toBeDefined();
105
+ });
106
+ });
107
+
108
+ describe("strategic-relationships.md", () => {
109
+ const reference = file("references/strategic-relationships.md");
110
+
111
+ it("explains every pattern core knows, in core's own words", () => {
112
+ for (const [key, pattern] of Object.entries(PATTERNS)) {
113
+ expect(reference, key).toContain(
114
+ `### \`${key}\` — ${pattern.name} (${pattern.abbreviation})`,
115
+ );
116
+ expect(reference, key).toContain(pattern.summary);
117
+ expect(reference, key).toContain(pattern.architecturalNature);
118
+ for (const tradeOff of pattern.tradeOffs)
119
+ expect(reference, key).toContain(`- ${tradeOff}`);
120
+ }
121
+ });
122
+
123
+ it("names nothing core does not", () => {
124
+ const documented = [...reference.matchAll(/^### `([\w-]+)`/gm)].map(
125
+ (m) => m[1],
126
+ );
127
+ expect(documented.sort()).toEqual(Object.keys(PATTERNS).sort());
128
+ });
129
+ });
130
+
131
+ describe("reconciliation.md", () => {
132
+ const reference = file("references/reconciliation.md");
133
+
134
+ it("gives a search recipe for every pattern core knows", () => {
135
+ for (const key of Object.keys(PATTERNS))
136
+ expect(reference, key).toMatch(new RegExp(`^\\| \`${key}\` \\|`, "m"));
137
+ });
138
+
139
+ it("names every disposition and link kind the schema allows", () => {
140
+ for (const value of [
141
+ ...schemaEnum("Disposition"),
142
+ ...schemaEnum("CommentLinkKind"),
143
+ ])
144
+ expect(reference, value).toContain(`\`${value}\``);
145
+ });
146
+
147
+ it("shows the evidence pair on a relationship, a consumable and a consumption", () => {
148
+ const carriers = jsonBlocks(reference).flatMap((block) => [
149
+ ...(block.relationships ?? []),
150
+ ...Object.values(block.provides ?? {}),
151
+ ...(block.consumes ?? []),
152
+ ]) as Array<{ comments?: unknown[]; disposition?: string }>;
153
+ expect(carriers).toHaveLength(3);
154
+ for (const carrier of carriers)
155
+ expect(carrier.comments?.length ?? 0).toBeGreaterThan(0);
156
+ expect(
157
+ carriers.filter((c) => c.disposition !== undefined),
158
+ ).not.toHaveLength(0);
159
+ });
160
+
161
+ it("writes comments the schema accepts, and never the default disposition", () => {
162
+ const kinds = schemaEnum("CommentLinkKind");
163
+ const dispositions = schemaEnum("Disposition");
164
+ let seen = 0;
165
+ const walk = (node: unknown): void => {
166
+ if (Array.isArray(node)) return void node.forEach(walk);
167
+ if (!node || typeof node !== "object") return;
168
+ const record = node as Record<string, unknown>;
169
+ if (record.disposition !== undefined) {
170
+ expect(dispositions).toContain(record.disposition);
171
+ expect(record.disposition).not.toBe("by-design");
172
+ }
173
+ for (const comment of (record.comments ?? []) as Array<
174
+ Record<string, unknown>
175
+ >) {
176
+ seen++;
177
+ expect(Object.keys(comment).sort()).toEqual(["link", "text"]);
178
+ expect(typeof comment.text).toBe("string");
179
+ const link = comment.link as Record<string, unknown>;
180
+ expect(kinds).toContain(link.kind);
181
+ expect(typeof link.url).toBe("string");
182
+ }
183
+ Object.values(record).forEach(walk);
184
+ };
185
+ jsonBlocks(reference).forEach(walk);
186
+ expect(seen).toBeGreaterThan(0);
187
+ });
188
+ });
189
+
190
+ describe("interview-playbook.md", () => {
191
+ const playbook = file("references/interview-playbook.md");
192
+
193
+ it("asks the two evidence questions once per intent, never per role", () => {
194
+ expect(playbook).toContain("## The two evidence questions");
195
+ expect(playbook).toContain("Once per intent, never per role");
196
+ for (const value of schemaEnum("Disposition"))
197
+ expect(playbook, value).toContain(`\`${value}\``);
198
+ });
199
+ });
200
+
201
+ describe("petstore.md", () => {
202
+ const example = file("examples/petstore.md");
203
+
204
+ it("works the Catalog–Inventory shared kernel through to a refactor", () => {
205
+ const section = example.slice(
206
+ example.indexOf("## Worked reconciliation"),
207
+ example.indexOf("## A policy reacting"),
208
+ );
209
+ expect(section).not.toBe("");
210
+ expect(section).toContain("sharesKernelWith");
211
+ expect(section).toContain('disposition: "refactor"');
212
+ expect(fencedBlocks(section, "ts")).toHaveLength(1);
213
+ });
214
+ });
215
+
216
+ describe("the bundle", () => {
217
+ it("ships no reference nothing else points at", () => {
218
+ const bundle = readBundle();
219
+ for (const entry of bundle) {
220
+ if (!entry.path.startsWith("references/")) continue;
221
+ const basename = entry.path.split("/").pop() as string;
222
+ const pointsAtIt = bundle.some(
223
+ (other) =>
224
+ other.path !== entry.path && other.content.includes(basename),
225
+ );
226
+ expect(pointsAtIt, entry.path).toBe(true);
227
+ }
228
+ });
61
229
  });
62
230
 
63
231
  describe("examples", () => {