@chrisdudek/yg 4.1.0 → 5.0.0-alpha.1

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.
@@ -6,34 +6,52 @@
6
6
  # graph — every node declares a type, and every type must be defined here.
7
7
  #
8
8
  # Changes to this file affect the entire graph and should be confirmed with the user.
9
- # All properties except description are optional — absence means no enforcement.
10
- # When a constraint is absent, anything is allowed. When present, only listed
11
- # values are permitted.
12
9
 
13
10
  node_types:
14
11
  <type-id>:
15
- description: <string> # required — what this type is for, when to use it
12
+ description: <string> # required — what this type is for, when to use it.
13
+ # Validator emits description-missing if absent.
16
14
 
17
- aspects: [<aspect-id>] # optional — aspects automatically applied to every node
18
- # of this type (channel 3 in aspect resolution).
19
- # These also cascade to children of nodes of this type
20
- # (channel 4). Use for invariants that ALL nodes of
21
- # this type must satisfy.
15
+ when: <file-predicate> # optional — per-file classification.
16
+ # Types WITH `when` are file-classifying: every file in
17
+ # a node's mapping must satisfy the predicate (forward
18
+ # check). Types WITHOUT `when` are organizational:
19
+ # parent-only nodes any mapping fires
20
+ # type-without-when-with-mapping.
21
+ #
22
+ # Grammar:
23
+ # path: <glob> — minimatch glob on repo-relative POSIX path
24
+ # content: <regex> — JavaScript regex against file content
25
+ # path + content combined — implicit all_of of both atoms
26
+ # all_of: [<predicate>, ...] — every child must satisfy
27
+ # any_of: [<predicate>, ...] — at least one child must satisfy
28
+ # not: <predicate> — single child negation
29
+ #
30
+ # See: yg knowledge read working-with-architecture
22
31
 
23
- parents: [<type-id>] # optional — allowed parent node types in the hierarchy.
24
- # If omitted, this type can appear under any parent.
25
- # If specified, nesting under an unlisted parent type
26
- # triggers a parent-type-forbidden error.
32
+ enforce: strict # optional — bidirectional enforcement.
33
+ # Requires `when`. Every repo file matching the type's
34
+ # `when` MUST belong to exactly one node of this type
35
+ # (backward scan). A matching file owned by no such node
36
+ # emits type-strict-orphan; one owned by a node of a
37
+ # different type emits type-strict-misplaced.
38
+ # Use only for types where missing the type means missing
39
+ # a critical aspect (security, audit, regulatory).
40
+
41
+ log_required: <boolean> # optional — default true. Set false for infrastructure
42
+ # or utility types where an approval log adds no value
43
+ # (e.g. config, types, constants).
44
+
45
+ aspects: # optional — aspects automatically applied to every
46
+ # node of this type (channel 3). Two forms per entry:
47
+ - <aspect-id> # bare string — unconditional
48
+ - id: <aspect-id> # object form — with per-site applicability filter
49
+ status: enforced # optional — explicit status override (channel 3).
50
+ # Must satisfy bump rule (bump up OK, downgrade is validator error).
51
+ when: <aspect-predicate> # optional — see schemas/yg-aspect.yaml for grammar
52
+ # These also cascade to children (channel 4).
53
+
54
+ parents: [<type-id>, ...] # optional — allowed parent node types in the hierarchy.
27
55
 
28
56
  relations: # optional — allowed relation targets by relation type.
29
- # If a relation type is listed, only the specified
30
- # target types are allowed. Unlisted relation types
31
- # are unrestricted. If omitted entirely, all relations
32
- # are allowed.
33
- <relation-type>: [<type-id>] # Relation types:
34
- # calls — runtime invocation
35
- # uses — compile-time dependency
36
- # extends — inheritance / specialization
37
- # implements — interface contract
38
- # emits — publishes events (must pair with listens)
39
- # listens — subscribes to events (must pair with emits)
57
+ <relation-type>: [<type-id>, ...] # calls | uses | extends | implements | emits | listens
@@ -1,6 +1,7 @@
1
1
  # yg-aspect.yaml — Schema for cross-cutting aspects
