dotdog 0.3.2 → 0.3.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +112 -11
  2. package/package.json +5 -12
package/dist/cli.js CHANGED
@@ -2636,6 +2636,15 @@ function parseBlocks(lines, start, end) {
2636
2636
  continue;
2637
2637
  }
2638
2638
  }
2639
+ const predMatch = line.match(/^###\s+Prediction:\s*(.+)/);
2640
+ if (predMatch) {
2641
+ const result = parseStructuredBlock(lines, i, end, "prediction", predMatch[1]);
2642
+ if (result) {
2643
+ blocks.push(result.node);
2644
+ i = result.nextLine;
2645
+ continue;
2646
+ }
2647
+ }
2639
2648
  if (/^\|.+\|/.test(line) && i + 1 < end && /^\|[-| ]+\|/.test(lines[i + 1])) {
2640
2649
  const table = parseTable(lines, i, end);
2641
2650
  if (table) {
@@ -2715,6 +2724,12 @@ function parseStructuredBlock(lines, start, end, kind, headerRest) {
2715
2724
  nextLine: i
2716
2725
  };
2717
2726
  }
2727
+ if (kind === "prediction") {
2728
+ return {
2729
+ node: buildPredictionNode(headerRest, description, yaml, start, i),
2730
+ nextLine: i
2731
+ };
2732
+ }
2718
2733
  return null;
2719
2734
  }
2720
2735
  function buildEntityNode(name, description, yaml, lineStart, lineEnd) {
@@ -2735,7 +2750,11 @@ function buildEntityNode(name, description, yaml, lineStart, lineEnd) {
2735
2750
  }
2736
2751
  const states = Array.isArray(yaml.states) ? yaml.states : [];
2737
2752
  const lifecycleStr = yaml.lifecycle || "";
2738
- const lifecycle = lifecycleStr ? lifecycleStr.split(/\s*→\s*/) : [];
2753
+ const lifecycleParts = lifecycleStr ? lifecycleStr.split(/\s*→\s*/).map((s) => s.trim()) : [];
2754
+ const lifecycle = [];
2755
+ for (let si = 0;si < lifecycleParts.length - 1; si++) {
2756
+ lifecycle.push(`${lifecycleParts[si]} → ${lifecycleParts[si + 1]}`);
2757
+ }
2739
2758
  return {
2740
2759
  kind: "entity",
2741
2760
  name,
@@ -2758,6 +2777,7 @@ function buildRelationshipNode(headerRest, description, yaml, lineStart, lineEnd
2758
2777
  source,
2759
2778
  target,
2760
2779
  verb: yaml.verb || "connects",
2780
+ description: description || yaml.description || "",
2761
2781
  cardinality: yaml.cardinality || "N:M",
2762
2782
  required: yaml.required === true,
2763
2783
  cascade: yaml.cascade || "none",
@@ -2782,6 +2802,22 @@ function buildEventNode(name, description, yaml, lineStart, lineEnd) {
2782
2802
  lineEnd
2783
2803
  };
2784
2804
  }
2805
+ function buildPredictionNode(name, description, yaml, lineStart, lineEnd) {
2806
+ return {
2807
+ kind: "prediction",
2808
+ statement: name,
2809
+ description,
2810
+ trigger: yaml.trigger || "",
2811
+ timeframe: yaml.timeframe || "",
2812
+ confidence: typeof yaml.confidence === "number" ? yaml.confidence : 0,
2813
+ measurement: yaml.measurement || "",
2814
+ preconditions: Array.isArray(yaml.preconditions) ? yaml.preconditions : [],
2815
+ postconditions: Array.isArray(yaml.postconditions) ? yaml.postconditions : [],
2816
+ yaml,
2817
+ lineStart: lineStart + 1,
2818
+ lineEnd
2819
+ };
2820
+ }
2785
2821
  function parseTable(lines, start, end) {
2786
2822
  const headerLine = lines[start];
2787
2823
  const headers = headerLine.split("|").map((h) => h.trim()).filter(Boolean);
@@ -2854,15 +2890,25 @@ function parseSimpleYAML(lines) {
2854
2890
  } else if (value.startsWith("[") && value.endsWith("]")) {
2855
2891
  currentObj[nestedKey] = value.slice(1, -1).split(",").map((s) => s.trim());
2856
2892
  } else {
2857
- const inlineObj = parseInlineObject(value);
2858
- if (inlineObj) {
2859
- currentObj[nestedKey] = inlineObj;
2860
- } else {
2861
- currentObj[nestedKey] = value;
2862
- }
2893
+ currentObj[nestedKey] = value;
2863
2894
  }
2864
2895
  continue;
2865
2896
  }
2897
+ if (nestedMatch && !inNested) {
2898
+ const key = nestedMatch[1];
2899
+ const value = (nestedMatch[2] || "").trim();
2900
+ if (value === "true")
2901
+ result[key] = true;
2902
+ else if (value === "false")
2903
+ result[key] = false;
2904
+ else if (/^-?\d+(\.\d+)?$/.test(value))
2905
+ result[key] = parseFloat(value);
2906
+ else if (value.startsWith("[") && value.endsWith("]"))
2907
+ result[key] = value.slice(1, -1).split(",").map((s) => s.trim());
2908
+ else
2909
+ result[key] = value;
2910
+ continue;
2911
+ }
2866
2912
  const deepMatch = line.match(/^\s{4}(\w[\w_]*):\s*(.+)?$/);
2867
2913
  if (deepMatch && inNested && nestedKey && typeof currentObj[nestedKey] === "object") {
2868
2914
  const deepNested = currentObj[nestedKey];
@@ -3285,9 +3331,9 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3285
3331
  const ast = parse(sources[f]);
3286
3332
  for (const section of ast.sections) {
3287
3333
  for (const block of section.blocks) {
3288
- if (block.kind === "entity" || block.kind === "event" || block.kind === "prediction") {
3334
+ if (block.kind === "entity" || block.kind === "event") {
3289
3335
  const compactProps = {};
3290
- for (const [key, val] of Object.entries(block.properties)) {
3336
+ for (const [key, val] of Object.entries(block.properties || {})) {
3291
3337
  let enc = "";
3292
3338
  const t = val.type || "string";
3293
3339
  if (t === "string")
@@ -3307,8 +3353,8 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3307
3353
  compactProps[key] = enc;
3308
3354
  }
3309
3355
  nodes.push({
3310
- i: block.name,
3311
- t: block.type,
3356
+ i: block.name || "",
3357
+ t: block.type || "",
3312
3358
  g: block.kind,
3313
3359
  d: block.description || "",
3314
3360
  p: compactProps,
@@ -3316,6 +3362,21 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3316
3362
  l: block.lifecycle || []
3317
3363
  });
3318
3364
  }
3365
+ if (block.kind === "prediction") {
3366
+ nodes.push({
3367
+ i: block.statement || block.name || "",
3368
+ t: "prediction",
3369
+ g: "prediction",
3370
+ d: block.description || "",
3371
+ p: {},
3372
+ s: [],
3373
+ l: [],
3374
+ cf: block.confidence || 0,
3375
+ tf: block.timeframe || "",
3376
+ tg: block.trigger || "",
3377
+ ms: block.measurement || ""
3378
+ });
3379
+ }
3319
3380
  if (block.kind === "relationship") {
3320
3381
  edges.push({
3321
3382
  s: block.source,
@@ -3347,6 +3408,46 @@ program2.command("compile [dir]").option("-o, --output <file>").action((d = ".",
3347
3408
  if (!found)
3348
3409
  console.log(source_default.yellow("No projects found."));
3349
3410
  });
3411
+ program2.command("tokens [dir]").action((d = ".") => {
3412
+ const dir = resolvePath2(d);
3413
+ const dirs = [join2(dir, "projects"), join2(dir, "specs"), dir];
3414
+ let found = false;
3415
+ for (const dd of dirs) {
3416
+ if (!existsSync2(dd))
3417
+ continue;
3418
+ const projects = readdirSync2(dd, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
3419
+ for (const p of projects) {
3420
+ const pd = join2(dd, p);
3421
+ if (!existsSync2(join2(pd, "SPEC.dog")))
3422
+ continue;
3423
+ const dagFile = join2(pd, `${p}.dag`);
3424
+ if (!existsSync2(dagFile))
3425
+ continue;
3426
+ found = true;
3427
+ const dogFiles = readdirSync2(pd).filter((f) => f.endsWith(".dog"));
3428
+ let sourceBytes = 0, contentBytes = 0;
3429
+ for (const f of dogFiles) {
3430
+ const bytes = Buffer.byteLength(readFileSync2(join2(pd, f), "utf-8"), "utf-8");
3431
+ sourceBytes += bytes;
3432
+ if (bytes >= 100)
3433
+ contentBytes += bytes;
3434
+ }
3435
+ const dagBytes = Buffer.byteLength(readFileSync2(dagFile, "utf-8"), "utf-8");
3436
+ const savings = sourceBytes > 0 ? Math.round((1 - dagBytes / sourceBytes) * 1000) / 10 : 0;
3437
+ console.log(source_default.bold(`
3438
+ ${p}`));
3439
+ console.log(source_default.gray(` ${dogFiles.length} .dog files: ${sourceBytes} bytes`));
3440
+ console.log(source_default.gray(` .dag file: ${dagBytes} bytes`));
3441
+ console.log(source_default.green(` ${savings}% smaller (${sourceBytes - dagBytes} bytes saved)`));
3442
+ if (contentBytes && contentBytes !== sourceBytes) {
3443
+ const cs = Math.round((1 - dagBytes / contentBytes) * 1000) / 10;
3444
+ console.log(source_default.gray(` content-only: ${contentBytes} bytes → ${cs}% savings`));
3445
+ }
3446
+ }
3447
+ }
3448
+ if (!found)
3449
+ console.log(source_default.yellow("No .dag files found. Run compile first."));
3450
+ });
3350
3451
  program2.command("visualize [dir]").option("-s, --save").action((d = ".", opts) => {
3351
3452
  const dir = resolvePath2(d);
3352
3453
  const dirs = [join2(dir, "projects"), join2(dir, "specs"), dir];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotdog",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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",
@@ -15,21 +15,14 @@
15
15
  "LICENSE"
16
16
  ],
17
17
  "keywords": [
18
- "spec",
19
18
  "specification",
20
- "ai",
21
- "agent",
22
- "mcp",
23
- "documentation",
24
19
  "cli",
25
- "dogfood",
26
- "spec-driven-development",
20
+ "mcp",
27
21
  "graph",
28
- "markdown",
29
- "yaml",
30
- "dotdog",
31
22
  "dag",
32
- "dog"
23
+ "yaml",
24
+ "markdown",
25
+ "spec-driven-development"
33
26
  ],
34
27
  "license": "MIT",
35
28
  "author": "specdog",