dotdog 0.4.1 → 0.5.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.
package/dist/cli.js CHANGED
@@ -3125,7 +3125,7 @@ function E(dag) {
3125
3125
  const seen = new Set;
3126
3126
  for (const node of N(dag)) {
3127
3127
  for (const e of nodeEdges(node)) {
3128
- const src = isV2(node) ? String(node[0]) : node.i || node.id || "";
3128
+ const src = nodeId(node);
3129
3129
  const key = `${src}→${e.t}:${e.v}`;
3130
3130
  if (!seen.has(key)) {
3131
3131
  seen.add(key);
@@ -3136,8 +3136,14 @@ function E(dag) {
3136
3136
  return edges;
3137
3137
  }
3138
3138
  var P = (dag) => dag.p || dag.project || "";
3139
- var ni = (n) => isV2(n) ? String(n[0]) : n.i || n.id || "";
3139
+ var nodeId = (n) => isV2(n) ? String(n[0]) : n.i || n.id || "";
3140
+ var nodeName = (n) => isV2(n) ? n[1] || String(n[0]) : n.i || n.id || n.name || "";
3141
+ function nodeMatches(n, value) {
3142
+ const q = (value || "").toLowerCase();
3143
+ return nodeId(n).toLowerCase() === q || nodeName(n).toLowerCase() === q;
3144
+ }
3140
3145
  var nt = (n) => isV2(n) ? n[2] || "" : n.t || n.type || "";
3146
+ var nd = (n) => isV2(n) ? n[3] || "" : n.d || n.description || "";
3141
3147
  function np(n) {
3142
3148
  if (isV2(n)) {
3143
3149
  const flat = n[4] || [];
@@ -3177,7 +3183,7 @@ function serve(dir = ".") {
3177
3183
  jsonrpc: "2.0",
3178
3184
  id,
3179
3185
  result: {
3180
- protocolVersion: "0.1.0",
3186
+ protocolVersion: "2024-11-05",
3181
3187
  serverInfo: { name: "spec-serve", version: "0.1.0" },
3182
3188
  capabilities: { tools: {} }
3183
3189
  }
@@ -3215,11 +3221,12 @@ function serve(dir = ".") {
3215
3221
  const dag = dagCache.get(args.project || [...dagCache.keys()][0] || "");
3216
3222
  if (!dag)
3217
3223
  return { jsonrpc: "2.0", id, error: { code: 404, message: "Project not found" } };
3218
- const node = N(dag).find((n) => ni(n).toLowerCase() === (args.name || "").toLowerCase());
3224
+ const node = N(dag).find((n) => nodeMatches(n, args.name || ""));
3219
3225
  if (!node)
3220
3226
  return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: "{}" }] } };
3221
- const edges = E(dag).filter((e) => es(e).toLowerCase() === ni(node).toLowerCase() || et(e).toLowerCase() === ni(node).toLowerCase());
3222
- return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(isV2(node) ? { id: String(node[0]), name: node[1] || "", type: node[2] || "", description: node[3] || "", properties: np(node), states: ns(node), edges } : { ...node, edges }) }] } };
3227
+ const idForEdges = nodeId(node);
3228
+ const edges = E(dag).filter((e) => es(e).toLowerCase() === idForEdges.toLowerCase() || et(e).toLowerCase() === idForEdges.toLowerCase());
3229
+ return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(isV2(node) ? { id: nodeId(node), name: nodeName(node), type: nt(node), description: nd(node), properties: np(node), states: ns(node), edges } : { ...node, edges }) }] } };
3223
3230
  }