2
2
  # Each aspect is a directory under .yggdrasil/aspects/ containing this file
3
- # plus any number of .md content files.
3
+ # plus any number of .md content files (for LLM aspects) or a check.mjs
4
+ # (for deterministic aspects).
4
5
  #
5
6
  # Aspect identifier = relative path from aspects/ to the directory
6
7
  # (e.g. observability/logging). Aspects can be organized in nested
@@ -11,10 +12,110 @@
11
12
  # They should state WHAT must be satisfied and WHY.
12
13
 
13
14
  name: CrossCuttingRequirementName # required — display name
14
- description: "Short description" # optional but recommended — shown in yg aspects output
15
- # and context packages, helps agents discover relevant aspects
15
+ description: "Short description" # required — shown in yg aspects output and context packages.
16
+ # Validator emits description-missing if absent.
16
17
 
17
- # implies: [other-aspect] # optionalother aspect identifiers included automatically
18
- # when this aspect is effective on a node. Recursive expansion
19
- # (A implies B implies C = all three effective). Must be acyclic
20
- # — CLI detects and rejects cycles.
18
+ reviewer: # requiredwhich reviewer verifies this aspect
19
+ type: llm # required: 'llm' or 'deterministic'
20
+ # llm — aspect ships content.md; an LLM reads it and judges the code.
21
+ # deterministicaspect ships check.mjs; the structure runner executes it
22
+ # locally with graph-aware ctx (files, fs, graph, parsers).
23
+ # Language-agnostic. No LLM call, zero token cost.
24
+ # tier: deep # optional, only when type: llm.
25
+ # If omitted, the aspect uses reviewer.default from yg-config.yaml.
26
+ # If present, must reference a key under reviewer.tiers in the config.
27
+ # Forbidden when type is 'deterministic'.
28
+
29
+ status: enforced # optional — aspect-level default. enum: draft | advisory | enforced.
30
+ # Absent → 'enforced'.
31
+ # draft = reviewer skipped, no verdict, no baseline, no drift.
32
+ # advisory = reviewer runs; refused → warning (no block).
33
+ # enforced = reviewer runs; refused → error (blocks check).
34
+ # This is only the aspect-level default. The effective status on a
35
+ # node is max() across cascading channels 1–6; channel 7 (implies)
36
+ # carries status_inherit instead. Downgrade attempts are validator
37
+ # errors. Advisory and enforced verdicts are recorded in the
38
+ # baseline; draft aspects get no verdict.
39
+
40
+ # implies: # optional — other aspects included automatically when this
41
+ # # aspect is effective on a node. Two forms:
42
+ # - simple-aspect-id # bare string — implied unconditionally (when outer aspect passes)
43
+ # - id: conditional-aspect-id # object form — imply only when `when` passes on the node
44
+ # when: <predicate> # see `when` section below for grammar
45
+ # status_inherit: strictest # optional — propagation modifier for this implies edge.
46
+ # enum: strictest | own-default.
47
+ # Absent → 'strictest' (implied aspect promotes to
48
+ # the implier's effective status if higher than the
49
+ # implied aspect's own default).
50
+ # 'own-default' anchors the implied aspect to its
51
+ # own aspect-level default (decouples from implier).
52
+ #
53
+ # ASYMMETRY NOTE: attach-site entries on channels
54
+ # 1–6 (node, ancestor, architecture type, ancestor
55
+ # type, flow, port) carry an explicit `status:`
56
+ # VALUE. Channel 7 (implies) carries a propagation
57
+ # MODIFIER (`status_inherit:`) instead. Implies is
58
+ # not a direct attach — the implied aspect's status
59
+ # is structurally derived from the implier's
60
+ # effective status on the node. The modifier
61
+ # selects how to derive; a value-overriding
62
+ # `status:` on an implies edge would couple the
63
+ # edge to a literal that becomes stale if the
64
+ # implied aspect's own default changes.
65
+ # Chains expand recursively. Cycles are forbidden — CLI detects.
66
+
67
+ # when: <predicate> # optional — applicability filter. If the predicate evaluates
68
+ # to false on a node, this aspect is not effective on that node
69
+ # regardless of which channel attached it. Combines with
70
+ # attach-site `when` declarations via AND.
71
+ #
72
+ # Grammar:
73
+ # when:
74
+ # all_of: [<clause>, ...] # AND
75
+ # any_of: [<clause>, ...] # OR
76
+ # not: <clause> # negation
77
+ # <atomic> # top-level atomics imply all_of
78
+ #
79
+ # Atomic clauses:
80
+ # relations:
81
+ # <relation-type>: # calls | uses | extends | implements | emits | listens
82
+ # target_type: <type-id> # match target node's declared type
83
+ # target: <node-path> # match exact node path (relative to model/)
84
+ # consumes_port: <port> # match a port consumed on this relation
85
+ # descendants: # same as relations but evaluated against any descendant in model/
86
+ # relations: {...}
87
+ # type: <type-id>
88
+ # has_port: <port-name>
89
+ # node:
90
+ # type: <type-id>
91
+ # has_port: <port-name>
92
+ # has_mapping: true | false
93
+ #
94
+ # Example:
95
+ # when:
96
+ # any_of:
97
+ # - relations: { calls: { target_type: service-client } }
98
+ # - descendants: { relations: { calls: { target_type: service-client } } }
99
+
100
+ # references: # optional — supporting files for the LLM reviewer.
101
+ # Permitted on LLM aspects ONLY (forbidden on deterministic).
102
+ # Each entry is a string (shorthand) OR an object { path, description? }.
103
+ #
104
+ # Example:
105
+ # references:
106
+ # - docs/error-codes.md # shorthand
107
+ # - path: source/cli/src/errors/codes.ts
108
+ # description: "Catalogue of valid error codes; reviewer rejects unknown codes."
109
+ #
110
+ # Constraints (validated by `yg check`):
111
+ # - Path is repo-root-relative.
112
+ # - No '..' that escapes the repo root; no leading '/'; no Windows drive letter; no '~'.
113
+ # - File must exist at check time and resolve (after symlink follow) to a regular file.
114
+ # - No duplicates within one aspect.
115
+ #
116
+ # Drift semantics: changes to referenced files cascade to all nodes where this
117
+ # aspect is effective — same as changes to content.md. Run `yg impact --file <ref>`
118
+ # before editing a widely-referenced file.
119
+ #
120
+ # Size limits: per-tier caps via reviewer.tiers.<tier>.references.* in yg-config.yaml.
121
+ # Defaults: 64 KiB per file, 256 KiB total per aspect.
@@ -2,12 +2,12 @@
2
2
  # Located at .yggdrasil/yg-config.yaml — one per project.
