@open-domain-specification/skill 0.1.11 → 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.
@@ -1,4 +1,4 @@
1
- export declare const SKILL_VERSION = "0.1.11";
1
+ export declare const SKILL_VERSION = "0.2.0";
2
2
  export declare const BUNDLE: ReadonlyArray<{
3
3
  path: string;
4
4
  content: string;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SKILL_VERSION = "0.1.11";
1
+ declare const SKILL_VERSION = "0.2.0";
2
2
 
3
3
  /** Where each agent looks for skills, relative to a project or home folder. */
4
4
  type SkillTarget = "claude" | "agents" | "codex";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SKILL_VERSION = "0.1.11";
1
+ declare const SKILL_VERSION = "0.2.0";
2
2
 
3
3
  /** Where each agent looks for skills, relative to a project or home folder. */
4
4
  type SkillTarget = "claude" | "agents" | "codex";
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ __export(index_exports, {
70
70
  module.exports = __toCommonJS(index_exports);
71
71
 
72
72
  // src/bundle.generated.ts
73
- var SKILL_VERSION = "0.1.11";
73
+ var SKILL_VERSION = "0.2.0";
74
74
  var BUNDLE = [
75
75
  { path: "examples/minimal.ods.json", content: `{
76
76
  "$schema": "./schema.json",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/bundle.generated.ts","../src/targets.ts","../src/install.ts"],"sourcesContent":["export * from \"./install\";\nexport * from \"./targets\";\n","// Generated by scripts/generate.mts; do not edit.\nexport const SKILL_VERSION = \"0.1.11\";\nexport const BUNDLE: ReadonlyArray<{ path: string; content: string }> = [\n\t{ path: \"examples/minimal.ods.json\", content: \"{\\n\\t\\\"$schema\\\": \\\"./schema.json\\\",\\n\\t\\\"id\\\": \\\"bookshop\\\",\\n\\t\\\"name\\\": \\\"Bookshop\\\",\\n\\t\\\"description\\\": \\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n\\t\\\"version\\\": \\\"0.1.0\\\",\\n\\t\\\"odsVersion\\\": \\\"1.0.0\\\",\\n\\t\\\"domains\\\": {\\n\\t\\t\\\"bookselling\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Bookselling\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Everything involved in selling books online\\\",\\n\\t\\t\\t\\\"subdomains\\\": {\\n\\t\\t\\t\\t\\\"sales\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Sales\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"core\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"Taking and fulfilling orders\\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"boundedcontexts\\\": {\\n\\t\\t\\\"orders\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Orders\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Owns orders from placement to dispatch\\\",\\n\\t\\t\\t\\\"subdomains\\\": [{ \\\"$ref\\\": \\\"#/domains/bookselling/subdomains/sales\\\" }],\\n\\t\\t\\t\\\"team\\\": { \\\"$ref\\\": \\\"#/teams/shop_team\\\" },\\n\\t\\t\\t\\\"aggregates\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"One customer's request to buy some books\\\",\\n\\t\\t\\t\\t\\t\\\"entities\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"The order itself\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"root\\\": true,\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"target\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/valueobjects/address\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"relation\\\": \\\"uses\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"label\\\": \\\"ships-to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"cardinality\\\": \\\"1\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"valueobjects\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"address\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Address\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"Where the order ships to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"lines\\\": { \\\"name\\\": \\\"lines\\\", \\\"type\\\": \\\"text\\\" },\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"postcode\\\": { \\\"name\\\": \\\"postcode\\\", \\\"type\\\": \\\"postcode\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": []\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"invariants\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"total_not_negative\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Total not negative\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"An order's total can never be below zero\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"constrains\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/entities/order/attributes/total\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"A customer placed an order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"event\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"published-language\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"schema\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/schemas/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"services\\\": {\\n\\t\\t\\t\\t\\\"order_api\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order API\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"The endpoints the web shop calls\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"application\\\",\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"place_order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"PlaceOrder\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"POST /orders\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"operation\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"open-host-service\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"raises\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/provides/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"policies\\\": {},\\n\\t\\t\\t\\\"glossary\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"definition\\\": \\\"A customer's request to buy some books, paid up front\\\",\\n\\t\\t\\t\\t\\t\\\"aliases\\\": [\\\"Purchase\\\"],\\n\\t\\t\\t\\t\\t\\\"embodiedBy\\\": { \\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order\\\" }\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"schemas\\\": {\\n\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"What other parts learn when an order is placed\\\",\\n\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"relationships\\\": [],\\n\\t\\\"teams\\\": {\\n\\t\\t\\\"shop_team\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Shop Team\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Runs the online shop\\\"\\n\\t\\t}\\n\\t}\\n}\\n\" },\n\t{ path: \"examples/minimal.workspace.ts\", content: \"// The same bookshop as minimal.ods.json, authored with the DSL.\\n// Run with `node minimal.workspace.ts` (Node 24) or `npx tsx minimal.workspace.ts`.\\nimport fs from \\\"node:fs\\\";\\nimport { Workspace } from \\\"@open-domain-specification/core\\\";\\n\\nexport const workspace = new Workspace(\\\"Bookshop\\\", {\\n\\todsVersion: \\\"1.0.0\\\",\\n\\tversion: \\\"0.1.0\\\",\\n\\tdescription:\\n\\t\\t\\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n});\\n\\nconst bookselling = workspace.addDomain(\\\"Bookselling\\\", {\\n\\tdescription: \\\"Everything involved in selling books online\\\",\\n});\\nconst sales = bookselling.addSubdomain(\\\"Sales\\\", {\\n\\ttype: \\\"core\\\",\\n\\tdescription: \\\"Taking and fulfilling orders\\\",\\n});\\nconst shopTeam = workspace.addTeam(\\\"Shop Team\\\", {\\n\\tdescription: \\\"Runs the online shop\\\",\\n});\\n\\nconst orders = sales.addBoundedcontext(\\\"Orders\\\", {\\n\\tdescription: \\\"Owns orders from placement to dispatch\\\",\\n\\tteam: shopTeam,\\n});\\n\\nconst orderAgg = orders.addAggregate(\\\"Order\\\", {\\n\\tdescription: \\\"One customer's request to buy some books\\\",\\n});\\nconst order = orderAgg.addRootEntity(\\\"Order\\\", {\\n\\tdescription: \\\"The order itself\\\",\\n});\\norder.addAttribute(\\\"orderNumber\\\", { type: \\\"order number\\\", identity: true });\\nconst total = order.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst address = orderAgg.addValueObject(\\\"Address\\\", {\\n\\tdescription: \\\"Where the order ships to\\\",\\n});\\naddress.addAttribute(\\\"lines\\\", { type: \\\"text\\\" });\\naddress.addAttribute(\\\"postcode\\\", { type: \\\"postcode\\\" });\\norder.uses(address, \\\"ships-to\\\", \\\"1\\\");\\n\\norderAgg\\n\\t.addInvariant(\\\"Total not negative\\\", {\\n\\t\\tdescription: \\\"An order's total can never be below zero\\\",\\n\\t})\\n\\t.constrains(total);\\n\\nconst orderPlacedSchema = orders.addSchema(\\\"OrderPlaced\\\", {\\n\\tdescription: \\\"What other parts learn when an order is placed\\\",\\n});\\norderPlacedSchema.addAttribute(\\\"orderNumber\\\", {\\n\\ttype: \\\"order number\\\",\\n\\tidentity: true,\\n});\\norderPlacedSchema.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst orderPlaced = orderAgg.provides(\\\"OrderPlaced\\\", {\\n\\ttype: \\\"event\\\",\\n\\tdescription: \\\"A customer placed an order\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: orderPlacedSchema,\\n});\\n\\nconst orderApi = orders.addService(\\\"Order API\\\", {\\n\\ttype: \\\"application\\\",\\n\\tdescription: \\\"The endpoints the web shop calls\\\",\\n});\\norderApi\\n\\t.provides(\\\"PlaceOrder\\\", {\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tdescription: \\\"POST /orders\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t})\\n\\t.raises(orderPlaced);\\n\\norders.addTerm(\\\"Order\\\", {\\n\\tdefinition: \\\"A customer's request to buy some books, paid up front\\\",\\n\\taliases: [\\\"Purchase\\\"],\\n\\tembodiedBy: orderAgg,\\n});\\n\\n// Generator: validate, then write the workspace beside its schema.\\nif (process.argv[1]?.endsWith(\\\"minimal.workspace.ts\\\")) {\\n\\tfor (const d of workspace.validate()) {\\n\\t\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t}\\n\\tfs.mkdirSync(\\\".ods\\\", { recursive: true });\\n\\tfs.writeFileSync(\\n\\t\\t\\\".ods/bookshop.json\\\",\\n\\t\\tJSON.stringify(\\n\\t\\t\\t{ $schema: \\\"./schema.json\\\", ...workspace.toSchema() },\\n\\t\\t\\tnull,\\n\\t\\t\\t2,\\n\\t\\t),\\n\\t);\\n}\\n\" },\n\t{ path: \"examples/petstore.md\", content: \"# Patterns from the Petstore example\\n\\nExcerpts from the ODS example workspace (`models/petstore/src/workspace.ts`\\nin the ODS repository). Each shows one pattern worth copying.\\n\\n## A context serving two subdomains, and a legacy one\\n\\n```ts\\nconst inventoryBC = workspace.addBoundedContext(\\\"Inventory BC\\\", {\\n\\tdescription: \\\"Projection for /store/inventory (status→count)\\\",\\n\\tsubdomains: [inventorySD, catalogSD],\\n\\tteam: petShopTeam,\\n});\\nconst identityBC = usersSD.addBoundedcontext(\\\"Identity BC\\\", {\\n\\tdescription: \\\"Owns User aggregate & user endpoints. Legacy: user status is an untyped int\\\",\\n\\tbigBallOfMud: true,\\n\\tteam: platformTeam,\\n});\\n```\\n\\n## Attributes backed by value objects, relations with cardinality, invariants on attributes\\n\\n```ts\\npetRoot.addAttribute(\\\"id\\\", { type: \\\"int64\\\", identity: true });\\npetRoot.addAttribute(\\\"status\\\", { type: \\\"PetStatus\\\", valueobject: petStatusVO });\\npetRoot.uses(categoryVO, \\\"categorized-as\\\", \\\"0..1\\\");\\npetRoot.uses(photoUrlVO, \\\"has-photo\\\", \\\"1..*\\\");\\npetAgg\\n\\t.addInvariant(\\\"NameRequired\\\", { description: \\\"Pet.name must be non-empty\\\" })\\n\\t.constrains(petRoot.attributes.get(\\\"name\\\")!);\\n```\\n\\n## A cross-aggregate reference by identity to the other root\\n\\n```ts\\norderRoot.references(petRoot, \\\"for-pet\\\", \\\"1\\\");\\n```\\n\\n## Published events with a payload schema, and an internal operation that raises one\\n\\n```ts\\nconst petStatusChangedSchema = catalogBC.addSchema(\\\"PetStatusChanged\\\");\\npetStatusChangedSchema.addAttribute(\\\"petId\\\", { type: \\\"int64\\\", identity: true });\\n\\nconst petStatusChanged = petAgg.provides(\\\"PetStatusChanged\\\", {\\n\\tdescription: \\\"Pet status changed (available|pending|sold)\\\",\\n\\ttype: \\\"event\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: petStatusChangedSchema,\\n});\\nconst _changePetStatus = petAgg\\n\\t.provides(\\\"ChangePetStatus\\\", {\\n\\t\\tdescription: \\\"Move a pet between available, pending and sold\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tinternal: true,\\n\\t\\tschema: petStatusChangedSchema,\\n\\t})\\n\\t.raises(petStatusChanged);\\n```\\n\\n## An open-host application service whose operations raise the aggregate's events\\n\\n```ts\\nconst petApp = catalogBC.addService(\\\"PetApp\\\", {\\n\\tdescription: \\\"Open-host service for /pet endpoints\\\",\\n\\ttype: \\\"application\\\",\\n});\\nconst _addPetOp = petApp\\n\\t.provides(\\\"AddPet\\\", {\\n\\t\\tdescription: \\\"POST /pet\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t\\tschema: registerPetSchema,\\n\\t})\\n\\t.raises(petRegistered);\\n```\\n\\n## A consumption through an anti-corruption layer, and the relationship that explains it\\n\\n```ts\\norderApp.consumes(getPetSummaryOp, { pattern: \\\"anti-corruption-layer\\\" });\\n\\nsalesBC.downstreamOf(catalogBC, {\\n\\ttype: \\\"customer-supplier\\\",\\n\\tupstreamRoles: [\\\"open-host-service\\\"],\\n\\tdownstreamRoles: [\\\"anti-corruption-layer\\\"],\\n\\tdescription: \\\"Sales needs pet availability; Catalog commits to the summary contract\\\",\\n});\\n```\\n\\n## Separate ways, on purpose\\n\\n```ts\\nidentityBC.separateWaysFrom(\\n\\tsalesBC,\\n\\t\\\"Orders are anonymous in Petstore v3; no integration by design\\\",\\n);\\n```\\n\\n## A policy reacting to events from two contexts\\n\\n```ts\\nsalesBC\\n\\t.addPolicy(\\\"Approve when pet available\\\", {\\n\\t\\tdescription: \\\"When a pet becomes available and an order for it is placed, approve the order\\\",\\n\\t})\\n\\t.on(petStatusChanged, orderPlaced)\\n\\t.then(approveOrder);\\n```\\n\\n## Conformist consumptions feeding a projection\\n\\n```ts\\ninventoryAgg.consumes(petStatusChanged, { pattern: \\\"conformist\\\" });\\ninventoryAgg.consumes(orderApproved, { pattern: \\\"conformist\\\" });\\n```\\n\\n## Glossary terms embodied by model elements\\n\\n```ts\\ncatalogBC.addTerm(\\\"Category\\\", {\\n\\tdefinition: \\\"The kind of animal a pet is, such as Dogs or Cats\\\",\\n\\taliases: [\\\"Species\\\"],\\n\\tembodiedBy: categoryVO,\\n});\\n```\\n\" },\n\t{ path: \"examples/validate.mjs\", content: \"#!/usr/bin/env node\\n// Validates one or more ODS workspace JSON files.\\n// Usage: node validate.mjs .ods/petstore.json [.ods/other.json ...]\\n// Exits 1 when a file fails to load or has an error-level diagnostic.\\nimport { readFileSync } from \\\"node:fs\\\";\\nimport { createRequire } from \\\"node:module\\\";\\n\\nconst require = createRequire(`${process.cwd()}/`);\\nlet Workspace;\\ntry {\\n\\t({ Workspace } = require(\\\"@open-domain-specification/core\\\"));\\n} catch {\\n\\tconsole.error(\\n\\t\\t\\\"@open-domain-specification/core is not installed here. Run from the project root, install it (npm i -D @open-domain-specification/core), or use: npx -p @open-domain-specification/core node validate.mjs <file>\\\",\\n\\t);\\n\\tprocess.exit(2);\\n}\\n\\nlet failed = false;\\nfor (const file of process.argv.slice(2)) {\\n\\tlet workspace;\\n\\ttry {\\n\\t\\tworkspace = Workspace.fromSchema(JSON.parse(readFileSync(file, \\\"utf8\\\")));\\n\\t} catch (error) {\\n\\t\\tconsole.log(\\n\\t\\t\\t`[load-error] ${file}: ${error instanceof Error ? error.message : error}`,\\n\\t\\t);\\n\\t\\tfailed = true;\\n\\t\\tcontinue;\\n\\t}\\n\\tconst diagnostics = workspace.validate();\\n\\tconsole.log(`${file}: ${diagnostics.length} diagnostic(s)`);\\n\\tfor (const d of diagnostics) {\\n\\t\\tconsole.log(` [${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t\\tif (d.severity === \\\"error\\\") failed = true;\\n\\t}\\n}\\nprocess.exit(failed ? 1 : 0);\\n\" },\n\t{ path: \"references/ddd-glossary.md\", content: \"# DDD terms in one sentence each\\n\\nUse these the first time a term comes up, filling the example with the user's own words.\\nNever repeat an explanation, and never explain a term the user already used correctly.\\n\\n- **Domain** — the whole area of business the system exists for, e.g. \\\"running the pet store\\\".\\n- **Subdomain** — one slice of that problem, e.g. \\\"the catalogue\\\" or \\\"taking orders\\\"; calling it\\n *core* only marks where your competitive effort goes, *supporting* means needed but ordinary,\\n *generic* means you would buy it.\\n- **Bounded context** — a boundary inside which every word has one exact meaning; your billing\\n \\\"Customer\\\" and your support \\\"Customer\\\" being different things is why they get separate\\n contexts.\\n- **Ubiquitous language / glossary** — the words a context uses, written down once so code,\\n conversations and documents all mean the same thing by \\\"Order\\\".\\n- **Team ownership** — the people who decide what a context means and how it changes.\\n- **Big ball of mud** — a context whose model nobody fully controls, flagged so that anything\\n talking to it translates rather than trusts.\\n- **Entity** — something that matters because of *which one* it is, like this particular order,\\n so it carries an identity.\\n- **Value object** — something that matters only by its values, like an address; two with the\\n same values are interchangeable.\\n- **Attribute** — one piece of information an entity, value object or message carries; the\\n identity attribute is the one that tells two entities apart.\\n- **Aggregate** — the cluster of things you change together and check rules across, named after\\n its *root*, the one thing you go through to change any of it; the order and its lines.\\n- **Invariant** — a rule that must always hold inside an aggregate, such as \\\"quantity is never\\n zero\\\".\\n- **Relation** — how one thing points at another: *includes* for parts that cannot exist alone,\\n *uses* for values it carries, *references* for another aggregate's root by identity.\\n- **Cardinality** — how many of the other thing: exactly one, at most one, any number, at least\\n one.\\n- **Operation** — something you can ask a part of the system to do, like \\\"place an order\\\"; in\\n conversation people often say *command*.\\n- **Event** — a fact that already happened, stated in the past tense, like \\\"order placed\\\", that\\n other parts can react to.\\n- **Consumable** — an operation or event that a part offers, and **consumption** is another part\\n using it.\\n- **Schema** — the shape of the information that travels with an operation or event.\\n- **Policy** — a rule of the form \\\"when this event happens, do that operation\\\", possibly across\\n contexts.\\n- **Application service** — the part that fronts an API or a screen and turns requests into\\n operations on aggregates.\\n- **Domain service** — business logic that does not belong to any single thing, like pricing\\n across several orders.\\n- **Upstream / downstream** — the side that is depended on, and the side that depends on it.\\n- **Customer-supplier** — a dependency where the downstream side gets a say before the upstream\\n side changes things.\\n- **Partnership** — two contexts whose teams plan and release together.\\n- **Shared kernel** — code or data two contexts both own and change.\\n- **Separate ways** — a deliberate decision that two contexts will not integrate.\\n- **Open host service** — the upstream side offers a documented API for anyone to use.\\n- **Published language** — the upstream side offers a documented message format everyone\\n agrees on.\\n- **Conformist** — the downstream side takes the upstream model as it comes.\\n- **Anti-corruption layer** — the downstream side copies and reshapes what it receives into its\\n own terms, so the upstream model cannot leak in.\\n\" },\n\t{ path: \"references/dsl-api.md\", content: \"# DSL reference (`@open-domain-specification/core`)\\n\\nEvery class is created through its parent and registers itself there, so `parent.addX(...)`\\nis the only call needed. Every attributes object accepts an optional `id` to fix the id\\nindependently of the name.\\n\\n| Receiver | Method | Creates / does |\\n|---|---|---|\\n| — | `new Workspace(name, { odsVersion, description, version, homepage?, logoUrl?, primaryColor?, id? })` | the workspace |\\n| `Workspace` | `addDomain(name, { description })` | a domain |\\n| `Workspace` | `addTeam(name, { description?, homepage? })` | a team |\\n| `Workspace` | `addBoundedContext(name, { description, subdomains?, bigBallOfMud?, team? })` | a context serving zero or more subdomains |\\n| `Workspace` | `addRelationship({...})` | a relationship; prefer the context helpers below |\\n| `Workspace` | `validate()` | the diagnostics list |\\n| `Workspace` | `toSchema()` / `Workspace.fromSchema(json)` | serialise / load |\\n| `Domain` | `addSubdomain(name, { type, description })` | a subdomain; `type` is `\\\"core\\\" \\\\| \\\"supporting\\\" \\\\| \\\"generic\\\"` |\\n| `Subdomain` | `addBoundedcontext(name, { description, bigBallOfMud?, team? })` | a context serving this subdomain |\\n| `BoundedContext` | `serves(subdomain)` | adds a served subdomain |\\n| `BoundedContext` | `ownedBy(team)` | sets the owning team |\\n| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description? })` | directed relationship, this side upstream; `type` defaults to `\\\"upstream-downstream\\\"`, or `\\\"customer-supplier\\\"` |\\n| `BoundedContext` | `downstreamOf(other, options)` | the same, this side downstream |\\n| `BoundedContext` | `partnerOf(other, description?)` | partnership |\\n| `BoundedContext` | `sharesKernelWith(other, description?)` | shared kernel |\\n| `BoundedContext` | `separateWaysFrom(other, description?)` | separate ways |\\n| `BoundedContext` | `addAggregate(name, { description })` | an aggregate |\\n| `BoundedContext` | `addService(name, { type, description })` | a service; `type` is `\\\"application\\\" \\\\| \\\"domain\\\"` |\\n| `BoundedContext` | `addPolicy(name, { description })` | a policy; chain `.on(...events).then(...operations)` |\\n| `BoundedContext` | `addTerm(name, { definition, aliases?, embodiedBy? })` | a glossary term; or chain `.embody(element)` |\\n| `BoundedContext` | `addSchema(name, { description? })` | a payload schema; add fields with `addAttribute` |\\n| `Aggregate` | `addRootEntity(name, { description })` | the root entity |\\n| `Aggregate` | `addEntity(name, { description, root? })` | an entity |\\n| `Aggregate` | `addValueObject(name, { description })` | a value object |\\n| `Aggregate` | `addInvariant(name, { description })` | an invariant; chain `.constrains(...entities, valueObjects or attributes)` |\\n| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema? })` | a consumable; `type` is `\\\"event\\\" \\\\| \\\"operation\\\"`, `pattern` is `\\\"open-host-service\\\" \\\\| \\\"published-language\\\"` |\\n| `Aggregate`, `Service` | `consumes(consumable, { pattern? })` | a consumption; `pattern` is `\\\"conformist\\\" \\\\| \\\"anti-corruption-layer\\\"` |\\n| `Consumable` | `raises(...events)` | the events an operation raises |\\n| `Entity`, `ValueObject`, `DataSchema` | `addAttribute(name, { type, description?, identity?, valueobject? })` | an attribute; `type` is free text |\\n| `Entity`, `ValueObject` | `uses(target, label, cardinality?)` | a `uses` relation |\\n| `Entity`, `ValueObject` | `includes(target, label, cardinality?)` | an `includes` relation |\\n| `Entity`, `ValueObject` | `references(target, label, cardinality?)` | a `references` relation; across aggregates target the root |\\n| `Entity`, `ValueObject` | `addRelation(target, { relation, label?, cardinality? })` | any relation explicitly |\\n| `Entity` | `.attributes.get(\\\"name\\\")` | look an attribute up, e.g. to constrain it |\\n\\n`cardinality` is `\\\"1\\\" | \\\"0..1\\\" | \\\"*\\\" | \\\"1..*\\\"`. Chainable methods (`raises`, `on`, `then`,\\n`constrains`, `embody`, `serves`, `ownedBy`) return their receiver.\\n\" },\n\t{ path: \"references/dsl-mode.md\", content: \"# DSL mode\\n\\nThe TypeScript source is the artefact; the JSON under `.ods/` is generated from it. Edit the\\nsource, run the generator, read the diagnostics it prints.\\n\\n## Find the generator\\n\\nLook for a file that imports `Workspace` from `@open-domain-specification/core`, builds the\\nmodel, and writes `workspace.toSchema()` to disk. The canonical shape (from the ODS example\\npackage) is:\\n\\n```ts\\nimport fs from \\\"node:fs\\\";\\nimport { workspace } from \\\"./petstore/workspace.ts\\\";\\n\\nfor (const d of workspace.validate()) {\\n\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n}\\n\\nfs.mkdirSync(\\\".ods\\\", { recursive: true });\\nfs.writeFileSync(\\n\\t\\\".ods/petstore.json\\\",\\n\\tJSON.stringify({ $schema: \\\"./schema.json\\\", ...workspace.toSchema() }, null, 2),\\n);\\n```\\n\\n`package.json` usually has a script for it (`build`, `ods`, `generate`, `model`). Node 24 runs\\n`.ts` files directly; on older Node use `npx tsx <file>`.\\n\\n## Loop\\n\\n1. Edit the model source. Keep the file's existing sections and ordering (domains, teams,\\n contexts, then one section per context).\\n2. Run the generator. It validates and rewrites the JSON.\\n3. Read every `[error]` and `[warning]` line and explain it to the user with\\n `validation-rules.md`.\\n4. Never hand-edit the emitted JSON; the next run overwrites it. If the user edits it, tell\\n them and offer to port the change into the source.\\n\\nIf the generator does not print diagnostics, add the four-line loop above before the write.\\n`toSchema()` does not emit `$schema`; spread it back in exactly as shown, so editors and the\\nVS Code extension keep the file associated with `schema.json`.\\n\\n## Ids and renames\\n\\nIds are derived from names with `snake_case` unless `id` is passed. Because ids are the JSON\\nkeys and the ref segments, renaming an element by changing its name silently changes its id\\nand breaks anything outside the source that points at it (documentation links, bookmarks,\\nother files). When renaming, pass the old id explicitly:\\n\\n```ts\\n// was: catalogBC.addAggregate(\\\"Pet\\\", {...})\\ncatalogBC.addAggregate(\\\"Listed Pet\\\", { id: \\\"pet\\\", description: \\\"...\\\" });\\n```\\n\\n## Conventions from the example\\n\\n- Name the variables after the element and its kind (`petAgg`, `petRoot`, `categoryVO`,\\n `petApp`), so refs read naturally in the code.\\n- Prefix a variable with `_` when the element is kept only for its side effect on the model\\n (an operation nobody references again).\\n- Create all consumables before the policies and consumptions that point at them.\\n- Full DSL surface: `dsl-api.md`. Patterns worth copying: `examples/petstore.md`.\\n\" },\n\t{ path: \"references/interview-playbook.md\", content: \"# Interview playbook\\n\\nYou are the facilitator. The user knows their system and their business; you know DDD. Your\\njob is to get the model out of their head without making them learn the vocabulary first.\\n\\n## Ground rules\\n\\n- One question per turn. Wait for the answer.\\n- No DDD word before its one-sentence explanation (see `ddd-glossary.md`), and each term is\\n explained once.\\n- After every answer, paraphrase it as the element you would record: \\\"So I'd note ... right?\\\"\\n- Write the increment as soon as a context or an aggregate is stable. Do not wait until the\\n whole interview is done; a model in the file beats a model in the chat.\\n- Skip any phase the existing workspace already covers. Read first, ask second.\\n- Keep the user's words. Descriptions and glossary definitions are written in their language,\\n not in DDD language.\\n\\n## Phase A: orientation (produces the Workspace)\\n\\n- \\\"In one or two sentences, what does this system do, and for whom?\\\" → `name`, `description`.\\n- \\\"Is there a homepage or logo I should link?\\\" → `homepage`, `logoUrl` (skip if none).\\n\\n## Phase B: the problem space (produces Domains and Subdomains with a type)\\n\\n- \\\"What are the big areas of the business this covers? Think of the headings you would put\\n on a whiteboard.\\\" → domains.\\n- Per area: \\\"What distinct jobs sit inside that area?\\\" → subdomains.\\n- Per subdomain: \\\"Is this something that makes you different from competitors, something you\\n need but any sensible way of doing it is fine, or something you would happily buy off the\\n shelf?\\\" → `core` / `supporting` / `generic`.\\n- Explain once: a subdomain is one slice of the problem; calling it core only marks where your\\n competitive effort goes.\\n\\n## Phase C: ownership (produces Teams, Bounded Contexts, `subdomains`, `bigBallOfMud`)\\n\\n- \\\"Which teams or people work on this, and which parts does each look after?\\\" → teams, and a\\n candidate context per part.\\n- \\\"If two teams both say <word>, do they mean exactly the same thing?\\\" If not, that is two\\n contexts. Explain once: a bounded context is a boundary inside which every word has one\\n exact meaning; your billing \\\"Customer\\\" and your support \\\"Customer\\\" being different is why\\n they get separate contexts.\\n- \\\"Which of the jobs from before does each part serve?\\\" → `subdomains` refs. One context may\\n serve several.\\n- \\\"Is any of these an old system that nobody fully understands, where the data model is a\\n mess?\\\" → `bigBallOfMud: true`. Explain: we flag it so anything talking to it knows to\\n translate rather than trust.\\n\\n## Phase D: the integration map (produces Relationships and seeds consumptions)\\n\\n- \\\"Which parts talk to each other? For each pair, who depends on whom?\\\" → `upstream-downstream`\\n with the depended-on side upstream.\\n- \\\"When the upstream team changes something, does the downstream team get a say beforehand?\\\"\\n Yes → `customer-supplier`.\\n- \\\"Do those two teams plan and release together, as one?\\\" → `partnership`.\\n- \\\"Do they share actual code or tables that both change?\\\" → `shared-kernel`.\\n- \\\"Are there two parts that you have decided, on purpose, should never integrate?\\\" →\\n `separate-ways`.\\n- \\\"How does the downstream side take the data: as it comes, or does it copy and reshape it\\n into its own terms?\\\" → `conformist` / `anti-corruption-layer`. Goes on `downstreamRoles`\\n and on each consumption's `pattern`.\\n- \\\"Does the upstream side publish a documented API, or a documented message format?\\\" →\\n `open-host-service` / `published-language`. Goes on `upstreamRoles` and on each exposed\\n consumable's `pattern`.\\n\\n## Phase E: inside one context (produces Aggregates, Entities, Value Objects, Invariants, Glossary)\\n\\nRepeat for each context the user wants detailed. Ask which one to start with.\\n\\n- \\\"Inside <context>, what are the things people talk about? Just list the nouns.\\\" → candidate\\n entities and value objects; every noun becomes a glossary term with the user's definition.\\n- Per noun: \\\"If two of these had identical details, would they still be two different things?\\\"\\n Yes → entity; no → value object. Explain once: an entity matters because of which one it is\\n (this order, not that one); a value object matters only by its values (an address).\\n- \\\"What identifies it: an order number, an email?\\\" → an attribute with `identity: true`.\\n- \\\"What details does it carry?\\\" → attributes, with `type` in the user's words.\\n- \\\"Which of these do you always change or check together? What must be true across all of\\n them at once?\\\" → the aggregate boundary. The thing they state the rule about is the root.\\n Explain once: an aggregate is the cluster you change together and check rules across; the\\n root is the one you name it after.\\n- \\\"What must never be allowed to happen to a <root>?\\\" → invariants, each constraining the\\n entity, value object or attribute it is about.\\n- \\\"Does a <root> point at things in another cluster, for example an order pointing at a\\n product?\\\" → `references` to that cluster's root; ask \\\"one or many?\\\" for cardinality.\\n- \\\"Does it contain things that cannot exist without it?\\\" → `includes`.\\n- \\\"Does it use a value like an address, money or a status?\\\" → `uses`.\\n\\n## Phase F: behaviour (produces Consumables, `raises`, Policies, Schemas)\\n\\n- \\\"What can someone ask this part to do?\\\" → `operation` consumables. Put an API entry point on\\n an application service, and a state change of one aggregate on that aggregate.\\n- \\\"When that happens, what fact would you announce to the rest of the business?\\\" → `event`\\n consumable, linked from the operation with `raises`. Events are past tense.\\n- \\\"Is that something only this part uses, or would other parts care?\\\" → `internal: true`, or\\n an upstream `pattern`.\\n- \\\"What information travels with that announcement or request?\\\" → a schema on the context,\\n attached with `schema`.\\n- \\\"When <event> happens, what do you then do automatically?\\\" → a policy with `on` the event\\n and `then` the operation. Either side may live in another context.\\n- \\\"Who outside this part listens for <event>?\\\" → a consumption on their aggregate or service,\\n with a downstream `pattern`.\\n- Close: \\\"Which of the words we used should I define, and does each map to one of the things\\n we modelled?\\\" → glossary terms with `embodiedBy`.\\n\\n## Phase G: validate and reflect\\n\\nRun validation. Explain each diagnostic in one plain sentence, propose the fix, and ask before\\napplying fixes for warnings. Then summarise what changed, in the user's words, and ask what to\\nmodel next.\\n\" },\n\t{ path: \"references/json-mode.md\", content: \"# JSON mode\\n\\nThe workspace files are the artefact. Each `.ods/*.json` file is one complete workspace, and\\nthe VS Code extension, the docs generator and anyone else load it with `Workspace.fromSchema`.\\n\\n## Files\\n\\n- `.ods/` (or the folder named by the VS Code setting `ods.folder`) at the project root.\\n- `.ods/schema.json`: the JSON Schema, written by the extension (`ODS: Write schema.json`).\\n Never edit it. If it is missing, copy it from\\n `node_modules/@open-domain-specification/core/dist/workspace.schema.json`.\\n- `.ods/<workspace-id>.json`: one workspace per file. The first key is\\n `\\\"$schema\\\": \\\"./schema.json\\\"`; the loader ignores it, editors use it for completion.\\n- Keep the file's `id` equal to its basename, and `odsVersion` equal to the other files' (use\\n `\\\"1.0.0\\\"` for a first file).\\n\\nThe smallest valid file is `examples/minimal.ods.json`. Copy it when creating a workspace, then\\ngrow it.\\n\\n## Editing rules\\n\\n- The schema is strict: every required field is present even when empty, and unknown fields\\n are rejected. `references/model-reference.md` lists them.\\n- Ids are the object keys. Create them as `snake_case` of the name, then never change them.\\n Renaming is changing `name`.\\n- Every `$ref` follows the grammar at the end of `model-reference.md` and points at something\\n that exists. A dangling ref makes the whole file fail to load; the extension then shows\\n \\\"Workspace file could not be loaded\\\" instead of diagnostics.\\n- Preserve the key order and two-space indentation of the file so diffs stay readable.\\n- Prefer several small edits, each followed by validation, over one large rewrite.\\n\\n## Validation\\n\\nThere is no CLI. Run `examples/validate.mjs` from the project root:\\n\\n```sh\\nnode .claude/skills/ods-authoring/examples/validate.mjs .ods/petstore.json\\n```\\n\\nOr inline:\\n\\n```sh\\nnode -e 'const {Workspace}=require(\\\"@open-domain-specification/core\\\");const f=process.argv[1];const ws=Workspace.fromSchema(JSON.parse(require(\\\"fs\\\").readFileSync(f,\\\"utf8\\\")));for(const d of ws.validate())console.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`)' .ods/petstore.json\\n```\\n\\nIf `@open-domain-specification/core` is not installed, prefix with\\n`npx -p @open-domain-specification/core` or install it as a devDependency. The VS Code Problems\\npanel shows the same diagnostics (source `ods`, code = rule id) and updates on save.\\n\\n## Several workspace files\\n\\nA `.ods` folder may hold several files. Treat each as its own workspace; refs never cross files.\\n\" },\n\t{ path: \"references/model-reference.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (dist/workspace.schema.json); do not edit. -->\\n\\n# ODS model reference\\n\\nThe complete shape of a workspace file. Collections keyed by id are JSON objects whose keys are the element ids; `array of` collections are JSON arrays. Required fields must be present even when empty (`{}` or `[]`).\\n\\n## Workspace\\n\\nRepresents a workspace in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `$schema` | string | no | Location of the JSON schema this document conforms to, usually the schema.json beside it. Ignored by the loader. |\\n| `boundedcontexts` | map of id to [BoundedContext](#boundedcontext) | yes | |\\n| `description` | string | yes | |\\n| `domains` | map of id to [Domain](#domain) | yes | |\\n| `homepage` | string | no | |\\n| `id` | string | yes | |\\n| `logoUrl` | string | no | |\\n| `name` | string | yes | |\\n| `odsVersion` | string | yes | |\\n| `primaryColor` | string | no | |\\n| `relationships` | array of [ContextRelationship](#contextrelationship) | yes | |\\n| `teams` | map of id to [Team](#team) | yes | |\\n| `version` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Aggregate\\n\\nRepresents an aggregate in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `entities` | map of id to [Entity](#entity) | yes | |\\n| `invariants` | map of id to [Invariant](#invariant) | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `valueobjects` | map of id to [ValueObject](#valueobject) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Attribute\\n\\nA named, typed property of an entity, value object or schema.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `identity` | boolean | no | True when this attribute is (part of) the identity of an entity. |\\n| `name` | string | yes | |\\n| `type` | string | yes | Free-form type name, e.g. `string`, `Money`, `Date`. |\\n| `valueobject` | `{ \\\"$ref\\\": string }` | no | The value object that models this attribute's type, when there is one. |\\n\\nNo other fields are allowed.\\n\\n## BoundedContext\\n\\nRepresents a bounded context in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aggregates` | map of id to [Aggregate](#aggregate) | yes | |\\n| `bigBallOfMud` | boolean | no | Marks a context whose model is not coherent (typically legacy) so that neighbours know to protect themselves from it. |\\n| `description` | string | yes | |\\n| `glossary` | map of id to [GlossaryTerm](#glossaryterm) | yes | |\\n| `name` | string | yes | |\\n| `policies` | map of id to [Policy](#policy) | yes | |\\n| `schemas` | map of id to [DataSchema](#dataschema) | yes | Payload shapes this context publishes or accepts, referenced by its consumables. |\\n| `services` | map of id to [Service](#service) | yes | |\\n| `subdomains` | array of `{ \\\"$ref\\\": string }` | yes | The subdomains this context serves; a context may serve several. |\\n| `team` | `{ \\\"$ref\\\": string }` | no | The team that owns this context. |\\n\\nNo other fields are allowed.\\n\\n## Consumable\\n\\nRepresents a consumable in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `internal` | boolean | no | True when the consumable stays inside its context: an event only local policies react to, or an operation only local callers issue. Internal consumables may not be consumed from another context. |\\n| `name` | string | yes | |\\n| `pattern` | \\\"open-host-service\\\" | \\\"published-language\\\" | no | The upstream role this consumable is offered under. Absent on internal consumables. |\\n| `raises` | array of `{ \\\"$ref\\\": string }` | no | For operations: the event consumables this operation may raise. |\\n| `schema` | `{ \\\"$ref\\\": string }` | no | The payload shape, one of the context's schemas. |\\n| `type` | \\\"event\\\" | \\\"operation\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Consumption\\n\\nRepresents a consumption in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumable` | `{ \\\"$ref\\\": string }` | yes | |\\n| `pattern` | \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | no | The downstream role the consumer adopts for this consumable. |\\n\\nNo other fields are allowed.\\n\\n## ContextRelationship\\n\\nA strategic relationship between two bounded contexts.\\n\\nOne of: [DirectedContextRelationship](#directedcontextrelationship), [SymmetricContextRelationship](#symmetriccontextrelationship).\\n\\n## DataSchema\\n\\nA named payload shape owned by a bounded context, shared by the consumables that carry it.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## DirectedContextRelationship\\n\\nAn upstream/downstream relationship between two bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `downstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `downstreamRoles` | array of \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | yes | |\\n| `type` | \\\"customer-supplier\\\" | \\\"upstream-downstream\\\" | yes | |\\n| `upstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `upstreamRoles` | array of \\\"open-host-service\\\" | \\\"published-language\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Domain\\n\\nRepresents a domain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `subdomains` | map of id to [Subdomain](#subdomain) | yes | |\\n\\nNo other fields are allowed.\\n\\n## EntityRelation\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `cardinality` | \\\"*\\\" | \\\"0..1\\\" | \\\"1\\\" | \\\"1..*\\\" | no | |\\n| `label` | string | no | |\\n| `relation` | \\\"includes\\\" | \\\"references\\\" | \\\"uses\\\" | yes | |\\n| `target` | `{ \\\"$ref\\\": string }` | yes | |\\n\\nNo other fields are allowed.\\n\\n## Entity\\n\\nRepresents an entity in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n| `root` | boolean | no | |\\n\\nNo other fields are allowed.\\n\\n## GlossaryTerm\\n\\nA term of the ubiquitous language of a bounded context.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aliases` | array of string | no | |\\n| `definition` | string | yes | |\\n| `embodiedBy` | `{ \\\"$ref\\\": string }` | no | The model element that embodies this term, when there is one. |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Invariant\\n\\nRepresents an invariant in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `constrains` | array of `{ \\\"$ref\\\": string }` | yes | The entities, value objects or attributes this invariant constrains. |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Policy\\n\\nA reaction: when these events happen, issue these commands.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `on` | array of `{ \\\"$ref\\\": string }` | yes | The event consumables that trigger this policy. |\\n| `then` | array of `{ \\\"$ref\\\": string }` | yes | The operation consumables this policy issues. |\\n\\nNo other fields are allowed.\\n\\n## Service\\n\\nRepresents a service in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `type` | \\\"application\\\" | \\\"domain\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Subdomain\\n\\nRepresents a subdomain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `type` | \\\"core\\\" | \\\"generic\\\" | \\\"supporting\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## SymmetricContextRelationship\\n\\nA relationship between two bounded contexts with no upstream or downstream side.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `participants` | array of unknown | yes | |\\n| `type` | \\\"partnership\\\" | \\\"separate-ways\\\" | \\\"shared-kernel\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Team\\n\\nA team that owns one or more bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `homepage` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## ValueObject\\n\\nRepresents a value object in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Ref grammar\\n\\nEvery cross-link is an object `{ \\\"$ref\\\": \\\"<path>\\\" }`. Paths are JSON pointers into the workspace, built from the ids that key each collection:\\n\\n| Element | Path |\\n|---|---|\\n| Team | `#/teams/<team>` |\\n| Domain | `#/domains/<domain>` |\\n| Subdomain | `#/domains/<domain>/subdomains/<subdomain>` |\\n| Bounded context | `#/boundedcontexts/<bc>` |\\n| Aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>` |\\n| Entity | `#/boundedcontexts/<bc>/aggregates/<aggregate>/entities/<entity>` |\\n| Value object | `#/boundedcontexts/<bc>/aggregates/<aggregate>/valueobjects/<vo>` |\\n| Invariant | `#/boundedcontexts/<bc>/aggregates/<aggregate>/invariants/<invariant>` |\\n| Attribute | `<owner path>/attributes/<attribute>` (owner is an entity, value object or schema) |\\n| Consumable of an aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>/provides/<consumable>` |\\n| Service | `#/boundedcontexts/<bc>/services/<service>` |\\n| Consumable of a service | `#/boundedcontexts/<bc>/services/<service>/provides/<consumable>` |\\n| Policy | `#/boundedcontexts/<bc>/policies/<policy>` |\\n| Glossary term | `#/boundedcontexts/<bc>/glossary/<term>` |\\n| Schema | `#/boundedcontexts/<bc>/schemas/<schema>` |\\n\\nA bounded context path never embeds the domain or subdomain, so moving a context between subdomains breaks no refs. A ref that points at nothing makes the whole file fail to load.\\n\" },\n\t{ path: \"references/translation-table.md\", content: \"# Translation table: what people say → what to record\\n\\nUse this while interviewing and when reviewing a draft for mis-modelling. `JSON` is where the\\nelement lives in a workspace file; `DSL` is the core call that creates it.\\n\\n| What they say | ODS element | JSON | DSL |\\n|---|---|---|---|\\n| \\\"the system for X\\\", \\\"our platform\\\" | Workspace | top-level `name`, `description` | `new Workspace(name, {...})` |\\n| \\\"the business areas\\\", \\\"departments\\\", \\\"lines of business\\\" | Domain | `domains.<id>` | `ws.addDomain(name, {description})` |\\n| \\\"the part that handles Y\\\", \\\"the Y job\\\" | Subdomain | `domains.<d>.subdomains.<id>` | `domain.addSubdomain(name, {type, description})` |\\n| \\\"what makes us different\\\", \\\"our secret sauce\\\" | subdomain type | `\\\"type\\\": \\\"core\\\"` | `type: \\\"core\\\"` |\\n| \\\"we need it but it's not special\\\", \\\"any sensible way works\\\" | subdomain type | `\\\"type\\\": \\\"supporting\\\"` | `type: \\\"supporting\\\"` |\\n| \\\"we'd buy it\\\", \\\"login / email / payments provider\\\" | subdomain type | `\\\"type\\\": \\\"generic\\\"` | `type: \\\"generic\\\"` |\\n| \\\"team X owns that\\\", \\\"that's Sam's service\\\" | Team, owning context | `teams.<id>`; context `team: {$ref}` | `ws.addTeam(name)`; `bc.ownedBy(team)` |\\n| \\\"in billing a customer means ..., in support it means ...\\\" | two Bounded Contexts | `boundedcontexts.<id>` twice | `subdomain.addBoundedcontext(name, {...})` twice |\\n| \\\"that part covers both Y and Z\\\" | context serving two subdomains | `subdomains: [{$ref Y}, {$ref Z}]` | `ws.addBoundedContext(name, {subdomains: [y, z]})` or `bc.serves(z)` |\\n| \\\"the old system\\\", \\\"legacy\\\", \\\"nobody understands its schema\\\" | big ball of mud | `\\\"bigBallOfMud\\\": true` | `bigBallOfMud: true` |\\n| \\\"A depends on B\\\", \\\"A calls B\\\", \\\"A reads B's data\\\" | upstream-downstream (B upstream) | `relationships[]` `type: \\\"upstream-downstream\\\"` | `a.downstreamOf(b, {...})` |\\n| \\\"they ask us before changing\\\", \\\"we're their customer\\\" | customer-supplier | `type: \\\"customer-supplier\\\"` | `a.downstreamOf(b, {type: \\\"customer-supplier\\\", ...})` |\\n| \\\"both teams change it together\\\", \\\"we release together\\\" | partnership | `type: \\\"partnership\\\"`, `participants` | `a.partnerOf(b)` |\\n| \\\"we share the same tables / library / code\\\" | shared kernel | `type: \\\"shared-kernel\\\"` | `a.sharesKernelWith(b)` |\\n| \\\"we deliberately don't integrate\\\" | separate ways | `type: \\\"separate-ways\\\"` | `a.separateWaysFrom(b, why)` |\\n| \\\"we use their API as-is\\\", \\\"we take whatever they send\\\" | conformist | consumption `pattern: \\\"conformist\\\"`; relationship `downstreamRoles` | `agg.consumes(c, {pattern: \\\"conformist\\\"})` |\\n| \\\"we copy and reshape their data\\\", \\\"we wrap their API\\\" | anti-corruption layer | `pattern: \\\"anti-corruption-layer\\\"` | `agg.consumes(c, {pattern: \\\"anti-corruption-layer\\\"})` |\\n| \\\"we expose a documented API\\\", \\\"there's a REST endpoint\\\" | open host service | operation `pattern: \\\"open-host-service\\\"`; relationship `upstreamRoles` | `svc.provides(name, {type: \\\"operation\\\", pattern: \\\"open-host-service\\\"})` |\\n| \\\"we publish a message format everyone agrees on\\\" | published language | event `pattern: \\\"published-language\\\"` | `agg.provides(name, {type: \\\"event\\\", pattern: \\\"published-language\\\"})` |\\n| \\\"this specific order\\\", \\\"the thing with a number\\\" | Entity | `aggregates.<a>.entities.<id>` | `agg.addEntity(name, {description})` |\\n| \\\"the main thing\\\", \\\"the one we name the rule about\\\" | root entity | `\\\"root\\\": true` | `agg.addRootEntity(name, {description})` |\\n| \\\"just a value\\\", \\\"an address\\\", \\\"money\\\", \\\"same values, same thing\\\" | Value Object | `aggregates.<a>.valueobjects.<id>` | `agg.addValueObject(name, {description})` |\\n| \\\"it has a field\\\", \\\"it's made of\\\" | Attribute | `attributes.<id>` with `type` in the user's words | `entity.addAttribute(name, {type})` |\\n| \\\"the number that identifies it\\\" | identity attribute | `\\\"identity\\\": true` | `identity: true` |\\n| \\\"its status is one of these values\\\" | attribute backed by a value object | `attributes.<id>.valueobject: {$ref}` | `addAttribute(name, {type, valueobject: vo})` |\\n| \\\"these change together\\\", \\\"one transaction\\\", \\\"the order and its lines\\\" | Aggregate | `aggregates.<id>` | `bc.addAggregate(name, {description})` |\\n| \\\"must never\\\", \\\"always has to\\\", \\\"can't be negative\\\", \\\"only when\\\" | Invariant | `invariants.<id>` with `constrains: [{$ref}]` | `agg.addInvariant(name, {description}).constrains(target)` |\\n| \\\"an order points at a pet\\\" (another cluster) | references relation | `relations[]` `relation: \\\"references\\\"` to the other root | `entity.references(otherRoot, label, cardinality)` |\\n| \\\"it contains lines that can't exist alone\\\" | includes relation | `relation: \\\"includes\\\"` | `entity.includes(child, label, cardinality)` |\\n| \\\"it has an address / a status\\\" | uses relation | `relation: \\\"uses\\\"` | `entity.uses(vo, label, cardinality)` |\\n| \\\"exactly one / at most one / any number / at least one\\\" | cardinality | `\\\"1\\\"` / `\\\"0..1\\\"` / `\\\"*\\\"` / `\\\"1..*\\\"` | third argument |\\n| \\\"you can ask it to ...\\\", \\\"POST /x\\\", \\\"the button does ...\\\" | operation | `provides.<id>` `type: \\\"operation\\\"` | `provides(name, {type: \\\"operation\\\", ...})` |\\n| \\\"then we tell everyone that ...\\\", a past-tense fact | event | `provides.<id>` `type: \\\"event\\\"` | `provides(name, {type: \\\"event\\\", ...})` |\\n| \\\"doing that announces ...\\\" | operation raises event | operation `raises: [{$ref event}]` | `op.raises(event)` |\\n| \\\"only we use that\\\", \\\"nobody outside needs it\\\" | internal consumable | `\\\"internal\\\": true`, no `pattern` | `internal: true` |\\n| \\\"what's in the message / the request body\\\" | Schema | `schemas.<id>` on the context; consumable `schema: {$ref}` | `bc.addSchema(name).addAttribute(...)`; `schema: s` |\\n| \\\"when X happens we then Y\\\", \\\"automatically after X\\\" | Policy | `policies.<id>` with `on: [event refs]`, `then: [operation refs]` | `bc.addPolicy(name, {description}).on(e).then(op)` |\\n| \\\"we listen for their X\\\" | consumption | `consumes: [{consumable: {$ref}, pattern}]` | `agg.consumes(theirEvent, {pattern})` |\\n| \\\"the API layer\\\", \\\"the endpoint handler\\\", \\\"the use case\\\" | application service | `services.<id>` `type: \\\"application\\\"` | `bc.addService(name, {type: \\\"application\\\", description})` |\\n| \\\"logic that doesn't belong to one thing\\\", \\\"pricing across orders\\\" | domain service | `type: \\\"domain\\\"` | `type: \\\"domain\\\"` |\\n| \\\"we call it ...\\\", \\\"a.k.a.\\\", \\\"sales say purchase\\\" | Glossary term | `glossary.<id>` with `definition`, `aliases`, `embodiedBy` | `bc.addTerm(name, {definition, aliases, embodiedBy})` |\\n| \\\"the database / queue / Kafka / the cloud\\\" | not modelled | — | say it is infrastructure and out of scope |\\n\" },\n\t{ path: \"references/validation-rules.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (RULE_CATALOG); do not edit. -->\\n\\n# Validation rules\\n\\n`Workspace.validate()` returns diagnostics `{ severity, rule, message, ref }`. Errors describe a model that contradicts itself and should be fixed before finishing. Warnings describe a decision that is missing; discuss them with the user rather than silently fixing them. Explain a diagnostic to the user in the plain words below, not by quoting the rule id.\\n\\n## `aggregate-root` (warning, error)\\n\\n**Requires:** Every aggregate has exactly one root entity.\\n\\n**Why it matters:** The root is the one thing you name the cluster after and reach it through; without it nobody knows where the rules are enforced, and with two the boundary is really two aggregates.\\n\\n**Usual fix:** Mark the entity the aggregate is named after with root: true, or split the aggregate if two entities genuinely lead their own clusters.\\n\\n## `cross-aggregate-reference` (error)\\n\\n**Requires:** A relation into another aggregate uses references and targets that aggregate's root.\\n\\n**Why it matters:** Aggregates are consistency boundaries; reaching inside another one couples the two so they can no longer change or be stored independently.\\n\\n**Usual fix:** Change the relation to \\\"references\\\" and point it at the other aggregate's root entity, holding only its identity.\\n\\n## `role-coherence` (warning)\\n\\n**Requires:** A consumable used from another context declares an upstream role, and the consumption declares a downstream role.\\n\\n**Why it matters:** Crossing a context boundary is an integration decision: how the provider offers it (a documented API or a published format) and how the consumer takes it (as-is or translated) should be explicit.\\n\\n**Usual fix:** Set pattern on the consumable to open-host-service or published-language, and pattern on the consumption to conformist or anti-corruption-layer.\\n\\n## `separate-ways` (error)\\n\\n**Requires:** Contexts that declare separate ways exchange no consumables.\\n\\n**Why it matters:** Separate ways is a deliberate decision not to integrate; a consumption between the two contradicts it.\\n\\n**Usual fix:** Remove the consumption, or remove the separate-ways relationship and declare the real one.\\n\\n## `internal-consumable` (error, warning)\\n\\n**Requires:** An internal consumable is never consumed, reacted to or issued from another context, and declares no upstream role.\\n\\n**Why it matters:** internal means the consumable stays inside its context; anything outside depending on it makes that promise false.\\n\\n**Usual fix:** Drop internal and give the consumable an upstream role, or stop the other context from using it.\\n\\n## `schema-context` (error)\\n\\n**Requires:** A consumable's payload schema belongs to the consumable's own context.\\n\\n**Why it matters:** The context that publishes a message owns its shape; borrowing another context's schema ties the two together.\\n\\n**Usual fix:** Move or copy the schema into the publishing context and point the consumable at that one.\\n\\n## `consumable-kind` (error)\\n\\n**Requires:** Policies react to events and issue operations; only operations raise events, and they raise only events.\\n\\n**Why it matters:** An event is a fact that happened, an operation is a request to do something; mixing them up makes flows unreadable.\\n\\n**Usual fix:** Check the type of each consumable a policy or raises list points at and swap it for the right kind.\\n\\n## `policy-complete` (warning)\\n\\n**Requires:** A policy reacts to at least one event and issues at least one operation.\\n\\n**Why it matters:** A policy is a rule of the form when this happens, do that; either half missing leaves nothing to enact.\\n\\n**Usual fix:** Add the missing event to on or the missing operation to then.\\n\\n## `context-serves-subdomain` (warning)\\n\\n**Requires:** Every bounded context serves at least one subdomain.\\n\\n**Why it matters:** A context that serves no subdomain has no place in the problem-space view, so nobody can see which part of the business it exists for.\\n\\n**Usual fix:** Add the subdomain the context serves to its subdomains list.\\n\" },\n\t{ path: \"SKILL.md\", content: \"---\\nname: ods-authoring\\ndescription: >\\n Author and evolve Open Domain Specification (ODS) workspaces, either as .ods/*.json files or\\n through the @open-domain-specification/core TypeScript DSL. Use when the user wants to model\\n their system or business domain, create or edit a domain model, bounded contexts, aggregates,\\n entities, events, policies, context maps or any DDD (domain-driven design) artefact, asks\\n \\\"what are our bounded contexts\\\", mentions ODS, .ods, open-ds or the ODS VS Code extension, or\\n has validation warnings from an ODS workspace. Acts as a DDD facilitator: interviews developers\\n who do not know DDD in plain language, maps their answers onto the model, validates the result.\\n---\\n\\n# Authoring ODS workspaces\\n\\n## Your role\\n\\nYou are a domain-modelling facilitator. The user knows their system; they usually do not know\\nDomain-Driven Design, and they should not need to. You ask plain-language questions, map the\\nanswers onto the ODS model, and explain each DDD term once, in one sentence, using their own\\nexample. Model in small increments, validate after every edit, and never lecture.\\n\\n## Step 0: detect the authoring mode\\n\\nDecide once, state it in one line, and do not re-detect every turn. The first match wins.\\n\\n1. **DSL mode.** A `.ts`, `.js` or `.mjs` file imports `@open-domain-specification/core`,\\n builds a `Workspace` and writes `toSchema()` output under `.ods/` or to a `workspace.json`;\\n or `package.json` depends on core and has a script (`build`, `ods`, `generate`, `model`)\\n that runs such a file. Emitted JSON is also recognisable: two-space indent and the key order\\n `id, name, description, version, odsVersion, ...`.\\n2. **JSON mode.** A `.ods/` folder (or the folder named by the VS Code setting `ods.folder`)\\n holds `*.json` workspace files next to a `schema.json`, and no generator matches.\\n3. **Neither.** Ask one question: keep the model as JSON files the VS Code extension edits, or\\n as TypeScript that generates them? Recommend JSON when there is no Node toolchain, and the\\n DSL when the model will be large or generated documentation is wanted.\\n\\nIn DSL mode never edit the emitted JSON; it is overwritten on the next run. In JSON mode never\\nintroduce a generator unless asked. Details: `references/json-mode.md`, `references/dsl-mode.md`.\\n\\n## Step 1: read what exists\\n\\nLoad every workspace file (or the DSL source) before proposing anything. Summarise it in the\\nuser's words: the business areas, the parts of the system and who owns them, the main things\\neach part manages, how the parts talk to each other, and how many validation problems there\\nare. Then ask what they want to change or add.\\n\\n## Step 2: interview\\n\\nWhen creating or expanding a model, follow `references/interview-playbook.md`. Strategic\\nquestions first (areas, ownership, integrations), then the detail of one part at a time. One\\nquestion per turn. After each answer, reflect it back as the element you would record (\\\"So I'd\\nnote an Order that must always point at exactly one Pet, right?\\\") before writing it. Stop\\ninterviewing as soon as you know enough for one coherent increment; you can always come back.\\n\\n## Step 3: translate\\n\\nMap answers with `references/translation-table.md`. Every element gets a `description` in\\nthe user's own words, and every noun they used more than once becomes a glossary term in its\\ncontext. Use `references/ddd-glossary.md` for the one-sentence explanations.\\n\\n## Step 4: edit\\n\\nFollow the mode reference for mechanics. Rules that hold in both modes:\\n\\n- Ids are the JSON keys and the segments of every `$ref`. They are derived from the name at\\n creation (`snake_case`) and then frozen. To rename something, change its `name` and keep the\\n id (in the DSL, pass `id` explicitly at the moment of renaming). Rewriting a key means\\n updating every ref that uses it, and confirming with the user first.\\n- Every required collection is present even when empty. A context always has `aggregates`,\\n `services`, `policies`, `glossary`, `schemas` and `subdomains`; an aggregate always has\\n `entities`, `valueobjects`, `invariants`, `provides`, `consumes`; an entity or value object\\n always has `attributes` and `relations`. See `references/model-reference.md`.\\n- Every `$ref` resolves to an element that exists. A dangling ref is a load failure, not a\\n warning: the whole file stops loading.\\n- Consumables (events and operations) live only under `provides` of an aggregate or a\\n service. Policies and consumptions point at them by ref.\\n- A payload schema belongs to the context that publishes the consumable.\\n- Reference another aggregate only through its root entity, with `references`.\\n\\n## Step 5: validate and explain\\n\\nValidate after every edit.\\n\\n- JSON mode: run the script in `examples/validate.mjs` (or the one-liner in\\n `references/json-mode.md`) against each file. The VS Code Problems panel shows the same\\n results, source `ods`, code = rule id.\\n- DSL mode: run the generator script. It prints `[severity] rule: message (ref)` lines and\\n rewrites the JSON.\\n\\nFor each diagnostic, tell the user in one plain sentence what it means and what you propose,\\nusing `references/validation-rules.md`. Errors block finishing. Warnings mark a missing\\ndecision: discuss them and let the user decide, rather than fixing them silently. If loading\\nthrows \\\"... with ref ... not found\\\", a ref is dangling: fix it first.\\n\\n## Educating without preaching\\n\\nThe first time a DDD term comes up, explain it in one sentence tied to the user's example,\\nthen move on. Never repeat an explanation, and never explain a term the user has already used\\ncorrectly. Say \\\"command\\\" in conversation if it helps, but the model's word is `operation`.\\n\\n## Defaults when the user cannot decide\\n\\n| Question | Default |\\n|---|---|\\n| Subdomain type unknown | `supporting` |\\n| Operation used by another context | `pattern: \\\"open-host-service\\\"` |\\n| Event used by another context | `pattern: \\\"published-language\\\"` |\\n| Consuming from a legacy or `bigBallOfMud` context | `pattern: \\\"anti-corruption-layer\\\"` |\\n| Consuming from any other context | `pattern: \\\"conformist\\\"` |\\n| Two contexts exchange consumables, nothing else known | relationship `upstream-downstream` |\\n| Cardinality unknown | omit it |\\n| Service type unknown | `application` if it fronts an API or UI, else `domain` |\\n\\n## Do not\\n\\n- Invent fields or values. The model is exactly `references/model-reference.md`.\\n- Put behaviour on domains or subdomains; they describe the problem, contexts hold solutions.\\n- Create an aggregate without a root, or reach inside another aggregate.\\n- Model infrastructure (databases, queues, brokers). Say it is out of scope.\\n- Force programming types into `type` when the user said \\\"money\\\", \\\"email\\\" or \\\"a date\\\".\\n- Emit `type: \\\"command\\\"`; the consumable types are `event` and `operation`.\\n- Let an operation raise an event of another context without pointing it out as unusual.\\n- Leave `upstreamRoles` or `downstreamRoles` empty on a directed relationship.\\n- Skip validation because the change was small.\\n\" },\n];\n","/** Where each agent looks for skills, relative to a project or home folder. */\nexport type SkillTarget = \"claude\" | \"agents\" | \"codex\";\n\n/** The folder name of the skill under every `skills/` directory. */\nexport const SKILL_NAME = \"ods-authoring\";\n\nexport const TARGETS: ReadonlyArray<{\n\tid: SkillTarget;\n\tlabel: string;\n\t/** The skills directory, relative to the root the skill is installed into. */\n\tdir: string;\n}> = [\n\t{ id: \"claude\", label: \"Claude Code\", dir: \".claude/skills\" },\n\t{ id: \"agents\", label: \"Agent Skills (.agents)\", dir: \".agents/skills\" },\n\t{ id: \"codex\", label: \"OpenAI Codex (.codex)\", dir: \".codex/skills\" },\n];\n\nexport const TARGET_DIRS: Record<SkillTarget, string> = Object.fromEntries(\n\tTARGETS.map((t) => [t.id, t.dir]),\n) as Record<SkillTarget, string>;\n\n/** The directory the skill lands in for a target, relative to `root`. */\nexport function skillDir(target: SkillTarget): string {\n\treturn `${TARGET_DIRS[target]}/${SKILL_NAME}`;\n}\n","import { BUNDLE, SKILL_VERSION } from \"./bundle.generated\";\nimport { SKILL_NAME, type SkillTarget, skillDir } from \"./targets\";\n\nexport type SkillFile = { path: string; content: string };\n\nconst STAMP = /<!-- ods-skill-version: ([^\\s]+) -->/;\n\nfunction stamp(content: string): string {\n\treturn `${content.trimEnd()}\\n\\n<!-- ods-skill-version: ${SKILL_VERSION} -->\\n`;\n}\n\n/** Every file of the skill bundle, paths relative to the skill folder. */\nexport function skillFiles(): SkillFile[] {\n\treturn BUNDLE.map((f) =>\n\t\tf.path === \"SKILL.md\" ? { ...f, content: stamp(f.content) } : { ...f },\n\t);\n}\n\nexport type InstallOptions = {\n\t/** The project or home folder the skill is installed into. */\n\troot: string;\n\ttarget: SkillTarget;\n\t/** Writes one file; receives an absolute-ish path built from `root`. */\n\twrite: (path: string, content: string) => Promise<void>;\n\t/** Override the files to install; defaults to the bundle. */\n\tfiles?: SkillFile[];\n};\n\n/** Writes the bundle into `<root>/<target skills dir>/ods-authoring/` and returns the paths written. */\nexport async function installSkill(options: InstallOptions): Promise<string[]> {\n\tconst base = `${options.root}/${skillDir(options.target)}`;\n\tconst written: string[] = [];\n\tfor (const file of options.files ?? skillFiles()) {\n\t\tconst path = `${base}/${file.path}`;\n\t\tawait options.write(path, file.content);\n\t\twritten.push(path);\n\t}\n\treturn written;\n}\n\nexport type InstallState = \"missing\" | \"stale\" | \"current\";\n\n/** Compares the version stamp of an installed SKILL.md with this package's. */\nexport async function isInstalled(\n\troot: string,\n\ttarget: SkillTarget,\n\tread: (path: string) => Promise<string | undefined>,\n): Promise<InstallState> {\n\tconst content = await read(`${root}/${skillDir(target)}/SKILL.md`);\n\tif (content === undefined) return \"missing\";\n\treturn STAMP.exec(content)?.[1] === SKILL_VERSION ? \"current\" : \"stale\";\n}\n\n/** A paragraph for AGENTS.md or copilot-instructions.md pointing agents at the installed skill. */\nexport function rulesSnippet(target: SkillTarget = \"agents\"): string {\n\treturn [\n\t\t\"## Domain model (Open Domain Specification)\",\n\t\t\"\",\n\t\t`This project keeps its domain model as an Open Domain Specification workspace. Before creating or editing anything under \\`.ods/\\` or a TypeScript file that builds a \\`Workspace\\` from \\`@open-domain-specification/core\\`, read \\`${skillDir(target)}/SKILL.md\\` and follow it: detect whether the model is authored as JSON or via the TypeScript DSL, interview the user in plain language before modelling, and validate after every change.`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport { SKILL_NAME, SKILL_VERSION };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,gBAAgB;AACtB,IAAM,SAA2D;AAAA,EACvE,EAAE,MAAM,6BAA6B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmyJ;AAAA,EACj1J,EAAE,MAAM,iCAAiC,SAAS,s9FAAmoG;AAAA,EACrrG,EAAE,MAAM,wBAAwB,SAAS,0nHAAqwH;AAAA,EAC9yH,EAAE,MAAM,yBAAyB,SAAS,2zCAAs2C;AAAA,EACh5C,EAAE,MAAM,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmvH;AAAA,EAClyH,EAAE,MAAM,yBAAyB,SAAS,48HAA2+H;AAAA,EACrhI,EAAE,MAAM,0BAA0B,SAAS,mkFAAulF;AAAA,EACloF,EAAE,MAAM,oCAAoC,SAAS,kkNAAk+M;AAAA,EACvhN,EAAE,MAAM,2BAA2B,SAAS,q+EAA++E;AAAA,EAC3hF,EAAE,MAAM,iCAAiC,SAAS,2uVAA6zV;AAAA,EAC/2V,EAAE,MAAM,mCAAmC,SAAS,m4MAAimN;AAAA,EACrpN,EAAE,MAAM,kCAAkC,SAAS,0lIAA0lI;AAAA,EAC7oI,EAAE,MAAM,YAAY,SAAS,s/NAAygO;AACviO;;;ACZO,IAAM,aAAa;AAEnB,IAAM,UAKR;AAAA,EACJ,EAAE,IAAI,UAAU,OAAO,eAAe,KAAK,iBAAiB;AAAA,EAC5D,EAAE,IAAI,UAAU,OAAO,0BAA0B,KAAK,iBAAiB;AAAA,EACvE,EAAE,IAAI,SAAS,OAAO,yBAAyB,KAAK,gBAAgB;AACrE;AAEO,IAAM,cAA2C,OAAO;AAAA,EAC9D,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC;AAGO,SAAS,SAAS,QAA6B;AACrD,SAAO,GAAG,YAAY,MAAM,CAAC,IAAI,UAAU;AAC5C;;;ACnBA,IAAM,QAAQ;AAEd,SAAS,MAAM,SAAyB;AACvC,SAAO,GAAG,QAAQ,QAAQ,CAAC;AAAA;AAAA,0BAA+B,aAAa;AAAA;AACxE;AAGO,SAAS,aAA0B;AACzC,SAAO,OAAO;AAAA,IAAI,CAAC,MAClB,EAAE,SAAS,aAAa,iCAAK,IAAL,EAAQ,SAAS,MAAM,EAAE,OAAO,EAAE,KAAI,mBAAK;AAAA,EACpE;AACD;AAaA,SAAsB,aAAa,SAA4C;AAAA;AA7B/E;AA8BC,UAAM,OAAO,GAAG,QAAQ,IAAI,IAAI,SAAS,QAAQ,MAAM,CAAC;AACxD,UAAM,UAAoB,CAAC;AAC3B,eAAW,SAAQ,aAAQ,UAAR,YAAiB,WAAW,GAAG;AACjD,YAAM,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI;AACjC,YAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,cAAQ,KAAK,IAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACR;AAAA;AAKA,SAAsB,YACrB,MACA,QACA,MACwB;AAAA;AA/CzB;AAgDC,UAAM,UAAU,MAAM,KAAK,GAAG,IAAI,IAAI,SAAS,MAAM,CAAC,WAAW;AACjE,QAAI,YAAY,OAAW,QAAO;AAClC,aAAO,WAAM,KAAK,OAAO,MAAlB,mBAAsB,QAAO,gBAAgB,YAAY;AAAA,EACjE;AAAA;AAGO,SAAS,aAAa,SAAsB,UAAkB;AACpE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,wOAAwO,SAAS,MAAM,CAAC;AAAA,IACxP;AAAA,EACD,EAAE,KAAK,IAAI;AACZ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/bundle.generated.ts","../src/targets.ts","../src/install.ts"],"sourcesContent":["export * from \"./install\";\nexport * from \"./targets\";\n","// Generated by scripts/generate.mts; do not edit.\nexport const SKILL_VERSION = \"0.2.0\";\nexport const BUNDLE: ReadonlyArray<{ path: string; content: string }> = [\n\t{ path: \"examples/minimal.ods.json\", content: \"{\\n\\t\\\"$schema\\\": \\\"./schema.json\\\",\\n\\t\\\"id\\\": \\\"bookshop\\\",\\n\\t\\\"name\\\": \\\"Bookshop\\\",\\n\\t\\\"description\\\": \\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n\\t\\\"version\\\": \\\"0.1.0\\\",\\n\\t\\\"odsVersion\\\": \\\"1.0.0\\\",\\n\\t\\\"domains\\\": {\\n\\t\\t\\\"bookselling\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Bookselling\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Everything involved in selling books online\\\",\\n\\t\\t\\t\\\"subdomains\\\": {\\n\\t\\t\\t\\t\\\"sales\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Sales\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"core\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"Taking and fulfilling orders\\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"boundedcontexts\\\": {\\n\\t\\t\\\"orders\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Orders\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Owns orders from placement to dispatch\\\",\\n\\t\\t\\t\\\"subdomains\\\": [{ \\\"$ref\\\": \\\"#/domains/bookselling/subdomains/sales\\\" }],\\n\\t\\t\\t\\\"team\\\": { \\\"$ref\\\": \\\"#/teams/shop_team\\\" },\\n\\t\\t\\t\\\"aggregates\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"One customer's request to buy some books\\\",\\n\\t\\t\\t\\t\\t\\\"entities\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"The order itself\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"root\\\": true,\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"target\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/valueobjects/address\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"relation\\\": \\\"uses\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"label\\\": \\\"ships-to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"cardinality\\\": \\\"1\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"valueobjects\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"address\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Address\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"Where the order ships to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"lines\\\": { \\\"name\\\": \\\"lines\\\", \\\"type\\\": \\\"text\\\" },\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"postcode\\\": { \\\"name\\\": \\\"postcode\\\", \\\"type\\\": \\\"postcode\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": []\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"invariants\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"total_not_negative\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Total not negative\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"An order's total can never be below zero\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"constrains\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/entities/order/attributes/total\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"A customer placed an order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"event\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"published-language\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"schema\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/schemas/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"services\\\": {\\n\\t\\t\\t\\t\\\"order_api\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order API\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"The endpoints the web shop calls\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"application\\\",\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"place_order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"PlaceOrder\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"POST /orders\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"operation\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"open-host-service\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"raises\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/provides/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"policies\\\": {},\\n\\t\\t\\t\\\"glossary\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"definition\\\": \\\"A customer's request to buy some books, paid up front\\\",\\n\\t\\t\\t\\t\\t\\\"aliases\\\": [\\\"Purchase\\\"],\\n\\t\\t\\t\\t\\t\\\"embodiedBy\\\": { \\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order\\\" }\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"schemas\\\": {\\n\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"What other parts learn when an order is placed\\\",\\n\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"relationships\\\": [],\\n\\t\\\"teams\\\": {\\n\\t\\t\\\"shop_team\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Shop Team\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Runs the online shop\\\"\\n\\t\\t}\\n\\t}\\n}\\n\" },\n\t{ path: \"examples/minimal.workspace.ts\", content: \"// The same bookshop as minimal.ods.json, authored with the DSL.\\n// Run with `node minimal.workspace.ts` (Node 24) or `npx tsx minimal.workspace.ts`.\\nimport fs from \\\"node:fs\\\";\\nimport { Workspace } from \\\"@open-domain-specification/core\\\";\\n\\nexport const workspace = new Workspace(\\\"Bookshop\\\", {\\n\\todsVersion: \\\"1.0.0\\\",\\n\\tversion: \\\"0.1.0\\\",\\n\\tdescription:\\n\\t\\t\\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n});\\n\\nconst bookselling = workspace.addDomain(\\\"Bookselling\\\", {\\n\\tdescription: \\\"Everything involved in selling books online\\\",\\n});\\nconst sales = bookselling.addSubdomain(\\\"Sales\\\", {\\n\\ttype: \\\"core\\\",\\n\\tdescription: \\\"Taking and fulfilling orders\\\",\\n});\\nconst shopTeam = workspace.addTeam(\\\"Shop Team\\\", {\\n\\tdescription: \\\"Runs the online shop\\\",\\n});\\n\\nconst orders = sales.addBoundedcontext(\\\"Orders\\\", {\\n\\tdescription: \\\"Owns orders from placement to dispatch\\\",\\n\\tteam: shopTeam,\\n});\\n\\nconst orderAgg = orders.addAggregate(\\\"Order\\\", {\\n\\tdescription: \\\"One customer's request to buy some books\\\",\\n});\\nconst order = orderAgg.addRootEntity(\\\"Order\\\", {\\n\\tdescription: \\\"The order itself\\\",\\n});\\norder.addAttribute(\\\"orderNumber\\\", { type: \\\"order number\\\", identity: true });\\nconst total = order.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst address = orderAgg.addValueObject(\\\"Address\\\", {\\n\\tdescription: \\\"Where the order ships to\\\",\\n});\\naddress.addAttribute(\\\"lines\\\", { type: \\\"text\\\" });\\naddress.addAttribute(\\\"postcode\\\", { type: \\\"postcode\\\" });\\norder.uses(address, \\\"ships-to\\\", \\\"1\\\");\\n\\norderAgg\\n\\t.addInvariant(\\\"Total not negative\\\", {\\n\\t\\tdescription: \\\"An order's total can never be below zero\\\",\\n\\t})\\n\\t.constrains(total);\\n\\nconst orderPlacedSchema = orders.addSchema(\\\"OrderPlaced\\\", {\\n\\tdescription: \\\"What other parts learn when an order is placed\\\",\\n});\\norderPlacedSchema.addAttribute(\\\"orderNumber\\\", {\\n\\ttype: \\\"order number\\\",\\n\\tidentity: true,\\n});\\norderPlacedSchema.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst orderPlaced = orderAgg.provides(\\\"OrderPlaced\\\", {\\n\\ttype: \\\"event\\\",\\n\\tdescription: \\\"A customer placed an order\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: orderPlacedSchema,\\n});\\n\\nconst orderApi = orders.addService(\\\"Order API\\\", {\\n\\ttype: \\\"application\\\",\\n\\tdescription: \\\"The endpoints the web shop calls\\\",\\n});\\norderApi\\n\\t.provides(\\\"PlaceOrder\\\", {\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tdescription: \\\"POST /orders\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t})\\n\\t.raises(orderPlaced);\\n\\norders.addTerm(\\\"Order\\\", {\\n\\tdefinition: \\\"A customer's request to buy some books, paid up front\\\",\\n\\taliases: [\\\"Purchase\\\"],\\n\\tembodiedBy: orderAgg,\\n});\\n\\n// Generator: validate, then write the workspace beside its schema.\\nif (process.argv[1]?.endsWith(\\\"minimal.workspace.ts\\\")) {\\n\\tfor (const d of workspace.validate()) {\\n\\t\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t}\\n\\tfs.mkdirSync(\\\".ods\\\", { recursive: true });\\n\\tfs.writeFileSync(\\n\\t\\t\\\".ods/bookshop.json\\\",\\n\\t\\tJSON.stringify(\\n\\t\\t\\t{ $schema: \\\"./schema.json\\\", ...workspace.toSchema() },\\n\\t\\t\\tnull,\\n\\t\\t\\t2,\\n\\t\\t),\\n\\t);\\n}\\n\" },\n\t{ path: \"examples/petstore.md\", content: \"# Patterns from the Petstore example\\n\\nExcerpts from the ODS example workspace (`models/petstore/src/workspace.ts`\\nin the ODS repository). Each shows one pattern worth copying.\\n\\n## A context serving two subdomains, and a legacy one\\n\\n```ts\\nconst inventoryBC = workspace.addBoundedContext(\\\"Inventory BC\\\", {\\n\\tdescription: \\\"Projection for /store/inventory (status→count)\\\",\\n\\tsubdomains: [inventorySD, catalogSD],\\n\\tteam: petShopTeam,\\n});\\nconst identityBC = usersSD.addBoundedcontext(\\\"Identity BC\\\", {\\n\\tdescription: \\\"Owns User aggregate & user endpoints. Legacy: user status is an untyped int\\\",\\n\\tbigBallOfMud: true,\\n\\tteam: platformTeam,\\n});\\n```\\n\\n## Attributes backed by value objects, relations with cardinality, invariants on attributes\\n\\n```ts\\npetRoot.addAttribute(\\\"id\\\", { type: \\\"int64\\\", identity: true });\\npetRoot.addAttribute(\\\"status\\\", { type: \\\"PetStatus\\\", valueobject: petStatusVO });\\npetRoot.uses(categoryVO, \\\"categorized-as\\\", \\\"0..1\\\");\\npetRoot.uses(photoUrlVO, \\\"has-photo\\\", \\\"1..*\\\");\\npetAgg\\n\\t.addInvariant(\\\"NameRequired\\\", { description: \\\"Pet.name must be non-empty\\\" })\\n\\t.constrains(petRoot.attributes.get(\\\"name\\\")!);\\n```\\n\\n## A cross-aggregate reference by identity to the other root\\n\\n```ts\\norderRoot.references(petRoot, \\\"for-pet\\\", \\\"1\\\");\\n```\\n\\n## Published events with a payload schema, and an internal operation that raises one\\n\\n```ts\\nconst petStatusChangedSchema = catalogBC.addSchema(\\\"PetStatusChanged\\\");\\npetStatusChangedSchema.addAttribute(\\\"petId\\\", { type: \\\"int64\\\", identity: true });\\n\\nconst petStatusChanged = petAgg.provides(\\\"PetStatusChanged\\\", {\\n\\tdescription: \\\"Pet status changed (available|pending|sold)\\\",\\n\\ttype: \\\"event\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: petStatusChangedSchema,\\n});\\nconst _changePetStatus = petAgg\\n\\t.provides(\\\"ChangePetStatus\\\", {\\n\\t\\tdescription: \\\"Move a pet between available, pending and sold\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tinternal: true,\\n\\t\\tschema: petStatusChangedSchema,\\n\\t})\\n\\t.raises(petStatusChanged);\\n```\\n\\n## An open-host application service whose operations raise the aggregate's events\\n\\n```ts\\nconst petApp = catalogBC.addService(\\\"PetApp\\\", {\\n\\tdescription: \\\"Open-host service for /pet endpoints\\\",\\n\\ttype: \\\"application\\\",\\n});\\nconst _addPetOp = petApp\\n\\t.provides(\\\"AddPet\\\", {\\n\\t\\tdescription: \\\"POST /pet\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t\\tschema: registerPetSchema,\\n\\t})\\n\\t.raises(petRegistered);\\n```\\n\\n## A consumption through an anti-corruption layer, and the relationship that explains it\\n\\n```ts\\norderApp.consumes(getPetSummaryOp, { pattern: \\\"anti-corruption-layer\\\" });\\n\\nsalesBC.downstreamOf(catalogBC, {\\n\\ttype: \\\"customer-supplier\\\",\\n\\tupstreamRoles: [\\\"open-host-service\\\"],\\n\\tdownstreamRoles: [\\\"anti-corruption-layer\\\"],\\n\\tdescription: \\\"Sales needs pet availability; Catalog commits to the summary contract\\\",\\n});\\n```\\n\\n## Separate ways, on purpose\\n\\n```ts\\nidentityBC.separateWaysFrom(\\n\\tsalesBC,\\n\\t\\\"Orders are anonymous in Petstore v3; no integration by design\\\",\\n);\\n```\\n\\n## A policy reacting to events from two contexts\\n\\n```ts\\nsalesBC\\n\\t.addPolicy(\\\"Approve when pet available\\\", {\\n\\t\\tdescription: \\\"When a pet becomes available and an order for it is placed, approve the order\\\",\\n\\t})\\n\\t.on(petStatusChanged, orderPlaced)\\n\\t.then(approveOrder);\\n```\\n\\n## Conformist consumptions feeding a projection\\n\\n```ts\\ninventoryAgg.consumes(petStatusChanged, { pattern: \\\"conformist\\\" });\\ninventoryAgg.consumes(orderApproved, { pattern: \\\"conformist\\\" });\\n```\\n\\n## Glossary terms embodied by model elements\\n\\n```ts\\ncatalogBC.addTerm(\\\"Category\\\", {\\n\\tdefinition: \\\"The kind of animal a pet is, such as Dogs or Cats\\\",\\n\\taliases: [\\\"Species\\\"],\\n\\tembodiedBy: categoryVO,\\n});\\n```\\n\" },\n\t{ path: \"examples/validate.mjs\", content: \"#!/usr/bin/env node\\n// Validates one or more ODS workspace JSON files.\\n// Usage: node validate.mjs .ods/petstore.json [.ods/other.json ...]\\n// Exits 1 when a file fails to load or has an error-level diagnostic.\\nimport { readFileSync } from \\\"node:fs\\\";\\nimport { createRequire } from \\\"node:module\\\";\\n\\nconst require = createRequire(`${process.cwd()}/`);\\nlet Workspace;\\ntry {\\n\\t({ Workspace } = require(\\\"@open-domain-specification/core\\\"));\\n} catch {\\n\\tconsole.error(\\n\\t\\t\\\"@open-domain-specification/core is not installed here. Run from the project root, install it (npm i -D @open-domain-specification/core), or use: npx -p @open-domain-specification/core node validate.mjs <file>\\\",\\n\\t);\\n\\tprocess.exit(2);\\n}\\n\\nlet failed = false;\\nfor (const file of process.argv.slice(2)) {\\n\\tlet workspace;\\n\\ttry {\\n\\t\\tworkspace = Workspace.fromSchema(JSON.parse(readFileSync(file, \\\"utf8\\\")));\\n\\t} catch (error) {\\n\\t\\tconsole.log(\\n\\t\\t\\t`[load-error] ${file}: ${error instanceof Error ? error.message : error}`,\\n\\t\\t);\\n\\t\\tfailed = true;\\n\\t\\tcontinue;\\n\\t}\\n\\tconst diagnostics = workspace.validate();\\n\\tconsole.log(`${file}: ${diagnostics.length} diagnostic(s)`);\\n\\tfor (const d of diagnostics) {\\n\\t\\tconsole.log(` [${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t\\tif (d.severity === \\\"error\\\") failed = true;\\n\\t}\\n}\\nprocess.exit(failed ? 1 : 0);\\n\" },\n\t{ path: \"references/ddd-glossary.md\", content: \"# DDD terms in one sentence each\\n\\nUse these the first time a term comes up, filling the example with the user's own words.\\nNever repeat an explanation, and never explain a term the user already used correctly.\\n\\n- **Domain** — the whole area of business the system exists for, e.g. \\\"running the pet store\\\".\\n- **Subdomain** — one slice of that problem, e.g. \\\"the catalogue\\\" or \\\"taking orders\\\"; calling it\\n *core* only marks where your competitive effort goes, *supporting* means needed but ordinary,\\n *generic* means you would buy it.\\n- **Bounded context** — a boundary inside which every word has one exact meaning; your billing\\n \\\"Customer\\\" and your support \\\"Customer\\\" being different things is why they get separate\\n contexts.\\n- **Ubiquitous language / glossary** — the words a context uses, written down once so code,\\n conversations and documents all mean the same thing by \\\"Order\\\".\\n- **Team ownership** — the people who decide what a context means and how it changes.\\n- **Big ball of mud** — a context whose model nobody fully controls, flagged so that anything\\n talking to it translates rather than trusts.\\n- **Entity** — something that matters because of *which one* it is, like this particular order,\\n so it carries an identity.\\n- **Value object** — something that matters only by its values, like an address; two with the\\n same values are interchangeable.\\n- **Attribute** — one piece of information an entity, value object or message carries; the\\n identity attribute is the one that tells two entities apart.\\n- **Aggregate** — the cluster of things you change together and check rules across, named after\\n its *root*, the one thing you go through to change any of it; the order and its lines.\\n- **Invariant** — a rule that must always hold inside an aggregate, such as \\\"quantity is never\\n zero\\\".\\n- **Relation** — how one thing points at another: *includes* for parts that cannot exist alone,\\n *uses* for values it carries, *references* for another aggregate's root by identity.\\n- **Cardinality** — how many of the other thing: exactly one, at most one, any number, at least\\n one.\\n- **Operation** — something you can ask a part of the system to do, like \\\"place an order\\\"; in\\n conversation people often say *command*.\\n- **Event** — a fact that already happened, stated in the past tense, like \\\"order placed\\\", that\\n other parts can react to.\\n- **Consumable** — an operation or event that a part offers, and **consumption** is another part\\n using it.\\n- **Schema** — the shape of the information that travels with an operation or event.\\n- **Policy** — a rule of the form \\\"when this event happens, do that operation\\\", possibly across\\n contexts.\\n- **Application service** — the part that fronts an API or a screen and turns requests into\\n operations on aggregates.\\n- **Domain service** — business logic that does not belong to any single thing, like pricing\\n across several orders.\\n- **Upstream / downstream** — the side that is depended on, and the side that depends on it.\\n- **Customer-supplier** — a dependency where the downstream side gets a say before the upstream\\n side changes things.\\n- **Partnership** — two contexts whose teams plan and release together.\\n- **Shared kernel** — code or data two contexts both own and change.\\n- **Separate ways** — a deliberate decision that two contexts will not integrate.\\n- **Open host service** — the upstream side offers a documented API for anyone to use.\\n- **Published language** — the upstream side offers a documented message format everyone\\n agrees on.\\n- **Conformist** — the downstream side takes the upstream model as it comes.\\n- **Anti-corruption layer** — the downstream side copies and reshapes what it receives into its\\n own terms, so the upstream model cannot leak in.\\n\" },\n\t{ path: \"references/dsl-api.md\", content: \"# DSL reference (`@open-domain-specification/core`)\\n\\nEvery class is created through its parent and registers itself there, so `parent.addX(...)`\\nis the only call needed. Every attributes object accepts an optional `id` to fix the id\\nindependently of the name.\\n\\n| Receiver | Method | Creates / does |\\n|---|---|---|\\n| — | `new Workspace(name, { odsVersion, description, version, homepage?, logoUrl?, primaryColor?, id? })` | the workspace |\\n| `Workspace` | `addDomain(name, { description })` | a domain |\\n| `Workspace` | `addTeam(name, { description?, homepage? })` | a team |\\n| `Workspace` | `addBoundedContext(name, { description, subdomains?, bigBallOfMud?, team? })` | a context serving zero or more subdomains |\\n| `Workspace` | `addRelationship({...})` | a relationship; prefer the context helpers below |\\n| `Workspace` | `validate()` | the diagnostics list |\\n| `Workspace` | `toSchema()` / `Workspace.fromSchema(json)` | serialise / load |\\n| `Domain` | `addSubdomain(name, { type, description })` | a subdomain; `type` is `\\\"core\\\" \\\\| \\\"supporting\\\" \\\\| \\\"generic\\\"` |\\n| `Subdomain` | `addBoundedcontext(name, { description, bigBallOfMud?, team? })` | a context serving this subdomain |\\n| `BoundedContext` | `serves(subdomain)` | adds a served subdomain |\\n| `BoundedContext` | `ownedBy(team)` | sets the owning team |\\n| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description? })` | directed relationship, this side upstream; `type` defaults to `\\\"upstream-downstream\\\"`, or `\\\"customer-supplier\\\"` |\\n| `BoundedContext` | `downstreamOf(other, options)` | the same, this side downstream |\\n| `BoundedContext` | `partnerOf(other, description?)` | partnership |\\n| `BoundedContext` | `sharesKernelWith(other, description?)` | shared kernel |\\n| `BoundedContext` | `separateWaysFrom(other, description?)` | separate ways |\\n| `BoundedContext` | `addAggregate(name, { description })` | an aggregate |\\n| `BoundedContext` | `addService(name, { type, description })` | a service; `type` is `\\\"application\\\" \\\\| \\\"domain\\\"` |\\n| `BoundedContext` | `addPolicy(name, { description })` | a policy; chain `.on(...events).then(...operations)` |\\n| `BoundedContext` | `addTerm(name, { definition, aliases?, embodiedBy? })` | a glossary term; or chain `.embody(element)` |\\n| `BoundedContext` | `addSchema(name, { description? })` | a payload schema; add fields with `addAttribute` |\\n| `Aggregate` | `addRootEntity(name, { description })` | the root entity |\\n| `Aggregate` | `addEntity(name, { description, root? })` | an entity |\\n| `Aggregate` | `addValueObject(name, { description })` | a value object |\\n| `Aggregate` | `addInvariant(name, { description })` | an invariant; chain `.constrains(...entities, valueObjects or attributes)` |\\n| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema? })` | a consumable; `type` is `\\\"event\\\" \\\\| \\\"operation\\\"`, `pattern` is `\\\"open-host-service\\\" \\\\| \\\"published-language\\\"` |\\n| `Aggregate`, `Service` | `consumes(consumable, { pattern? })` | a consumption; `pattern` is `\\\"conformist\\\" \\\\| \\\"anti-corruption-layer\\\"` |\\n| `Consumable` | `raises(...events)` | the events an operation raises |\\n| `Entity`, `ValueObject`, `DataSchema` | `addAttribute(name, { type, description?, identity?, valueobject? })` | an attribute; `type` is free text |\\n| `Entity`, `ValueObject` | `uses(target, label, cardinality?)` | a `uses` relation |\\n| `Entity`, `ValueObject` | `includes(target, label, cardinality?)` | an `includes` relation |\\n| `Entity`, `ValueObject` | `references(target, label, cardinality?)` | a `references` relation; across aggregates target the root |\\n| `Entity`, `ValueObject` | `addRelation(target, { relation, label?, cardinality? })` | any relation explicitly |\\n| `Entity` | `.attributes.get(\\\"name\\\")` | look an attribute up, e.g. to constrain it |\\n\\n`cardinality` is `\\\"1\\\" | \\\"0..1\\\" | \\\"*\\\" | \\\"1..*\\\"`. Chainable methods (`raises`, `on`, `then`,\\n`constrains`, `embody`, `serves`, `ownedBy`) return their receiver.\\n\" },\n\t{ path: \"references/dsl-mode.md\", content: \"# DSL mode\\n\\nThe TypeScript source is the artefact; the JSON under `.ods/` is generated from it. Edit the\\nsource, run the generator, read the diagnostics it prints.\\n\\n## Find the generator\\n\\nLook for a file that imports `Workspace` from `@open-domain-specification/core`, builds the\\nmodel, and writes `workspace.toSchema()` to disk. The canonical shape (from the ODS example\\npackage) is:\\n\\n```ts\\nimport fs from \\\"node:fs\\\";\\nimport { workspace } from \\\"./petstore/workspace.ts\\\";\\n\\nfor (const d of workspace.validate()) {\\n\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n}\\n\\nfs.mkdirSync(\\\".ods\\\", { recursive: true });\\nfs.writeFileSync(\\n\\t\\\".ods/petstore.json\\\",\\n\\tJSON.stringify({ $schema: \\\"./schema.json\\\", ...workspace.toSchema() }, null, 2),\\n);\\n```\\n\\n`package.json` usually has a script for it (`build`, `ods`, `generate`, `model`). Node 24 runs\\n`.ts` files directly; on older Node use `npx tsx <file>`.\\n\\n## Loop\\n\\n1. Edit the model source. Keep the file's existing sections and ordering (domains, teams,\\n contexts, then one section per context).\\n2. Run the generator. It validates and rewrites the JSON.\\n3. Read every `[error]` and `[warning]` line and explain it to the user with\\n `validation-rules.md`.\\n4. Never hand-edit the emitted JSON; the next run overwrites it. If the user edits it, tell\\n them and offer to port the change into the source.\\n\\nIf the generator does not print diagnostics, add the four-line loop above before the write.\\n`toSchema()` does not emit `$schema`; spread it back in exactly as shown, so editors and the\\nVS Code extension keep the file associated with `schema.json`.\\n\\n## Ids and renames\\n\\nIds are derived from names with `snake_case` unless `id` is passed. Because ids are the JSON\\nkeys and the ref segments, renaming an element by changing its name silently changes its id\\nand breaks anything outside the source that points at it (documentation links, bookmarks,\\nother files). When renaming, pass the old id explicitly:\\n\\n```ts\\n// was: catalogBC.addAggregate(\\\"Pet\\\", {...})\\ncatalogBC.addAggregate(\\\"Listed Pet\\\", { id: \\\"pet\\\", description: \\\"...\\\" });\\n```\\n\\n## Conventions from the example\\n\\n- Name the variables after the element and its kind (`petAgg`, `petRoot`, `categoryVO`,\\n `petApp`), so refs read naturally in the code.\\n- Prefix a variable with `_` when the element is kept only for its side effect on the model\\n (an operation nobody references again).\\n- Create all consumables before the policies and consumptions that point at them.\\n- Full DSL surface: `dsl-api.md`. Patterns worth copying: `examples/petstore.md`.\\n\" },\n\t{ path: \"references/interview-playbook.md\", content: \"# Interview playbook\\n\\nYou are the facilitator. The user knows their system and their business; you know DDD. Your\\njob is to get the model out of their head without making them learn the vocabulary first.\\n\\n## Ground rules\\n\\n- One question per turn. Wait for the answer.\\n- No DDD word before its one-sentence explanation (see `ddd-glossary.md`), and each term is\\n explained once.\\n- After every answer, paraphrase it as the element you would record: \\\"So I'd note ... right?\\\"\\n- Write the increment as soon as a context or an aggregate is stable. Do not wait until the\\n whole interview is done; a model in the file beats a model in the chat.\\n- Skip any phase the existing workspace already covers. Read first, ask second.\\n- Keep the user's words. Descriptions and glossary definitions are written in their language,\\n not in DDD language.\\n\\n## Phase A: orientation (produces the Workspace)\\n\\n- \\\"In one or two sentences, what does this system do, and for whom?\\\" → `name`, `description`.\\n- \\\"Is there a homepage or logo I should link?\\\" → `homepage`, `logoUrl` (skip if none).\\n\\n## Phase B: the problem space (produces Domains and Subdomains with a type)\\n\\n- \\\"What are the big areas of the business this covers? Think of the headings you would put\\n on a whiteboard.\\\" → domains.\\n- Per area: \\\"What distinct jobs sit inside that area?\\\" → subdomains.\\n- Per subdomain: \\\"Is this something that makes you different from competitors, something you\\n need but any sensible way of doing it is fine, or something you would happily buy off the\\n shelf?\\\" → `core` / `supporting` / `generic`.\\n- Explain once: a subdomain is one slice of the problem; calling it core only marks where your\\n competitive effort goes.\\n\\n## Phase C: ownership (produces Teams, Bounded Contexts, `subdomains`, `bigBallOfMud`)\\n\\n- \\\"Which teams or people work on this, and which parts does each look after?\\\" → teams, and a\\n candidate context per part.\\n- \\\"If two teams both say <word>, do they mean exactly the same thing?\\\" If not, that is two\\n contexts. Explain once: a bounded context is a boundary inside which every word has one\\n exact meaning; your billing \\\"Customer\\\" and your support \\\"Customer\\\" being different is why\\n they get separate contexts.\\n- \\\"Which of the jobs from before does each part serve?\\\" → `subdomains` refs. One context may\\n serve several.\\n- \\\"Is any of these an old system that nobody fully understands, where the data model is a\\n mess?\\\" → `bigBallOfMud: true`. Explain: we flag it so anything talking to it knows to\\n translate rather than trust.\\n\\n## Phase D: the integration map (produces Relationships and seeds consumptions)\\n\\n- \\\"Which parts talk to each other? For each pair, who depends on whom?\\\" → `upstream-downstream`\\n with the depended-on side upstream.\\n- \\\"When the upstream team changes something, does the downstream team get a say beforehand?\\\"\\n Yes → `customer-supplier`.\\n- \\\"Do those two teams plan and release together, as one?\\\" → `partnership`.\\n- \\\"Do they share actual code or tables that both change?\\\" → `shared-kernel`.\\n- \\\"Are there two parts that you have decided, on purpose, should never integrate?\\\" →\\n `separate-ways`.\\n- \\\"How does the downstream side take the data: as it comes, or does it copy and reshape it\\n into its own terms?\\\" → `conformist` / `anti-corruption-layer`. Goes on `downstreamRoles`\\n and on each consumption's `pattern`.\\n- \\\"Does the upstream side publish a documented API, or a documented message format?\\\" →\\n `open-host-service` / `published-language`. Goes on `upstreamRoles` and on each exposed\\n consumable's `pattern`.\\n\\n## Phase E: inside one context (produces Aggregates, Entities, Value Objects, Invariants, Glossary)\\n\\nRepeat for each context the user wants detailed. Ask which one to start with.\\n\\n- \\\"Inside <context>, what are the things people talk about? Just list the nouns.\\\" → candidate\\n entities and value objects; every noun becomes a glossary term with the user's definition.\\n- Per noun: \\\"If two of these had identical details, would they still be two different things?\\\"\\n Yes → entity; no → value object. Explain once: an entity matters because of which one it is\\n (this order, not that one); a value object matters only by its values (an address).\\n- \\\"What identifies it: an order number, an email?\\\" → an attribute with `identity: true`.\\n- \\\"What details does it carry?\\\" → attributes, with `type` in the user's words.\\n- \\\"Which of these do you always change or check together? What must be true across all of\\n them at once?\\\" → the aggregate boundary. The thing they state the rule about is the root.\\n Explain once: an aggregate is the cluster you change together and check rules across; the\\n root is the one you name it after.\\n- \\\"What must never be allowed to happen to a <root>?\\\" → invariants, each constraining the\\n entity, value object or attribute it is about.\\n- \\\"Does a <root> point at things in another cluster, for example an order pointing at a\\n product?\\\" → `references` to that cluster's root; ask \\\"one or many?\\\" for cardinality.\\n- \\\"Does it contain things that cannot exist without it?\\\" → `includes`.\\n- \\\"Does it use a value like an address, money or a status?\\\" → `uses`.\\n\\n## Phase F: behaviour (produces Consumables, `raises`, Policies, Schemas)\\n\\n- \\\"What can someone ask this part to do?\\\" → `operation` consumables. Put an API entry point on\\n an application service, and a state change of one aggregate on that aggregate.\\n- \\\"When that happens, what fact would you announce to the rest of the business?\\\" → `event`\\n consumable, linked from the operation with `raises`. Events are past tense.\\n- \\\"Is that something only this part uses, or would other parts care?\\\" → `internal: true`, or\\n an upstream `pattern`.\\n- \\\"What information travels with that announcement or request?\\\" → a schema on the context,\\n attached with `schema`.\\n- \\\"When <event> happens, what do you then do automatically?\\\" → a policy with `on` the event\\n and `then` the operation. Either side may live in another context.\\n- \\\"Who outside this part listens for <event>?\\\" → a consumption on their aggregate or service,\\n with a downstream `pattern`.\\n- Close: \\\"Which of the words we used should I define, and does each map to one of the things\\n we modelled?\\\" → glossary terms with `embodiedBy`.\\n\\n## Phase G: validate and reflect\\n\\nRun validation. Explain each diagnostic in one plain sentence, propose the fix, and ask before\\napplying fixes for warnings. Then summarise what changed, in the user's words, and ask what to\\nmodel next.\\n\" },\n\t{ path: \"references/json-mode.md\", content: \"# JSON mode\\n\\nThe workspace files are the artefact. Each `.ods/*.json` file is one complete workspace, and\\nthe VS Code extension, the docs generator and anyone else load it with `Workspace.fromSchema`.\\n\\n## Files\\n\\n- `.ods/` (or the folder named by the VS Code setting `ods.folder`) at the project root.\\n- `.ods/schema.json`: the JSON Schema, written by the extension (`ODS: Write schema.json`).\\n Never edit it. If it is missing, copy it from\\n `node_modules/@open-domain-specification/core/dist/workspace.schema.json`.\\n- `.ods/<workspace-id>.json`: one workspace per file. The first key is\\n `\\\"$schema\\\": \\\"./schema.json\\\"`; the loader ignores it, editors use it for completion.\\n- Keep the file's `id` equal to its basename, and `odsVersion` equal to the other files' (use\\n `\\\"1.0.0\\\"` for a first file).\\n\\nThe smallest valid file is `examples/minimal.ods.json`. Copy it when creating a workspace, then\\ngrow it.\\n\\n## Editing rules\\n\\n- The schema is strict: every required field is present even when empty, and unknown fields\\n are rejected. `references/model-reference.md` lists them.\\n- Ids are the object keys. Create them as `snake_case` of the name, then never change them.\\n Renaming is changing `name`.\\n- Every `$ref` follows the grammar at the end of `model-reference.md` and points at something\\n that exists. A dangling ref makes the whole file fail to load; the extension then shows\\n \\\"Workspace file could not be loaded\\\" instead of diagnostics.\\n- Preserve the key order and two-space indentation of the file so diffs stay readable.\\n- Prefer several small edits, each followed by validation, over one large rewrite.\\n\\n## Validation\\n\\nThere is no CLI. Run `examples/validate.mjs` from the project root:\\n\\n```sh\\nnode .claude/skills/ods-authoring/examples/validate.mjs .ods/petstore.json\\n```\\n\\nOr inline:\\n\\n```sh\\nnode -e 'const {Workspace}=require(\\\"@open-domain-specification/core\\\");const f=process.argv[1];const ws=Workspace.fromSchema(JSON.parse(require(\\\"fs\\\").readFileSync(f,\\\"utf8\\\")));for(const d of ws.validate())console.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`)' .ods/petstore.json\\n```\\n\\nIf `@open-domain-specification/core` is not installed, prefix with\\n`npx -p @open-domain-specification/core` or install it as a devDependency. The VS Code Problems\\npanel shows the same diagnostics (source `ods`, code = rule id) and updates on save.\\n\\n## Several workspace files\\n\\nA `.ods` folder may hold several files. Treat each as its own workspace; refs never cross files.\\n\" },\n\t{ path: \"references/model-reference.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (dist/workspace.schema.json); do not edit. -->\\n\\n# ODS model reference\\n\\nThe complete shape of a workspace file. Collections keyed by id are JSON objects whose keys are the element ids; `array of` collections are JSON arrays. Required fields must be present even when empty (`{}` or `[]`).\\n\\n## Workspace\\n\\nRepresents a workspace in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `$schema` | string | no | Location of the JSON schema this document conforms to, usually the schema.json beside it. Ignored by the loader. |\\n| `boundedcontexts` | map of id to [BoundedContext](#boundedcontext) | yes | |\\n| `description` | string | yes | |\\n| `domains` | map of id to [Domain](#domain) | yes | |\\n| `homepage` | string | no | |\\n| `id` | string | yes | |\\n| `logoUrl` | string | no | |\\n| `name` | string | yes | |\\n| `odsVersion` | string | yes | |\\n| `primaryColor` | string | no | |\\n| `relationships` | array of [ContextRelationship](#contextrelationship) | yes | |\\n| `teams` | map of id to [Team](#team) | yes | |\\n| `version` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Aggregate\\n\\nRepresents an aggregate in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `entities` | map of id to [Entity](#entity) | yes | |\\n| `invariants` | map of id to [Invariant](#invariant) | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `valueobjects` | map of id to [ValueObject](#valueobject) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Attribute\\n\\nA named, typed property of an entity, value object or schema.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `identity` | boolean | no | True when this attribute is (part of) the identity of an entity. |\\n| `name` | string | yes | |\\n| `type` | string | yes | Free-form type name, e.g. `string`, `Money`, `Date`. |\\n| `valueobject` | `{ \\\"$ref\\\": string }` | no | The value object that models this attribute's type, when there is one. |\\n\\nNo other fields are allowed.\\n\\n## BoundedContext\\n\\nRepresents a bounded context in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aggregates` | map of id to [Aggregate](#aggregate) | yes | |\\n| `bigBallOfMud` | boolean | no | Marks a context whose model is not coherent (typically legacy) so that neighbours know to protect themselves from it. |\\n| `description` | string | yes | |\\n| `glossary` | map of id to [GlossaryTerm](#glossaryterm) | yes | |\\n| `name` | string | yes | |\\n| `policies` | map of id to [Policy](#policy) | yes | |\\n| `schemas` | map of id to [DataSchema](#dataschema) | yes | Payload shapes this context publishes or accepts, referenced by its consumables. |\\n| `services` | map of id to [Service](#service) | yes | |\\n| `subdomains` | array of `{ \\\"$ref\\\": string }` | yes | The subdomains this context serves; a context may serve several. |\\n| `team` | `{ \\\"$ref\\\": string }` | no | The team that owns this context. |\\n\\nNo other fields are allowed.\\n\\n## Consumable\\n\\nRepresents a consumable in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `internal` | boolean | no | True when the consumable stays inside its context: an event only local policies react to, or an operation only local callers issue. Internal consumables may not be consumed from another context. |\\n| `name` | string | yes | |\\n| `pattern` | \\\"open-host-service\\\" | \\\"published-language\\\" | no | The upstream role this consumable is offered under. Absent on internal consumables. |\\n| `raises` | array of `{ \\\"$ref\\\": string }` | no | For operations: the event consumables this operation may raise. |\\n| `schema` | `{ \\\"$ref\\\": string }` | no | The payload shape, one of the context's schemas. |\\n| `type` | \\\"event\\\" | \\\"operation\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Consumption\\n\\nRepresents a consumption in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumable` | `{ \\\"$ref\\\": string }` | yes | |\\n| `pattern` | \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | no | The downstream role the consumer adopts for this consumable. |\\n\\nNo other fields are allowed.\\n\\n## ContextRelationship\\n\\nA strategic relationship between two bounded contexts.\\n\\nOne of: [DirectedContextRelationship](#directedcontextrelationship), [SymmetricContextRelationship](#symmetriccontextrelationship).\\n\\n## DataSchema\\n\\nA named payload shape owned by a bounded context, shared by the consumables that carry it.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## DirectedContextRelationship\\n\\nAn upstream/downstream relationship between two bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `downstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `downstreamRoles` | array of \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | yes | |\\n| `type` | \\\"customer-supplier\\\" | \\\"upstream-downstream\\\" | yes | |\\n| `upstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `upstreamRoles` | array of \\\"open-host-service\\\" | \\\"published-language\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Domain\\n\\nRepresents a domain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `subdomains` | map of id to [Subdomain](#subdomain) | yes | |\\n\\nNo other fields are allowed.\\n\\n## EntityRelation\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `cardinality` | \\\"*\\\" | \\\"0..1\\\" | \\\"1\\\" | \\\"1..*\\\" | no | |\\n| `label` | string | no | |\\n| `relation` | \\\"includes\\\" | \\\"references\\\" | \\\"uses\\\" | yes | |\\n| `target` | `{ \\\"$ref\\\": string }` | yes | |\\n\\nNo other fields are allowed.\\n\\n## Entity\\n\\nRepresents an entity in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n| `root` | boolean | no | |\\n\\nNo other fields are allowed.\\n\\n## GlossaryTerm\\n\\nA term of the ubiquitous language of a bounded context.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aliases` | array of string | no | |\\n| `definition` | string | yes | |\\n| `embodiedBy` | `{ \\\"$ref\\\": string }` | no | The model element that embodies this term, when there is one. |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Invariant\\n\\nRepresents an invariant in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `constrains` | array of `{ \\\"$ref\\\": string }` | yes | The entities, value objects or attributes this invariant constrains. |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Policy\\n\\nA reaction: when these events happen, issue these commands.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `on` | array of `{ \\\"$ref\\\": string }` | yes | The event consumables that trigger this policy. |\\n| `then` | array of `{ \\\"$ref\\\": string }` | yes | The operation consumables this policy issues. |\\n\\nNo other fields are allowed.\\n\\n## Service\\n\\nRepresents a service in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `type` | \\\"application\\\" | \\\"domain\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Subdomain\\n\\nRepresents a subdomain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `type` | \\\"core\\\" | \\\"generic\\\" | \\\"supporting\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## SymmetricContextRelationship\\n\\nA relationship between two bounded contexts with no upstream or downstream side.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `participants` | array of unknown | yes | |\\n| `type` | \\\"partnership\\\" | \\\"separate-ways\\\" | \\\"shared-kernel\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Team\\n\\nA team that owns one or more bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `homepage` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## ValueObject\\n\\nRepresents a value object in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Ref grammar\\n\\nEvery cross-link is an object `{ \\\"$ref\\\": \\\"<path>\\\" }`. Paths are JSON pointers into the workspace, built from the ids that key each collection:\\n\\n| Element | Path |\\n|---|---|\\n| Team | `#/teams/<team>` |\\n| Domain | `#/domains/<domain>` |\\n| Subdomain | `#/domains/<domain>/subdomains/<subdomain>` |\\n| Bounded context | `#/boundedcontexts/<bc>` |\\n| Aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>` |\\n| Entity | `#/boundedcontexts/<bc>/aggregates/<aggregate>/entities/<entity>` |\\n| Value object | `#/boundedcontexts/<bc>/aggregates/<aggregate>/valueobjects/<vo>` |\\n| Invariant | `#/boundedcontexts/<bc>/aggregates/<aggregate>/invariants/<invariant>` |\\n| Attribute | `<owner path>/attributes/<attribute>` (owner is an entity, value object or schema) |\\n| Consumable of an aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>/provides/<consumable>` |\\n| Service | `#/boundedcontexts/<bc>/services/<service>` |\\n| Consumable of a service | `#/boundedcontexts/<bc>/services/<service>/provides/<consumable>` |\\n| Policy | `#/boundedcontexts/<bc>/policies/<policy>` |\\n| Glossary term | `#/boundedcontexts/<bc>/glossary/<term>` |\\n| Schema | `#/boundedcontexts/<bc>/schemas/<schema>` |\\n\\nA bounded context path never embeds the domain or subdomain, so moving a context between subdomains breaks no refs. A ref that points at nothing makes the whole file fail to load.\\n\" },\n\t{ path: \"references/translation-table.md\", content: \"# Translation table: what people say → what to record\\n\\nUse this while interviewing and when reviewing a draft for mis-modelling. `JSON` is where the\\nelement lives in a workspace file; `DSL` is the core call that creates it.\\n\\n| What they say | ODS element | JSON | DSL |\\n|---|---|---|---|\\n| \\\"the system for X\\\", \\\"our platform\\\" | Workspace | top-level `name`, `description` | `new Workspace(name, {...})` |\\n| \\\"the business areas\\\", \\\"departments\\\", \\\"lines of business\\\" | Domain | `domains.<id>` | `ws.addDomain(name, {description})` |\\n| \\\"the part that handles Y\\\", \\\"the Y job\\\" | Subdomain | `domains.<d>.subdomains.<id>` | `domain.addSubdomain(name, {type, description})` |\\n| \\\"what makes us different\\\", \\\"our secret sauce\\\" | subdomain type | `\\\"type\\\": \\\"core\\\"` | `type: \\\"core\\\"` |\\n| \\\"we need it but it's not special\\\", \\\"any sensible way works\\\" | subdomain type | `\\\"type\\\": \\\"supporting\\\"` | `type: \\\"supporting\\\"` |\\n| \\\"we'd buy it\\\", \\\"login / email / payments provider\\\" | subdomain type | `\\\"type\\\": \\\"generic\\\"` | `type: \\\"generic\\\"` |\\n| \\\"team X owns that\\\", \\\"that's Sam's service\\\" | Team, owning context | `teams.<id>`; context `team: {$ref}` | `ws.addTeam(name)`; `bc.ownedBy(team)` |\\n| \\\"in billing a customer means ..., in support it means ...\\\" | two Bounded Contexts | `boundedcontexts.<id>` twice | `subdomain.addBoundedcontext(name, {...})` twice |\\n| \\\"that part covers both Y and Z\\\" | context serving two subdomains | `subdomains: [{$ref Y}, {$ref Z}]` | `ws.addBoundedContext(name, {subdomains: [y, z]})` or `bc.serves(z)` |\\n| \\\"the old system\\\", \\\"legacy\\\", \\\"nobody understands its schema\\\" | big ball of mud | `\\\"bigBallOfMud\\\": true` | `bigBallOfMud: true` |\\n| \\\"A depends on B\\\", \\\"A calls B\\\", \\\"A reads B's data\\\" | upstream-downstream (B upstream) | `relationships[]` `type: \\\"upstream-downstream\\\"` | `a.downstreamOf(b, {...})` |\\n| \\\"they ask us before changing\\\", \\\"we're their customer\\\" | customer-supplier | `type: \\\"customer-supplier\\\"` | `a.downstreamOf(b, {type: \\\"customer-supplier\\\", ...})` |\\n| \\\"both teams change it together\\\", \\\"we release together\\\" | partnership | `type: \\\"partnership\\\"`, `participants` | `a.partnerOf(b)` |\\n| \\\"we share the same tables / library / code\\\" | shared kernel | `type: \\\"shared-kernel\\\"` | `a.sharesKernelWith(b)` |\\n| \\\"we deliberately don't integrate\\\" | separate ways | `type: \\\"separate-ways\\\"` | `a.separateWaysFrom(b, why)` |\\n| \\\"we use their API as-is\\\", \\\"we take whatever they send\\\" | conformist | consumption `pattern: \\\"conformist\\\"`; relationship `downstreamRoles` | `agg.consumes(c, {pattern: \\\"conformist\\\"})` |\\n| \\\"we copy and reshape their data\\\", \\\"we wrap their API\\\" | anti-corruption layer | `pattern: \\\"anti-corruption-layer\\\"` | `agg.consumes(c, {pattern: \\\"anti-corruption-layer\\\"})` |\\n| \\\"we expose a documented API\\\", \\\"there's a REST endpoint\\\" | open host service | operation `pattern: \\\"open-host-service\\\"`; relationship `upstreamRoles` | `svc.provides(name, {type: \\\"operation\\\", pattern: \\\"open-host-service\\\"})` |\\n| \\\"we publish a message format everyone agrees on\\\" | published language | event `pattern: \\\"published-language\\\"` | `agg.provides(name, {type: \\\"event\\\", pattern: \\\"published-language\\\"})` |\\n| \\\"this specific order\\\", \\\"the thing with a number\\\" | Entity | `aggregates.<a>.entities.<id>` | `agg.addEntity(name, {description})` |\\n| \\\"the main thing\\\", \\\"the one we name the rule about\\\" | root entity | `\\\"root\\\": true` | `agg.addRootEntity(name, {description})` |\\n| \\\"just a value\\\", \\\"an address\\\", \\\"money\\\", \\\"same values, same thing\\\" | Value Object | `aggregates.<a>.valueobjects.<id>` | `agg.addValueObject(name, {description})` |\\n| \\\"it has a field\\\", \\\"it's made of\\\" | Attribute | `attributes.<id>` with `type` in the user's words | `entity.addAttribute(name, {type})` |\\n| \\\"the number that identifies it\\\" | identity attribute | `\\\"identity\\\": true` | `identity: true` |\\n| \\\"its status is one of these values\\\" | attribute backed by a value object | `attributes.<id>.valueobject: {$ref}` | `addAttribute(name, {type, valueobject: vo})` |\\n| \\\"these change together\\\", \\\"one transaction\\\", \\\"the order and its lines\\\" | Aggregate | `aggregates.<id>` | `bc.addAggregate(name, {description})` |\\n| \\\"must never\\\", \\\"always has to\\\", \\\"can't be negative\\\", \\\"only when\\\" | Invariant | `invariants.<id>` with `constrains: [{$ref}]` | `agg.addInvariant(name, {description}).constrains(target)` |\\n| \\\"an order points at a pet\\\" (another cluster) | references relation | `relations[]` `relation: \\\"references\\\"` to the other root | `entity.references(otherRoot, label, cardinality)` |\\n| \\\"it contains lines that can't exist alone\\\" | includes relation | `relation: \\\"includes\\\"` | `entity.includes(child, label, cardinality)` |\\n| \\\"it has an address / a status\\\" | uses relation | `relation: \\\"uses\\\"` | `entity.uses(vo, label, cardinality)` |\\n| \\\"exactly one / at most one / any number / at least one\\\" | cardinality | `\\\"1\\\"` / `\\\"0..1\\\"` / `\\\"*\\\"` / `\\\"1..*\\\"` | third argument |\\n| \\\"you can ask it to ...\\\", \\\"POST /x\\\", \\\"the button does ...\\\" | operation | `provides.<id>` `type: \\\"operation\\\"` | `provides(name, {type: \\\"operation\\\", ...})` |\\n| \\\"then we tell everyone that ...\\\", a past-tense fact | event | `provides.<id>` `type: \\\"event\\\"` | `provides(name, {type: \\\"event\\\", ...})` |\\n| \\\"doing that announces ...\\\" | operation raises event | operation `raises: [{$ref event}]` | `op.raises(event)` |\\n| \\\"only we use that\\\", \\\"nobody outside needs it\\\" | internal consumable | `\\\"internal\\\": true`, no `pattern` | `internal: true` |\\n| \\\"what's in the message / the request body\\\" | Schema | `schemas.<id>` on the context; consumable `schema: {$ref}` | `bc.addSchema(name).addAttribute(...)`; `schema: s` |\\n| \\\"when X happens we then Y\\\", \\\"automatically after X\\\" | Policy | `policies.<id>` with `on: [event refs]`, `then: [operation refs]` | `bc.addPolicy(name, {description}).on(e).then(op)` |\\n| \\\"we listen for their X\\\" | consumption | `consumes: [{consumable: {$ref}, pattern}]` | `agg.consumes(theirEvent, {pattern})` |\\n| \\\"the API layer\\\", \\\"the endpoint handler\\\", \\\"the use case\\\" | application service | `services.<id>` `type: \\\"application\\\"` | `bc.addService(name, {type: \\\"application\\\", description})` |\\n| \\\"logic that doesn't belong to one thing\\\", \\\"pricing across orders\\\" | domain service | `type: \\\"domain\\\"` | `type: \\\"domain\\\"` |\\n| \\\"we call it ...\\\", \\\"a.k.a.\\\", \\\"sales say purchase\\\" | Glossary term | `glossary.<id>` with `definition`, `aliases`, `embodiedBy` | `bc.addTerm(name, {definition, aliases, embodiedBy})` |\\n| \\\"the database / queue / Kafka / the cloud\\\" | not modelled | — | say it is infrastructure and out of scope |\\n\" },\n\t{ path: \"references/validation-rules.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (RULE_CATALOG); do not edit. -->\\n\\n# Validation rules\\n\\n`Workspace.validate()` returns diagnostics `{ severity, rule, message, ref }`. Errors describe a model that contradicts itself and should be fixed before finishing. Warnings describe a decision that is missing; discuss them with the user rather than silently fixing them. Explain a diagnostic to the user in the plain words below, not by quoting the rule id.\\n\\n## `aggregate-root` (warning, error)\\n\\n**Requires:** Every aggregate has exactly one root entity.\\n\\n**Why it matters:** The root is the one thing you name the cluster after and reach it through; without it nobody knows where the rules are enforced, and with two the boundary is really two aggregates.\\n\\n**Usual fix:** Mark the entity the aggregate is named after with root: true, or split the aggregate if two entities genuinely lead their own clusters.\\n\\n## `cross-aggregate-reference` (error)\\n\\n**Requires:** A relation into another aggregate uses references and targets that aggregate's root.\\n\\n**Why it matters:** Aggregates are consistency boundaries; reaching inside another one couples the two so they can no longer change or be stored independently.\\n\\n**Usual fix:** Change the relation to \\\"references\\\" and point it at the other aggregate's root entity, holding only its identity.\\n\\n## `role-coherence` (warning)\\n\\n**Requires:** A consumable used from another context declares an upstream role, and the consumption declares a downstream role.\\n\\n**Why it matters:** Crossing a context boundary is an integration decision: how the provider offers it (a documented API or a published format) and how the consumer takes it (as-is or translated) should be explicit.\\n\\n**Usual fix:** Set pattern on the consumable to open-host-service or published-language, and pattern on the consumption to conformist or anti-corruption-layer.\\n\\n## `separate-ways` (error)\\n\\n**Requires:** Contexts that declare separate ways exchange no consumables.\\n\\n**Why it matters:** Separate ways is a deliberate decision not to integrate; a consumption between the two contradicts it.\\n\\n**Usual fix:** Remove the consumption, or remove the separate-ways relationship and declare the real one.\\n\\n## `internal-consumable` (error, warning)\\n\\n**Requires:** An internal consumable is never consumed, reacted to or issued from another context, and declares no upstream role.\\n\\n**Why it matters:** internal means the consumable stays inside its context; anything outside depending on it makes that promise false.\\n\\n**Usual fix:** Drop internal and give the consumable an upstream role, or stop the other context from using it.\\n\\n## `schema-context` (error)\\n\\n**Requires:** A consumable's payload schema belongs to the consumable's own context.\\n\\n**Why it matters:** The context that publishes a message owns its shape; borrowing another context's schema ties the two together.\\n\\n**Usual fix:** Move or copy the schema into the publishing context and point the consumable at that one.\\n\\n## `consumable-kind` (error)\\n\\n**Requires:** Policies react to events and issue operations; only operations raise events, and they raise only events.\\n\\n**Why it matters:** An event is a fact that happened, an operation is a request to do something; mixing them up makes flows unreadable.\\n\\n**Usual fix:** Check the type of each consumable a policy or raises list points at and swap it for the right kind.\\n\\n## `policy-complete` (warning)\\n\\n**Requires:** A policy reacts to at least one event and issues at least one operation.\\n\\n**Why it matters:** A policy is a rule of the form when this happens, do that; either half missing leaves nothing to enact.\\n\\n**Usual fix:** Add the missing event to on or the missing operation to then.\\n\\n## `context-serves-subdomain` (warning)\\n\\n**Requires:** Every bounded context serves at least one subdomain.\\n\\n**Why it matters:** A context that serves no subdomain has no place in the problem-space view, so nobody can see which part of the business it exists for.\\n\\n**Usual fix:** Add the subdomain the context serves to its subdomains list.\\n\" },\n\t{ path: \"SKILL.md\", content: \"---\\nname: ods-authoring\\ndescription: >\\n Author and evolve Open Domain Specification (ODS) workspaces, either as .ods/*.json files or\\n through the @open-domain-specification/core TypeScript DSL. Use when the user wants to model\\n their system or business domain, create or edit a domain model, bounded contexts, aggregates,\\n entities, events, policies, context maps or any DDD (domain-driven design) artefact, asks\\n \\\"what are our bounded contexts\\\", mentions ODS, .ods, open-ds or the ODS VS Code extension, or\\n has validation warnings from an ODS workspace. Acts as a DDD facilitator: interviews developers\\n who do not know DDD in plain language, maps their answers onto the model, validates the result.\\n---\\n\\n# Authoring ODS workspaces\\n\\n## Your role\\n\\nYou are a domain-modelling facilitator. The user knows their system; they usually do not know\\nDomain-Driven Design, and they should not need to. You ask plain-language questions, map the\\nanswers onto the ODS model, and explain each DDD term once, in one sentence, using their own\\nexample. Model in small increments, validate after every edit, and never lecture.\\n\\n## Step 0: detect the authoring mode\\n\\nDecide once, state it in one line, and do not re-detect every turn. The first match wins.\\n\\n1. **DSL mode.** A `.ts`, `.js` or `.mjs` file imports `@open-domain-specification/core`,\\n builds a `Workspace` and writes `toSchema()` output under `.ods/` or to a `workspace.json`;\\n or `package.json` depends on core and has a script (`build`, `ods`, `generate`, `model`)\\n that runs such a file. Emitted JSON is also recognisable: two-space indent and the key order\\n `id, name, description, version, odsVersion, ...`.\\n2. **JSON mode.** A `.ods/` folder (or the folder named by the VS Code setting `ods.folder`)\\n holds `*.json` workspace files next to a `schema.json`, and no generator matches.\\n3. **Neither.** Ask one question: keep the model as JSON files the VS Code extension edits, or\\n as TypeScript that generates them? Recommend JSON when there is no Node toolchain, and the\\n DSL when the model will be large or generated documentation is wanted.\\n\\nIn DSL mode never edit the emitted JSON; it is overwritten on the next run. In JSON mode never\\nintroduce a generator unless asked. Details: `references/json-mode.md`, `references/dsl-mode.md`.\\n\\n## Step 1: read what exists\\n\\nLoad every workspace file (or the DSL source) before proposing anything. Summarise it in the\\nuser's words: the business areas, the parts of the system and who owns them, the main things\\neach part manages, how the parts talk to each other, and how many validation problems there\\nare. Then ask what they want to change or add.\\n\\n## Step 2: interview\\n\\nWhen creating or expanding a model, follow `references/interview-playbook.md`. Strategic\\nquestions first (areas, ownership, integrations), then the detail of one part at a time. One\\nquestion per turn. After each answer, reflect it back as the element you would record (\\\"So I'd\\nnote an Order that must always point at exactly one Pet, right?\\\") before writing it. Stop\\ninterviewing as soon as you know enough for one coherent increment; you can always come back.\\n\\n## Step 3: translate\\n\\nMap answers with `references/translation-table.md`. Every element gets a `description` in\\nthe user's own words, and every noun they used more than once becomes a glossary term in its\\ncontext. Use `references/ddd-glossary.md` for the one-sentence explanations.\\n\\n## Step 4: edit\\n\\nFollow the mode reference for mechanics. Rules that hold in both modes:\\n\\n- Ids are the JSON keys and the segments of every `$ref`. They are derived from the name at\\n creation (`snake_case`) and then frozen. To rename something, change its `name` and keep the\\n id (in the DSL, pass `id` explicitly at the moment of renaming). Rewriting a key means\\n updating every ref that uses it, and confirming with the user first.\\n- Every required collection is present even when empty. A context always has `aggregates`,\\n `services`, `policies`, `glossary`, `schemas` and `subdomains`; an aggregate always has\\n `entities`, `valueobjects`, `invariants`, `provides`, `consumes`; an entity or value object\\n always has `attributes` and `relations`. See `references/model-reference.md`.\\n- Every `$ref` resolves to an element that exists. A dangling ref is a load failure, not a\\n warning: the whole file stops loading.\\n- Consumables (events and operations) live only under `provides` of an aggregate or a\\n service. Policies and consumptions point at them by ref.\\n- A payload schema belongs to the context that publishes the consumable.\\n- Reference another aggregate only through its root entity, with `references`.\\n\\n## Step 5: validate and explain\\n\\nValidate after every edit.\\n\\n- JSON mode: run the script in `examples/validate.mjs` (or the one-liner in\\n `references/json-mode.md`) against each file. The VS Code Problems panel shows the same\\n results, source `ods`, code = rule id.\\n- DSL mode: run the generator script. It prints `[severity] rule: message (ref)` lines and\\n rewrites the JSON.\\n\\nFor each diagnostic, tell the user in one plain sentence what it means and what you propose,\\nusing `references/validation-rules.md`. Errors block finishing. Warnings mark a missing\\ndecision: discuss them and let the user decide, rather than fixing them silently. If loading\\nthrows \\\"... with ref ... not found\\\", a ref is dangling: fix it first.\\n\\n## Educating without preaching\\n\\nThe first time a DDD term comes up, explain it in one sentence tied to the user's example,\\nthen move on. Never repeat an explanation, and never explain a term the user has already used\\ncorrectly. Say \\\"command\\\" in conversation if it helps, but the model's word is `operation`.\\n\\n## Defaults when the user cannot decide\\n\\n| Question | Default |\\n|---|---|\\n| Subdomain type unknown | `supporting` |\\n| Operation used by another context | `pattern: \\\"open-host-service\\\"` |\\n| Event used by another context | `pattern: \\\"published-language\\\"` |\\n| Consuming from a legacy or `bigBallOfMud` context | `pattern: \\\"anti-corruption-layer\\\"` |\\n| Consuming from any other context | `pattern: \\\"conformist\\\"` |\\n| Two contexts exchange consumables, nothing else known | relationship `upstream-downstream` |\\n| Cardinality unknown | omit it |\\n| Service type unknown | `application` if it fronts an API or UI, else `domain` |\\n\\n## Do not\\n\\n- Invent fields or values. The model is exactly `references/model-reference.md`.\\n- Put behaviour on domains or subdomains; they describe the problem, contexts hold solutions.\\n- Create an aggregate without a root, or reach inside another aggregate.\\n- Model infrastructure (databases, queues, brokers). Say it is out of scope.\\n- Force programming types into `type` when the user said \\\"money\\\", \\\"email\\\" or \\\"a date\\\".\\n- Emit `type: \\\"command\\\"`; the consumable types are `event` and `operation`.\\n- Let an operation raise an event of another context without pointing it out as unusual.\\n- Leave `upstreamRoles` or `downstreamRoles` empty on a directed relationship.\\n- Skip validation because the change was small.\\n\" },\n];\n","/** Where each agent looks for skills, relative to a project or home folder. */\nexport type SkillTarget = \"claude\" | \"agents\" | \"codex\";\n\n/** The folder name of the skill under every `skills/` directory. */\nexport const SKILL_NAME = \"ods-authoring\";\n\nexport const TARGETS: ReadonlyArray<{\n\tid: SkillTarget;\n\tlabel: string;\n\t/** The skills directory, relative to the root the skill is installed into. */\n\tdir: string;\n}> = [\n\t{ id: \"claude\", label: \"Claude Code\", dir: \".claude/skills\" },\n\t{ id: \"agents\", label: \"Agent Skills (.agents)\", dir: \".agents/skills\" },\n\t{ id: \"codex\", label: \"OpenAI Codex (.codex)\", dir: \".codex/skills\" },\n];\n\nexport const TARGET_DIRS: Record<SkillTarget, string> = Object.fromEntries(\n\tTARGETS.map((t) => [t.id, t.dir]),\n) as Record<SkillTarget, string>;\n\n/** The directory the skill lands in for a target, relative to `root`. */\nexport function skillDir(target: SkillTarget): string {\n\treturn `${TARGET_DIRS[target]}/${SKILL_NAME}`;\n}\n","import { BUNDLE, SKILL_VERSION } from \"./bundle.generated\";\nimport { SKILL_NAME, type SkillTarget, skillDir } from \"./targets\";\n\nexport type SkillFile = { path: string; content: string };\n\nconst STAMP = /<!-- ods-skill-version: ([^\\s]+) -->/;\n\nfunction stamp(content: string): string {\n\treturn `${content.trimEnd()}\\n\\n<!-- ods-skill-version: ${SKILL_VERSION} -->\\n`;\n}\n\n/** Every file of the skill bundle, paths relative to the skill folder. */\nexport function skillFiles(): SkillFile[] {\n\treturn BUNDLE.map((f) =>\n\t\tf.path === \"SKILL.md\" ? { ...f, content: stamp(f.content) } : { ...f },\n\t);\n}\n\nexport type InstallOptions = {\n\t/** The project or home folder the skill is installed into. */\n\troot: string;\n\ttarget: SkillTarget;\n\t/** Writes one file; receives an absolute-ish path built from `root`. */\n\twrite: (path: string, content: string) => Promise<void>;\n\t/** Override the files to install; defaults to the bundle. */\n\tfiles?: SkillFile[];\n};\n\n/** Writes the bundle into `<root>/<target skills dir>/ods-authoring/` and returns the paths written. */\nexport async function installSkill(options: InstallOptions): Promise<string[]> {\n\tconst base = `${options.root}/${skillDir(options.target)}`;\n\tconst written: string[] = [];\n\tfor (const file of options.files ?? skillFiles()) {\n\t\tconst path = `${base}/${file.path}`;\n\t\tawait options.write(path, file.content);\n\t\twritten.push(path);\n\t}\n\treturn written;\n}\n\nexport type InstallState = \"missing\" | \"stale\" | \"current\";\n\n/** Compares the version stamp of an installed SKILL.md with this package's. */\nexport async function isInstalled(\n\troot: string,\n\ttarget: SkillTarget,\n\tread: (path: string) => Promise<string | undefined>,\n): Promise<InstallState> {\n\tconst content = await read(`${root}/${skillDir(target)}/SKILL.md`);\n\tif (content === undefined) return \"missing\";\n\treturn STAMP.exec(content)?.[1] === SKILL_VERSION ? \"current\" : \"stale\";\n}\n\n/** A paragraph for AGENTS.md or copilot-instructions.md pointing agents at the installed skill. */\nexport function rulesSnippet(target: SkillTarget = \"agents\"): string {\n\treturn [\n\t\t\"## Domain model (Open Domain Specification)\",\n\t\t\"\",\n\t\t`This project keeps its domain model as an Open Domain Specification workspace. Before creating or editing anything under \\`.ods/\\` or a TypeScript file that builds a \\`Workspace\\` from \\`@open-domain-specification/core\\`, read \\`${skillDir(target)}/SKILL.md\\` and follow it: detect whether the model is authored as JSON or via the TypeScript DSL, interview the user in plain language before modelling, and validate after every change.`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport { SKILL_NAME, SKILL_VERSION };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,gBAAgB;AACtB,IAAM,SAA2D;AAAA,EACvE,EAAE,MAAM,6BAA6B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmyJ;AAAA,EACj1J,EAAE,MAAM,iCAAiC,SAAS,s9FAAmoG;AAAA,EACrrG,EAAE,MAAM,wBAAwB,SAAS,0nHAAqwH;AAAA,EAC9yH,EAAE,MAAM,yBAAyB,SAAS,2zCAAs2C;AAAA,EACh5C,EAAE,MAAM,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmvH;AAAA,EAClyH,EAAE,MAAM,yBAAyB,SAAS,48HAA2+H;AAAA,EACrhI,EAAE,MAAM,0BAA0B,SAAS,mkFAAulF;AAAA,EACloF,EAAE,MAAM,oCAAoC,SAAS,kkNAAk+M;AAAA,EACvhN,EAAE,MAAM,2BAA2B,SAAS,q+EAA++E;AAAA,EAC3hF,EAAE,MAAM,iCAAiC,SAAS,2uVAA6zV;AAAA,EAC/2V,EAAE,MAAM,mCAAmC,SAAS,m4MAAimN;AAAA,EACrpN,EAAE,MAAM,kCAAkC,SAAS,0lIAA0lI;AAAA,EAC7oI,EAAE,MAAM,YAAY,SAAS,s/NAAygO;AACviO;;;ACZO,IAAM,aAAa;AAEnB,IAAM,UAKR;AAAA,EACJ,EAAE,IAAI,UAAU,OAAO,eAAe,KAAK,iBAAiB;AAAA,EAC5D,EAAE,IAAI,UAAU,OAAO,0BAA0B,KAAK,iBAAiB;AAAA,EACvE,EAAE,IAAI,SAAS,OAAO,yBAAyB,KAAK,gBAAgB;AACrE;AAEO,IAAM,cAA2C,OAAO;AAAA,EAC9D,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC;AAGO,SAAS,SAAS,QAA6B;AACrD,SAAO,GAAG,YAAY,MAAM,CAAC,IAAI,UAAU;AAC5C;;;ACnBA,IAAM,QAAQ;AAEd,SAAS,MAAM,SAAyB;AACvC,SAAO,GAAG,QAAQ,QAAQ,CAAC;AAAA;AAAA,0BAA+B,aAAa;AAAA;AACxE;AAGO,SAAS,aAA0B;AACzC,SAAO,OAAO;AAAA,IAAI,CAAC,MAClB,EAAE,SAAS,aAAa,iCAAK,IAAL,EAAQ,SAAS,MAAM,EAAE,OAAO,EAAE,KAAI,mBAAK;AAAA,EACpE;AACD;AAaA,SAAsB,aAAa,SAA4C;AAAA;AA7B/E;AA8BC,UAAM,OAAO,GAAG,QAAQ,IAAI,IAAI,SAAS,QAAQ,MAAM,CAAC;AACxD,UAAM,UAAoB,CAAC;AAC3B,eAAW,SAAQ,aAAQ,UAAR,YAAiB,WAAW,GAAG;AACjD,YAAM,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI;AACjC,YAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,cAAQ,KAAK,IAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACR;AAAA;AAKA,SAAsB,YACrB,MACA,QACA,MACwB;AAAA;AA/CzB;AAgDC,UAAM,UAAU,MAAM,KAAK,GAAG,IAAI,IAAI,SAAS,MAAM,CAAC,WAAW;AACjE,QAAI,YAAY,OAAW,QAAO;AAClC,aAAO,WAAM,KAAK,OAAO,MAAlB,mBAAsB,QAAO,gBAAgB,YAAY;AAAA,EACjE;AAAA;AAGO,SAAS,aAAa,SAAsB,UAAkB;AACpE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,wOAAwO,SAAS,MAAM,CAAC;AAAA,IACxP;AAAA,EACD,EAAE,KAAK,IAAI;AACZ;","names":[]}
package/dist/index.mjs CHANGED
@@ -39,7 +39,7 @@ var __async = (__this, __arguments, generator) => {
39
39
  };
40
40
 
41
41
  // src/bundle.generated.ts
42
- var SKILL_VERSION = "0.1.11";
42
+ var SKILL_VERSION = "0.2.0";
43
43
  var BUNDLE = [
44
44
  { path: "examples/minimal.ods.json", content: `{
45
45
  "$schema": "./schema.json",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundle.generated.ts","../src/targets.ts","../src/install.ts"],"sourcesContent":["// Generated by scripts/generate.mts; do not edit.\nexport const SKILL_VERSION = \"0.1.11\";\nexport const BUNDLE: ReadonlyArray<{ path: string; content: string }> = [\n\t{ path: \"examples/minimal.ods.json\", content: \"{\\n\\t\\\"$schema\\\": \\\"./schema.json\\\",\\n\\t\\\"id\\\": \\\"bookshop\\\",\\n\\t\\\"name\\\": \\\"Bookshop\\\",\\n\\t\\\"description\\\": \\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n\\t\\\"version\\\": \\\"0.1.0\\\",\\n\\t\\\"odsVersion\\\": \\\"1.0.0\\\",\\n\\t\\\"domains\\\": {\\n\\t\\t\\\"bookselling\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Bookselling\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Everything involved in selling books online\\\",\\n\\t\\t\\t\\\"subdomains\\\": {\\n\\t\\t\\t\\t\\\"sales\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Sales\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"core\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"Taking and fulfilling orders\\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"boundedcontexts\\\": {\\n\\t\\t\\\"orders\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Orders\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Owns orders from placement to dispatch\\\",\\n\\t\\t\\t\\\"subdomains\\\": [{ \\\"$ref\\\": \\\"#/domains/bookselling/subdomains/sales\\\" }],\\n\\t\\t\\t\\\"team\\\": { \\\"$ref\\\": \\\"#/teams/shop_team\\\" },\\n\\t\\t\\t\\\"aggregates\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"One customer's request to buy some books\\\",\\n\\t\\t\\t\\t\\t\\\"entities\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"The order itself\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"root\\\": true,\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"target\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/valueobjects/address\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"relation\\\": \\\"uses\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"label\\\": \\\"ships-to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"cardinality\\\": \\\"1\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"valueobjects\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"address\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Address\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"Where the order ships to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"lines\\\": { \\\"name\\\": \\\"lines\\\", \\\"type\\\": \\\"text\\\" },\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"postcode\\\": { \\\"name\\\": \\\"postcode\\\", \\\"type\\\": \\\"postcode\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": []\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"invariants\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"total_not_negative\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Total not negative\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"An order's total can never be below zero\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"constrains\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/entities/order/attributes/total\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"A customer placed an order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"event\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"published-language\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"schema\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/schemas/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"services\\\": {\\n\\t\\t\\t\\t\\\"order_api\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order API\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"The endpoints the web shop calls\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"application\\\",\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"place_order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"PlaceOrder\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"POST /orders\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"operation\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"open-host-service\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"raises\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/provides/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"policies\\\": {},\\n\\t\\t\\t\\\"glossary\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"definition\\\": \\\"A customer's request to buy some books, paid up front\\\",\\n\\t\\t\\t\\t\\t\\\"aliases\\\": [\\\"Purchase\\\"],\\n\\t\\t\\t\\t\\t\\\"embodiedBy\\\": { \\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order\\\" }\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"schemas\\\": {\\n\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"What other parts learn when an order is placed\\\",\\n\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"relationships\\\": [],\\n\\t\\\"teams\\\": {\\n\\t\\t\\\"shop_team\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Shop Team\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Runs the online shop\\\"\\n\\t\\t}\\n\\t}\\n}\\n\" },\n\t{ path: \"examples/minimal.workspace.ts\", content: \"// The same bookshop as minimal.ods.json, authored with the DSL.\\n// Run with `node minimal.workspace.ts` (Node 24) or `npx tsx minimal.workspace.ts`.\\nimport fs from \\\"node:fs\\\";\\nimport { Workspace } from \\\"@open-domain-specification/core\\\";\\n\\nexport const workspace = new Workspace(\\\"Bookshop\\\", {\\n\\todsVersion: \\\"1.0.0\\\",\\n\\tversion: \\\"0.1.0\\\",\\n\\tdescription:\\n\\t\\t\\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n});\\n\\nconst bookselling = workspace.addDomain(\\\"Bookselling\\\", {\\n\\tdescription: \\\"Everything involved in selling books online\\\",\\n});\\nconst sales = bookselling.addSubdomain(\\\"Sales\\\", {\\n\\ttype: \\\"core\\\",\\n\\tdescription: \\\"Taking and fulfilling orders\\\",\\n});\\nconst shopTeam = workspace.addTeam(\\\"Shop Team\\\", {\\n\\tdescription: \\\"Runs the online shop\\\",\\n});\\n\\nconst orders = sales.addBoundedcontext(\\\"Orders\\\", {\\n\\tdescription: \\\"Owns orders from placement to dispatch\\\",\\n\\tteam: shopTeam,\\n});\\n\\nconst orderAgg = orders.addAggregate(\\\"Order\\\", {\\n\\tdescription: \\\"One customer's request to buy some books\\\",\\n});\\nconst order = orderAgg.addRootEntity(\\\"Order\\\", {\\n\\tdescription: \\\"The order itself\\\",\\n});\\norder.addAttribute(\\\"orderNumber\\\", { type: \\\"order number\\\", identity: true });\\nconst total = order.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst address = orderAgg.addValueObject(\\\"Address\\\", {\\n\\tdescription: \\\"Where the order ships to\\\",\\n});\\naddress.addAttribute(\\\"lines\\\", { type: \\\"text\\\" });\\naddress.addAttribute(\\\"postcode\\\", { type: \\\"postcode\\\" });\\norder.uses(address, \\\"ships-to\\\", \\\"1\\\");\\n\\norderAgg\\n\\t.addInvariant(\\\"Total not negative\\\", {\\n\\t\\tdescription: \\\"An order's total can never be below zero\\\",\\n\\t})\\n\\t.constrains(total);\\n\\nconst orderPlacedSchema = orders.addSchema(\\\"OrderPlaced\\\", {\\n\\tdescription: \\\"What other parts learn when an order is placed\\\",\\n});\\norderPlacedSchema.addAttribute(\\\"orderNumber\\\", {\\n\\ttype: \\\"order number\\\",\\n\\tidentity: true,\\n});\\norderPlacedSchema.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst orderPlaced = orderAgg.provides(\\\"OrderPlaced\\\", {\\n\\ttype: \\\"event\\\",\\n\\tdescription: \\\"A customer placed an order\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: orderPlacedSchema,\\n});\\n\\nconst orderApi = orders.addService(\\\"Order API\\\", {\\n\\ttype: \\\"application\\\",\\n\\tdescription: \\\"The endpoints the web shop calls\\\",\\n});\\norderApi\\n\\t.provides(\\\"PlaceOrder\\\", {\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tdescription: \\\"POST /orders\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t})\\n\\t.raises(orderPlaced);\\n\\norders.addTerm(\\\"Order\\\", {\\n\\tdefinition: \\\"A customer's request to buy some books, paid up front\\\",\\n\\taliases: [\\\"Purchase\\\"],\\n\\tembodiedBy: orderAgg,\\n});\\n\\n// Generator: validate, then write the workspace beside its schema.\\nif (process.argv[1]?.endsWith(\\\"minimal.workspace.ts\\\")) {\\n\\tfor (const d of workspace.validate()) {\\n\\t\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t}\\n\\tfs.mkdirSync(\\\".ods\\\", { recursive: true });\\n\\tfs.writeFileSync(\\n\\t\\t\\\".ods/bookshop.json\\\",\\n\\t\\tJSON.stringify(\\n\\t\\t\\t{ $schema: \\\"./schema.json\\\", ...workspace.toSchema() },\\n\\t\\t\\tnull,\\n\\t\\t\\t2,\\n\\t\\t),\\n\\t);\\n}\\n\" },\n\t{ path: \"examples/petstore.md\", content: \"# Patterns from the Petstore example\\n\\nExcerpts from the ODS example workspace (`models/petstore/src/workspace.ts`\\nin the ODS repository). Each shows one pattern worth copying.\\n\\n## A context serving two subdomains, and a legacy one\\n\\n```ts\\nconst inventoryBC = workspace.addBoundedContext(\\\"Inventory BC\\\", {\\n\\tdescription: \\\"Projection for /store/inventory (status→count)\\\",\\n\\tsubdomains: [inventorySD, catalogSD],\\n\\tteam: petShopTeam,\\n});\\nconst identityBC = usersSD.addBoundedcontext(\\\"Identity BC\\\", {\\n\\tdescription: \\\"Owns User aggregate & user endpoints. Legacy: user status is an untyped int\\\",\\n\\tbigBallOfMud: true,\\n\\tteam: platformTeam,\\n});\\n```\\n\\n## Attributes backed by value objects, relations with cardinality, invariants on attributes\\n\\n```ts\\npetRoot.addAttribute(\\\"id\\\", { type: \\\"int64\\\", identity: true });\\npetRoot.addAttribute(\\\"status\\\", { type: \\\"PetStatus\\\", valueobject: petStatusVO });\\npetRoot.uses(categoryVO, \\\"categorized-as\\\", \\\"0..1\\\");\\npetRoot.uses(photoUrlVO, \\\"has-photo\\\", \\\"1..*\\\");\\npetAgg\\n\\t.addInvariant(\\\"NameRequired\\\", { description: \\\"Pet.name must be non-empty\\\" })\\n\\t.constrains(petRoot.attributes.get(\\\"name\\\")!);\\n```\\n\\n## A cross-aggregate reference by identity to the other root\\n\\n```ts\\norderRoot.references(petRoot, \\\"for-pet\\\", \\\"1\\\");\\n```\\n\\n## Published events with a payload schema, and an internal operation that raises one\\n\\n```ts\\nconst petStatusChangedSchema = catalogBC.addSchema(\\\"PetStatusChanged\\\");\\npetStatusChangedSchema.addAttribute(\\\"petId\\\", { type: \\\"int64\\\", identity: true });\\n\\nconst petStatusChanged = petAgg.provides(\\\"PetStatusChanged\\\", {\\n\\tdescription: \\\"Pet status changed (available|pending|sold)\\\",\\n\\ttype: \\\"event\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: petStatusChangedSchema,\\n});\\nconst _changePetStatus = petAgg\\n\\t.provides(\\\"ChangePetStatus\\\", {\\n\\t\\tdescription: \\\"Move a pet between available, pending and sold\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tinternal: true,\\n\\t\\tschema: petStatusChangedSchema,\\n\\t})\\n\\t.raises(petStatusChanged);\\n```\\n\\n## An open-host application service whose operations raise the aggregate's events\\n\\n```ts\\nconst petApp = catalogBC.addService(\\\"PetApp\\\", {\\n\\tdescription: \\\"Open-host service for /pet endpoints\\\",\\n\\ttype: \\\"application\\\",\\n});\\nconst _addPetOp = petApp\\n\\t.provides(\\\"AddPet\\\", {\\n\\t\\tdescription: \\\"POST /pet\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t\\tschema: registerPetSchema,\\n\\t})\\n\\t.raises(petRegistered);\\n```\\n\\n## A consumption through an anti-corruption layer, and the relationship that explains it\\n\\n```ts\\norderApp.consumes(getPetSummaryOp, { pattern: \\\"anti-corruption-layer\\\" });\\n\\nsalesBC.downstreamOf(catalogBC, {\\n\\ttype: \\\"customer-supplier\\\",\\n\\tupstreamRoles: [\\\"open-host-service\\\"],\\n\\tdownstreamRoles: [\\\"anti-corruption-layer\\\"],\\n\\tdescription: \\\"Sales needs pet availability; Catalog commits to the summary contract\\\",\\n});\\n```\\n\\n## Separate ways, on purpose\\n\\n```ts\\nidentityBC.separateWaysFrom(\\n\\tsalesBC,\\n\\t\\\"Orders are anonymous in Petstore v3; no integration by design\\\",\\n);\\n```\\n\\n## A policy reacting to events from two contexts\\n\\n```ts\\nsalesBC\\n\\t.addPolicy(\\\"Approve when pet available\\\", {\\n\\t\\tdescription: \\\"When a pet becomes available and an order for it is placed, approve the order\\\",\\n\\t})\\n\\t.on(petStatusChanged, orderPlaced)\\n\\t.then(approveOrder);\\n```\\n\\n## Conformist consumptions feeding a projection\\n\\n```ts\\ninventoryAgg.consumes(petStatusChanged, { pattern: \\\"conformist\\\" });\\ninventoryAgg.consumes(orderApproved, { pattern: \\\"conformist\\\" });\\n```\\n\\n## Glossary terms embodied by model elements\\n\\n```ts\\ncatalogBC.addTerm(\\\"Category\\\", {\\n\\tdefinition: \\\"The kind of animal a pet is, such as Dogs or Cats\\\",\\n\\taliases: [\\\"Species\\\"],\\n\\tembodiedBy: categoryVO,\\n});\\n```\\n\" },\n\t{ path: \"examples/validate.mjs\", content: \"#!/usr/bin/env node\\n// Validates one or more ODS workspace JSON files.\\n// Usage: node validate.mjs .ods/petstore.json [.ods/other.json ...]\\n// Exits 1 when a file fails to load or has an error-level diagnostic.\\nimport { readFileSync } from \\\"node:fs\\\";\\nimport { createRequire } from \\\"node:module\\\";\\n\\nconst require = createRequire(`${process.cwd()}/`);\\nlet Workspace;\\ntry {\\n\\t({ Workspace } = require(\\\"@open-domain-specification/core\\\"));\\n} catch {\\n\\tconsole.error(\\n\\t\\t\\\"@open-domain-specification/core is not installed here. Run from the project root, install it (npm i -D @open-domain-specification/core), or use: npx -p @open-domain-specification/core node validate.mjs <file>\\\",\\n\\t);\\n\\tprocess.exit(2);\\n}\\n\\nlet failed = false;\\nfor (const file of process.argv.slice(2)) {\\n\\tlet workspace;\\n\\ttry {\\n\\t\\tworkspace = Workspace.fromSchema(JSON.parse(readFileSync(file, \\\"utf8\\\")));\\n\\t} catch (error) {\\n\\t\\tconsole.log(\\n\\t\\t\\t`[load-error] ${file}: ${error instanceof Error ? error.message : error}`,\\n\\t\\t);\\n\\t\\tfailed = true;\\n\\t\\tcontinue;\\n\\t}\\n\\tconst diagnostics = workspace.validate();\\n\\tconsole.log(`${file}: ${diagnostics.length} diagnostic(s)`);\\n\\tfor (const d of diagnostics) {\\n\\t\\tconsole.log(` [${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t\\tif (d.severity === \\\"error\\\") failed = true;\\n\\t}\\n}\\nprocess.exit(failed ? 1 : 0);\\n\" },\n\t{ path: \"references/ddd-glossary.md\", content: \"# DDD terms in one sentence each\\n\\nUse these the first time a term comes up, filling the example with the user's own words.\\nNever repeat an explanation, and never explain a term the user already used correctly.\\n\\n- **Domain** — the whole area of business the system exists for, e.g. \\\"running the pet store\\\".\\n- **Subdomain** — one slice of that problem, e.g. \\\"the catalogue\\\" or \\\"taking orders\\\"; calling it\\n *core* only marks where your competitive effort goes, *supporting* means needed but ordinary,\\n *generic* means you would buy it.\\n- **Bounded context** — a boundary inside which every word has one exact meaning; your billing\\n \\\"Customer\\\" and your support \\\"Customer\\\" being different things is why they get separate\\n contexts.\\n- **Ubiquitous language / glossary** — the words a context uses, written down once so code,\\n conversations and documents all mean the same thing by \\\"Order\\\".\\n- **Team ownership** — the people who decide what a context means and how it changes.\\n- **Big ball of mud** — a context whose model nobody fully controls, flagged so that anything\\n talking to it translates rather than trusts.\\n- **Entity** — something that matters because of *which one* it is, like this particular order,\\n so it carries an identity.\\n- **Value object** — something that matters only by its values, like an address; two with the\\n same values are interchangeable.\\n- **Attribute** — one piece of information an entity, value object or message carries; the\\n identity attribute is the one that tells two entities apart.\\n- **Aggregate** — the cluster of things you change together and check rules across, named after\\n its *root*, the one thing you go through to change any of it; the order and its lines.\\n- **Invariant** — a rule that must always hold inside an aggregate, such as \\\"quantity is never\\n zero\\\".\\n- **Relation** — how one thing points at another: *includes* for parts that cannot exist alone,\\n *uses* for values it carries, *references* for another aggregate's root by identity.\\n- **Cardinality** — how many of the other thing: exactly one, at most one, any number, at least\\n one.\\n- **Operation** — something you can ask a part of the system to do, like \\\"place an order\\\"; in\\n conversation people often say *command*.\\n- **Event** — a fact that already happened, stated in the past tense, like \\\"order placed\\\", that\\n other parts can react to.\\n- **Consumable** — an operation or event that a part offers, and **consumption** is another part\\n using it.\\n- **Schema** — the shape of the information that travels with an operation or event.\\n- **Policy** — a rule of the form \\\"when this event happens, do that operation\\\", possibly across\\n contexts.\\n- **Application service** — the part that fronts an API or a screen and turns requests into\\n operations on aggregates.\\n- **Domain service** — business logic that does not belong to any single thing, like pricing\\n across several orders.\\n- **Upstream / downstream** — the side that is depended on, and the side that depends on it.\\n- **Customer-supplier** — a dependency where the downstream side gets a say before the upstream\\n side changes things.\\n- **Partnership** — two contexts whose teams plan and release together.\\n- **Shared kernel** — code or data two contexts both own and change.\\n- **Separate ways** — a deliberate decision that two contexts will not integrate.\\n- **Open host service** — the upstream side offers a documented API for anyone to use.\\n- **Published language** — the upstream side offers a documented message format everyone\\n agrees on.\\n- **Conformist** — the downstream side takes the upstream model as it comes.\\n- **Anti-corruption layer** — the downstream side copies and reshapes what it receives into its\\n own terms, so the upstream model cannot leak in.\\n\" },\n\t{ path: \"references/dsl-api.md\", content: \"# DSL reference (`@open-domain-specification/core`)\\n\\nEvery class is created through its parent and registers itself there, so `parent.addX(...)`\\nis the only call needed. Every attributes object accepts an optional `id` to fix the id\\nindependently of the name.\\n\\n| Receiver | Method | Creates / does |\\n|---|---|---|\\n| — | `new Workspace(name, { odsVersion, description, version, homepage?, logoUrl?, primaryColor?, id? })` | the workspace |\\n| `Workspace` | `addDomain(name, { description })` | a domain |\\n| `Workspace` | `addTeam(name, { description?, homepage? })` | a team |\\n| `Workspace` | `addBoundedContext(name, { description, subdomains?, bigBallOfMud?, team? })` | a context serving zero or more subdomains |\\n| `Workspace` | `addRelationship({...})` | a relationship; prefer the context helpers below |\\n| `Workspace` | `validate()` | the diagnostics list |\\n| `Workspace` | `toSchema()` / `Workspace.fromSchema(json)` | serialise / load |\\n| `Domain` | `addSubdomain(name, { type, description })` | a subdomain; `type` is `\\\"core\\\" \\\\| \\\"supporting\\\" \\\\| \\\"generic\\\"` |\\n| `Subdomain` | `addBoundedcontext(name, { description, bigBallOfMud?, team? })` | a context serving this subdomain |\\n| `BoundedContext` | `serves(subdomain)` | adds a served subdomain |\\n| `BoundedContext` | `ownedBy(team)` | sets the owning team |\\n| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description? })` | directed relationship, this side upstream; `type` defaults to `\\\"upstream-downstream\\\"`, or `\\\"customer-supplier\\\"` |\\n| `BoundedContext` | `downstreamOf(other, options)` | the same, this side downstream |\\n| `BoundedContext` | `partnerOf(other, description?)` | partnership |\\n| `BoundedContext` | `sharesKernelWith(other, description?)` | shared kernel |\\n| `BoundedContext` | `separateWaysFrom(other, description?)` | separate ways |\\n| `BoundedContext` | `addAggregate(name, { description })` | an aggregate |\\n| `BoundedContext` | `addService(name, { type, description })` | a service; `type` is `\\\"application\\\" \\\\| \\\"domain\\\"` |\\n| `BoundedContext` | `addPolicy(name, { description })` | a policy; chain `.on(...events).then(...operations)` |\\n| `BoundedContext` | `addTerm(name, { definition, aliases?, embodiedBy? })` | a glossary term; or chain `.embody(element)` |\\n| `BoundedContext` | `addSchema(name, { description? })` | a payload schema; add fields with `addAttribute` |\\n| `Aggregate` | `addRootEntity(name, { description })` | the root entity |\\n| `Aggregate` | `addEntity(name, { description, root? })` | an entity |\\n| `Aggregate` | `addValueObject(name, { description })` | a value object |\\n| `Aggregate` | `addInvariant(name, { description })` | an invariant; chain `.constrains(...entities, valueObjects or attributes)` |\\n| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema? })` | a consumable; `type` is `\\\"event\\\" \\\\| \\\"operation\\\"`, `pattern` is `\\\"open-host-service\\\" \\\\| \\\"published-language\\\"` |\\n| `Aggregate`, `Service` | `consumes(consumable, { pattern? })` | a consumption; `pattern` is `\\\"conformist\\\" \\\\| \\\"anti-corruption-layer\\\"` |\\n| `Consumable` | `raises(...events)` | the events an operation raises |\\n| `Entity`, `ValueObject`, `DataSchema` | `addAttribute(name, { type, description?, identity?, valueobject? })` | an attribute; `type` is free text |\\n| `Entity`, `ValueObject` | `uses(target, label, cardinality?)` | a `uses` relation |\\n| `Entity`, `ValueObject` | `includes(target, label, cardinality?)` | an `includes` relation |\\n| `Entity`, `ValueObject` | `references(target, label, cardinality?)` | a `references` relation; across aggregates target the root |\\n| `Entity`, `ValueObject` | `addRelation(target, { relation, label?, cardinality? })` | any relation explicitly |\\n| `Entity` | `.attributes.get(\\\"name\\\")` | look an attribute up, e.g. to constrain it |\\n\\n`cardinality` is `\\\"1\\\" | \\\"0..1\\\" | \\\"*\\\" | \\\"1..*\\\"`. Chainable methods (`raises`, `on`, `then`,\\n`constrains`, `embody`, `serves`, `ownedBy`) return their receiver.\\n\" },\n\t{ path: \"references/dsl-mode.md\", content: \"# DSL mode\\n\\nThe TypeScript source is the artefact; the JSON under `.ods/` is generated from it. Edit the\\nsource, run the generator, read the diagnostics it prints.\\n\\n## Find the generator\\n\\nLook for a file that imports `Workspace` from `@open-domain-specification/core`, builds the\\nmodel, and writes `workspace.toSchema()` to disk. The canonical shape (from the ODS example\\npackage) is:\\n\\n```ts\\nimport fs from \\\"node:fs\\\";\\nimport { workspace } from \\\"./petstore/workspace.ts\\\";\\n\\nfor (const d of workspace.validate()) {\\n\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n}\\n\\nfs.mkdirSync(\\\".ods\\\", { recursive: true });\\nfs.writeFileSync(\\n\\t\\\".ods/petstore.json\\\",\\n\\tJSON.stringify({ $schema: \\\"./schema.json\\\", ...workspace.toSchema() }, null, 2),\\n);\\n```\\n\\n`package.json` usually has a script for it (`build`, `ods`, `generate`, `model`). Node 24 runs\\n`.ts` files directly; on older Node use `npx tsx <file>`.\\n\\n## Loop\\n\\n1. Edit the model source. Keep the file's existing sections and ordering (domains, teams,\\n contexts, then one section per context).\\n2. Run the generator. It validates and rewrites the JSON.\\n3. Read every `[error]` and `[warning]` line and explain it to the user with\\n `validation-rules.md`.\\n4. Never hand-edit the emitted JSON; the next run overwrites it. If the user edits it, tell\\n them and offer to port the change into the source.\\n\\nIf the generator does not print diagnostics, add the four-line loop above before the write.\\n`toSchema()` does not emit `$schema`; spread it back in exactly as shown, so editors and the\\nVS Code extension keep the file associated with `schema.json`.\\n\\n## Ids and renames\\n\\nIds are derived from names with `snake_case` unless `id` is passed. Because ids are the JSON\\nkeys and the ref segments, renaming an element by changing its name silently changes its id\\nand breaks anything outside the source that points at it (documentation links, bookmarks,\\nother files). When renaming, pass the old id explicitly:\\n\\n```ts\\n// was: catalogBC.addAggregate(\\\"Pet\\\", {...})\\ncatalogBC.addAggregate(\\\"Listed Pet\\\", { id: \\\"pet\\\", description: \\\"...\\\" });\\n```\\n\\n## Conventions from the example\\n\\n- Name the variables after the element and its kind (`petAgg`, `petRoot`, `categoryVO`,\\n `petApp`), so refs read naturally in the code.\\n- Prefix a variable with `_` when the element is kept only for its side effect on the model\\n (an operation nobody references again).\\n- Create all consumables before the policies and consumptions that point at them.\\n- Full DSL surface: `dsl-api.md`. Patterns worth copying: `examples/petstore.md`.\\n\" },\n\t{ path: \"references/interview-playbook.md\", content: \"# Interview playbook\\n\\nYou are the facilitator. The user knows their system and their business; you know DDD. Your\\njob is to get the model out of their head without making them learn the vocabulary first.\\n\\n## Ground rules\\n\\n- One question per turn. Wait for the answer.\\n- No DDD word before its one-sentence explanation (see `ddd-glossary.md`), and each term is\\n explained once.\\n- After every answer, paraphrase it as the element you would record: \\\"So I'd note ... right?\\\"\\n- Write the increment as soon as a context or an aggregate is stable. Do not wait until the\\n whole interview is done; a model in the file beats a model in the chat.\\n- Skip any phase the existing workspace already covers. Read first, ask second.\\n- Keep the user's words. Descriptions and glossary definitions are written in their language,\\n not in DDD language.\\n\\n## Phase A: orientation (produces the Workspace)\\n\\n- \\\"In one or two sentences, what does this system do, and for whom?\\\" → `name`, `description`.\\n- \\\"Is there a homepage or logo I should link?\\\" → `homepage`, `logoUrl` (skip if none).\\n\\n## Phase B: the problem space (produces Domains and Subdomains with a type)\\n\\n- \\\"What are the big areas of the business this covers? Think of the headings you would put\\n on a whiteboard.\\\" → domains.\\n- Per area: \\\"What distinct jobs sit inside that area?\\\" → subdomains.\\n- Per subdomain: \\\"Is this something that makes you different from competitors, something you\\n need but any sensible way of doing it is fine, or something you would happily buy off the\\n shelf?\\\" → `core` / `supporting` / `generic`.\\n- Explain once: a subdomain is one slice of the problem; calling it core only marks where your\\n competitive effort goes.\\n\\n## Phase C: ownership (produces Teams, Bounded Contexts, `subdomains`, `bigBallOfMud`)\\n\\n- \\\"Which teams or people work on this, and which parts does each look after?\\\" → teams, and a\\n candidate context per part.\\n- \\\"If two teams both say <word>, do they mean exactly the same thing?\\\" If not, that is two\\n contexts. Explain once: a bounded context is a boundary inside which every word has one\\n exact meaning; your billing \\\"Customer\\\" and your support \\\"Customer\\\" being different is why\\n they get separate contexts.\\n- \\\"Which of the jobs from before does each part serve?\\\" → `subdomains` refs. One context may\\n serve several.\\n- \\\"Is any of these an old system that nobody fully understands, where the data model is a\\n mess?\\\" → `bigBallOfMud: true`. Explain: we flag it so anything talking to it knows to\\n translate rather than trust.\\n\\n## Phase D: the integration map (produces Relationships and seeds consumptions)\\n\\n- \\\"Which parts talk to each other? For each pair, who depends on whom?\\\" → `upstream-downstream`\\n with the depended-on side upstream.\\n- \\\"When the upstream team changes something, does the downstream team get a say beforehand?\\\"\\n Yes → `customer-supplier`.\\n- \\\"Do those two teams plan and release together, as one?\\\" → `partnership`.\\n- \\\"Do they share actual code or tables that both change?\\\" → `shared-kernel`.\\n- \\\"Are there two parts that you have decided, on purpose, should never integrate?\\\" →\\n `separate-ways`.\\n- \\\"How does the downstream side take the data: as it comes, or does it copy and reshape it\\n into its own terms?\\\" → `conformist` / `anti-corruption-layer`. Goes on `downstreamRoles`\\n and on each consumption's `pattern`.\\n- \\\"Does the upstream side publish a documented API, or a documented message format?\\\" →\\n `open-host-service` / `published-language`. Goes on `upstreamRoles` and on each exposed\\n consumable's `pattern`.\\n\\n## Phase E: inside one context (produces Aggregates, Entities, Value Objects, Invariants, Glossary)\\n\\nRepeat for each context the user wants detailed. Ask which one to start with.\\n\\n- \\\"Inside <context>, what are the things people talk about? Just list the nouns.\\\" → candidate\\n entities and value objects; every noun becomes a glossary term with the user's definition.\\n- Per noun: \\\"If two of these had identical details, would they still be two different things?\\\"\\n Yes → entity; no → value object. Explain once: an entity matters because of which one it is\\n (this order, not that one); a value object matters only by its values (an address).\\n- \\\"What identifies it: an order number, an email?\\\" → an attribute with `identity: true`.\\n- \\\"What details does it carry?\\\" → attributes, with `type` in the user's words.\\n- \\\"Which of these do you always change or check together? What must be true across all of\\n them at once?\\\" → the aggregate boundary. The thing they state the rule about is the root.\\n Explain once: an aggregate is the cluster you change together and check rules across; the\\n root is the one you name it after.\\n- \\\"What must never be allowed to happen to a <root>?\\\" → invariants, each constraining the\\n entity, value object or attribute it is about.\\n- \\\"Does a <root> point at things in another cluster, for example an order pointing at a\\n product?\\\" → `references` to that cluster's root; ask \\\"one or many?\\\" for cardinality.\\n- \\\"Does it contain things that cannot exist without it?\\\" → `includes`.\\n- \\\"Does it use a value like an address, money or a status?\\\" → `uses`.\\n\\n## Phase F: behaviour (produces Consumables, `raises`, Policies, Schemas)\\n\\n- \\\"What can someone ask this part to do?\\\" → `operation` consumables. Put an API entry point on\\n an application service, and a state change of one aggregate on that aggregate.\\n- \\\"When that happens, what fact would you announce to the rest of the business?\\\" → `event`\\n consumable, linked from the operation with `raises`. Events are past tense.\\n- \\\"Is that something only this part uses, or would other parts care?\\\" → `internal: true`, or\\n an upstream `pattern`.\\n- \\\"What information travels with that announcement or request?\\\" → a schema on the context,\\n attached with `schema`.\\n- \\\"When <event> happens, what do you then do automatically?\\\" → a policy with `on` the event\\n and `then` the operation. Either side may live in another context.\\n- \\\"Who outside this part listens for <event>?\\\" → a consumption on their aggregate or service,\\n with a downstream `pattern`.\\n- Close: \\\"Which of the words we used should I define, and does each map to one of the things\\n we modelled?\\\" → glossary terms with `embodiedBy`.\\n\\n## Phase G: validate and reflect\\n\\nRun validation. Explain each diagnostic in one plain sentence, propose the fix, and ask before\\napplying fixes for warnings. Then summarise what changed, in the user's words, and ask what to\\nmodel next.\\n\" },\n\t{ path: \"references/json-mode.md\", content: \"# JSON mode\\n\\nThe workspace files are the artefact. Each `.ods/*.json` file is one complete workspace, and\\nthe VS Code extension, the docs generator and anyone else load it with `Workspace.fromSchema`.\\n\\n## Files\\n\\n- `.ods/` (or the folder named by the VS Code setting `ods.folder`) at the project root.\\n- `.ods/schema.json`: the JSON Schema, written by the extension (`ODS: Write schema.json`).\\n Never edit it. If it is missing, copy it from\\n `node_modules/@open-domain-specification/core/dist/workspace.schema.json`.\\n- `.ods/<workspace-id>.json`: one workspace per file. The first key is\\n `\\\"$schema\\\": \\\"./schema.json\\\"`; the loader ignores it, editors use it for completion.\\n- Keep the file's `id` equal to its basename, and `odsVersion` equal to the other files' (use\\n `\\\"1.0.0\\\"` for a first file).\\n\\nThe smallest valid file is `examples/minimal.ods.json`. Copy it when creating a workspace, then\\ngrow it.\\n\\n## Editing rules\\n\\n- The schema is strict: every required field is present even when empty, and unknown fields\\n are rejected. `references/model-reference.md` lists them.\\n- Ids are the object keys. Create them as `snake_case` of the name, then never change them.\\n Renaming is changing `name`.\\n- Every `$ref` follows the grammar at the end of `model-reference.md` and points at something\\n that exists. A dangling ref makes the whole file fail to load; the extension then shows\\n \\\"Workspace file could not be loaded\\\" instead of diagnostics.\\n- Preserve the key order and two-space indentation of the file so diffs stay readable.\\n- Prefer several small edits, each followed by validation, over one large rewrite.\\n\\n## Validation\\n\\nThere is no CLI. Run `examples/validate.mjs` from the project root:\\n\\n```sh\\nnode .claude/skills/ods-authoring/examples/validate.mjs .ods/petstore.json\\n```\\n\\nOr inline:\\n\\n```sh\\nnode -e 'const {Workspace}=require(\\\"@open-domain-specification/core\\\");const f=process.argv[1];const ws=Workspace.fromSchema(JSON.parse(require(\\\"fs\\\").readFileSync(f,\\\"utf8\\\")));for(const d of ws.validate())console.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`)' .ods/petstore.json\\n```\\n\\nIf `@open-domain-specification/core` is not installed, prefix with\\n`npx -p @open-domain-specification/core` or install it as a devDependency. The VS Code Problems\\npanel shows the same diagnostics (source `ods`, code = rule id) and updates on save.\\n\\n## Several workspace files\\n\\nA `.ods` folder may hold several files. Treat each as its own workspace; refs never cross files.\\n\" },\n\t{ path: \"references/model-reference.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (dist/workspace.schema.json); do not edit. -->\\n\\n# ODS model reference\\n\\nThe complete shape of a workspace file. Collections keyed by id are JSON objects whose keys are the element ids; `array of` collections are JSON arrays. Required fields must be present even when empty (`{}` or `[]`).\\n\\n## Workspace\\n\\nRepresents a workspace in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `$schema` | string | no | Location of the JSON schema this document conforms to, usually the schema.json beside it. Ignored by the loader. |\\n| `boundedcontexts` | map of id to [BoundedContext](#boundedcontext) | yes | |\\n| `description` | string | yes | |\\n| `domains` | map of id to [Domain](#domain) | yes | |\\n| `homepage` | string | no | |\\n| `id` | string | yes | |\\n| `logoUrl` | string | no | |\\n| `name` | string | yes | |\\n| `odsVersion` | string | yes | |\\n| `primaryColor` | string | no | |\\n| `relationships` | array of [ContextRelationship](#contextrelationship) | yes | |\\n| `teams` | map of id to [Team](#team) | yes | |\\n| `version` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Aggregate\\n\\nRepresents an aggregate in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `entities` | map of id to [Entity](#entity) | yes | |\\n| `invariants` | map of id to [Invariant](#invariant) | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `valueobjects` | map of id to [ValueObject](#valueobject) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Attribute\\n\\nA named, typed property of an entity, value object or schema.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `identity` | boolean | no | True when this attribute is (part of) the identity of an entity. |\\n| `name` | string | yes | |\\n| `type` | string | yes | Free-form type name, e.g. `string`, `Money`, `Date`. |\\n| `valueobject` | `{ \\\"$ref\\\": string }` | no | The value object that models this attribute's type, when there is one. |\\n\\nNo other fields are allowed.\\n\\n## BoundedContext\\n\\nRepresents a bounded context in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aggregates` | map of id to [Aggregate](#aggregate) | yes | |\\n| `bigBallOfMud` | boolean | no | Marks a context whose model is not coherent (typically legacy) so that neighbours know to protect themselves from it. |\\n| `description` | string | yes | |\\n| `glossary` | map of id to [GlossaryTerm](#glossaryterm) | yes | |\\n| `name` | string | yes | |\\n| `policies` | map of id to [Policy](#policy) | yes | |\\n| `schemas` | map of id to [DataSchema](#dataschema) | yes | Payload shapes this context publishes or accepts, referenced by its consumables. |\\n| `services` | map of id to [Service](#service) | yes | |\\n| `subdomains` | array of `{ \\\"$ref\\\": string }` | yes | The subdomains this context serves; a context may serve several. |\\n| `team` | `{ \\\"$ref\\\": string }` | no | The team that owns this context. |\\n\\nNo other fields are allowed.\\n\\n## Consumable\\n\\nRepresents a consumable in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `internal` | boolean | no | True when the consumable stays inside its context: an event only local policies react to, or an operation only local callers issue. Internal consumables may not be consumed from another context. |\\n| `name` | string | yes | |\\n| `pattern` | \\\"open-host-service\\\" | \\\"published-language\\\" | no | The upstream role this consumable is offered under. Absent on internal consumables. |\\n| `raises` | array of `{ \\\"$ref\\\": string }` | no | For operations: the event consumables this operation may raise. |\\n| `schema` | `{ \\\"$ref\\\": string }` | no | The payload shape, one of the context's schemas. |\\n| `type` | \\\"event\\\" | \\\"operation\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Consumption\\n\\nRepresents a consumption in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumable` | `{ \\\"$ref\\\": string }` | yes | |\\n| `pattern` | \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | no | The downstream role the consumer adopts for this consumable. |\\n\\nNo other fields are allowed.\\n\\n## ContextRelationship\\n\\nA strategic relationship between two bounded contexts.\\n\\nOne of: [DirectedContextRelationship](#directedcontextrelationship), [SymmetricContextRelationship](#symmetriccontextrelationship).\\n\\n## DataSchema\\n\\nA named payload shape owned by a bounded context, shared by the consumables that carry it.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## DirectedContextRelationship\\n\\nAn upstream/downstream relationship between two bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `downstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `downstreamRoles` | array of \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | yes | |\\n| `type` | \\\"customer-supplier\\\" | \\\"upstream-downstream\\\" | yes | |\\n| `upstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `upstreamRoles` | array of \\\"open-host-service\\\" | \\\"published-language\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Domain\\n\\nRepresents a domain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `subdomains` | map of id to [Subdomain](#subdomain) | yes | |\\n\\nNo other fields are allowed.\\n\\n## EntityRelation\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `cardinality` | \\\"*\\\" | \\\"0..1\\\" | \\\"1\\\" | \\\"1..*\\\" | no | |\\n| `label` | string | no | |\\n| `relation` | \\\"includes\\\" | \\\"references\\\" | \\\"uses\\\" | yes | |\\n| `target` | `{ \\\"$ref\\\": string }` | yes | |\\n\\nNo other fields are allowed.\\n\\n## Entity\\n\\nRepresents an entity in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n| `root` | boolean | no | |\\n\\nNo other fields are allowed.\\n\\n## GlossaryTerm\\n\\nA term of the ubiquitous language of a bounded context.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aliases` | array of string | no | |\\n| `definition` | string | yes | |\\n| `embodiedBy` | `{ \\\"$ref\\\": string }` | no | The model element that embodies this term, when there is one. |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Invariant\\n\\nRepresents an invariant in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `constrains` | array of `{ \\\"$ref\\\": string }` | yes | The entities, value objects or attributes this invariant constrains. |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Policy\\n\\nA reaction: when these events happen, issue these commands.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `on` | array of `{ \\\"$ref\\\": string }` | yes | The event consumables that trigger this policy. |\\n| `then` | array of `{ \\\"$ref\\\": string }` | yes | The operation consumables this policy issues. |\\n\\nNo other fields are allowed.\\n\\n## Service\\n\\nRepresents a service in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `type` | \\\"application\\\" | \\\"domain\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Subdomain\\n\\nRepresents a subdomain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `type` | \\\"core\\\" | \\\"generic\\\" | \\\"supporting\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## SymmetricContextRelationship\\n\\nA relationship between two bounded contexts with no upstream or downstream side.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `participants` | array of unknown | yes | |\\n| `type` | \\\"partnership\\\" | \\\"separate-ways\\\" | \\\"shared-kernel\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Team\\n\\nA team that owns one or more bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `homepage` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## ValueObject\\n\\nRepresents a value object in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Ref grammar\\n\\nEvery cross-link is an object `{ \\\"$ref\\\": \\\"<path>\\\" }`. Paths are JSON pointers into the workspace, built from the ids that key each collection:\\n\\n| Element | Path |\\n|---|---|\\n| Team | `#/teams/<team>` |\\n| Domain | `#/domains/<domain>` |\\n| Subdomain | `#/domains/<domain>/subdomains/<subdomain>` |\\n| Bounded context | `#/boundedcontexts/<bc>` |\\n| Aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>` |\\n| Entity | `#/boundedcontexts/<bc>/aggregates/<aggregate>/entities/<entity>` |\\n| Value object | `#/boundedcontexts/<bc>/aggregates/<aggregate>/valueobjects/<vo>` |\\n| Invariant | `#/boundedcontexts/<bc>/aggregates/<aggregate>/invariants/<invariant>` |\\n| Attribute | `<owner path>/attributes/<attribute>` (owner is an entity, value object or schema) |\\n| Consumable of an aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>/provides/<consumable>` |\\n| Service | `#/boundedcontexts/<bc>/services/<service>` |\\n| Consumable of a service | `#/boundedcontexts/<bc>/services/<service>/provides/<consumable>` |\\n| Policy | `#/boundedcontexts/<bc>/policies/<policy>` |\\n| Glossary term | `#/boundedcontexts/<bc>/glossary/<term>` |\\n| Schema | `#/boundedcontexts/<bc>/schemas/<schema>` |\\n\\nA bounded context path never embeds the domain or subdomain, so moving a context between subdomains breaks no refs. A ref that points at nothing makes the whole file fail to load.\\n\" },\n\t{ path: \"references/translation-table.md\", content: \"# Translation table: what people say → what to record\\n\\nUse this while interviewing and when reviewing a draft for mis-modelling. `JSON` is where the\\nelement lives in a workspace file; `DSL` is the core call that creates it.\\n\\n| What they say | ODS element | JSON | DSL |\\n|---|---|---|---|\\n| \\\"the system for X\\\", \\\"our platform\\\" | Workspace | top-level `name`, `description` | `new Workspace(name, {...})` |\\n| \\\"the business areas\\\", \\\"departments\\\", \\\"lines of business\\\" | Domain | `domains.<id>` | `ws.addDomain(name, {description})` |\\n| \\\"the part that handles Y\\\", \\\"the Y job\\\" | Subdomain | `domains.<d>.subdomains.<id>` | `domain.addSubdomain(name, {type, description})` |\\n| \\\"what makes us different\\\", \\\"our secret sauce\\\" | subdomain type | `\\\"type\\\": \\\"core\\\"` | `type: \\\"core\\\"` |\\n| \\\"we need it but it's not special\\\", \\\"any sensible way works\\\" | subdomain type | `\\\"type\\\": \\\"supporting\\\"` | `type: \\\"supporting\\\"` |\\n| \\\"we'd buy it\\\", \\\"login / email / payments provider\\\" | subdomain type | `\\\"type\\\": \\\"generic\\\"` | `type: \\\"generic\\\"` |\\n| \\\"team X owns that\\\", \\\"that's Sam's service\\\" | Team, owning context | `teams.<id>`; context `team: {$ref}` | `ws.addTeam(name)`; `bc.ownedBy(team)` |\\n| \\\"in billing a customer means ..., in support it means ...\\\" | two Bounded Contexts | `boundedcontexts.<id>` twice | `subdomain.addBoundedcontext(name, {...})` twice |\\n| \\\"that part covers both Y and Z\\\" | context serving two subdomains | `subdomains: [{$ref Y}, {$ref Z}]` | `ws.addBoundedContext(name, {subdomains: [y, z]})` or `bc.serves(z)` |\\n| \\\"the old system\\\", \\\"legacy\\\", \\\"nobody understands its schema\\\" | big ball of mud | `\\\"bigBallOfMud\\\": true` | `bigBallOfMud: true` |\\n| \\\"A depends on B\\\", \\\"A calls B\\\", \\\"A reads B's data\\\" | upstream-downstream (B upstream) | `relationships[]` `type: \\\"upstream-downstream\\\"` | `a.downstreamOf(b, {...})` |\\n| \\\"they ask us before changing\\\", \\\"we're their customer\\\" | customer-supplier | `type: \\\"customer-supplier\\\"` | `a.downstreamOf(b, {type: \\\"customer-supplier\\\", ...})` |\\n| \\\"both teams change it together\\\", \\\"we release together\\\" | partnership | `type: \\\"partnership\\\"`, `participants` | `a.partnerOf(b)` |\\n| \\\"we share the same tables / library / code\\\" | shared kernel | `type: \\\"shared-kernel\\\"` | `a.sharesKernelWith(b)` |\\n| \\\"we deliberately don't integrate\\\" | separate ways | `type: \\\"separate-ways\\\"` | `a.separateWaysFrom(b, why)` |\\n| \\\"we use their API as-is\\\", \\\"we take whatever they send\\\" | conformist | consumption `pattern: \\\"conformist\\\"`; relationship `downstreamRoles` | `agg.consumes(c, {pattern: \\\"conformist\\\"})` |\\n| \\\"we copy and reshape their data\\\", \\\"we wrap their API\\\" | anti-corruption layer | `pattern: \\\"anti-corruption-layer\\\"` | `agg.consumes(c, {pattern: \\\"anti-corruption-layer\\\"})` |\\n| \\\"we expose a documented API\\\", \\\"there's a REST endpoint\\\" | open host service | operation `pattern: \\\"open-host-service\\\"`; relationship `upstreamRoles` | `svc.provides(name, {type: \\\"operation\\\", pattern: \\\"open-host-service\\\"})` |\\n| \\\"we publish a message format everyone agrees on\\\" | published language | event `pattern: \\\"published-language\\\"` | `agg.provides(name, {type: \\\"event\\\", pattern: \\\"published-language\\\"})` |\\n| \\\"this specific order\\\", \\\"the thing with a number\\\" | Entity | `aggregates.<a>.entities.<id>` | `agg.addEntity(name, {description})` |\\n| \\\"the main thing\\\", \\\"the one we name the rule about\\\" | root entity | `\\\"root\\\": true` | `agg.addRootEntity(name, {description})` |\\n| \\\"just a value\\\", \\\"an address\\\", \\\"money\\\", \\\"same values, same thing\\\" | Value Object | `aggregates.<a>.valueobjects.<id>` | `agg.addValueObject(name, {description})` |\\n| \\\"it has a field\\\", \\\"it's made of\\\" | Attribute | `attributes.<id>` with `type` in the user's words | `entity.addAttribute(name, {type})` |\\n| \\\"the number that identifies it\\\" | identity attribute | `\\\"identity\\\": true` | `identity: true` |\\n| \\\"its status is one of these values\\\" | attribute backed by a value object | `attributes.<id>.valueobject: {$ref}` | `addAttribute(name, {type, valueobject: vo})` |\\n| \\\"these change together\\\", \\\"one transaction\\\", \\\"the order and its lines\\\" | Aggregate | `aggregates.<id>` | `bc.addAggregate(name, {description})` |\\n| \\\"must never\\\", \\\"always has to\\\", \\\"can't be negative\\\", \\\"only when\\\" | Invariant | `invariants.<id>` with `constrains: [{$ref}]` | `agg.addInvariant(name, {description}).constrains(target)` |\\n| \\\"an order points at a pet\\\" (another cluster) | references relation | `relations[]` `relation: \\\"references\\\"` to the other root | `entity.references(otherRoot, label, cardinality)` |\\n| \\\"it contains lines that can't exist alone\\\" | includes relation | `relation: \\\"includes\\\"` | `entity.includes(child, label, cardinality)` |\\n| \\\"it has an address / a status\\\" | uses relation | `relation: \\\"uses\\\"` | `entity.uses(vo, label, cardinality)` |\\n| \\\"exactly one / at most one / any number / at least one\\\" | cardinality | `\\\"1\\\"` / `\\\"0..1\\\"` / `\\\"*\\\"` / `\\\"1..*\\\"` | third argument |\\n| \\\"you can ask it to ...\\\", \\\"POST /x\\\", \\\"the button does ...\\\" | operation | `provides.<id>` `type: \\\"operation\\\"` | `provides(name, {type: \\\"operation\\\", ...})` |\\n| \\\"then we tell everyone that ...\\\", a past-tense fact | event | `provides.<id>` `type: \\\"event\\\"` | `provides(name, {type: \\\"event\\\", ...})` |\\n| \\\"doing that announces ...\\\" | operation raises event | operation `raises: [{$ref event}]` | `op.raises(event)` |\\n| \\\"only we use that\\\", \\\"nobody outside needs it\\\" | internal consumable | `\\\"internal\\\": true`, no `pattern` | `internal: true` |\\n| \\\"what's in the message / the request body\\\" | Schema | `schemas.<id>` on the context; consumable `schema: {$ref}` | `bc.addSchema(name).addAttribute(...)`; `schema: s` |\\n| \\\"when X happens we then Y\\\", \\\"automatically after X\\\" | Policy | `policies.<id>` with `on: [event refs]`, `then: [operation refs]` | `bc.addPolicy(name, {description}).on(e).then(op)` |\\n| \\\"we listen for their X\\\" | consumption | `consumes: [{consumable: {$ref}, pattern}]` | `agg.consumes(theirEvent, {pattern})` |\\n| \\\"the API layer\\\", \\\"the endpoint handler\\\", \\\"the use case\\\" | application service | `services.<id>` `type: \\\"application\\\"` | `bc.addService(name, {type: \\\"application\\\", description})` |\\n| \\\"logic that doesn't belong to one thing\\\", \\\"pricing across orders\\\" | domain service | `type: \\\"domain\\\"` | `type: \\\"domain\\\"` |\\n| \\\"we call it ...\\\", \\\"a.k.a.\\\", \\\"sales say purchase\\\" | Glossary term | `glossary.<id>` with `definition`, `aliases`, `embodiedBy` | `bc.addTerm(name, {definition, aliases, embodiedBy})` |\\n| \\\"the database / queue / Kafka / the cloud\\\" | not modelled | — | say it is infrastructure and out of scope |\\n\" },\n\t{ path: \"references/validation-rules.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (RULE_CATALOG); do not edit. -->\\n\\n# Validation rules\\n\\n`Workspace.validate()` returns diagnostics `{ severity, rule, message, ref }`. Errors describe a model that contradicts itself and should be fixed before finishing. Warnings describe a decision that is missing; discuss them with the user rather than silently fixing them. Explain a diagnostic to the user in the plain words below, not by quoting the rule id.\\n\\n## `aggregate-root` (warning, error)\\n\\n**Requires:** Every aggregate has exactly one root entity.\\n\\n**Why it matters:** The root is the one thing you name the cluster after and reach it through; without it nobody knows where the rules are enforced, and with two the boundary is really two aggregates.\\n\\n**Usual fix:** Mark the entity the aggregate is named after with root: true, or split the aggregate if two entities genuinely lead their own clusters.\\n\\n## `cross-aggregate-reference` (error)\\n\\n**Requires:** A relation into another aggregate uses references and targets that aggregate's root.\\n\\n**Why it matters:** Aggregates are consistency boundaries; reaching inside another one couples the two so they can no longer change or be stored independently.\\n\\n**Usual fix:** Change the relation to \\\"references\\\" and point it at the other aggregate's root entity, holding only its identity.\\n\\n## `role-coherence` (warning)\\n\\n**Requires:** A consumable used from another context declares an upstream role, and the consumption declares a downstream role.\\n\\n**Why it matters:** Crossing a context boundary is an integration decision: how the provider offers it (a documented API or a published format) and how the consumer takes it (as-is or translated) should be explicit.\\n\\n**Usual fix:** Set pattern on the consumable to open-host-service or published-language, and pattern on the consumption to conformist or anti-corruption-layer.\\n\\n## `separate-ways` (error)\\n\\n**Requires:** Contexts that declare separate ways exchange no consumables.\\n\\n**Why it matters:** Separate ways is a deliberate decision not to integrate; a consumption between the two contradicts it.\\n\\n**Usual fix:** Remove the consumption, or remove the separate-ways relationship and declare the real one.\\n\\n## `internal-consumable` (error, warning)\\n\\n**Requires:** An internal consumable is never consumed, reacted to or issued from another context, and declares no upstream role.\\n\\n**Why it matters:** internal means the consumable stays inside its context; anything outside depending on it makes that promise false.\\n\\n**Usual fix:** Drop internal and give the consumable an upstream role, or stop the other context from using it.\\n\\n## `schema-context` (error)\\n\\n**Requires:** A consumable's payload schema belongs to the consumable's own context.\\n\\n**Why it matters:** The context that publishes a message owns its shape; borrowing another context's schema ties the two together.\\n\\n**Usual fix:** Move or copy the schema into the publishing context and point the consumable at that one.\\n\\n## `consumable-kind` (error)\\n\\n**Requires:** Policies react to events and issue operations; only operations raise events, and they raise only events.\\n\\n**Why it matters:** An event is a fact that happened, an operation is a request to do something; mixing them up makes flows unreadable.\\n\\n**Usual fix:** Check the type of each consumable a policy or raises list points at and swap it for the right kind.\\n\\n## `policy-complete` (warning)\\n\\n**Requires:** A policy reacts to at least one event and issues at least one operation.\\n\\n**Why it matters:** A policy is a rule of the form when this happens, do that; either half missing leaves nothing to enact.\\n\\n**Usual fix:** Add the missing event to on or the missing operation to then.\\n\\n## `context-serves-subdomain` (warning)\\n\\n**Requires:** Every bounded context serves at least one subdomain.\\n\\n**Why it matters:** A context that serves no subdomain has no place in the problem-space view, so nobody can see which part of the business it exists for.\\n\\n**Usual fix:** Add the subdomain the context serves to its subdomains list.\\n\" },\n\t{ path: \"SKILL.md\", content: \"---\\nname: ods-authoring\\ndescription: >\\n Author and evolve Open Domain Specification (ODS) workspaces, either as .ods/*.json files or\\n through the @open-domain-specification/core TypeScript DSL. Use when the user wants to model\\n their system or business domain, create or edit a domain model, bounded contexts, aggregates,\\n entities, events, policies, context maps or any DDD (domain-driven design) artefact, asks\\n \\\"what are our bounded contexts\\\", mentions ODS, .ods, open-ds or the ODS VS Code extension, or\\n has validation warnings from an ODS workspace. Acts as a DDD facilitator: interviews developers\\n who do not know DDD in plain language, maps their answers onto the model, validates the result.\\n---\\n\\n# Authoring ODS workspaces\\n\\n## Your role\\n\\nYou are a domain-modelling facilitator. The user knows their system; they usually do not know\\nDomain-Driven Design, and they should not need to. You ask plain-language questions, map the\\nanswers onto the ODS model, and explain each DDD term once, in one sentence, using their own\\nexample. Model in small increments, validate after every edit, and never lecture.\\n\\n## Step 0: detect the authoring mode\\n\\nDecide once, state it in one line, and do not re-detect every turn. The first match wins.\\n\\n1. **DSL mode.** A `.ts`, `.js` or `.mjs` file imports `@open-domain-specification/core`,\\n builds a `Workspace` and writes `toSchema()` output under `.ods/` or to a `workspace.json`;\\n or `package.json` depends on core and has a script (`build`, `ods`, `generate`, `model`)\\n that runs such a file. Emitted JSON is also recognisable: two-space indent and the key order\\n `id, name, description, version, odsVersion, ...`.\\n2. **JSON mode.** A `.ods/` folder (or the folder named by the VS Code setting `ods.folder`)\\n holds `*.json` workspace files next to a `schema.json`, and no generator matches.\\n3. **Neither.** Ask one question: keep the model as JSON files the VS Code extension edits, or\\n as TypeScript that generates them? Recommend JSON when there is no Node toolchain, and the\\n DSL when the model will be large or generated documentation is wanted.\\n\\nIn DSL mode never edit the emitted JSON; it is overwritten on the next run. In JSON mode never\\nintroduce a generator unless asked. Details: `references/json-mode.md`, `references/dsl-mode.md`.\\n\\n## Step 1: read what exists\\n\\nLoad every workspace file (or the DSL source) before proposing anything. Summarise it in the\\nuser's words: the business areas, the parts of the system and who owns them, the main things\\neach part manages, how the parts talk to each other, and how many validation problems there\\nare. Then ask what they want to change or add.\\n\\n## Step 2: interview\\n\\nWhen creating or expanding a model, follow `references/interview-playbook.md`. Strategic\\nquestions first (areas, ownership, integrations), then the detail of one part at a time. One\\nquestion per turn. After each answer, reflect it back as the element you would record (\\\"So I'd\\nnote an Order that must always point at exactly one Pet, right?\\\") before writing it. Stop\\ninterviewing as soon as you know enough for one coherent increment; you can always come back.\\n\\n## Step 3: translate\\n\\nMap answers with `references/translation-table.md`. Every element gets a `description` in\\nthe user's own words, and every noun they used more than once becomes a glossary term in its\\ncontext. Use `references/ddd-glossary.md` for the one-sentence explanations.\\n\\n## Step 4: edit\\n\\nFollow the mode reference for mechanics. Rules that hold in both modes:\\n\\n- Ids are the JSON keys and the segments of every `$ref`. They are derived from the name at\\n creation (`snake_case`) and then frozen. To rename something, change its `name` and keep the\\n id (in the DSL, pass `id` explicitly at the moment of renaming). Rewriting a key means\\n updating every ref that uses it, and confirming with the user first.\\n- Every required collection is present even when empty. A context always has `aggregates`,\\n `services`, `policies`, `glossary`, `schemas` and `subdomains`; an aggregate always has\\n `entities`, `valueobjects`, `invariants`, `provides`, `consumes`; an entity or value object\\n always has `attributes` and `relations`. See `references/model-reference.md`.\\n- Every `$ref` resolves to an element that exists. A dangling ref is a load failure, not a\\n warning: the whole file stops loading.\\n- Consumables (events and operations) live only under `provides` of an aggregate or a\\n service. Policies and consumptions point at them by ref.\\n- A payload schema belongs to the context that publishes the consumable.\\n- Reference another aggregate only through its root entity, with `references`.\\n\\n## Step 5: validate and explain\\n\\nValidate after every edit.\\n\\n- JSON mode: run the script in `examples/validate.mjs` (or the one-liner in\\n `references/json-mode.md`) against each file. The VS Code Problems panel shows the same\\n results, source `ods`, code = rule id.\\n- DSL mode: run the generator script. It prints `[severity] rule: message (ref)` lines and\\n rewrites the JSON.\\n\\nFor each diagnostic, tell the user in one plain sentence what it means and what you propose,\\nusing `references/validation-rules.md`. Errors block finishing. Warnings mark a missing\\ndecision: discuss them and let the user decide, rather than fixing them silently. If loading\\nthrows \\\"... with ref ... not found\\\", a ref is dangling: fix it first.\\n\\n## Educating without preaching\\n\\nThe first time a DDD term comes up, explain it in one sentence tied to the user's example,\\nthen move on. Never repeat an explanation, and never explain a term the user has already used\\ncorrectly. Say \\\"command\\\" in conversation if it helps, but the model's word is `operation`.\\n\\n## Defaults when the user cannot decide\\n\\n| Question | Default |\\n|---|---|\\n| Subdomain type unknown | `supporting` |\\n| Operation used by another context | `pattern: \\\"open-host-service\\\"` |\\n| Event used by another context | `pattern: \\\"published-language\\\"` |\\n| Consuming from a legacy or `bigBallOfMud` context | `pattern: \\\"anti-corruption-layer\\\"` |\\n| Consuming from any other context | `pattern: \\\"conformist\\\"` |\\n| Two contexts exchange consumables, nothing else known | relationship `upstream-downstream` |\\n| Cardinality unknown | omit it |\\n| Service type unknown | `application` if it fronts an API or UI, else `domain` |\\n\\n## Do not\\n\\n- Invent fields or values. The model is exactly `references/model-reference.md`.\\n- Put behaviour on domains or subdomains; they describe the problem, contexts hold solutions.\\n- Create an aggregate without a root, or reach inside another aggregate.\\n- Model infrastructure (databases, queues, brokers). Say it is out of scope.\\n- Force programming types into `type` when the user said \\\"money\\\", \\\"email\\\" or \\\"a date\\\".\\n- Emit `type: \\\"command\\\"`; the consumable types are `event` and `operation`.\\n- Let an operation raise an event of another context without pointing it out as unusual.\\n- Leave `upstreamRoles` or `downstreamRoles` empty on a directed relationship.\\n- Skip validation because the change was small.\\n\" },\n];\n","/** Where each agent looks for skills, relative to a project or home folder. */\nexport type SkillTarget = \"claude\" | \"agents\" | \"codex\";\n\n/** The folder name of the skill under every `skills/` directory. */\nexport const SKILL_NAME = \"ods-authoring\";\n\nexport const TARGETS: ReadonlyArray<{\n\tid: SkillTarget;\n\tlabel: string;\n\t/** The skills directory, relative to the root the skill is installed into. */\n\tdir: string;\n}> = [\n\t{ id: \"claude\", label: \"Claude Code\", dir: \".claude/skills\" },\n\t{ id: \"agents\", label: \"Agent Skills (.agents)\", dir: \".agents/skills\" },\n\t{ id: \"codex\", label: \"OpenAI Codex (.codex)\", dir: \".codex/skills\" },\n];\n\nexport const TARGET_DIRS: Record<SkillTarget, string> = Object.fromEntries(\n\tTARGETS.map((t) => [t.id, t.dir]),\n) as Record<SkillTarget, string>;\n\n/** The directory the skill lands in for a target, relative to `root`. */\nexport function skillDir(target: SkillTarget): string {\n\treturn `${TARGET_DIRS[target]}/${SKILL_NAME}`;\n}\n","import { BUNDLE, SKILL_VERSION } from \"./bundle.generated\";\nimport { SKILL_NAME, type SkillTarget, skillDir } from \"./targets\";\n\nexport type SkillFile = { path: string; content: string };\n\nconst STAMP = /<!-- ods-skill-version: ([^\\s]+) -->/;\n\nfunction stamp(content: string): string {\n\treturn `${content.trimEnd()}\\n\\n<!-- ods-skill-version: ${SKILL_VERSION} -->\\n`;\n}\n\n/** Every file of the skill bundle, paths relative to the skill folder. */\nexport function skillFiles(): SkillFile[] {\n\treturn BUNDLE.map((f) =>\n\t\tf.path === \"SKILL.md\" ? { ...f, content: stamp(f.content) } : { ...f },\n\t);\n}\n\nexport type InstallOptions = {\n\t/** The project or home folder the skill is installed into. */\n\troot: string;\n\ttarget: SkillTarget;\n\t/** Writes one file; receives an absolute-ish path built from `root`. */\n\twrite: (path: string, content: string) => Promise<void>;\n\t/** Override the files to install; defaults to the bundle. */\n\tfiles?: SkillFile[];\n};\n\n/** Writes the bundle into `<root>/<target skills dir>/ods-authoring/` and returns the paths written. */\nexport async function installSkill(options: InstallOptions): Promise<string[]> {\n\tconst base = `${options.root}/${skillDir(options.target)}`;\n\tconst written: string[] = [];\n\tfor (const file of options.files ?? skillFiles()) {\n\t\tconst path = `${base}/${file.path}`;\n\t\tawait options.write(path, file.content);\n\t\twritten.push(path);\n\t}\n\treturn written;\n}\n\nexport type InstallState = \"missing\" | \"stale\" | \"current\";\n\n/** Compares the version stamp of an installed SKILL.md with this package's. */\nexport async function isInstalled(\n\troot: string,\n\ttarget: SkillTarget,\n\tread: (path: string) => Promise<string | undefined>,\n): Promise<InstallState> {\n\tconst content = await read(`${root}/${skillDir(target)}/SKILL.md`);\n\tif (content === undefined) return \"missing\";\n\treturn STAMP.exec(content)?.[1] === SKILL_VERSION ? \"current\" : \"stale\";\n}\n\n/** A paragraph for AGENTS.md or copilot-instructions.md pointing agents at the installed skill. */\nexport function rulesSnippet(target: SkillTarget = \"agents\"): string {\n\treturn [\n\t\t\"## Domain model (Open Domain Specification)\",\n\t\t\"\",\n\t\t`This project keeps its domain model as an Open Domain Specification workspace. Before creating or editing anything under \\`.ods/\\` or a TypeScript file that builds a \\`Workspace\\` from \\`@open-domain-specification/core\\`, read \\`${skillDir(target)}/SKILL.md\\` and follow it: detect whether the model is authored as JSON or via the TypeScript DSL, interview the user in plain language before modelling, and validate after every change.`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport { SKILL_NAME, SKILL_VERSION };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACO,IAAM,gBAAgB;AACtB,IAAM,SAA2D;AAAA,EACvE,EAAE,MAAM,6BAA6B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmyJ;AAAA,EACj1J,EAAE,MAAM,iCAAiC,SAAS,s9FAAmoG;AAAA,EACrrG,EAAE,MAAM,wBAAwB,SAAS,0nHAAqwH;AAAA,EAC9yH,EAAE,MAAM,yBAAyB,SAAS,2zCAAs2C;AAAA,EACh5C,EAAE,MAAM,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmvH;AAAA,EAClyH,EAAE,MAAM,yBAAyB,SAAS,48HAA2+H;AAAA,EACrhI,EAAE,MAAM,0BAA0B,SAAS,mkFAAulF;AAAA,EACloF,EAAE,MAAM,oCAAoC,SAAS,kkNAAk+M;AAAA,EACvhN,EAAE,MAAM,2BAA2B,SAAS,q+EAA++E;AAAA,EAC3hF,EAAE,MAAM,iCAAiC,SAAS,2uVAA6zV;AAAA,EAC/2V,EAAE,MAAM,mCAAmC,SAAS,m4MAAimN;AAAA,EACrpN,EAAE,MAAM,kCAAkC,SAAS,0lIAA0lI;AAAA,EAC7oI,EAAE,MAAM,YAAY,SAAS,s/NAAygO;AACviO;;;ACZO,IAAM,aAAa;AAEnB,IAAM,UAKR;AAAA,EACJ,EAAE,IAAI,UAAU,OAAO,eAAe,KAAK,iBAAiB;AAAA,EAC5D,EAAE,IAAI,UAAU,OAAO,0BAA0B,KAAK,iBAAiB;AAAA,EACvE,EAAE,IAAI,SAAS,OAAO,yBAAyB,KAAK,gBAAgB;AACrE;AAEO,IAAM,cAA2C,OAAO;AAAA,EAC9D,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC;AAGO,SAAS,SAAS,QAA6B;AACrD,SAAO,GAAG,YAAY,MAAM,CAAC,IAAI,UAAU;AAC5C;;;ACnBA,IAAM,QAAQ;AAEd,SAAS,MAAM,SAAyB;AACvC,SAAO,GAAG,QAAQ,QAAQ,CAAC;AAAA;AAAA,0BAA+B,aAAa;AAAA;AACxE;AAGO,SAAS,aAA0B;AACzC,SAAO,OAAO;AAAA,IAAI,CAAC,MAClB,EAAE,SAAS,aAAa,iCAAK,IAAL,EAAQ,SAAS,MAAM,EAAE,OAAO,EAAE,KAAI,mBAAK;AAAA,EACpE;AACD;AAaA,SAAsB,aAAa,SAA4C;AAAA;AA7B/E;AA8BC,UAAM,OAAO,GAAG,QAAQ,IAAI,IAAI,SAAS,QAAQ,MAAM,CAAC;AACxD,UAAM,UAAoB,CAAC;AAC3B,eAAW,SAAQ,aAAQ,UAAR,YAAiB,WAAW,GAAG;AACjD,YAAM,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI;AACjC,YAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,cAAQ,KAAK,IAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACR;AAAA;AAKA,SAAsB,YACrB,MACA,QACA,MACwB;AAAA;AA/CzB;AAgDC,UAAM,UAAU,MAAM,KAAK,GAAG,IAAI,IAAI,SAAS,MAAM,CAAC,WAAW;AACjE,QAAI,YAAY,OAAW,QAAO;AAClC,aAAO,WAAM,KAAK,OAAO,MAAlB,mBAAsB,QAAO,gBAAgB,YAAY;AAAA,EACjE;AAAA;AAGO,SAAS,aAAa,SAAsB,UAAkB;AACpE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,wOAAwO,SAAS,MAAM,CAAC;AAAA,IACxP;AAAA,EACD,EAAE,KAAK,IAAI;AACZ;","names":[]}
1
+ {"version":3,"sources":["../src/bundle.generated.ts","../src/targets.ts","../src/install.ts"],"sourcesContent":["// Generated by scripts/generate.mts; do not edit.\nexport const SKILL_VERSION = \"0.2.0\";\nexport const BUNDLE: ReadonlyArray<{ path: string; content: string }> = [\n\t{ path: \"examples/minimal.ods.json\", content: \"{\\n\\t\\\"$schema\\\": \\\"./schema.json\\\",\\n\\t\\\"id\\\": \\\"bookshop\\\",\\n\\t\\\"name\\\": \\\"Bookshop\\\",\\n\\t\\\"description\\\": \\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n\\t\\\"version\\\": \\\"0.1.0\\\",\\n\\t\\\"odsVersion\\\": \\\"1.0.0\\\",\\n\\t\\\"domains\\\": {\\n\\t\\t\\\"bookselling\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Bookselling\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Everything involved in selling books online\\\",\\n\\t\\t\\t\\\"subdomains\\\": {\\n\\t\\t\\t\\t\\\"sales\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Sales\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"core\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"Taking and fulfilling orders\\\"\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"boundedcontexts\\\": {\\n\\t\\t\\\"orders\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Orders\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Owns orders from placement to dispatch\\\",\\n\\t\\t\\t\\\"subdomains\\\": [{ \\\"$ref\\\": \\\"#/domains/bookselling/subdomains/sales\\\" }],\\n\\t\\t\\t\\\"team\\\": { \\\"$ref\\\": \\\"#/teams/shop_team\\\" },\\n\\t\\t\\t\\\"aggregates\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"One customer's request to buy some books\\\",\\n\\t\\t\\t\\t\\t\\\"entities\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"The order itself\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"root\\\": true,\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"target\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/valueobjects/address\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"relation\\\": \\\"uses\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"label\\\": \\\"ships-to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"cardinality\\\": \\\"1\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"valueobjects\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"address\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Address\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"Where the order ships to\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"lines\\\": { \\\"name\\\": \\\"lines\\\", \\\"type\\\": \\\"text\\\" },\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"postcode\\\": { \\\"name\\\": \\\"postcode\\\", \\\"type\\\": \\\"postcode\\\" }\\n\\t\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\t\\\"relations\\\": []\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"invariants\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"total_not_negative\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"Total not negative\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"An order's total can never be below zero\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"constrains\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/entities/order/attributes/total\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"A customer placed an order\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"event\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"published-language\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"schema\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/schemas/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"services\\\": {\\n\\t\\t\\t\\t\\\"order_api\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order API\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"The endpoints the web shop calls\\\",\\n\\t\\t\\t\\t\\t\\\"type\\\": \\\"application\\\",\\n\\t\\t\\t\\t\\t\\\"provides\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"place_order\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"PlaceOrder\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"description\\\": \\\"POST /orders\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"operation\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"pattern\\\": \\\"open-host-service\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"raises\\\": [\\n\\t\\t\\t\\t\\t\\t\\t\\t{\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order/provides/order_placed\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t\\t\\t]\\n\\t\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\\"consumes\\\": []\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"policies\\\": {},\\n\\t\\t\\t\\\"glossary\\\": {\\n\\t\\t\\t\\t\\\"order\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"Order\\\",\\n\\t\\t\\t\\t\\t\\\"definition\\\": \\\"A customer's request to buy some books, paid up front\\\",\\n\\t\\t\\t\\t\\t\\\"aliases\\\": [\\\"Purchase\\\"],\\n\\t\\t\\t\\t\\t\\\"embodiedBy\\\": { \\\"$ref\\\": \\\"#/boundedcontexts/orders/aggregates/order\\\" }\\n\\t\\t\\t\\t}\\n\\t\\t\\t},\\n\\t\\t\\t\\\"schemas\\\": {\\n\\t\\t\\t\\t\\\"order_placed\\\": {\\n\\t\\t\\t\\t\\t\\\"name\\\": \\\"OrderPlaced\\\",\\n\\t\\t\\t\\t\\t\\\"description\\\": \\\"What other parts learn when an order is placed\\\",\\n\\t\\t\\t\\t\\t\\\"attributes\\\": {\\n\\t\\t\\t\\t\\t\\t\\\"order_number\\\": {\\n\\t\\t\\t\\t\\t\\t\\t\\\"name\\\": \\\"orderNumber\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"type\\\": \\\"order number\\\",\\n\\t\\t\\t\\t\\t\\t\\t\\\"identity\\\": true\\n\\t\\t\\t\\t\\t\\t},\\n\\t\\t\\t\\t\\t\\t\\\"total\\\": { \\\"name\\\": \\\"total\\\", \\\"type\\\": \\\"money\\\" }\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t}\\n\\t},\\n\\t\\\"relationships\\\": [],\\n\\t\\\"teams\\\": {\\n\\t\\t\\\"shop_team\\\": {\\n\\t\\t\\t\\\"name\\\": \\\"Shop Team\\\",\\n\\t\\t\\t\\\"description\\\": \\\"Runs the online shop\\\"\\n\\t\\t}\\n\\t}\\n}\\n\" },\n\t{ path: \"examples/minimal.workspace.ts\", content: \"// The same bookshop as minimal.ods.json, authored with the DSL.\\n// Run with `node minimal.workspace.ts` (Node 24) or `npx tsx minimal.workspace.ts`.\\nimport fs from \\\"node:fs\\\";\\nimport { Workspace } from \\\"@open-domain-specification/core\\\";\\n\\nexport const workspace = new Workspace(\\\"Bookshop\\\", {\\n\\todsVersion: \\\"1.0.0\\\",\\n\\tversion: \\\"0.1.0\\\",\\n\\tdescription:\\n\\t\\t\\\"A small online bookshop: a catalogue of titles and the orders customers place for them.\\\",\\n});\\n\\nconst bookselling = workspace.addDomain(\\\"Bookselling\\\", {\\n\\tdescription: \\\"Everything involved in selling books online\\\",\\n});\\nconst sales = bookselling.addSubdomain(\\\"Sales\\\", {\\n\\ttype: \\\"core\\\",\\n\\tdescription: \\\"Taking and fulfilling orders\\\",\\n});\\nconst shopTeam = workspace.addTeam(\\\"Shop Team\\\", {\\n\\tdescription: \\\"Runs the online shop\\\",\\n});\\n\\nconst orders = sales.addBoundedcontext(\\\"Orders\\\", {\\n\\tdescription: \\\"Owns orders from placement to dispatch\\\",\\n\\tteam: shopTeam,\\n});\\n\\nconst orderAgg = orders.addAggregate(\\\"Order\\\", {\\n\\tdescription: \\\"One customer's request to buy some books\\\",\\n});\\nconst order = orderAgg.addRootEntity(\\\"Order\\\", {\\n\\tdescription: \\\"The order itself\\\",\\n});\\norder.addAttribute(\\\"orderNumber\\\", { type: \\\"order number\\\", identity: true });\\nconst total = order.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst address = orderAgg.addValueObject(\\\"Address\\\", {\\n\\tdescription: \\\"Where the order ships to\\\",\\n});\\naddress.addAttribute(\\\"lines\\\", { type: \\\"text\\\" });\\naddress.addAttribute(\\\"postcode\\\", { type: \\\"postcode\\\" });\\norder.uses(address, \\\"ships-to\\\", \\\"1\\\");\\n\\norderAgg\\n\\t.addInvariant(\\\"Total not negative\\\", {\\n\\t\\tdescription: \\\"An order's total can never be below zero\\\",\\n\\t})\\n\\t.constrains(total);\\n\\nconst orderPlacedSchema = orders.addSchema(\\\"OrderPlaced\\\", {\\n\\tdescription: \\\"What other parts learn when an order is placed\\\",\\n});\\norderPlacedSchema.addAttribute(\\\"orderNumber\\\", {\\n\\ttype: \\\"order number\\\",\\n\\tidentity: true,\\n});\\norderPlacedSchema.addAttribute(\\\"total\\\", { type: \\\"money\\\" });\\n\\nconst orderPlaced = orderAgg.provides(\\\"OrderPlaced\\\", {\\n\\ttype: \\\"event\\\",\\n\\tdescription: \\\"A customer placed an order\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: orderPlacedSchema,\\n});\\n\\nconst orderApi = orders.addService(\\\"Order API\\\", {\\n\\ttype: \\\"application\\\",\\n\\tdescription: \\\"The endpoints the web shop calls\\\",\\n});\\norderApi\\n\\t.provides(\\\"PlaceOrder\\\", {\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tdescription: \\\"POST /orders\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t})\\n\\t.raises(orderPlaced);\\n\\norders.addTerm(\\\"Order\\\", {\\n\\tdefinition: \\\"A customer's request to buy some books, paid up front\\\",\\n\\taliases: [\\\"Purchase\\\"],\\n\\tembodiedBy: orderAgg,\\n});\\n\\n// Generator: validate, then write the workspace beside its schema.\\nif (process.argv[1]?.endsWith(\\\"minimal.workspace.ts\\\")) {\\n\\tfor (const d of workspace.validate()) {\\n\\t\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t}\\n\\tfs.mkdirSync(\\\".ods\\\", { recursive: true });\\n\\tfs.writeFileSync(\\n\\t\\t\\\".ods/bookshop.json\\\",\\n\\t\\tJSON.stringify(\\n\\t\\t\\t{ $schema: \\\"./schema.json\\\", ...workspace.toSchema() },\\n\\t\\t\\tnull,\\n\\t\\t\\t2,\\n\\t\\t),\\n\\t);\\n}\\n\" },\n\t{ path: \"examples/petstore.md\", content: \"# Patterns from the Petstore example\\n\\nExcerpts from the ODS example workspace (`models/petstore/src/workspace.ts`\\nin the ODS repository). Each shows one pattern worth copying.\\n\\n## A context serving two subdomains, and a legacy one\\n\\n```ts\\nconst inventoryBC = workspace.addBoundedContext(\\\"Inventory BC\\\", {\\n\\tdescription: \\\"Projection for /store/inventory (status→count)\\\",\\n\\tsubdomains: [inventorySD, catalogSD],\\n\\tteam: petShopTeam,\\n});\\nconst identityBC = usersSD.addBoundedcontext(\\\"Identity BC\\\", {\\n\\tdescription: \\\"Owns User aggregate & user endpoints. Legacy: user status is an untyped int\\\",\\n\\tbigBallOfMud: true,\\n\\tteam: platformTeam,\\n});\\n```\\n\\n## Attributes backed by value objects, relations with cardinality, invariants on attributes\\n\\n```ts\\npetRoot.addAttribute(\\\"id\\\", { type: \\\"int64\\\", identity: true });\\npetRoot.addAttribute(\\\"status\\\", { type: \\\"PetStatus\\\", valueobject: petStatusVO });\\npetRoot.uses(categoryVO, \\\"categorized-as\\\", \\\"0..1\\\");\\npetRoot.uses(photoUrlVO, \\\"has-photo\\\", \\\"1..*\\\");\\npetAgg\\n\\t.addInvariant(\\\"NameRequired\\\", { description: \\\"Pet.name must be non-empty\\\" })\\n\\t.constrains(petRoot.attributes.get(\\\"name\\\")!);\\n```\\n\\n## A cross-aggregate reference by identity to the other root\\n\\n```ts\\norderRoot.references(petRoot, \\\"for-pet\\\", \\\"1\\\");\\n```\\n\\n## Published events with a payload schema, and an internal operation that raises one\\n\\n```ts\\nconst petStatusChangedSchema = catalogBC.addSchema(\\\"PetStatusChanged\\\");\\npetStatusChangedSchema.addAttribute(\\\"petId\\\", { type: \\\"int64\\\", identity: true });\\n\\nconst petStatusChanged = petAgg.provides(\\\"PetStatusChanged\\\", {\\n\\tdescription: \\\"Pet status changed (available|pending|sold)\\\",\\n\\ttype: \\\"event\\\",\\n\\tpattern: \\\"published-language\\\",\\n\\tschema: petStatusChangedSchema,\\n});\\nconst _changePetStatus = petAgg\\n\\t.provides(\\\"ChangePetStatus\\\", {\\n\\t\\tdescription: \\\"Move a pet between available, pending and sold\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tinternal: true,\\n\\t\\tschema: petStatusChangedSchema,\\n\\t})\\n\\t.raises(petStatusChanged);\\n```\\n\\n## An open-host application service whose operations raise the aggregate's events\\n\\n```ts\\nconst petApp = catalogBC.addService(\\\"PetApp\\\", {\\n\\tdescription: \\\"Open-host service for /pet endpoints\\\",\\n\\ttype: \\\"application\\\",\\n});\\nconst _addPetOp = petApp\\n\\t.provides(\\\"AddPet\\\", {\\n\\t\\tdescription: \\\"POST /pet\\\",\\n\\t\\ttype: \\\"operation\\\",\\n\\t\\tpattern: \\\"open-host-service\\\",\\n\\t\\tschema: registerPetSchema,\\n\\t})\\n\\t.raises(petRegistered);\\n```\\n\\n## A consumption through an anti-corruption layer, and the relationship that explains it\\n\\n```ts\\norderApp.consumes(getPetSummaryOp, { pattern: \\\"anti-corruption-layer\\\" });\\n\\nsalesBC.downstreamOf(catalogBC, {\\n\\ttype: \\\"customer-supplier\\\",\\n\\tupstreamRoles: [\\\"open-host-service\\\"],\\n\\tdownstreamRoles: [\\\"anti-corruption-layer\\\"],\\n\\tdescription: \\\"Sales needs pet availability; Catalog commits to the summary contract\\\",\\n});\\n```\\n\\n## Separate ways, on purpose\\n\\n```ts\\nidentityBC.separateWaysFrom(\\n\\tsalesBC,\\n\\t\\\"Orders are anonymous in Petstore v3; no integration by design\\\",\\n);\\n```\\n\\n## A policy reacting to events from two contexts\\n\\n```ts\\nsalesBC\\n\\t.addPolicy(\\\"Approve when pet available\\\", {\\n\\t\\tdescription: \\\"When a pet becomes available and an order for it is placed, approve the order\\\",\\n\\t})\\n\\t.on(petStatusChanged, orderPlaced)\\n\\t.then(approveOrder);\\n```\\n\\n## Conformist consumptions feeding a projection\\n\\n```ts\\ninventoryAgg.consumes(petStatusChanged, { pattern: \\\"conformist\\\" });\\ninventoryAgg.consumes(orderApproved, { pattern: \\\"conformist\\\" });\\n```\\n\\n## Glossary terms embodied by model elements\\n\\n```ts\\ncatalogBC.addTerm(\\\"Category\\\", {\\n\\tdefinition: \\\"The kind of animal a pet is, such as Dogs or Cats\\\",\\n\\taliases: [\\\"Species\\\"],\\n\\tembodiedBy: categoryVO,\\n});\\n```\\n\" },\n\t{ path: \"examples/validate.mjs\", content: \"#!/usr/bin/env node\\n// Validates one or more ODS workspace JSON files.\\n// Usage: node validate.mjs .ods/petstore.json [.ods/other.json ...]\\n// Exits 1 when a file fails to load or has an error-level diagnostic.\\nimport { readFileSync } from \\\"node:fs\\\";\\nimport { createRequire } from \\\"node:module\\\";\\n\\nconst require = createRequire(`${process.cwd()}/`);\\nlet Workspace;\\ntry {\\n\\t({ Workspace } = require(\\\"@open-domain-specification/core\\\"));\\n} catch {\\n\\tconsole.error(\\n\\t\\t\\\"@open-domain-specification/core is not installed here. Run from the project root, install it (npm i -D @open-domain-specification/core), or use: npx -p @open-domain-specification/core node validate.mjs <file>\\\",\\n\\t);\\n\\tprocess.exit(2);\\n}\\n\\nlet failed = false;\\nfor (const file of process.argv.slice(2)) {\\n\\tlet workspace;\\n\\ttry {\\n\\t\\tworkspace = Workspace.fromSchema(JSON.parse(readFileSync(file, \\\"utf8\\\")));\\n\\t} catch (error) {\\n\\t\\tconsole.log(\\n\\t\\t\\t`[load-error] ${file}: ${error instanceof Error ? error.message : error}`,\\n\\t\\t);\\n\\t\\tfailed = true;\\n\\t\\tcontinue;\\n\\t}\\n\\tconst diagnostics = workspace.validate();\\n\\tconsole.log(`${file}: ${diagnostics.length} diagnostic(s)`);\\n\\tfor (const d of diagnostics) {\\n\\t\\tconsole.log(` [${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n\\t\\tif (d.severity === \\\"error\\\") failed = true;\\n\\t}\\n}\\nprocess.exit(failed ? 1 : 0);\\n\" },\n\t{ path: \"references/ddd-glossary.md\", content: \"# DDD terms in one sentence each\\n\\nUse these the first time a term comes up, filling the example with the user's own words.\\nNever repeat an explanation, and never explain a term the user already used correctly.\\n\\n- **Domain** — the whole area of business the system exists for, e.g. \\\"running the pet store\\\".\\n- **Subdomain** — one slice of that problem, e.g. \\\"the catalogue\\\" or \\\"taking orders\\\"; calling it\\n *core* only marks where your competitive effort goes, *supporting* means needed but ordinary,\\n *generic* means you would buy it.\\n- **Bounded context** — a boundary inside which every word has one exact meaning; your billing\\n \\\"Customer\\\" and your support \\\"Customer\\\" being different things is why they get separate\\n contexts.\\n- **Ubiquitous language / glossary** — the words a context uses, written down once so code,\\n conversations and documents all mean the same thing by \\\"Order\\\".\\n- **Team ownership** — the people who decide what a context means and how it changes.\\n- **Big ball of mud** — a context whose model nobody fully controls, flagged so that anything\\n talking to it translates rather than trusts.\\n- **Entity** — something that matters because of *which one* it is, like this particular order,\\n so it carries an identity.\\n- **Value object** — something that matters only by its values, like an address; two with the\\n same values are interchangeable.\\n- **Attribute** — one piece of information an entity, value object or message carries; the\\n identity attribute is the one that tells two entities apart.\\n- **Aggregate** — the cluster of things you change together and check rules across, named after\\n its *root*, the one thing you go through to change any of it; the order and its lines.\\n- **Invariant** — a rule that must always hold inside an aggregate, such as \\\"quantity is never\\n zero\\\".\\n- **Relation** — how one thing points at another: *includes* for parts that cannot exist alone,\\n *uses* for values it carries, *references* for another aggregate's root by identity.\\n- **Cardinality** — how many of the other thing: exactly one, at most one, any number, at least\\n one.\\n- **Operation** — something you can ask a part of the system to do, like \\\"place an order\\\"; in\\n conversation people often say *command*.\\n- **Event** — a fact that already happened, stated in the past tense, like \\\"order placed\\\", that\\n other parts can react to.\\n- **Consumable** — an operation or event that a part offers, and **consumption** is another part\\n using it.\\n- **Schema** — the shape of the information that travels with an operation or event.\\n- **Policy** — a rule of the form \\\"when this event happens, do that operation\\\", possibly across\\n contexts.\\n- **Application service** — the part that fronts an API or a screen and turns requests into\\n operations on aggregates.\\n- **Domain service** — business logic that does not belong to any single thing, like pricing\\n across several orders.\\n- **Upstream / downstream** — the side that is depended on, and the side that depends on it.\\n- **Customer-supplier** — a dependency where the downstream side gets a say before the upstream\\n side changes things.\\n- **Partnership** — two contexts whose teams plan and release together.\\n- **Shared kernel** — code or data two contexts both own and change.\\n- **Separate ways** — a deliberate decision that two contexts will not integrate.\\n- **Open host service** — the upstream side offers a documented API for anyone to use.\\n- **Published language** — the upstream side offers a documented message format everyone\\n agrees on.\\n- **Conformist** — the downstream side takes the upstream model as it comes.\\n- **Anti-corruption layer** — the downstream side copies and reshapes what it receives into its\\n own terms, so the upstream model cannot leak in.\\n\" },\n\t{ path: \"references/dsl-api.md\", content: \"# DSL reference (`@open-domain-specification/core`)\\n\\nEvery class is created through its parent and registers itself there, so `parent.addX(...)`\\nis the only call needed. Every attributes object accepts an optional `id` to fix the id\\nindependently of the name.\\n\\n| Receiver | Method | Creates / does |\\n|---|---|---|\\n| — | `new Workspace(name, { odsVersion, description, version, homepage?, logoUrl?, primaryColor?, id? })` | the workspace |\\n| `Workspace` | `addDomain(name, { description })` | a domain |\\n| `Workspace` | `addTeam(name, { description?, homepage? })` | a team |\\n| `Workspace` | `addBoundedContext(name, { description, subdomains?, bigBallOfMud?, team? })` | a context serving zero or more subdomains |\\n| `Workspace` | `addRelationship({...})` | a relationship; prefer the context helpers below |\\n| `Workspace` | `validate()` | the diagnostics list |\\n| `Workspace` | `toSchema()` / `Workspace.fromSchema(json)` | serialise / load |\\n| `Domain` | `addSubdomain(name, { type, description })` | a subdomain; `type` is `\\\"core\\\" \\\\| \\\"supporting\\\" \\\\| \\\"generic\\\"` |\\n| `Subdomain` | `addBoundedcontext(name, { description, bigBallOfMud?, team? })` | a context serving this subdomain |\\n| `BoundedContext` | `serves(subdomain)` | adds a served subdomain |\\n| `BoundedContext` | `ownedBy(team)` | sets the owning team |\\n| `BoundedContext` | `upstreamOf(other, { type?, upstreamRoles?, downstreamRoles?, description? })` | directed relationship, this side upstream; `type` defaults to `\\\"upstream-downstream\\\"`, or `\\\"customer-supplier\\\"` |\\n| `BoundedContext` | `downstreamOf(other, options)` | the same, this side downstream |\\n| `BoundedContext` | `partnerOf(other, description?)` | partnership |\\n| `BoundedContext` | `sharesKernelWith(other, description?)` | shared kernel |\\n| `BoundedContext` | `separateWaysFrom(other, description?)` | separate ways |\\n| `BoundedContext` | `addAggregate(name, { description })` | an aggregate |\\n| `BoundedContext` | `addService(name, { type, description })` | a service; `type` is `\\\"application\\\" \\\\| \\\"domain\\\"` |\\n| `BoundedContext` | `addPolicy(name, { description })` | a policy; chain `.on(...events).then(...operations)` |\\n| `BoundedContext` | `addTerm(name, { definition, aliases?, embodiedBy? })` | a glossary term; or chain `.embody(element)` |\\n| `BoundedContext` | `addSchema(name, { description? })` | a payload schema; add fields with `addAttribute` |\\n| `Aggregate` | `addRootEntity(name, { description })` | the root entity |\\n| `Aggregate` | `addEntity(name, { description, root? })` | an entity |\\n| `Aggregate` | `addValueObject(name, { description })` | a value object |\\n| `Aggregate` | `addInvariant(name, { description })` | an invariant; chain `.constrains(...entities, valueObjects or attributes)` |\\n| `Aggregate`, `Service` | `provides(name, { type, description, pattern?, internal?, schema? })` | a consumable; `type` is `\\\"event\\\" \\\\| \\\"operation\\\"`, `pattern` is `\\\"open-host-service\\\" \\\\| \\\"published-language\\\"` |\\n| `Aggregate`, `Service` | `consumes(consumable, { pattern? })` | a consumption; `pattern` is `\\\"conformist\\\" \\\\| \\\"anti-corruption-layer\\\"` |\\n| `Consumable` | `raises(...events)` | the events an operation raises |\\n| `Entity`, `ValueObject`, `DataSchema` | `addAttribute(name, { type, description?, identity?, valueobject? })` | an attribute; `type` is free text |\\n| `Entity`, `ValueObject` | `uses(target, label, cardinality?)` | a `uses` relation |\\n| `Entity`, `ValueObject` | `includes(target, label, cardinality?)` | an `includes` relation |\\n| `Entity`, `ValueObject` | `references(target, label, cardinality?)` | a `references` relation; across aggregates target the root |\\n| `Entity`, `ValueObject` | `addRelation(target, { relation, label?, cardinality? })` | any relation explicitly |\\n| `Entity` | `.attributes.get(\\\"name\\\")` | look an attribute up, e.g. to constrain it |\\n\\n`cardinality` is `\\\"1\\\" | \\\"0..1\\\" | \\\"*\\\" | \\\"1..*\\\"`. Chainable methods (`raises`, `on`, `then`,\\n`constrains`, `embody`, `serves`, `ownedBy`) return their receiver.\\n\" },\n\t{ path: \"references/dsl-mode.md\", content: \"# DSL mode\\n\\nThe TypeScript source is the artefact; the JSON under `.ods/` is generated from it. Edit the\\nsource, run the generator, read the diagnostics it prints.\\n\\n## Find the generator\\n\\nLook for a file that imports `Workspace` from `@open-domain-specification/core`, builds the\\nmodel, and writes `workspace.toSchema()` to disk. The canonical shape (from the ODS example\\npackage) is:\\n\\n```ts\\nimport fs from \\\"node:fs\\\";\\nimport { workspace } from \\\"./petstore/workspace.ts\\\";\\n\\nfor (const d of workspace.validate()) {\\n\\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\\n}\\n\\nfs.mkdirSync(\\\".ods\\\", { recursive: true });\\nfs.writeFileSync(\\n\\t\\\".ods/petstore.json\\\",\\n\\tJSON.stringify({ $schema: \\\"./schema.json\\\", ...workspace.toSchema() }, null, 2),\\n);\\n```\\n\\n`package.json` usually has a script for it (`build`, `ods`, `generate`, `model`). Node 24 runs\\n`.ts` files directly; on older Node use `npx tsx <file>`.\\n\\n## Loop\\n\\n1. Edit the model source. Keep the file's existing sections and ordering (domains, teams,\\n contexts, then one section per context).\\n2. Run the generator. It validates and rewrites the JSON.\\n3. Read every `[error]` and `[warning]` line and explain it to the user with\\n `validation-rules.md`.\\n4. Never hand-edit the emitted JSON; the next run overwrites it. If the user edits it, tell\\n them and offer to port the change into the source.\\n\\nIf the generator does not print diagnostics, add the four-line loop above before the write.\\n`toSchema()` does not emit `$schema`; spread it back in exactly as shown, so editors and the\\nVS Code extension keep the file associated with `schema.json`.\\n\\n## Ids and renames\\n\\nIds are derived from names with `snake_case` unless `id` is passed. Because ids are the JSON\\nkeys and the ref segments, renaming an element by changing its name silently changes its id\\nand breaks anything outside the source that points at it (documentation links, bookmarks,\\nother files). When renaming, pass the old id explicitly:\\n\\n```ts\\n// was: catalogBC.addAggregate(\\\"Pet\\\", {...})\\ncatalogBC.addAggregate(\\\"Listed Pet\\\", { id: \\\"pet\\\", description: \\\"...\\\" });\\n```\\n\\n## Conventions from the example\\n\\n- Name the variables after the element and its kind (`petAgg`, `petRoot`, `categoryVO`,\\n `petApp`), so refs read naturally in the code.\\n- Prefix a variable with `_` when the element is kept only for its side effect on the model\\n (an operation nobody references again).\\n- Create all consumables before the policies and consumptions that point at them.\\n- Full DSL surface: `dsl-api.md`. Patterns worth copying: `examples/petstore.md`.\\n\" },\n\t{ path: \"references/interview-playbook.md\", content: \"# Interview playbook\\n\\nYou are the facilitator. The user knows their system and their business; you know DDD. Your\\njob is to get the model out of their head without making them learn the vocabulary first.\\n\\n## Ground rules\\n\\n- One question per turn. Wait for the answer.\\n- No DDD word before its one-sentence explanation (see `ddd-glossary.md`), and each term is\\n explained once.\\n- After every answer, paraphrase it as the element you would record: \\\"So I'd note ... right?\\\"\\n- Write the increment as soon as a context or an aggregate is stable. Do not wait until the\\n whole interview is done; a model in the file beats a model in the chat.\\n- Skip any phase the existing workspace already covers. Read first, ask second.\\n- Keep the user's words. Descriptions and glossary definitions are written in their language,\\n not in DDD language.\\n\\n## Phase A: orientation (produces the Workspace)\\n\\n- \\\"In one or two sentences, what does this system do, and for whom?\\\" → `name`, `description`.\\n- \\\"Is there a homepage or logo I should link?\\\" → `homepage`, `logoUrl` (skip if none).\\n\\n## Phase B: the problem space (produces Domains and Subdomains with a type)\\n\\n- \\\"What are the big areas of the business this covers? Think of the headings you would put\\n on a whiteboard.\\\" → domains.\\n- Per area: \\\"What distinct jobs sit inside that area?\\\" → subdomains.\\n- Per subdomain: \\\"Is this something that makes you different from competitors, something you\\n need but any sensible way of doing it is fine, or something you would happily buy off the\\n shelf?\\\" → `core` / `supporting` / `generic`.\\n- Explain once: a subdomain is one slice of the problem; calling it core only marks where your\\n competitive effort goes.\\n\\n## Phase C: ownership (produces Teams, Bounded Contexts, `subdomains`, `bigBallOfMud`)\\n\\n- \\\"Which teams or people work on this, and which parts does each look after?\\\" → teams, and a\\n candidate context per part.\\n- \\\"If two teams both say <word>, do they mean exactly the same thing?\\\" If not, that is two\\n contexts. Explain once: a bounded context is a boundary inside which every word has one\\n exact meaning; your billing \\\"Customer\\\" and your support \\\"Customer\\\" being different is why\\n they get separate contexts.\\n- \\\"Which of the jobs from before does each part serve?\\\" → `subdomains` refs. One context may\\n serve several.\\n- \\\"Is any of these an old system that nobody fully understands, where the data model is a\\n mess?\\\" → `bigBallOfMud: true`. Explain: we flag it so anything talking to it knows to\\n translate rather than trust.\\n\\n## Phase D: the integration map (produces Relationships and seeds consumptions)\\n\\n- \\\"Which parts talk to each other? For each pair, who depends on whom?\\\" → `upstream-downstream`\\n with the depended-on side upstream.\\n- \\\"When the upstream team changes something, does the downstream team get a say beforehand?\\\"\\n Yes → `customer-supplier`.\\n- \\\"Do those two teams plan and release together, as one?\\\" → `partnership`.\\n- \\\"Do they share actual code or tables that both change?\\\" → `shared-kernel`.\\n- \\\"Are there two parts that you have decided, on purpose, should never integrate?\\\" →\\n `separate-ways`.\\n- \\\"How does the downstream side take the data: as it comes, or does it copy and reshape it\\n into its own terms?\\\" → `conformist` / `anti-corruption-layer`. Goes on `downstreamRoles`\\n and on each consumption's `pattern`.\\n- \\\"Does the upstream side publish a documented API, or a documented message format?\\\" →\\n `open-host-service` / `published-language`. Goes on `upstreamRoles` and on each exposed\\n consumable's `pattern`.\\n\\n## Phase E: inside one context (produces Aggregates, Entities, Value Objects, Invariants, Glossary)\\n\\nRepeat for each context the user wants detailed. Ask which one to start with.\\n\\n- \\\"Inside <context>, what are the things people talk about? Just list the nouns.\\\" → candidate\\n entities and value objects; every noun becomes a glossary term with the user's definition.\\n- Per noun: \\\"If two of these had identical details, would they still be two different things?\\\"\\n Yes → entity; no → value object. Explain once: an entity matters because of which one it is\\n (this order, not that one); a value object matters only by its values (an address).\\n- \\\"What identifies it: an order number, an email?\\\" → an attribute with `identity: true`.\\n- \\\"What details does it carry?\\\" → attributes, with `type` in the user's words.\\n- \\\"Which of these do you always change or check together? What must be true across all of\\n them at once?\\\" → the aggregate boundary. The thing they state the rule about is the root.\\n Explain once: an aggregate is the cluster you change together and check rules across; the\\n root is the one you name it after.\\n- \\\"What must never be allowed to happen to a <root>?\\\" → invariants, each constraining the\\n entity, value object or attribute it is about.\\n- \\\"Does a <root> point at things in another cluster, for example an order pointing at a\\n product?\\\" → `references` to that cluster's root; ask \\\"one or many?\\\" for cardinality.\\n- \\\"Does it contain things that cannot exist without it?\\\" → `includes`.\\n- \\\"Does it use a value like an address, money or a status?\\\" → `uses`.\\n\\n## Phase F: behaviour (produces Consumables, `raises`, Policies, Schemas)\\n\\n- \\\"What can someone ask this part to do?\\\" → `operation` consumables. Put an API entry point on\\n an application service, and a state change of one aggregate on that aggregate.\\n- \\\"When that happens, what fact would you announce to the rest of the business?\\\" → `event`\\n consumable, linked from the operation with `raises`. Events are past tense.\\n- \\\"Is that something only this part uses, or would other parts care?\\\" → `internal: true`, or\\n an upstream `pattern`.\\n- \\\"What information travels with that announcement or request?\\\" → a schema on the context,\\n attached with `schema`.\\n- \\\"When <event> happens, what do you then do automatically?\\\" → a policy with `on` the event\\n and `then` the operation. Either side may live in another context.\\n- \\\"Who outside this part listens for <event>?\\\" → a consumption on their aggregate or service,\\n with a downstream `pattern`.\\n- Close: \\\"Which of the words we used should I define, and does each map to one of the things\\n we modelled?\\\" → glossary terms with `embodiedBy`.\\n\\n## Phase G: validate and reflect\\n\\nRun validation. Explain each diagnostic in one plain sentence, propose the fix, and ask before\\napplying fixes for warnings. Then summarise what changed, in the user's words, and ask what to\\nmodel next.\\n\" },\n\t{ path: \"references/json-mode.md\", content: \"# JSON mode\\n\\nThe workspace files are the artefact. Each `.ods/*.json` file is one complete workspace, and\\nthe VS Code extension, the docs generator and anyone else load it with `Workspace.fromSchema`.\\n\\n## Files\\n\\n- `.ods/` (or the folder named by the VS Code setting `ods.folder`) at the project root.\\n- `.ods/schema.json`: the JSON Schema, written by the extension (`ODS: Write schema.json`).\\n Never edit it. If it is missing, copy it from\\n `node_modules/@open-domain-specification/core/dist/workspace.schema.json`.\\n- `.ods/<workspace-id>.json`: one workspace per file. The first key is\\n `\\\"$schema\\\": \\\"./schema.json\\\"`; the loader ignores it, editors use it for completion.\\n- Keep the file's `id` equal to its basename, and `odsVersion` equal to the other files' (use\\n `\\\"1.0.0\\\"` for a first file).\\n\\nThe smallest valid file is `examples/minimal.ods.json`. Copy it when creating a workspace, then\\ngrow it.\\n\\n## Editing rules\\n\\n- The schema is strict: every required field is present even when empty, and unknown fields\\n are rejected. `references/model-reference.md` lists them.\\n- Ids are the object keys. Create them as `snake_case` of the name, then never change them.\\n Renaming is changing `name`.\\n- Every `$ref` follows the grammar at the end of `model-reference.md` and points at something\\n that exists. A dangling ref makes the whole file fail to load; the extension then shows\\n \\\"Workspace file could not be loaded\\\" instead of diagnostics.\\n- Preserve the key order and two-space indentation of the file so diffs stay readable.\\n- Prefer several small edits, each followed by validation, over one large rewrite.\\n\\n## Validation\\n\\nThere is no CLI. Run `examples/validate.mjs` from the project root:\\n\\n```sh\\nnode .claude/skills/ods-authoring/examples/validate.mjs .ods/petstore.json\\n```\\n\\nOr inline:\\n\\n```sh\\nnode -e 'const {Workspace}=require(\\\"@open-domain-specification/core\\\");const f=process.argv[1];const ws=Workspace.fromSchema(JSON.parse(require(\\\"fs\\\").readFileSync(f,\\\"utf8\\\")));for(const d of ws.validate())console.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`)' .ods/petstore.json\\n```\\n\\nIf `@open-domain-specification/core` is not installed, prefix with\\n`npx -p @open-domain-specification/core` or install it as a devDependency. The VS Code Problems\\npanel shows the same diagnostics (source `ods`, code = rule id) and updates on save.\\n\\n## Several workspace files\\n\\nA `.ods` folder may hold several files. Treat each as its own workspace; refs never cross files.\\n\" },\n\t{ path: \"references/model-reference.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (dist/workspace.schema.json); do not edit. -->\\n\\n# ODS model reference\\n\\nThe complete shape of a workspace file. Collections keyed by id are JSON objects whose keys are the element ids; `array of` collections are JSON arrays. Required fields must be present even when empty (`{}` or `[]`).\\n\\n## Workspace\\n\\nRepresents a workspace in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `$schema` | string | no | Location of the JSON schema this document conforms to, usually the schema.json beside it. Ignored by the loader. |\\n| `boundedcontexts` | map of id to [BoundedContext](#boundedcontext) | yes | |\\n| `description` | string | yes | |\\n| `domains` | map of id to [Domain](#domain) | yes | |\\n| `homepage` | string | no | |\\n| `id` | string | yes | |\\n| `logoUrl` | string | no | |\\n| `name` | string | yes | |\\n| `odsVersion` | string | yes | |\\n| `primaryColor` | string | no | |\\n| `relationships` | array of [ContextRelationship](#contextrelationship) | yes | |\\n| `teams` | map of id to [Team](#team) | yes | |\\n| `version` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Aggregate\\n\\nRepresents an aggregate in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `entities` | map of id to [Entity](#entity) | yes | |\\n| `invariants` | map of id to [Invariant](#invariant) | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `valueobjects` | map of id to [ValueObject](#valueobject) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Attribute\\n\\nA named, typed property of an entity, value object or schema.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `identity` | boolean | no | True when this attribute is (part of) the identity of an entity. |\\n| `name` | string | yes | |\\n| `type` | string | yes | Free-form type name, e.g. `string`, `Money`, `Date`. |\\n| `valueobject` | `{ \\\"$ref\\\": string }` | no | The value object that models this attribute's type, when there is one. |\\n\\nNo other fields are allowed.\\n\\n## BoundedContext\\n\\nRepresents a bounded context in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aggregates` | map of id to [Aggregate](#aggregate) | yes | |\\n| `bigBallOfMud` | boolean | no | Marks a context whose model is not coherent (typically legacy) so that neighbours know to protect themselves from it. |\\n| `description` | string | yes | |\\n| `glossary` | map of id to [GlossaryTerm](#glossaryterm) | yes | |\\n| `name` | string | yes | |\\n| `policies` | map of id to [Policy](#policy) | yes | |\\n| `schemas` | map of id to [DataSchema](#dataschema) | yes | Payload shapes this context publishes or accepts, referenced by its consumables. |\\n| `services` | map of id to [Service](#service) | yes | |\\n| `subdomains` | array of `{ \\\"$ref\\\": string }` | yes | The subdomains this context serves; a context may serve several. |\\n| `team` | `{ \\\"$ref\\\": string }` | no | The team that owns this context. |\\n\\nNo other fields are allowed.\\n\\n## Consumable\\n\\nRepresents a consumable in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `internal` | boolean | no | True when the consumable stays inside its context: an event only local policies react to, or an operation only local callers issue. Internal consumables may not be consumed from another context. |\\n| `name` | string | yes | |\\n| `pattern` | \\\"open-host-service\\\" | \\\"published-language\\\" | no | The upstream role this consumable is offered under. Absent on internal consumables. |\\n| `raises` | array of `{ \\\"$ref\\\": string }` | no | For operations: the event consumables this operation may raise. |\\n| `schema` | `{ \\\"$ref\\\": string }` | no | The payload shape, one of the context's schemas. |\\n| `type` | \\\"event\\\" | \\\"operation\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Consumption\\n\\nRepresents a consumption in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumable` | `{ \\\"$ref\\\": string }` | yes | |\\n| `pattern` | \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | no | The downstream role the consumer adopts for this consumable. |\\n\\nNo other fields are allowed.\\n\\n## ContextRelationship\\n\\nA strategic relationship between two bounded contexts.\\n\\nOne of: [DirectedContextRelationship](#directedcontextrelationship), [SymmetricContextRelationship](#symmetriccontextrelationship).\\n\\n## DataSchema\\n\\nA named payload shape owned by a bounded context, shared by the consumables that carry it.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## DirectedContextRelationship\\n\\nAn upstream/downstream relationship between two bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `downstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `downstreamRoles` | array of \\\"anti-corruption-layer\\\" | \\\"conformist\\\" | yes | |\\n| `type` | \\\"customer-supplier\\\" | \\\"upstream-downstream\\\" | yes | |\\n| `upstream` | `{ \\\"$ref\\\": string }` | yes | |\\n| `upstreamRoles` | array of \\\"open-host-service\\\" | \\\"published-language\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Domain\\n\\nRepresents a domain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `subdomains` | map of id to [Subdomain](#subdomain) | yes | |\\n\\nNo other fields are allowed.\\n\\n## EntityRelation\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `cardinality` | \\\"*\\\" | \\\"0..1\\\" | \\\"1\\\" | \\\"1..*\\\" | no | |\\n| `label` | string | no | |\\n| `relation` | \\\"includes\\\" | \\\"references\\\" | \\\"uses\\\" | yes | |\\n| `target` | `{ \\\"$ref\\\": string }` | yes | |\\n\\nNo other fields are allowed.\\n\\n## Entity\\n\\nRepresents an entity in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n| `root` | boolean | no | |\\n\\nNo other fields are allowed.\\n\\n## GlossaryTerm\\n\\nA term of the ubiquitous language of a bounded context.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `aliases` | array of string | no | |\\n| `definition` | string | yes | |\\n| `embodiedBy` | `{ \\\"$ref\\\": string }` | no | The model element that embodies this term, when there is one. |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Invariant\\n\\nRepresents an invariant in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `constrains` | array of `{ \\\"$ref\\\": string }` | yes | The entities, value objects or attributes this invariant constrains. |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## Policy\\n\\nA reaction: when these events happen, issue these commands.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `on` | array of `{ \\\"$ref\\\": string }` | yes | The event consumables that trigger this policy. |\\n| `then` | array of `{ \\\"$ref\\\": string }` | yes | The operation consumables this policy issues. |\\n\\nNo other fields are allowed.\\n\\n## Service\\n\\nRepresents a service in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `consumes` | array of [Consumption](#consumption) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `provides` | map of id to [Consumable](#consumable) | yes | |\\n| `type` | \\\"application\\\" | \\\"domain\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Subdomain\\n\\nRepresents a subdomain in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `type` | \\\"core\\\" | \\\"generic\\\" | \\\"supporting\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## SymmetricContextRelationship\\n\\nA relationship between two bounded contexts with no upstream or downstream side.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `participants` | array of unknown | yes | |\\n| `type` | \\\"partnership\\\" | \\\"separate-ways\\\" | \\\"shared-kernel\\\" | yes | |\\n\\nNo other fields are allowed.\\n\\n## Team\\n\\nA team that owns one or more bounded contexts.\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `description` | string | no | |\\n| `homepage` | string | no | |\\n| `name` | string | yes | |\\n\\nNo other fields are allowed.\\n\\n## ValueObject\\n\\nRepresents a value object in the Open Domain Specification (ODS).\\n\\n| Field | Type | Required | Notes |\\n|---|---|---|---|\\n| `attributes` | map of id to [Attribute](#attribute) | yes | |\\n| `description` | string | yes | |\\n| `name` | string | yes | |\\n| `relations` | array of [EntityRelation](#entityrelation) | yes | |\\n\\nNo other fields are allowed.\\n\\n## Ref grammar\\n\\nEvery cross-link is an object `{ \\\"$ref\\\": \\\"<path>\\\" }`. Paths are JSON pointers into the workspace, built from the ids that key each collection:\\n\\n| Element | Path |\\n|---|---|\\n| Team | `#/teams/<team>` |\\n| Domain | `#/domains/<domain>` |\\n| Subdomain | `#/domains/<domain>/subdomains/<subdomain>` |\\n| Bounded context | `#/boundedcontexts/<bc>` |\\n| Aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>` |\\n| Entity | `#/boundedcontexts/<bc>/aggregates/<aggregate>/entities/<entity>` |\\n| Value object | `#/boundedcontexts/<bc>/aggregates/<aggregate>/valueobjects/<vo>` |\\n| Invariant | `#/boundedcontexts/<bc>/aggregates/<aggregate>/invariants/<invariant>` |\\n| Attribute | `<owner path>/attributes/<attribute>` (owner is an entity, value object or schema) |\\n| Consumable of an aggregate | `#/boundedcontexts/<bc>/aggregates/<aggregate>/provides/<consumable>` |\\n| Service | `#/boundedcontexts/<bc>/services/<service>` |\\n| Consumable of a service | `#/boundedcontexts/<bc>/services/<service>/provides/<consumable>` |\\n| Policy | `#/boundedcontexts/<bc>/policies/<policy>` |\\n| Glossary term | `#/boundedcontexts/<bc>/glossary/<term>` |\\n| Schema | `#/boundedcontexts/<bc>/schemas/<schema>` |\\n\\nA bounded context path never embeds the domain or subdomain, so moving a context between subdomains breaks no refs. A ref that points at nothing makes the whole file fail to load.\\n\" },\n\t{ path: \"references/translation-table.md\", content: \"# Translation table: what people say → what to record\\n\\nUse this while interviewing and when reviewing a draft for mis-modelling. `JSON` is where the\\nelement lives in a workspace file; `DSL` is the core call that creates it.\\n\\n| What they say | ODS element | JSON | DSL |\\n|---|---|---|---|\\n| \\\"the system for X\\\", \\\"our platform\\\" | Workspace | top-level `name`, `description` | `new Workspace(name, {...})` |\\n| \\\"the business areas\\\", \\\"departments\\\", \\\"lines of business\\\" | Domain | `domains.<id>` | `ws.addDomain(name, {description})` |\\n| \\\"the part that handles Y\\\", \\\"the Y job\\\" | Subdomain | `domains.<d>.subdomains.<id>` | `domain.addSubdomain(name, {type, description})` |\\n| \\\"what makes us different\\\", \\\"our secret sauce\\\" | subdomain type | `\\\"type\\\": \\\"core\\\"` | `type: \\\"core\\\"` |\\n| \\\"we need it but it's not special\\\", \\\"any sensible way works\\\" | subdomain type | `\\\"type\\\": \\\"supporting\\\"` | `type: \\\"supporting\\\"` |\\n| \\\"we'd buy it\\\", \\\"login / email / payments provider\\\" | subdomain type | `\\\"type\\\": \\\"generic\\\"` | `type: \\\"generic\\\"` |\\n| \\\"team X owns that\\\", \\\"that's Sam's service\\\" | Team, owning context | `teams.<id>`; context `team: {$ref}` | `ws.addTeam(name)`; `bc.ownedBy(team)` |\\n| \\\"in billing a customer means ..., in support it means ...\\\" | two Bounded Contexts | `boundedcontexts.<id>` twice | `subdomain.addBoundedcontext(name, {...})` twice |\\n| \\\"that part covers both Y and Z\\\" | context serving two subdomains | `subdomains: [{$ref Y}, {$ref Z}]` | `ws.addBoundedContext(name, {subdomains: [y, z]})` or `bc.serves(z)` |\\n| \\\"the old system\\\", \\\"legacy\\\", \\\"nobody understands its schema\\\" | big ball of mud | `\\\"bigBallOfMud\\\": true` | `bigBallOfMud: true` |\\n| \\\"A depends on B\\\", \\\"A calls B\\\", \\\"A reads B's data\\\" | upstream-downstream (B upstream) | `relationships[]` `type: \\\"upstream-downstream\\\"` | `a.downstreamOf(b, {...})` |\\n| \\\"they ask us before changing\\\", \\\"we're their customer\\\" | customer-supplier | `type: \\\"customer-supplier\\\"` | `a.downstreamOf(b, {type: \\\"customer-supplier\\\", ...})` |\\n| \\\"both teams change it together\\\", \\\"we release together\\\" | partnership | `type: \\\"partnership\\\"`, `participants` | `a.partnerOf(b)` |\\n| \\\"we share the same tables / library / code\\\" | shared kernel | `type: \\\"shared-kernel\\\"` | `a.sharesKernelWith(b)` |\\n| \\\"we deliberately don't integrate\\\" | separate ways | `type: \\\"separate-ways\\\"` | `a.separateWaysFrom(b, why)` |\\n| \\\"we use their API as-is\\\", \\\"we take whatever they send\\\" | conformist | consumption `pattern: \\\"conformist\\\"`; relationship `downstreamRoles` | `agg.consumes(c, {pattern: \\\"conformist\\\"})` |\\n| \\\"we copy and reshape their data\\\", \\\"we wrap their API\\\" | anti-corruption layer | `pattern: \\\"anti-corruption-layer\\\"` | `agg.consumes(c, {pattern: \\\"anti-corruption-layer\\\"})` |\\n| \\\"we expose a documented API\\\", \\\"there's a REST endpoint\\\" | open host service | operation `pattern: \\\"open-host-service\\\"`; relationship `upstreamRoles` | `svc.provides(name, {type: \\\"operation\\\", pattern: \\\"open-host-service\\\"})` |\\n| \\\"we publish a message format everyone agrees on\\\" | published language | event `pattern: \\\"published-language\\\"` | `agg.provides(name, {type: \\\"event\\\", pattern: \\\"published-language\\\"})` |\\n| \\\"this specific order\\\", \\\"the thing with a number\\\" | Entity | `aggregates.<a>.entities.<id>` | `agg.addEntity(name, {description})` |\\n| \\\"the main thing\\\", \\\"the one we name the rule about\\\" | root entity | `\\\"root\\\": true` | `agg.addRootEntity(name, {description})` |\\n| \\\"just a value\\\", \\\"an address\\\", \\\"money\\\", \\\"same values, same thing\\\" | Value Object | `aggregates.<a>.valueobjects.<id>` | `agg.addValueObject(name, {description})` |\\n| \\\"it has a field\\\", \\\"it's made of\\\" | Attribute | `attributes.<id>` with `type` in the user's words | `entity.addAttribute(name, {type})` |\\n| \\\"the number that identifies it\\\" | identity attribute | `\\\"identity\\\": true` | `identity: true` |\\n| \\\"its status is one of these values\\\" | attribute backed by a value object | `attributes.<id>.valueobject: {$ref}` | `addAttribute(name, {type, valueobject: vo})` |\\n| \\\"these change together\\\", \\\"one transaction\\\", \\\"the order and its lines\\\" | Aggregate | `aggregates.<id>` | `bc.addAggregate(name, {description})` |\\n| \\\"must never\\\", \\\"always has to\\\", \\\"can't be negative\\\", \\\"only when\\\" | Invariant | `invariants.<id>` with `constrains: [{$ref}]` | `agg.addInvariant(name, {description}).constrains(target)` |\\n| \\\"an order points at a pet\\\" (another cluster) | references relation | `relations[]` `relation: \\\"references\\\"` to the other root | `entity.references(otherRoot, label, cardinality)` |\\n| \\\"it contains lines that can't exist alone\\\" | includes relation | `relation: \\\"includes\\\"` | `entity.includes(child, label, cardinality)` |\\n| \\\"it has an address / a status\\\" | uses relation | `relation: \\\"uses\\\"` | `entity.uses(vo, label, cardinality)` |\\n| \\\"exactly one / at most one / any number / at least one\\\" | cardinality | `\\\"1\\\"` / `\\\"0..1\\\"` / `\\\"*\\\"` / `\\\"1..*\\\"` | third argument |\\n| \\\"you can ask it to ...\\\", \\\"POST /x\\\", \\\"the button does ...\\\" | operation | `provides.<id>` `type: \\\"operation\\\"` | `provides(name, {type: \\\"operation\\\", ...})` |\\n| \\\"then we tell everyone that ...\\\", a past-tense fact | event | `provides.<id>` `type: \\\"event\\\"` | `provides(name, {type: \\\"event\\\", ...})` |\\n| \\\"doing that announces ...\\\" | operation raises event | operation `raises: [{$ref event}]` | `op.raises(event)` |\\n| \\\"only we use that\\\", \\\"nobody outside needs it\\\" | internal consumable | `\\\"internal\\\": true`, no `pattern` | `internal: true` |\\n| \\\"what's in the message / the request body\\\" | Schema | `schemas.<id>` on the context; consumable `schema: {$ref}` | `bc.addSchema(name).addAttribute(...)`; `schema: s` |\\n| \\\"when X happens we then Y\\\", \\\"automatically after X\\\" | Policy | `policies.<id>` with `on: [event refs]`, `then: [operation refs]` | `bc.addPolicy(name, {description}).on(e).then(op)` |\\n| \\\"we listen for their X\\\" | consumption | `consumes: [{consumable: {$ref}, pattern}]` | `agg.consumes(theirEvent, {pattern})` |\\n| \\\"the API layer\\\", \\\"the endpoint handler\\\", \\\"the use case\\\" | application service | `services.<id>` `type: \\\"application\\\"` | `bc.addService(name, {type: \\\"application\\\", description})` |\\n| \\\"logic that doesn't belong to one thing\\\", \\\"pricing across orders\\\" | domain service | `type: \\\"domain\\\"` | `type: \\\"domain\\\"` |\\n| \\\"we call it ...\\\", \\\"a.k.a.\\\", \\\"sales say purchase\\\" | Glossary term | `glossary.<id>` with `definition`, `aliases`, `embodiedBy` | `bc.addTerm(name, {definition, aliases, embodiedBy})` |\\n| \\\"the database / queue / Kafka / the cloud\\\" | not modelled | — | say it is infrastructure and out of scope |\\n\" },\n\t{ path: \"references/validation-rules.md\", content: \"<!-- Generated by packages/skill/scripts/generate.mts from @open-domain-specification/core (RULE_CATALOG); do not edit. -->\\n\\n# Validation rules\\n\\n`Workspace.validate()` returns diagnostics `{ severity, rule, message, ref }`. Errors describe a model that contradicts itself and should be fixed before finishing. Warnings describe a decision that is missing; discuss them with the user rather than silently fixing them. Explain a diagnostic to the user in the plain words below, not by quoting the rule id.\\n\\n## `aggregate-root` (warning, error)\\n\\n**Requires:** Every aggregate has exactly one root entity.\\n\\n**Why it matters:** The root is the one thing you name the cluster after and reach it through; without it nobody knows where the rules are enforced, and with two the boundary is really two aggregates.\\n\\n**Usual fix:** Mark the entity the aggregate is named after with root: true, or split the aggregate if two entities genuinely lead their own clusters.\\n\\n## `cross-aggregate-reference` (error)\\n\\n**Requires:** A relation into another aggregate uses references and targets that aggregate's root.\\n\\n**Why it matters:** Aggregates are consistency boundaries; reaching inside another one couples the two so they can no longer change or be stored independently.\\n\\n**Usual fix:** Change the relation to \\\"references\\\" and point it at the other aggregate's root entity, holding only its identity.\\n\\n## `role-coherence` (warning)\\n\\n**Requires:** A consumable used from another context declares an upstream role, and the consumption declares a downstream role.\\n\\n**Why it matters:** Crossing a context boundary is an integration decision: how the provider offers it (a documented API or a published format) and how the consumer takes it (as-is or translated) should be explicit.\\n\\n**Usual fix:** Set pattern on the consumable to open-host-service or published-language, and pattern on the consumption to conformist or anti-corruption-layer.\\n\\n## `separate-ways` (error)\\n\\n**Requires:** Contexts that declare separate ways exchange no consumables.\\n\\n**Why it matters:** Separate ways is a deliberate decision not to integrate; a consumption between the two contradicts it.\\n\\n**Usual fix:** Remove the consumption, or remove the separate-ways relationship and declare the real one.\\n\\n## `internal-consumable` (error, warning)\\n\\n**Requires:** An internal consumable is never consumed, reacted to or issued from another context, and declares no upstream role.\\n\\n**Why it matters:** internal means the consumable stays inside its context; anything outside depending on it makes that promise false.\\n\\n**Usual fix:** Drop internal and give the consumable an upstream role, or stop the other context from using it.\\n\\n## `schema-context` (error)\\n\\n**Requires:** A consumable's payload schema belongs to the consumable's own context.\\n\\n**Why it matters:** The context that publishes a message owns its shape; borrowing another context's schema ties the two together.\\n\\n**Usual fix:** Move or copy the schema into the publishing context and point the consumable at that one.\\n\\n## `consumable-kind` (error)\\n\\n**Requires:** Policies react to events and issue operations; only operations raise events, and they raise only events.\\n\\n**Why it matters:** An event is a fact that happened, an operation is a request to do something; mixing them up makes flows unreadable.\\n\\n**Usual fix:** Check the type of each consumable a policy or raises list points at and swap it for the right kind.\\n\\n## `policy-complete` (warning)\\n\\n**Requires:** A policy reacts to at least one event and issues at least one operation.\\n\\n**Why it matters:** A policy is a rule of the form when this happens, do that; either half missing leaves nothing to enact.\\n\\n**Usual fix:** Add the missing event to on or the missing operation to then.\\n\\n## `context-serves-subdomain` (warning)\\n\\n**Requires:** Every bounded context serves at least one subdomain.\\n\\n**Why it matters:** A context that serves no subdomain has no place in the problem-space view, so nobody can see which part of the business it exists for.\\n\\n**Usual fix:** Add the subdomain the context serves to its subdomains list.\\n\" },\n\t{ path: \"SKILL.md\", content: \"---\\nname: ods-authoring\\ndescription: >\\n Author and evolve Open Domain Specification (ODS) workspaces, either as .ods/*.json files or\\n through the @open-domain-specification/core TypeScript DSL. Use when the user wants to model\\n their system or business domain, create or edit a domain model, bounded contexts, aggregates,\\n entities, events, policies, context maps or any DDD (domain-driven design) artefact, asks\\n \\\"what are our bounded contexts\\\", mentions ODS, .ods, open-ds or the ODS VS Code extension, or\\n has validation warnings from an ODS workspace. Acts as a DDD facilitator: interviews developers\\n who do not know DDD in plain language, maps their answers onto the model, validates the result.\\n---\\n\\n# Authoring ODS workspaces\\n\\n## Your role\\n\\nYou are a domain-modelling facilitator. The user knows their system; they usually do not know\\nDomain-Driven Design, and they should not need to. You ask plain-language questions, map the\\nanswers onto the ODS model, and explain each DDD term once, in one sentence, using their own\\nexample. Model in small increments, validate after every edit, and never lecture.\\n\\n## Step 0: detect the authoring mode\\n\\nDecide once, state it in one line, and do not re-detect every turn. The first match wins.\\n\\n1. **DSL mode.** A `.ts`, `.js` or `.mjs` file imports `@open-domain-specification/core`,\\n builds a `Workspace` and writes `toSchema()` output under `.ods/` or to a `workspace.json`;\\n or `package.json` depends on core and has a script (`build`, `ods`, `generate`, `model`)\\n that runs such a file. Emitted JSON is also recognisable: two-space indent and the key order\\n `id, name, description, version, odsVersion, ...`.\\n2. **JSON mode.** A `.ods/` folder (or the folder named by the VS Code setting `ods.folder`)\\n holds `*.json` workspace files next to a `schema.json`, and no generator matches.\\n3. **Neither.** Ask one question: keep the model as JSON files the VS Code extension edits, or\\n as TypeScript that generates them? Recommend JSON when there is no Node toolchain, and the\\n DSL when the model will be large or generated documentation is wanted.\\n\\nIn DSL mode never edit the emitted JSON; it is overwritten on the next run. In JSON mode never\\nintroduce a generator unless asked. Details: `references/json-mode.md`, `references/dsl-mode.md`.\\n\\n## Step 1: read what exists\\n\\nLoad every workspace file (or the DSL source) before proposing anything. Summarise it in the\\nuser's words: the business areas, the parts of the system and who owns them, the main things\\neach part manages, how the parts talk to each other, and how many validation problems there\\nare. Then ask what they want to change or add.\\n\\n## Step 2: interview\\n\\nWhen creating or expanding a model, follow `references/interview-playbook.md`. Strategic\\nquestions first (areas, ownership, integrations), then the detail of one part at a time. One\\nquestion per turn. After each answer, reflect it back as the element you would record (\\\"So I'd\\nnote an Order that must always point at exactly one Pet, right?\\\") before writing it. Stop\\ninterviewing as soon as you know enough for one coherent increment; you can always come back.\\n\\n## Step 3: translate\\n\\nMap answers with `references/translation-table.md`. Every element gets a `description` in\\nthe user's own words, and every noun they used more than once becomes a glossary term in its\\ncontext. Use `references/ddd-glossary.md` for the one-sentence explanations.\\n\\n## Step 4: edit\\n\\nFollow the mode reference for mechanics. Rules that hold in both modes:\\n\\n- Ids are the JSON keys and the segments of every `$ref`. They are derived from the name at\\n creation (`snake_case`) and then frozen. To rename something, change its `name` and keep the\\n id (in the DSL, pass `id` explicitly at the moment of renaming). Rewriting a key means\\n updating every ref that uses it, and confirming with the user first.\\n- Every required collection is present even when empty. A context always has `aggregates`,\\n `services`, `policies`, `glossary`, `schemas` and `subdomains`; an aggregate always has\\n `entities`, `valueobjects`, `invariants`, `provides`, `consumes`; an entity or value object\\n always has `attributes` and `relations`. See `references/model-reference.md`.\\n- Every `$ref` resolves to an element that exists. A dangling ref is a load failure, not a\\n warning: the whole file stops loading.\\n- Consumables (events and operations) live only under `provides` of an aggregate or a\\n service. Policies and consumptions point at them by ref.\\n- A payload schema belongs to the context that publishes the consumable.\\n- Reference another aggregate only through its root entity, with `references`.\\n\\n## Step 5: validate and explain\\n\\nValidate after every edit.\\n\\n- JSON mode: run the script in `examples/validate.mjs` (or the one-liner in\\n `references/json-mode.md`) against each file. The VS Code Problems panel shows the same\\n results, source `ods`, code = rule id.\\n- DSL mode: run the generator script. It prints `[severity] rule: message (ref)` lines and\\n rewrites the JSON.\\n\\nFor each diagnostic, tell the user in one plain sentence what it means and what you propose,\\nusing `references/validation-rules.md`. Errors block finishing. Warnings mark a missing\\ndecision: discuss them and let the user decide, rather than fixing them silently. If loading\\nthrows \\\"... with ref ... not found\\\", a ref is dangling: fix it first.\\n\\n## Educating without preaching\\n\\nThe first time a DDD term comes up, explain it in one sentence tied to the user's example,\\nthen move on. Never repeat an explanation, and never explain a term the user has already used\\ncorrectly. Say \\\"command\\\" in conversation if it helps, but the model's word is `operation`.\\n\\n## Defaults when the user cannot decide\\n\\n| Question | Default |\\n|---|---|\\n| Subdomain type unknown | `supporting` |\\n| Operation used by another context | `pattern: \\\"open-host-service\\\"` |\\n| Event used by another context | `pattern: \\\"published-language\\\"` |\\n| Consuming from a legacy or `bigBallOfMud` context | `pattern: \\\"anti-corruption-layer\\\"` |\\n| Consuming from any other context | `pattern: \\\"conformist\\\"` |\\n| Two contexts exchange consumables, nothing else known | relationship `upstream-downstream` |\\n| Cardinality unknown | omit it |\\n| Service type unknown | `application` if it fronts an API or UI, else `domain` |\\n\\n## Do not\\n\\n- Invent fields or values. The model is exactly `references/model-reference.md`.\\n- Put behaviour on domains or subdomains; they describe the problem, contexts hold solutions.\\n- Create an aggregate without a root, or reach inside another aggregate.\\n- Model infrastructure (databases, queues, brokers). Say it is out of scope.\\n- Force programming types into `type` when the user said \\\"money\\\", \\\"email\\\" or \\\"a date\\\".\\n- Emit `type: \\\"command\\\"`; the consumable types are `event` and `operation`.\\n- Let an operation raise an event of another context without pointing it out as unusual.\\n- Leave `upstreamRoles` or `downstreamRoles` empty on a directed relationship.\\n- Skip validation because the change was small.\\n\" },\n];\n","/** Where each agent looks for skills, relative to a project or home folder. */\nexport type SkillTarget = \"claude\" | \"agents\" | \"codex\";\n\n/** The folder name of the skill under every `skills/` directory. */\nexport const SKILL_NAME = \"ods-authoring\";\n\nexport const TARGETS: ReadonlyArray<{\n\tid: SkillTarget;\n\tlabel: string;\n\t/** The skills directory, relative to the root the skill is installed into. */\n\tdir: string;\n}> = [\n\t{ id: \"claude\", label: \"Claude Code\", dir: \".claude/skills\" },\n\t{ id: \"agents\", label: \"Agent Skills (.agents)\", dir: \".agents/skills\" },\n\t{ id: \"codex\", label: \"OpenAI Codex (.codex)\", dir: \".codex/skills\" },\n];\n\nexport const TARGET_DIRS: Record<SkillTarget, string> = Object.fromEntries(\n\tTARGETS.map((t) => [t.id, t.dir]),\n) as Record<SkillTarget, string>;\n\n/** The directory the skill lands in for a target, relative to `root`. */\nexport function skillDir(target: SkillTarget): string {\n\treturn `${TARGET_DIRS[target]}/${SKILL_NAME}`;\n}\n","import { BUNDLE, SKILL_VERSION } from \"./bundle.generated\";\nimport { SKILL_NAME, type SkillTarget, skillDir } from \"./targets\";\n\nexport type SkillFile = { path: string; content: string };\n\nconst STAMP = /<!-- ods-skill-version: ([^\\s]+) -->/;\n\nfunction stamp(content: string): string {\n\treturn `${content.trimEnd()}\\n\\n<!-- ods-skill-version: ${SKILL_VERSION} -->\\n`;\n}\n\n/** Every file of the skill bundle, paths relative to the skill folder. */\nexport function skillFiles(): SkillFile[] {\n\treturn BUNDLE.map((f) =>\n\t\tf.path === \"SKILL.md\" ? { ...f, content: stamp(f.content) } : { ...f },\n\t);\n}\n\nexport type InstallOptions = {\n\t/** The project or home folder the skill is installed into. */\n\troot: string;\n\ttarget: SkillTarget;\n\t/** Writes one file; receives an absolute-ish path built from `root`. */\n\twrite: (path: string, content: string) => Promise<void>;\n\t/** Override the files to install; defaults to the bundle. */\n\tfiles?: SkillFile[];\n};\n\n/** Writes the bundle into `<root>/<target skills dir>/ods-authoring/` and returns the paths written. */\nexport async function installSkill(options: InstallOptions): Promise<string[]> {\n\tconst base = `${options.root}/${skillDir(options.target)}`;\n\tconst written: string[] = [];\n\tfor (const file of options.files ?? skillFiles()) {\n\t\tconst path = `${base}/${file.path}`;\n\t\tawait options.write(path, file.content);\n\t\twritten.push(path);\n\t}\n\treturn written;\n}\n\nexport type InstallState = \"missing\" | \"stale\" | \"current\";\n\n/** Compares the version stamp of an installed SKILL.md with this package's. */\nexport async function isInstalled(\n\troot: string,\n\ttarget: SkillTarget,\n\tread: (path: string) => Promise<string | undefined>,\n): Promise<InstallState> {\n\tconst content = await read(`${root}/${skillDir(target)}/SKILL.md`);\n\tif (content === undefined) return \"missing\";\n\treturn STAMP.exec(content)?.[1] === SKILL_VERSION ? \"current\" : \"stale\";\n}\n\n/** A paragraph for AGENTS.md or copilot-instructions.md pointing agents at the installed skill. */\nexport function rulesSnippet(target: SkillTarget = \"agents\"): string {\n\treturn [\n\t\t\"## Domain model (Open Domain Specification)\",\n\t\t\"\",\n\t\t`This project keeps its domain model as an Open Domain Specification workspace. Before creating or editing anything under \\`.ods/\\` or a TypeScript file that builds a \\`Workspace\\` from \\`@open-domain-specification/core\\`, read \\`${skillDir(target)}/SKILL.md\\` and follow it: detect whether the model is authored as JSON or via the TypeScript DSL, interview the user in plain language before modelling, and validate after every change.`,\n\t\t\"\",\n\t].join(\"\\n\");\n}\n\nexport { SKILL_NAME, SKILL_VERSION };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACO,IAAM,gBAAgB;AACtB,IAAM,SAA2D;AAAA,EACvE,EAAE,MAAM,6BAA6B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmyJ;AAAA,EACj1J,EAAE,MAAM,iCAAiC,SAAS,s9FAAmoG;AAAA,EACrrG,EAAE,MAAM,wBAAwB,SAAS,0nHAAqwH;AAAA,EAC9yH,EAAE,MAAM,yBAAyB,SAAS,2zCAAs2C;AAAA,EACh5C,EAAE,MAAM,8BAA8B,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAmvH;AAAA,EAClyH,EAAE,MAAM,yBAAyB,SAAS,48HAA2+H;AAAA,EACrhI,EAAE,MAAM,0BAA0B,SAAS,mkFAAulF;AAAA,EACloF,EAAE,MAAM,oCAAoC,SAAS,kkNAAk+M;AAAA,EACvhN,EAAE,MAAM,2BAA2B,SAAS,q+EAA++E;AAAA,EAC3hF,EAAE,MAAM,iCAAiC,SAAS,2uVAA6zV;AAAA,EAC/2V,EAAE,MAAM,mCAAmC,SAAS,m4MAAimN;AAAA,EACrpN,EAAE,MAAM,kCAAkC,SAAS,0lIAA0lI;AAAA,EAC7oI,EAAE,MAAM,YAAY,SAAS,s/NAAygO;AACviO;;;ACZO,IAAM,aAAa;AAEnB,IAAM,UAKR;AAAA,EACJ,EAAE,IAAI,UAAU,OAAO,eAAe,KAAK,iBAAiB;AAAA,EAC5D,EAAE,IAAI,UAAU,OAAO,0BAA0B,KAAK,iBAAiB;AAAA,EACvE,EAAE,IAAI,SAAS,OAAO,yBAAyB,KAAK,gBAAgB;AACrE;AAEO,IAAM,cAA2C,OAAO;AAAA,EAC9D,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC;AACjC;AAGO,SAAS,SAAS,QAA6B;AACrD,SAAO,GAAG,YAAY,MAAM,CAAC,IAAI,UAAU;AAC5C;;;ACnBA,IAAM,QAAQ;AAEd,SAAS,MAAM,SAAyB;AACvC,SAAO,GAAG,QAAQ,QAAQ,CAAC;AAAA;AAAA,0BAA+B,aAAa;AAAA;AACxE;AAGO,SAAS,aAA0B;AACzC,SAAO,OAAO;AAAA,IAAI,CAAC,MAClB,EAAE,SAAS,aAAa,iCAAK,IAAL,EAAQ,SAAS,MAAM,EAAE,OAAO,EAAE,KAAI,mBAAK;AAAA,EACpE;AACD;AAaA,SAAsB,aAAa,SAA4C;AAAA;AA7B/E;AA8BC,UAAM,OAAO,GAAG,QAAQ,IAAI,IAAI,SAAS,QAAQ,MAAM,CAAC;AACxD,UAAM,UAAoB,CAAC;AAC3B,eAAW,SAAQ,aAAQ,UAAR,YAAiB,WAAW,GAAG;AACjD,YAAM,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI;AACjC,YAAM,QAAQ,MAAM,MAAM,KAAK,OAAO;AACtC,cAAQ,KAAK,IAAI;AAAA,IAClB;AACA,WAAO;AAAA,EACR;AAAA;AAKA,SAAsB,YACrB,MACA,QACA,MACwB;AAAA;AA/CzB;AAgDC,UAAM,UAAU,MAAM,KAAK,GAAG,IAAI,IAAI,SAAS,MAAM,CAAC,WAAW;AACjE,QAAI,YAAY,OAAW,QAAO;AAClC,aAAO,WAAM,KAAK,OAAO,MAAlB,mBAAsB,QAAO,gBAAgB,YAAY;AAAA,EACjE;AAAA;AAGO,SAAS,aAAa,SAAsB,UAAkB;AACpE,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,wOAAwO,SAAS,MAAM,CAAC;AAAA,IACxP;AAAA,EACD,EAAE,KAAK,IAAI;AACZ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-domain-specification/skill",
3
- "version": "0.1.11",
3
+ "version": "0.2.0",
4
4
  "description": "An installable agent skill for authoring Open Domain Specification (ODS) workspaces",
5
5
  "author": "Jonathan Turnock <jonathan.turnock@outlook.com>",
6
6
  "homepage": "https://docs.open-ds.io/docs/ai-skill/",
@@ -37,11 +37,11 @@
37
37
  "claude-code"
38
38
  ],
39
39
  "devDependencies": {
40
- "@open-domain-specification/core": "^0.1.10",
40
+ "@open-domain-specification/core": "^0.2.0",
41
41
  "@types/node": "^24.2.1",
42
42
  "tsup": "^8.5.0",
43
43
  "typescript": "^5.9.2",
44
44
  "vitest": "^3.2.4"
45
45
  },
46
- "gitHead": "cf8a197dc2f4b7b2ad7ab7dc031ecdeae7162588"
46
+ "gitHead": "0d3b5e2bb8be80a6bf5a4271366a18c159630e34"
47
47
  }
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/generate.mts; do not edit.
2
- export const SKILL_VERSION = "0.1.11";
2
+ export const SKILL_VERSION = "0.2.0";
3
3
  export const BUNDLE: ReadonlyArray<{ path: string; content: string }> = [
4
4
  { path: "examples/minimal.ods.json", content: "{\n\t\"$schema\": \"./schema.json\",\n\t\"id\": \"bookshop\",\n\t\"name\": \"Bookshop\",\n\t\"description\": \"A small online bookshop: a catalogue of titles and the orders customers place for them.\",\n\t\"version\": \"0.1.0\",\n\t\"odsVersion\": \"1.0.0\",\n\t\"domains\": {\n\t\t\"bookselling\": {\n\t\t\t\"name\": \"Bookselling\",\n\t\t\t\"description\": \"Everything involved in selling books online\",\n\t\t\t\"subdomains\": {\n\t\t\t\t\"sales\": {\n\t\t\t\t\t\"name\": \"Sales\",\n\t\t\t\t\t\"type\": \"core\",\n\t\t\t\t\t\"description\": \"Taking and fulfilling orders\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"boundedcontexts\": {\n\t\t\"orders\": {\n\t\t\t\"name\": \"Orders\",\n\t\t\t\"description\": \"Owns orders from placement to dispatch\",\n\t\t\t\"subdomains\": [{ \"$ref\": \"#/domains/bookselling/subdomains/sales\" }],\n\t\t\t\"team\": { \"$ref\": \"#/teams/shop_team\" },\n\t\t\t\"aggregates\": {\n\t\t\t\t\"order\": {\n\t\t\t\t\t\"name\": \"Order\",\n\t\t\t\t\t\"description\": \"One customer's request to buy some books\",\n\t\t\t\t\t\"entities\": {\n\t\t\t\t\t\t\"order\": {\n\t\t\t\t\t\t\t\"name\": \"Order\",\n\t\t\t\t\t\t\t\"description\": \"The order itself\",\n\t\t\t\t\t\t\t\"root\": true,\n\t\t\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\t\t\"order_number\": {\n\t\t\t\t\t\t\t\t\t\"name\": \"orderNumber\",\n\t\t\t\t\t\t\t\t\t\"type\": \"order number\",\n\t\t\t\t\t\t\t\t\t\"identity\": true\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"total\": { \"name\": \"total\", \"type\": \"money\" }\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"relations\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"target\": {\n\t\t\t\t\t\t\t\t\t\t\"$ref\": \"#/boundedcontexts/orders/aggregates/order/valueobjects/address\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\"relation\": \"uses\",\n\t\t\t\t\t\t\t\t\t\"label\": \"ships-to\",\n\t\t\t\t\t\t\t\t\t\"cardinality\": \"1\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"valueobjects\": {\n\t\t\t\t\t\t\"address\": {\n\t\t\t\t\t\t\t\"name\": \"Address\",\n\t\t\t\t\t\t\t\"description\": \"Where the order ships to\",\n\t\t\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\t\t\"lines\": { \"name\": \"lines\", \"type\": \"text\" },\n\t\t\t\t\t\t\t\t\"postcode\": { \"name\": \"postcode\", \"type\": \"postcode\" }\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"relations\": []\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"invariants\": {\n\t\t\t\t\t\t\"total_not_negative\": {\n\t\t\t\t\t\t\t\"name\": \"Total not negative\",\n\t\t\t\t\t\t\t\"description\": \"An order's total can never be below zero\",\n\t\t\t\t\t\t\t\"constrains\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"$ref\": \"#/boundedcontexts/orders/aggregates/order/entities/order/attributes/total\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"provides\": {\n\t\t\t\t\t\t\"order_placed\": {\n\t\t\t\t\t\t\t\"name\": \"OrderPlaced\",\n\t\t\t\t\t\t\t\"description\": \"A customer placed an order\",\n\t\t\t\t\t\t\t\"type\": \"event\",\n\t\t\t\t\t\t\t\"pattern\": \"published-language\",\n\t\t\t\t\t\t\t\"schema\": {\n\t\t\t\t\t\t\t\t\"$ref\": \"#/boundedcontexts/orders/schemas/order_placed\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"consumes\": []\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"services\": {\n\t\t\t\t\"order_api\": {\n\t\t\t\t\t\"name\": \"Order API\",\n\t\t\t\t\t\"description\": \"The endpoints the web shop calls\",\n\t\t\t\t\t\"type\": \"application\",\n\t\t\t\t\t\"provides\": {\n\t\t\t\t\t\t\"place_order\": {\n\t\t\t\t\t\t\t\"name\": \"PlaceOrder\",\n\t\t\t\t\t\t\t\"description\": \"POST /orders\",\n\t\t\t\t\t\t\t\"type\": \"operation\",\n\t\t\t\t\t\t\t\"pattern\": \"open-host-service\",\n\t\t\t\t\t\t\t\"raises\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"$ref\": \"#/boundedcontexts/orders/aggregates/order/provides/order_placed\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"consumes\": []\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"policies\": {},\n\t\t\t\"glossary\": {\n\t\t\t\t\"order\": {\n\t\t\t\t\t\"name\": \"Order\",\n\t\t\t\t\t\"definition\": \"A customer's request to buy some books, paid up front\",\n\t\t\t\t\t\"aliases\": [\"Purchase\"],\n\t\t\t\t\t\"embodiedBy\": { \"$ref\": \"#/boundedcontexts/orders/aggregates/order\" }\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"schemas\": {\n\t\t\t\t\"order_placed\": {\n\t\t\t\t\t\"name\": \"OrderPlaced\",\n\t\t\t\t\t\"description\": \"What other parts learn when an order is placed\",\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"order_number\": {\n\t\t\t\t\t\t\t\"name\": \"orderNumber\",\n\t\t\t\t\t\t\t\"type\": \"order number\",\n\t\t\t\t\t\t\t\"identity\": true\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"total\": { \"name\": \"total\", \"type\": \"money\" }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"relationships\": [],\n\t\"teams\": {\n\t\t\"shop_team\": {\n\t\t\t\"name\": \"Shop Team\",\n\t\t\t\"description\": \"Runs the online shop\"\n\t\t}\n\t}\n}\n" },
5
5
  { path: "examples/minimal.workspace.ts", content: "// The same bookshop as minimal.ods.json, authored with the DSL.\n// Run with `node minimal.workspace.ts` (Node 24) or `npx tsx minimal.workspace.ts`.\nimport fs from \"node:fs\";\nimport { Workspace } from \"@open-domain-specification/core\";\n\nexport const workspace = new Workspace(\"Bookshop\", {\n\todsVersion: \"1.0.0\",\n\tversion: \"0.1.0\",\n\tdescription:\n\t\t\"A small online bookshop: a catalogue of titles and the orders customers place for them.\",\n});\n\nconst bookselling = workspace.addDomain(\"Bookselling\", {\n\tdescription: \"Everything involved in selling books online\",\n});\nconst sales = bookselling.addSubdomain(\"Sales\", {\n\ttype: \"core\",\n\tdescription: \"Taking and fulfilling orders\",\n});\nconst shopTeam = workspace.addTeam(\"Shop Team\", {\n\tdescription: \"Runs the online shop\",\n});\n\nconst orders = sales.addBoundedcontext(\"Orders\", {\n\tdescription: \"Owns orders from placement to dispatch\",\n\tteam: shopTeam,\n});\n\nconst orderAgg = orders.addAggregate(\"Order\", {\n\tdescription: \"One customer's request to buy some books\",\n});\nconst order = orderAgg.addRootEntity(\"Order\", {\n\tdescription: \"The order itself\",\n});\norder.addAttribute(\"orderNumber\", { type: \"order number\", identity: true });\nconst total = order.addAttribute(\"total\", { type: \"money\" });\n\nconst address = orderAgg.addValueObject(\"Address\", {\n\tdescription: \"Where the order ships to\",\n});\naddress.addAttribute(\"lines\", { type: \"text\" });\naddress.addAttribute(\"postcode\", { type: \"postcode\" });\norder.uses(address, \"ships-to\", \"1\");\n\norderAgg\n\t.addInvariant(\"Total not negative\", {\n\t\tdescription: \"An order's total can never be below zero\",\n\t})\n\t.constrains(total);\n\nconst orderPlacedSchema = orders.addSchema(\"OrderPlaced\", {\n\tdescription: \"What other parts learn when an order is placed\",\n});\norderPlacedSchema.addAttribute(\"orderNumber\", {\n\ttype: \"order number\",\n\tidentity: true,\n});\norderPlacedSchema.addAttribute(\"total\", { type: \"money\" });\n\nconst orderPlaced = orderAgg.provides(\"OrderPlaced\", {\n\ttype: \"event\",\n\tdescription: \"A customer placed an order\",\n\tpattern: \"published-language\",\n\tschema: orderPlacedSchema,\n});\n\nconst orderApi = orders.addService(\"Order API\", {\n\ttype: \"application\",\n\tdescription: \"The endpoints the web shop calls\",\n});\norderApi\n\t.provides(\"PlaceOrder\", {\n\t\ttype: \"operation\",\n\t\tdescription: \"POST /orders\",\n\t\tpattern: \"open-host-service\",\n\t})\n\t.raises(orderPlaced);\n\norders.addTerm(\"Order\", {\n\tdefinition: \"A customer's request to buy some books, paid up front\",\n\taliases: [\"Purchase\"],\n\tembodiedBy: orderAgg,\n});\n\n// Generator: validate, then write the workspace beside its schema.\nif (process.argv[1]?.endsWith(\"minimal.workspace.ts\")) {\n\tfor (const d of workspace.validate()) {\n\t\tconsole.log(`[${d.severity}] ${d.rule}: ${d.message} (${d.ref})`);\n\t}\n\tfs.mkdirSync(\".ods\", { recursive: true });\n\tfs.writeFileSync(\n\t\t\".ods/bookshop.json\",\n\t\tJSON.stringify(\n\t\t\t{ $schema: \"./schema.json\", ...workspace.toSchema() },\n\t\t\tnull,\n\t\t\t2,\n\t\t),\n\t);\n}\n" },