3224
3231
  if (name === "traverse") {
3225
3232
  const dag = dagCache.get(args.project || [...dagCache.keys()][0] || "");
@@ -3229,23 +3236,25 @@ function serve(dir = ".") {
3229
3236
  const visitedNodes = new Set;
3230
3237
  const visitedEdges = new Set;
3231
3238
  const subgraph = { nodes: [], edges: [] };
3232
- const queue = [{ id: args.from, depth: 0 }];
3239
+ const start = N(dag).find((n) => nodeMatches(n, args.from || ""));
3240
+ const queue = [{ id: start ? nodeId(start) : args.from, depth: 0 }];
3233
3241
  while (queue.length > 0) {
3234
3242
  const curr = queue.shift();
3235
- if (visitedNodes.has(curr.id) || curr.depth > depth)
3243
+ const node = N(dag).find((n) => nodeMatches(n, curr.id));
3244
+ const currId = node ? nodeId(node) : curr.id;
3245
+ if (visitedNodes.has(currId) || curr.depth > depth)
3236
3246
  continue;
3237
- visitedNodes.add(curr.id);
3238
- const node = N(dag).find((n) => ni(n).toLowerCase() === curr.id.toLowerCase());
3247
+ visitedNodes.add(currId);
3239
3248
  if (node)
3240
- subgraph.nodes.push(isV2(node) ? { id: String(node[0]), name: node[1] || "", type: node[2] || "", description: node[3] || "", properties: np(node), states: ns(node), edges: nodeEdges(node) } : node);
3241
- const edges = E(dag).filter((e) => es(e).toLowerCase() === curr.id.toLowerCase() || et(e).toLowerCase() === curr.id.toLowerCase());
3249
+ subgraph.nodes.push(isV2(node) ? { id: nodeId(node), name: nodeName(node), type: nt(node), description: nd(node), properties: np(node), states: ns(node), edges: nodeEdges(node) } : node);
3250
+ const edges = E(dag).filter((e) => es(e).toLowerCase() === currId.toLowerCase() || et(e).toLowerCase() === currId.toLowerCase());
3242
3251
  for (const e of edges) {
3243
3252
  const edgeKey = `${es(e)}→${et(e)}`;
3244
3253
  if (!visitedEdges.has(edgeKey)) {
3245
3254
  visitedEdges.add(edgeKey);
3246
3255
  subgraph.edges.push(e);
3247
3256
  }
3248
- const next = es(e).toLowerCase() === curr.id.toLowerCase() ? et(e) : es(e);
3257
+ const next = es(e).toLowerCase() === currId.toLowerCase() ? et(e) : es(e);
3249
3258
  if (!visitedNodes.has(next))
3250
3259
  queue.push({ id: next, depth: curr.depth + 1 });
3251
3260
  }
@@ -3258,7 +3267,7 @@ function serve(dir = ".") {
3258
3267
  return { jsonrpc: "2.0", id, error: { code: 404, message: "Project not found" } };
3259
3268
  const q = (args.q || "").toLowerCase();
3260
3269
  const type = (args.type || "").toLowerCase();
3261
- const results = N(dag).filter((n) => ni(n).toLowerCase().includes(q) && (!type || nt(n).toLowerCase().includes(type)));
3270
+ const results = N(dag).filter((n) => (nodeName(n).toLowerCase().includes(q) || nodeId(n).toLowerCase().includes(q) || nt(n).toLowerCase().includes(q)) && (!type || nt(n).toLowerCase().includes(type)));
3262
3271
  return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(results) }] } };
3263
3272
  }