3
3
  # Edit this after running yg init to describe your project.
4
4
 
5
- version: "4.0.0" # managed by CLI — do not edit manually. Tracks the CLI version
5
+ version: "5.0.0" # managed by CLI — do not edit manually. Tracks the CLI version
6
6
  # that last initialized or upgraded this config.
7
7
 
8
8
  quality: # optional — quality thresholds
9
9
  max_direct_relations: 10 # maximum outgoing relations per node (warning if above)
10
- max_mapping_source_files: 10 # maximum source files per node (warning if above, for nodes with aspects)
10
+ max_node_chars: 40000 # per-node character budget — mapped source + aspect reference files (error if above; binaries skipped; opt out per-node via sizeExempt)
11
11
 
12
12
  parallel: 1 # optional — concurrency limit for batch approve (positive integer, default: 1)
13
13
 
@@ -15,13 +15,29 @@ debug: false # optional — when true, appends all command
15
15
  # Default: false (off). Log is append-only; rotate or delete manually.
16
16
 
17
17
  reviewer: # required — aspect verification during yg approve
18
- active: ollama # required when multiple providers configured
19
- consensus: 1 # positive odd integer >= 1 (3+ for majority vote)
20
- ollama: # provider-specific config
21
- model: "qwen3.5:9b" # model ID
22
- endpoint: "http://localhost:11434" # custom endpoint
23
- temperature: 0 # reduces variability keep at 0
24
- max_tokens: auto # auto = query provider, or explicit number
25
- context_length_field: "" # ollama model_info key for context window size
26
- claude-code: # alternative provider — uses claude CLI subprocess
27
- model: haiku # haiku, sonnet, or opus
18
+ default: standard # required when more than one tier is configured; optional with exactly one tier.
19
+ # Must reference one of the keys under reviewer.tiers.
20
+ tiers: # required — named tier configurations, minimum one entry.
21
+ standard: # tier name — referenced from aspects via reviewer.tier:
22
+ provider: ollama # provider id (one of: ollama, openai, anthropic, google,
23
+ # openai-compatible, claude-code, codex, gemini-cli)
24
+ consensus: 1 # positive odd integer >= 1 (3+ for majority vote). Per-tier.
25
+ config: # provider-specific settings same fields the provider accepts.
26
+ model: "qwen3.5:9b" # model id
27
+ endpoint: "http://localhost:11434" # custom endpoint (required for ollama, openai-compatible)
28
+ temperature: 0 # reduces variability — keep at 0
29
+ max_tokens: auto # auto = query provider, or explicit positive integer
30
+ context_length_field: "" # ollama: model_info key for context window size
31
+ # timeout: 300 # Per-call timeout in SECONDS (default 300). Only CLI providers.
32
+ # references: # optional — limits on aspect reference files for aspects resolving to this tier
33
+ # max_bytes_per_file: 65536 # positive integer; default 65536 (64 KiB)
34
+ # max_total_bytes_per_aspect: 262144 # positive integer; default 262144 (256 KiB)
35
+ # Add more tiers as needed (e.g. a `deep` tier with a higher-capability model for critical aspects).
36
+ # An aspect references a non-default tier via:
37
+ #
38
+ # reviewer:
39
+ # type: llm
40
+ # tier: deep
41
+ #
42
+ # Tier names match ^[a-zA-Z][a-zA-Z0-9_-]{0,62}$ and `default` is reserved.
43
+ # API keys live in yg-secrets.yaml (api_key only) — never in this file.
@@ -9,14 +9,17 @@
9
9
  # listing a parent node covers all its children.
10
10
 
11
11
  name: EndToEndProcessName # required — display name
12
- description: "What this business process does" # optional but recommended — shown in
13
- # yg flows output and context packages
12
+ description: "What this business process does" # required — shown in yg flows output and
13
+ # context packages. Validator emits description-missing if absent.
14
14
 
15
15
  nodes: # required, non-empty — participant nodes (alias: participants)
16
16
  - orders/order-service # paths relative to model/
17
17
  - payments/payment-service # each participant (and its descendants) must satisfy
18
18
  - inventory/inventory-service # any flow-level aspects declared below
19
19
 
20
- # aspects: [requires-saga] # optional — aspect identifiers propagated to ALL participants
21
- # (channel 5 in aspect resolution). Use for requirements that
22
- # apply to every node in this process but not globally.
20
+ aspects: # optional — aspects propagate to all flow participants (channel 5)
21
+ - simple-aspect # bare string
22
+ - id: conditional-aspect # object form with per-site applicability filter
23
+ status: enforced # optional — explicit status override (channel 5).
24
+ # Must satisfy bump rule (bump up OK, downgrade is validator error).
25
+ when: <predicate> # optional — see schemas/yg-aspect.yaml for grammar
@@ -6,26 +6,46 @@
6
6
 
7
7
  name: ComponentName # required — display name
8
8
  type: service # required — must match a type defined in yg-architecture.yaml