3264
3273
  if (name === "summary") {
@@ -3282,11 +3291,11 @@ function serve(dir = ".") {
3282
3291
  const dag = dagCache.get(args.project || [...dagCache.keys()][0] || "");
3283
3292
  if (!dag)
3284
3293
  return { jsonrpc: "2.0", id, error: { code: 404, message: "Project not found" } };
3285
- const node = N(dag).find((n) => ni(n).toLowerCase() === (args.entity || "").toLowerCase());
3294
+ const node = N(dag).find((n) => nodeMatches(n, args.entity || ""));
3286
3295
  if (!node)
3287
3296
  return { jsonrpc: "2.0", id, error: { code: 404, message: "Entity not found" } };
3288
3297
  return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify({
3289
- entity: ni(node),
3298
+ entity: nodeName(node),
3290
3299
  properties: np(node),
3291
3300
  states: ns(node),
3292
3301
  lifecycle: nl(node)
@@ -3592,18 +3601,18 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3592
3601
  nodes.forEach((n, i) => nodeIds.set(n.i, i));
3593
3602
  const v2nodes = [];
3594
3603
  for (let j = 0;j < nodes.length; j++) {
3595
- const nd = nodes[j];
3604
+ const nd2 = nodes[j];
3596
3605
  const props = [];
3597
- if (nd.p)
3598
- for (const [k, v] of Object.entries(nd.p))
3606
+ if (nd2.p)
3607
+ for (const [k, v] of Object.entries(nd2.p))
3599
3608
  props.push(k, v);
3600
- const states = nd.s || [];
3609
+ const states = nd2.s || [];
3601
3610
  const outEdges = [];
3602
3611
  const seen = new Set;
3603
3612
  for (const e of edges) {
3604
- if (e.s !== nd.i && e.t !== nd.i)
3613
+ if (e.s !== nd2.i && e.t !== nd2.i)
3605
3614
  continue;
3606
- const tid = nodeIds.get(e.s === nd.i ? e.t : e.s);
3615
+ const tid = nodeIds.get(e.s === nd2.i ? e.t : e.s);
3607
3616
  if (tid === undefined)
3608
3617
  continue;
3609
3618
  const key = `${j}→${tid}:${e.v}`;
@@ -3617,17 +3626,17 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3617
3626
  ee.push(1);
3618
3627
  outEdges.push(ee);
3619
3628
  }
3620
- const entry = [j, nd.i || "", nd.t || "", nd.d || "", props, states, outEdges];
3621
- if (nd.g === "prediction") {
3629
+ const entry = [j, nd2.i || "", nd2.t || "", nd2.d || "", props, states, outEdges];
3630
+ if (nd2.g === "prediction") {
3622
3631
  const f = [];
3623
- if (nd.cf)
3624
- f.push(nd.cf);
3625
- if (nd.tf)
3626
- f.push(nd.tf);
3627
- if (nd.tg)
3628
- f.push(nd.tg);
3629
- if (nd.ms)
3630
- f.push(nd.ms);
3632
+ if (nd2.cf)
3633
+ f.push(nd2.cf);
3634
+ if (nd2.tf)
3635
+ f.push(nd2.tf);
3636
+ if (nd2.tg)
3637
+ f.push(nd2.tg);
3638
+ if (nd2.ms)
3639
+ f.push(nd2.ms);
3631
3640
  if (f.length)
3632
3641
  entry.push(f);
3633
3642
  }
@@ -3709,7 +3718,7 @@ program2.command("visualize [dir]").option("-s, --save").action((d = ".", opts)
3709
3718
  const dag = JSON.parse(readFileSync3(dagFile, "utf-8"));
3710
3719
  const nodes = dag.n || dag.nodes || [];
3711
3720
  const isV22 = (n) => Array.isArray(n) && typeof n[0] === "number";
3712
- const nodeName = (n) => isV22(n) ? nodes[n[0]] ? nodes[n[0]][1] || String(n[0]) : String(n[0]) : n.i || n.id || "";
3721
+ const nodeName2 = (n) => isV22(n) ? nodes[n[0]] ? nodes[n[0]][1] || String(n[0]) : String(n[0]) : n.i || n.id || "";
3713
3722
  const slug = (s) => s.replace(/\s+/g, "_").replace(/^[^a-zA-Z]+/, "n_");
3714
3723
  let out = "```mermaid\ngraph LR\n";
3715
3724
  for (const n of nodes) {
@@ -4189,6 +4198,142 @@ program2.command("staleness [dir]").action((d = ".") => {
4189
4198
  }
4190
4199
  }
4191
4200
  });
4201
+ program2.command("verify [dir]").description("Verify spec-code alignment. --init auto-generates verify section in plan.dog").option("-i, --init", "Auto-generate verify section from codebase scan").action((d = ".", opts) => {
4202
+ const dir = resolvePath2(d);
4203
+ const dirs = [join3(dir, "projects"), join3(dir, "specs"), dir];
4204
+ console.log(source_default.bold(opts.init ? `Auto-Generating Verify Section
4205
+ ` : `Verification Audit
4206
+ `));
4207
+ for (const dd of dirs) {
4208
+ if (!existsSync3(dd))
4209
+ continue;
4210
+ const projects = readdirSync3(dd, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
4211
+ for (const p of projects) {
4212
+ const pd = join3(dd, p);
4213
+ if (!existsSync3(join3(pd, "SPEC.dog")))
4214
+ continue;
4215
+ const planFile = join3(pd, "plan.dog");
4216
+ if (!existsSync3(planFile)) {
4217
+ console.log(source_default.yellow(` ${p}: No plan.dog`));
4218
+ continue;
4219
+ }
4220
+ const dagFile = join3(pd, `${p}.dag`);
4221
+ if (!existsSync3(dagFile)) {
4222
+ console.log(source_default.yellow(` ${p}: No .dag file. Run compile first.`));
4223
+ continue;
4224
+ }
4225
+ const dag = JSON.parse(readFileSync3(dagFile, "utf-8"));
4226
+ const plan = readFileSync3(planFile, "utf-8");
4227
+ if (opts.init) {
4228
+ const entities = [];
4229
+ const props = new Map;
4230
+ for (const node of dag.n || []) {
4231
+ const name = node[1] || String(node[0]);
4232
+ entities.push(name);
4233
+ if (node[4])
4234
+ props.set(name, node[4].map((p2) => p2[0]));
4235
+ }
4236
+ let verify = `
4237
+ ## Verify
4238
+
4239
+ `;
4240
+ for (const entity of entities) {
4241
+ const nameLower = entity.toLowerCase().replace(/[^a-z0-9]/g, "");
4242
+ let matchFile = "";
4243
+ const skip = new Set(["node_modules", ".git", "dist", ".bun", "dev", "build"]);
4244
+ const codeDirs = [join3(dir, "src"), join3(dir, "lib"), join3(dir, "app"), dir];
4245
+ for (const cd of codeDirs) {
4246
+ if (!existsSync3(cd))
4247
+ continue;
4248
+ try {
4249
+ const allFiles = readdirSync3(cd, { recursive: true }).filter((f) => {
4250
+ if (!(f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".py") || f.endsWith(".sol") || f.endsWith(".go")))
4251
+ return false;
4252
+ for (const part of f.split("/")) {
4253
+ if (skip.has(part))
4254
+ return false;
4255
+ }
4256
+ return true;
4257
+ });
4258
+ const match = allFiles.find((f) => f.toLowerCase().includes(nameLower));
4259
+ if (match) {
4260
+ matchFile = join3(cd, match).replace(dir, ".");
4261
+ break;
4262
+ }
4263
+ } catch (_) {}
4264
+ }
4265
+ verify += `### Entity: ${entity}
4266
+ `;
4267
+ if (matchFile) {
4268
+ verify += ` file: ${matchFile}
4269
+ `;
4270
+ const fullPath = join3(dir, matchFile.replace("./", ""));
4271
+ if (existsSync3(fullPath)) {
4272
+ const code = readFileSync3(fullPath, "utf-8");
4273
+ const codeProps = [...code.matchAll(/\b(\w+)\s*[:?]\s*\w+/g)].map((m) => m[1]).filter((v, i, a) => a.indexOf(v) === i);
4274
+ if (codeProps.length > 0) {
4275
+ verify += ` properties: [${codeProps.slice(0, 10).join(", ")}]
4276
+ `;
4277
+ }
4278
+ }
4279
+ } else {
4280
+ verify += ` # no matching file found — map manually
4281
+ `;
4282
+ }
4283
+ verify += `
4284
+ `;
4285
+ }
4286
+ const updatedPlan = plan.includes("## Verify") ? plan : plan + verify;
4287
+ writeFileSync2(planFile, updatedPlan);
4288
+ console.log(source_default.green(` ${p}: Verify section generated in plan.dog`));
4289
+ } else {
4290
+ const verifyMatch = plan.match(/## Verify\n([\s\S]*?)(?=\n## |$)/);
4291
+ if (!verifyMatch) {
4292
+ console.log(source_default.yellow(` ${p}: No ## Verify section. Run: dotdog verify --init`));
4293
+ continue;
4294
+ }
4295
+ const verifyBlock = verifyMatch[1];
4296
+ const entityBlocks = [...verifyBlock.matchAll(/### Entity: (\w+)\n([\s\S]*?)(?=### Entity:|$)/g)];
4297
+ let checks = 0, passed = 0;
4298
+ for (const [, ename, ebody] of entityBlocks) {
4299
+ checks++;
4300
+ const fileMatch = ebody.match(/file:\s*(.+)/);
4301
+ const propMatch = ebody.match(/properties:\s*\[([^\]]+)\]/);
4302
+ if (!fileMatch)
4303
+ continue;
4304
+ const filePath = join3(dir, fileMatch[1].trim().replace("./", ""));
4305
+ if (!existsSync3(filePath)) {
4306
+ console.log(source_default.red(` ✗ ${ename}: file ${fileMatch[1].trim()} not found`));
4307
+ continue;
4308
+ }
4309
+ const code = readFileSync3(filePath, "utf-8");
4310
+ const cleanCode = code.replace(/\/\/.*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
4311
+ if (propMatch) {
4312
+ const props = propMatch[1].split(",").map((s) => s.trim());
4313
+ let propPass = 0;
4314
+ for (const prop of props) {
4315
+ const snakeVariant = prop.replace(/_/g, "");
4316
+ const camelVariant = prop.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
4317
+ if (cleanCode.includes(prop) || cleanCode.includes(snakeVariant) || cleanCode.includes(camelVariant)) {
4318
+ propPass++;
4319
+ } else {
4320
+ console.log(source_default.yellow(` ⚠ ${ename}.${prop}: not found in ${fileMatch[1].trim()}`));
4321
+ }
4322
+ }
4323
+ if (propPass === props.length)
4324
+ passed++;
4325
+ }
4326
+ }
4327
+ if (checks === 0)
4328
+ console.log(source_default.yellow(` ${p}: No entities mapped. Run: dotdog verify --init`));
4329
+ else if (passed === checks)
4330
+ console.log(source_default.green(` ${p}: ${passed}/${checks} entities verified`));
4331
+ else
4332
+ console.log(source_default.bold(` ${p}: ${passed}/${checks} entities verified`));
4333
+ }
4334
+ }
4335
+ }
4336
+ });
4192
4337
  program2.command("woof").action(() => {
4193
4338
  console.log(" / \\__");
4194
4339
  console.log(" ( @\\___");
@@ -0,0 +1,43 @@
1
+ # E-Commerce Platform
2
+
3
+ ## Product
4
+
5
+ An online store. Customers browse products, add to cart, checkout, and track orders.
6
+
7
+ ## What the User Sees
8
+
9
+ ### Screen: Product Page
10
+
11
+ +------------------------------------------+
12
+ | [Logo] Shop Categories Cart(3) |
13
+ |------------------------------------------|
14
+ | |
15
+ | [Product Image] |
16
+ | |
17
+ | Wireless Headphones $79 |
18
+ | 4.2 stars (128 reviews) |
19
+ | [Add to Cart] |
20
+ +------------------------------------------+
21
+
22
+ ### Screen: Checkout
23
+
24
+ +------------------------------------------+
25
+ | Checkout |
26
+ |------------------------------------------|
27
+ | Shipping: Alex Smith |
28
+ | 123 Main St, NY 10001 |
29
+ | |
30
+ | Payment: Visa ****4242 |
31
+ | |
32
+ | Order total: $79.00 |
33
+ | [Place Order] |
34
+ +------------------------------------------+
35
+
36
+ ## User Stories
37
+
38
+ | ID | Story | Pri | Acceptance |
39
+ |----|-------|-----|------------|
40
+ | US-01 | Customer adds product to cart | P0 | Cart count updates |
41
+ | US-02 | Customer checks out | P0 | Order created, inventory reduced |
42
+ | US-03 | Customer tracks order | P1 | Status and tracking number visible |
43
+ | US-04 | Customer leaves a review | P2 | Review appears on product page |
@@ -0,0 +1,9 @@
1
+ # E-Commerce Platform — Constitution
2
+
3
+ ## Core Rules
4
+
5
+ - Inventory is source of truth — never oversell
6
+ - Prices are immutable once an order is placed
7
+ - Payment is atomic — charge and fulfill or refund
8
+ - Customer data is encrypted at rest
9
+ - Orders flow through defined state machine
@@ -0,0 +1,58 @@
1
+ # E-Commerce — Data Model
2
+
3
+ ## Entities
4
+
5
+ ### Entity: Product
6
+
7
+ An item available for purchase.
8
+
9
+ ```yaml
10
+ entity: Product
11
+ type: entity
12
+ properties:
13
+ id:
14
+ type: string
15
+ required: true
16
+ name:
17
+ type: string
18
+ required: true
19
+ price_cents:
20
+ type: number
21
+ required: true
22
+ inventory:
23
+ type: number
24
+ required: true
25
+ default: 0
26
+ states: [draft, published, archived, out_of_stock]
27
+ lifecycle: draft → published → out_of_stock → archived
28
+ ```
29
+
30
+ ### Entity: Order
31
+
32
+ A completed purchase.
33
+
34
+ ```yaml
35
+ entity: Order
36
+ type: entity
37
+ properties:
38
+ id:
39
+ type: string
40
+ required: true
41
+ total_cents:
42
+ type: number
43
+ required: true
44
+ customer_email:
45
+ type: string
46
+ required: true
47
+ states: [pending, confirmed, shipped, delivered, refunded]
48
+ lifecycle: pending → confirmed → shipped → delivered
49
+ ```
50
+
51
+ ### Relationship: Order → Product
52
+
53
+ ```yaml
54
+ relationship: Order → Product
55
+ verb: contains
56
+ cardinality: N:N
57
+ required: true
58
+ ```
@@ -0,0 +1,37 @@
1
+ # SaaS Platform
2
+
3
+ ## Product
4
+
5
+ A multi-tenant SaaS platform. Organizations sign up, invite team members, manage subscriptions, and use the product.
6
+
7
+ ## What the User Sees
8
+
9
+ ### Screen: Dashboard
10
+
11
+ +------------------------------------------+
12
+ | Dashboard [Settings] |
13
+ |------------------------------------------|
14
+ | Welcome back, Alex |
15
+ | |
16
+ | [Projects: 12] [Team: 5] [Usage: 67%]|
17
+ +------------------------------------------+
18
+
19
+ ### Screen: Billing
20
+
21
+ +------------------------------------------+
22
+ | Billing [Upgrade] |
23
+ |------------------------------------------|
24
+ | Plan: Pro — $29/mo |
25
+ | Next invoice: Jun 30 — $29.00 |
26
+ | |
27
+ | Payment method: Visa ****4242 |
28
+ +------------------------------------------+
29
+
30
+ ## User Stories
31
+
32
+ | ID | Story | Pri | Acceptance |
33
+ |----|-------|-----|------------|
34
+ | US-01 | User creates an organization | P0 | Org appears in workspace selector |
35
+ | US-02 | User invites team member | P0 | Invitee receives email, joins org |
36
+ | US-03 | Org upgrades subscription | P1 | Plan changes, invoice generated |
37
+ | US-04 | Admin views usage report | P2 | Usage metrics displayed by project |
@@ -0,0 +1,98 @@
1
+ # SaaS Platform — Data Model
2
+
3
+ ## Entities
4
+
5
+ ### Entity: Organization
6
+
7
+ A workspace that contains projects and members.
8
+
9
+ ```yaml
10
+ entity: Organization
11
+ type: entity
12
+ properties:
13
+ id:
14
+ type: string
15
+ required: true
16
+ name:
17
+ type: string
18
+ required: true
19
+ plan:
20
+ type: enum
21
+ required: true
22
+ values: [free, pro, enterprise]
23
+ seats:
24
+ type: number
25
+ required: true
26
+ default: 1
27
+ states: [active, suspended, deleted]
28
+ lifecycle: active → suspended → deleted
29
+ ```
30
+
31
+ ### Entity: User
32
+
33
+ A person with access to one or more organizations.
34
+
35
+ ```yaml
36
+ entity: User
37
+ type: entity
38
+ properties:
39
+ id:
40
+ type: string
41
+ required: true
42
+ email:
43
+ type: string
44
+ required: true
45
+ name:
46
+ type: string
47
+ required: true
48
+ role:
49
+ type: enum
50
+ required: true
51
+ values: [owner, admin, member]
52
+ states: [active, invited, suspended]
53
+ lifecycle: invited → active → suspended
54
+ ```
55
+
56
+ ### Entity: Subscription
57
+
58
+ Billing plan for an organization.
59
+
60
+ ```yaml
61
+ entity: Subscription
62
+ type: entity
63
+ properties:
64
+ id:
65
+ type: string
66
+ required: true
67
+ plan:
68
+ type: enum
69
+ required: true
70
+ values: [free, pro, enterprise]
71
+ price_cents:
72
+ type: number
73
+ required: true
74
+ billing_cycle:
75
+ type: enum
76
+ required: true
77
+ values: [monthly, annual]
78
+ states: [active, past_due, canceled]
79
+ lifecycle: active → past_due → canceled
80
+ ```
81
+
82
+ ### Relationship: Organization → User
83
+
84
+ ```yaml
85
+ relationship: Organization → User
86
+ verb: has_member
87
+ cardinality: N:N
88
+ required: true
89
+ ```
90
+
91
+ ### Relationship: Organization → Subscription
92
+
93
+ ```yaml
94
+ relationship: Organization → Subscription
95
+ verb: has
96
+ cardinality: 1:1
97
+ required: true
98
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotdog",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tool for structured software specifications. Validate .dog files, compile .dag graphs, query via MCP.",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -35,7 +35,7 @@
35
35
  "yaml"
36
36
  ],
37
37
  "license": "MIT",
38
- "author": "specdog",
38
+ "author": "Justin Diclemente",
39
39
  "repository": "github:specdog/dotdog",
40
40
  "dependencies": {
41
41
  "commander": "^15.0.0",