9
- description: "What this node does" # optional but recommended — shown in context output,
10
- # helps agents understand purpose without reading code
9
+ description: "What this node does" # required — shown in context output and helps agents
10
+ # understand purpose. Validator emits description-missing if absent.
11
11
 
12
- aspects: # optional — aspect identifiers applied directly to this node
13
- - aspect-id # must match a directory name under aspects/
14
- # these aspects also cascade to all child nodes
12
+ aspects: # optional — aspect identifiers applied directly to this node.
13
+ # Two forms per entry:
14
+ - simple-aspect # bare string always effective once attached (channel 1)
15
+ - id: conditional-aspect # object form — attach with per-site applicability filter
16
+ status: enforced # optional — explicit status override (channel 1).
17
+ # Must satisfy bump rule (bump up OK, downgrade is validator error).
18
+ when: <predicate> # optional — see schemas/yg-aspect.yaml for grammar
19
+ # Aspects cascade to all child nodes.
15
20
 
16
21
  ports: # optional — named entry points with required aspects
17
22
  port-name: # consumers of this node reference ports via consumes
18
23
  description: "What this port provides" # required
19
- aspects: [aspect-id] # required — aspects that consumers must satisfy (channel 6)
24
+ aspects: # required — aspects consumers must satisfy (channel 6)
25
+ - simple-aspect # bare string form
26
+ - id: conditional-aspect
27
+ status: enforced # optional — explicit status override (channel 6).
28
+ # Must satisfy bump rule (bump up OK, downgrade is validator error).
29
+ when: <predicate> # optional — object form with per-attach applicability filter
20
30
 
21
31
  relations: # optional — outgoing dependencies to other nodes
22
32
  - target: other/module-path # required — node path relative to model/
23
33
  type: calls # required — calls | uses | extends | implements | emits | listens
24
34
  consumes: [port-name] # optional — port names consumed from target
25
- # required when target declares ports — otherwise check warns
35
+ # required when target declares ports — otherwise yg check emits a
36
+ # BLOCKING ERROR (port-missing-consumes) that fails the architecture gate.
37
+ # There is no waiver; resolve by adding consumes or removing the ports.
38
+ # Naming a target that declares no ports raises consumes-without-ports.
26
39
 
27
40
  mapping: # optional — source files and directories owned by this node
28
41
  - src/modules/component/ # directory — all files inside are owned (recursive)
29
42
  - src/modules/component.ts # file — exact match
30
43
  # paths are relative to repository root
31
44
  # each source file must have exactly one owner node
45
+
46
+ sizeExempt: # optional — opt out of the per-node character budget (quality.max_node_chars)
47
+ reason: "package-lock.json is npm-generated and cannot be split" # required — non-empty justification
48
+ # Binary files (images, fonts, archives, etc.) count 0 toward the budget
49
+ # automatically — they never need sizeExempt. Use sizeExempt ONLY for a
50
+ # large unsplittable TEXT artifact (e.g. a generated lockfile), not images.
51
+ # Normal oversized nodes must be split.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chrisdudek/yg",
3
- "version": "4.1.0",
4
- "description": "Continuous architecture enforcement for AI-assisted development. Aspects, review, enforcement.",
3
+ "version": "5.0.0-alpha.1",
4
+ "description": "Architecture rules your coding agent can't ignore. Written in Markdown, verified on every change, enforced in the agent's loop — not after on a PR. Works with Claude Code, Cursor, Copilot, Codex, Cline.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "yg": "dist/bin.js"
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "dist/**/*.js",
11
11
  "dist/**/*.d.ts",
12
+ "dist/**/*.wasm",
12
13
  "graph-schemas/"
13
14
  ],
14
15
  "engines": {
@@ -24,17 +25,23 @@
24
25
  "lint": "eslint src/",
25
26
  "lint:fix": "eslint src/ --fix",
26
27
  "format": "prettier --write \"src/**/*.ts\"",
27
- "typecheck": "tsc --noEmit"
28
+ "typecheck": "tsc -p tsconfig.check.json"
28
29
  },
29
30
  "keywords": [
30
31
  "yggdrasil",
31
- "ai-agents",
32
32
  "architecture-enforcement",
33
- "code-review",
34
- "ai-governance",
33
+ "code-guardrails",
34
+ "agentic-guardrails",
35
+ "rule-engine",
36
+ "agents-md",
37
+ "claude-md",
38
+ "claude-code",
39
+ "ai-agents",
40
+ "coding-agents",
41
+ "ai-coding",
42
+ "ai-assisted-development",
35
43
  "cli",
36
- "developer-tools",
37
- "ai-coding"
44
+ "developer-tools"
38
45
  ],
39
46
  "author": "Krzysztof Dudek <me@chrisdudek.com>",
40
47
  "license": "MIT",
@@ -48,27 +55,56 @@
48
55
  "url": "git+https://github.com/krzysztofdudek/Yggdrasil.git",
49
56
  "directory": "source/cli"
50
57
  },
58
+ "exports": {
59
+ "./ast": {
60
+ "import": "./dist/ast.js",
61
+ "types": "./dist/ast.d.ts"
62
+ },
63
+ "./structure": {
64
+ "import": "./dist/structure.js",
65
+ "types": "./dist/structure.d.ts"
66
+ }
67
+ },
51
68
  "publishConfig": {
52
69
  "access": "public"
53
70
  },
54
71
  "dependencies": {
55
- "@clack/prompts": "^1.2.0",
72
+ "@clack/prompts": "^1.5.0",
56
73
  "chalk": "^5.6.2",
57
- "commander": "^14.0.3",
74
+ "commander": "^15.0.0",
58
75
  "ignore": "^7.0.5",
59
- "semver": "^7.7.4",
60
- "yaml": "^2.8.2"
76
+ "minimatch": "^10.2.5",
77
+ "minisearch": "^7.2.0",
78
+ "semver": "^7.8.1",
79
+ "smol-toml": "^1.6.1",
80
+ "web-tree-sitter": "^0.26.9",
81
+ "yaml": "^2.9.0"
61
82
  },
62
83
  "devDependencies": {
63
84
  "@eslint/js": "^10.0.1",
64
- "@types/node": "^25.3.0",
85
+ "@tree-sitter-grammars/tree-sitter-kotlin": "^1.1.0",
86
+ "@tree-sitter-grammars/tree-sitter-toml": "^0.7.0",
87
+ "@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
88
+ "@types/node": "^25.9.1",
65
89
  "@types/semver": "^7.7.1",
66
- "@vitest/coverage-v8": "^4.0.18",
67
- "eslint": "^10.0.1",
68
- "prettier": "^3.8.1",
90
+ "@vitest/coverage-v8": "^4.1.7",
91
+ "eslint": "^10.4.1",
92
+ "prettier": "^3.8.3",
93
+ "tree-sitter-c": "^0.24.1",
94
+ "tree-sitter-c-sharp": "^0.23.5",
95
+ "tree-sitter-cpp": "^0.23.4",
96
+ "tree-sitter-go": "^0.25.0",
97
+ "tree-sitter-java": "^0.23.5",
98
+ "tree-sitter-javascript": "^0.25.0",
99
+ "tree-sitter-json": "^0.24.8",
100
+ "tree-sitter-php": "^0.24.2",
101
+ "tree-sitter-python": "^0.25.0",
102
+ "tree-sitter-ruby": "^0.23.1",
103
+ "tree-sitter-rust": "^0.24.0",
104
+ "tree-sitter-typescript": "^0.23.2",
69
105
  "tsup": "^8.5.1",
70
- "typescript": "^6.0.2",
71
- "typescript-eslint": "^8.56.0",
72
- "vitest": "^4.0.18"
106
+ "typescript": "^6.0.3",
107
+ "typescript-eslint": "^8.60.0",
108
+ "vitest": "^4.1.7"
73
109
  }
74
110
  }