metaobjects 0.9.0__py3-none-any.whl
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.
- metaobjects/__init__.py +75 -0
- metaobjects/agent_context/__init__.py +55 -0
- metaobjects/agent_context/_content/README.md +14 -0
- metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
- metaobjects/agent_context/_content/servers/java.meta.json +5 -0
- metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
- metaobjects/agent_context/_content/servers/python.meta.json +5 -0
- metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
- metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
- metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
- metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
- metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
- metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
- metaobjects/agent_context/assemble.py +133 -0
- metaobjects/agent_context/content_root.py +54 -0
- metaobjects/agent_context/scaffold.py +191 -0
- metaobjects/agent_context/types.py +44 -0
- metaobjects/attr_class_map.py +23 -0
- metaobjects/cli.py +696 -0
- metaobjects/codegen/__init__.py +0 -0
- metaobjects/codegen/config.py +11 -0
- metaobjects/codegen/constants.py +13 -0
- metaobjects/codegen/extract_delegate_emitter.py +384 -0
- metaobjects/codegen/extract_schema_emitter.py +139 -0
- metaobjects/codegen/format.py +31 -0
- metaobjects/codegen/fr010_field_mapping.py +220 -0
- metaobjects/codegen/generator.py +62 -0
- metaobjects/codegen/generator_registry.py +163 -0
- metaobjects/codegen/generators/__init__.py +0 -0
- metaobjects/codegen/generators/entity_model.py +263 -0
- metaobjects/codegen/generators/extractor_generator.py +317 -0
- metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
- metaobjects/codegen/generators/m2m_codegen.py +192 -0
- metaobjects/codegen/generators/output_parser_generator.py +272 -0
- metaobjects/codegen/generators/output_prompt_generator.py +192 -0
- metaobjects/codegen/generators/payload_vo_generator.py +672 -0
- metaobjects/codegen/generators/render_helper_generator.py +451 -0
- metaobjects/codegen/generators/router_generator.py +635 -0
- metaobjects/codegen/generators/template_generator.py +70 -0
- metaobjects/codegen/generators/tph_plan.py +120 -0
- metaobjects/codegen/generators/trace_helper_generator.py +336 -0
- metaobjects/codegen/instance_artifacts.py +15 -0
- metaobjects/codegen/output_format_spec_emitter.py +79 -0
- metaobjects/codegen/overwrite_policy.py +27 -0
- metaobjects/codegen/runner.py +110 -0
- metaobjects/codegen/runtime/__init__.py +6 -0
- metaobjects/codegen/runtime/filter_parser.py +193 -0
- metaobjects/codegen/type_map.py +84 -0
- metaobjects/core_types.py +809 -0
- metaobjects/datatype.py +19 -0
- metaobjects/documentation/__init__.py +28 -0
- metaobjects/documentation/doc_constants.py +20 -0
- metaobjects/documentation/doc_provider.py +20 -0
- metaobjects/documentation/doc_schema.py +24 -0
- metaobjects/errors.py +124 -0
- metaobjects/loader/__init__.py +0 -0
- metaobjects/loader/merge.py +287 -0
- metaobjects/loader/meta_data_loader.py +245 -0
- metaobjects/loader/sources/__init__.py +24 -0
- metaobjects/loader/sources/directory_source.py +50 -0
- metaobjects/loader/sources/file_source.py +41 -0
- metaobjects/loader/sources/meta_data_source.py +67 -0
- metaobjects/loader/sources/uri_source.py +56 -0
- metaobjects/loader/validate_discriminator.py +181 -0
- metaobjects/loader/validate_field_readonly.py +146 -0
- metaobjects/loader/validate_source_parameter_ref.py +159 -0
- metaobjects/loader/validate_source_physical_names.py +140 -0
- metaobjects/loader/validation_passes.py +1513 -0
- metaobjects/meta/__init__.py +1 -0
- metaobjects/meta/core/__init__.py +0 -0
- metaobjects/meta/core/attr/__init__.py +0 -0
- metaobjects/meta/core/attr/attr_constants.py +31 -0
- metaobjects/meta/core/attr/meta_attr.py +136 -0
- metaobjects/meta/core/field/__init__.py +0 -0
- metaobjects/meta/core/field/field_constants.py +105 -0
- metaobjects/meta/core/field/meta_field.py +76 -0
- metaobjects/meta/core/identity/__init__.py +0 -0
- metaobjects/meta/core/identity/identity_constants.py +19 -0
- metaobjects/meta/core/identity/meta_identity.py +8 -0
- metaobjects/meta/core/object/__init__.py +0 -0
- metaobjects/meta/core/object/meta_object.py +65 -0
- metaobjects/meta/core/object/meta_object_aware.py +43 -0
- metaobjects/meta/core/object/object_class_registry.py +56 -0
- metaobjects/meta/core/object/object_constants.py +13 -0
- metaobjects/meta/core/object/object_extract.py +400 -0
- metaobjects/meta/core/object/value_object.py +70 -0
- metaobjects/meta/core/relationship/__init__.py +0 -0
- metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
- metaobjects/meta/core/relationship/meta_relationship.py +54 -0
- metaobjects/meta/core/relationship/relationship_constants.py +51 -0
- metaobjects/meta/core/validator/__init__.py +0 -0
- metaobjects/meta/core/validator/validator_constants.py +18 -0
- metaobjects/meta/meta_data.py +206 -0
- metaobjects/meta/meta_root.py +8 -0
- metaobjects/meta/persistence/__init__.py +0 -0
- metaobjects/meta/persistence/db/__init__.py +1 -0
- metaobjects/meta/persistence/db/db_constants.py +41 -0
- metaobjects/meta/persistence/db/db_provider.py +60 -0
- metaobjects/meta/persistence/origin/__init__.py +0 -0
- metaobjects/meta/persistence/origin/meta_origin.py +8 -0
- metaobjects/meta/persistence/origin/origin_constants.py +20 -0
- metaobjects/meta/persistence/source/__init__.py +0 -0
- metaobjects/meta/persistence/source/meta_source.py +137 -0
- metaobjects/meta/persistence/source/source_constants.py +115 -0
- metaobjects/meta/presentation/__init__.py +0 -0
- metaobjects/meta/presentation/layout/__init__.py +0 -0
- metaobjects/meta/presentation/layout/layout_constants.py +13 -0
- metaobjects/meta/presentation/layout/meta_layout.py +8 -0
- metaobjects/meta/presentation/view/__init__.py +0 -0
- metaobjects/meta/presentation/view/meta_view.py +8 -0
- metaobjects/meta/presentation/view/view_constants.py +22 -0
- metaobjects/meta/template/__init__.py +0 -0
- metaobjects/meta/template/meta_template.py +46 -0
- metaobjects/meta/template/template_constants.py +112 -0
- metaobjects/meta/template/template_provider.py +43 -0
- metaobjects/parser.py +380 -0
- metaobjects/parser_yaml.py +82 -0
- metaobjects/provider.py +111 -0
- metaobjects/py.typed +0 -0
- metaobjects/registry.py +210 -0
- metaobjects/registry_manifest.py +223 -0
- metaobjects/render/__init__.py +74 -0
- metaobjects/render/email_document.py +14 -0
- metaobjects/render/escapers.py +109 -0
- metaobjects/render/extract/__init__.py +59 -0
- metaobjects/render/extract/coerce.py +279 -0
- metaobjects/render/extract/extract.py +211 -0
- metaobjects/render/extract/extract_map.py +61 -0
- metaobjects/render/extract/json_forgiving_reader.py +203 -0
- metaobjects/render/extract/locate.py +65 -0
- metaobjects/render/extract/normalize.py +96 -0
- metaobjects/render/extract/strip.py +20 -0
- metaobjects/render/extract/types.py +332 -0
- metaobjects/render/extract/xml_forgiving_reader.py +162 -0
- metaobjects/render/filesystem_provider.py +51 -0
- metaobjects/render/prompt/__init__.py +32 -0
- metaobjects/render/prompt/output_format_renderer.py +340 -0
- metaobjects/render/prompt/output_format_spec.py +28 -0
- metaobjects/render/prompt/prompt_field.py +29 -0
- metaobjects/render/prompt/prompt_overrides.py +29 -0
- metaobjects/render/prompt/prompt_style.py +38 -0
- metaobjects/render/renderer.py +358 -0
- metaobjects/render/verify.py +266 -0
- metaobjects/runtime/__init__.py +39 -0
- metaobjects/runtime/llm_recorder.py +210 -0
- metaobjects/runtime/n2m_resolver.py +155 -0
- metaobjects/runtime/object_manager.py +715 -0
- metaobjects/runtime/tph.py +50 -0
- metaobjects/serializer_json.py +172 -0
- metaobjects/shared/__init__.py +0 -0
- metaobjects/shared/base_types.py +16 -0
- metaobjects/shared/separators.py +4 -0
- metaobjects/shared/structural.py +9 -0
- metaobjects/source/__init__.py +79 -0
- metaobjects/source/error_source.py +266 -0
- metaobjects/source/json_path.py +106 -0
- metaobjects/source/semantic_diff.py +98 -0
- metaobjects/source/yaml_positions.py +174 -0
- metaobjects/super_resolve.py +128 -0
- metaobjects/yaml_desugar.py +481 -0
- metaobjects-0.9.0.dist-info/METADATA +97 -0
- metaobjects-0.9.0.dist-info/RECORD +181 -0
- metaobjects-0.9.0.dist-info/WHEEL +4 -0
- metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
- metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
metaobjects/cli.py
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
"""The ``metaobjects`` console-script — codegen `gen` + drift `verify` (SP-E Unit 2).
|
|
2
|
+
|
|
3
|
+
Two subcommands:
|
|
4
|
+
|
|
5
|
+
metaobjects gen <metadataDir> --out <dir> [--package <pkg>]
|
|
6
|
+
Load metadata, run the Python codegen generator suite, and write files
|
|
7
|
+
under ``--out`` (guarded by the @generated header). Prints each written
|
|
8
|
+
file. Non-zero exit on a load error.
|
|
9
|
+
|
|
10
|
+
metaobjects verify <metadataDir> [--codegen] [--templates] [--db URL] ...
|
|
11
|
+
Drift gate with explicit subverbs (ADR-0021 D2 — one verify vocabulary
|
|
12
|
+
across ports):
|
|
13
|
+
|
|
14
|
+
--codegen regenerate into a temp dir + diff against the committed
|
|
15
|
+
``--out`` tree (Python's historical default behavior).
|
|
16
|
+
--templates template/prompt ``{{field}}`` ↔ payload-VO drift — the
|
|
17
|
+
render ``verify()`` gate — resolving each ``template.*``
|
|
18
|
+
node's refs via a filesystem provider rooted at
|
|
19
|
+
``--templates-root``.
|
|
20
|
+
--db URL REJECTED in the Python port (exit 2): schema verify is the
|
|
21
|
+
migrate engine (ADR-0015), not this CLI.
|
|
22
|
+
|
|
23
|
+
Combining flags runs each requested mode and aggregates the exit code
|
|
24
|
+
(non-zero if ANY mode drifts). A bare ``verify`` keeps the historical
|
|
25
|
+
default = ``--codegen`` (back-compat) and prints a one-line note that the
|
|
26
|
+
explicit subverbs exist.
|
|
27
|
+
|
|
28
|
+
Named ``metaobjects``, NOT ``meta``: ``meta`` is the Node schema CLI. This CLI
|
|
29
|
+
intentionally has NO ``migrate`` subcommand — schema is owned by the Node
|
|
30
|
+
toolchain (ADR-0015). The ``--codegen`` mode shares the exact same generation
|
|
31
|
+
code path as ``gen`` (verify = gen-to-temp + diff), so drift can never be a
|
|
32
|
+
generator-wiring divergence between the two commands.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import json
|
|
39
|
+
import sys
|
|
40
|
+
import tempfile
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
from metaobjects import MetaDataLoader
|
|
44
|
+
from metaobjects.agent_context import (
|
|
45
|
+
AGENT_CONTEXT_MANIFEST_PATH,
|
|
46
|
+
Manifest,
|
|
47
|
+
agent_context_staleness,
|
|
48
|
+
assemble,
|
|
49
|
+
installed_metaobjects_version,
|
|
50
|
+
make_stack,
|
|
51
|
+
plan_scaffold,
|
|
52
|
+
resolve_agent_context_root,
|
|
53
|
+
)
|
|
54
|
+
from metaobjects.meta.meta_data import MetaData
|
|
55
|
+
from metaobjects.codegen.config import GenConfig
|
|
56
|
+
from metaobjects.codegen.generator import Generator
|
|
57
|
+
from metaobjects.codegen.generators.entity_model import entity_model
|
|
58
|
+
from metaobjects.codegen.generators.extractor_generator import extractor_generator
|
|
59
|
+
from metaobjects.codegen.generators.filter_allowlist_generator import (
|
|
60
|
+
filter_allowlist_generator,
|
|
61
|
+
)
|
|
62
|
+
from metaobjects.codegen.generators.output_parser_generator import (
|
|
63
|
+
output_parser_generator,
|
|
64
|
+
)
|
|
65
|
+
from metaobjects.codegen.generators.output_prompt_generator import (
|
|
66
|
+
output_prompt_generator,
|
|
67
|
+
)
|
|
68
|
+
from metaobjects.codegen.generators.payload_vo_generator import payload_vo_generator
|
|
69
|
+
from metaobjects.codegen.generators.router_generator import router_generator
|
|
70
|
+
from metaobjects.codegen.generator_registry import (
|
|
71
|
+
GENERATOR_REGISTRY,
|
|
72
|
+
get_generator,
|
|
73
|
+
list_generators,
|
|
74
|
+
)
|
|
75
|
+
from metaobjects.codegen.runner import run_gen
|
|
76
|
+
from metaobjects.codegen.generators.render_helper_generator import (
|
|
77
|
+
_derive_payload_field_tree,
|
|
78
|
+
_resolve_payload_vo,
|
|
79
|
+
)
|
|
80
|
+
from metaobjects.meta.template import template_constants as tc
|
|
81
|
+
from metaobjects.render.filesystem_provider import FilesystemProvider
|
|
82
|
+
from metaobjects.render.verify import (
|
|
83
|
+
ERR_REQUIRED_SLOT_UNUSED,
|
|
84
|
+
PayloadField,
|
|
85
|
+
verify as render_verify,
|
|
86
|
+
)
|
|
87
|
+
from metaobjects.shared.base_types import TYPE_TEMPLATE
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _default_generators() -> list[Generator]:
|
|
91
|
+
"""The default codegen suite — the no-config generators every project gets.
|
|
92
|
+
|
|
93
|
+
``template_generator`` is excluded: it requires a caller-supplied text
|
|
94
|
+
provider + Mustache template and is not a zero-config per-entity emitter.
|
|
95
|
+
"""
|
|
96
|
+
return [
|
|
97
|
+
entity_model(),
|
|
98
|
+
router_generator(),
|
|
99
|
+
filter_allowlist_generator(),
|
|
100
|
+
payload_vo_generator(),
|
|
101
|
+
output_parser_generator(),
|
|
102
|
+
output_prompt_generator(),
|
|
103
|
+
extractor_generator(),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _load_root(metadata_dir: str) -> tuple[MetaData | None, list[str]]:
|
|
108
|
+
"""Load metadata; return ``(root, error_messages)``. ``root`` is None on error."""
|
|
109
|
+
result = MetaDataLoader.from_directory(metadata_dir)
|
|
110
|
+
if result.errors:
|
|
111
|
+
msgs = [f"{e.code}: {e.message}" for e in result.errors]
|
|
112
|
+
return None, msgs
|
|
113
|
+
return result.root, []
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _resolve_generators(names: str) -> tuple[list[Generator], list[str]]:
|
|
117
|
+
"""Resolve a comma-separated list of STABLE generator names via the registry.
|
|
118
|
+
|
|
119
|
+
Returns ``(generators, errors)``. An unknown name produces a clear error and
|
|
120
|
+
no generators (so the caller can fail with exit code != 0).
|
|
121
|
+
"""
|
|
122
|
+
requested = [n.strip() for n in names.split(",") if n.strip()]
|
|
123
|
+
gens: list[Generator] = []
|
|
124
|
+
errors: list[str] = []
|
|
125
|
+
for n in requested:
|
|
126
|
+
entry = get_generator(n)
|
|
127
|
+
if entry is None:
|
|
128
|
+
known = ", ".join(sorted(GENERATOR_REGISTRY))
|
|
129
|
+
errors.append(f"unknown generator {n!r}; known: {known}")
|
|
130
|
+
continue
|
|
131
|
+
gens.append(entry.factory())
|
|
132
|
+
if not errors and not gens:
|
|
133
|
+
errors.append("no generators selected (empty --generators list)")
|
|
134
|
+
return gens, errors
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _generate(
|
|
138
|
+
metadata_dir: str, out_dir: str, generators: list[Generator] | None = None
|
|
139
|
+
) -> tuple[list[str], list[str]]:
|
|
140
|
+
"""Run the generator suite into ``out_dir``.
|
|
141
|
+
|
|
142
|
+
``generators`` defaults to the zero-config default suite; pass a registry-
|
|
143
|
+
resolved subset for ``--generators``. Returns ``(written_paths, errors)``. On
|
|
144
|
+
a load error, ``errors`` is non-empty and no files are written.
|
|
145
|
+
"""
|
|
146
|
+
root, errors = _load_root(metadata_dir)
|
|
147
|
+
if root is None:
|
|
148
|
+
return [], errors
|
|
149
|
+
config = GenConfig(out_dir=out_dir)
|
|
150
|
+
suite = generators if generators is not None else _default_generators()
|
|
151
|
+
result = run_gen(config, root, generators=suite)
|
|
152
|
+
written = [path for path, status in result.files if status != "refused"]
|
|
153
|
+
return written, []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _cmd_list(_args: argparse.Namespace) -> int:
|
|
157
|
+
"""Print each registered generator ``<stable-name> — <description>`` and exit 0.
|
|
158
|
+
|
|
159
|
+
Does NOT run codegen — pure discoverability (ADR-0021 D3).
|
|
160
|
+
"""
|
|
161
|
+
for entry in list_generators():
|
|
162
|
+
print(f"{entry.name} — {entry.description}")
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _warn_if_agent_context_stale() -> None:
|
|
167
|
+
"""Print ONE advisory line to stderr if the scaffolded agent context is stale.
|
|
168
|
+
|
|
169
|
+
Reads ``<cwd>/.metaobjects/.agent-context.json`` (if present), compares its
|
|
170
|
+
stamped ``generatedBy`` to the installed version, and nudges a re-scaffold on
|
|
171
|
+
any drift. Advisory only: never raises, never changes the exit code, never
|
|
172
|
+
writes — a missing or corrupt manifest is silently ignored.
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
manifest_path = Path.cwd() / AGENT_CONTEXT_MANIFEST_PATH
|
|
176
|
+
if not manifest_path.is_file():
|
|
177
|
+
return
|
|
178
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
179
|
+
if not isinstance(manifest, dict):
|
|
180
|
+
return
|
|
181
|
+
msg = agent_context_staleness(manifest, installed_metaobjects_version())
|
|
182
|
+
if msg is not None:
|
|
183
|
+
print(msg, file=sys.stderr)
|
|
184
|
+
except Exception: # noqa: BLE001 — advisory; any failure is silently ignored
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _cmd_gen(args: argparse.Namespace) -> int:
|
|
189
|
+
# `--list` is a pure discoverability path: print the registry and exit, no codegen.
|
|
190
|
+
if getattr(args, "list", False):
|
|
191
|
+
return _cmd_list(args)
|
|
192
|
+
|
|
193
|
+
_warn_if_agent_context_stale()
|
|
194
|
+
|
|
195
|
+
if args.metadata_dir is None or args.out is None:
|
|
196
|
+
print(
|
|
197
|
+
"error: gen requires <metadata_dir> and --out (or use --list).",
|
|
198
|
+
file=sys.stderr,
|
|
199
|
+
)
|
|
200
|
+
return 2
|
|
201
|
+
|
|
202
|
+
generators: list[Generator] | None = None
|
|
203
|
+
if args.generators:
|
|
204
|
+
generators, gen_errors = _resolve_generators(args.generators)
|
|
205
|
+
if gen_errors:
|
|
206
|
+
print("error: invalid --generators selection:", file=sys.stderr)
|
|
207
|
+
for msg in gen_errors:
|
|
208
|
+
print(f" {msg}", file=sys.stderr)
|
|
209
|
+
return 1
|
|
210
|
+
|
|
211
|
+
written, errors = _generate(args.metadata_dir, args.out, generators)
|
|
212
|
+
if errors:
|
|
213
|
+
print("error: failed to load metadata:", file=sys.stderr)
|
|
214
|
+
for msg in errors:
|
|
215
|
+
print(f" {msg}", file=sys.stderr)
|
|
216
|
+
return 1
|
|
217
|
+
for path in written:
|
|
218
|
+
print(path)
|
|
219
|
+
print(f"metaobjects gen: wrote {len(written)} file(s) to {args.out}")
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _relative_set(root: Path) -> dict[str, str]:
|
|
224
|
+
"""Map every ``*.py`` file under ``root`` to its content, keyed by rel path.
|
|
225
|
+
|
|
226
|
+
Scoped to ``*.py`` because the Python codegen suite emits only Python sources;
|
|
227
|
+
if a generator ever emits a non-``.py`` artifact, broaden this glob so ``verify``
|
|
228
|
+
drift-checks it too.
|
|
229
|
+
"""
|
|
230
|
+
files: dict[str, str] = {}
|
|
231
|
+
if root.exists():
|
|
232
|
+
for p in sorted(root.rglob("*.py")):
|
|
233
|
+
files[str(p.relative_to(root))] = p.read_text()
|
|
234
|
+
return files
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _verify_codegen(args: argparse.Namespace) -> int:
|
|
238
|
+
"""``verify --codegen`` — regenerate to a temp dir + diff vs committed ``--out``.
|
|
239
|
+
|
|
240
|
+
Python's historical ``verify`` behavior, unchanged. Reuses the exact ``gen``
|
|
241
|
+
code path (gen-to-temp + diff), so drift can never be a generator-wiring
|
|
242
|
+
divergence between the two commands.
|
|
243
|
+
"""
|
|
244
|
+
if args.out is None:
|
|
245
|
+
print(
|
|
246
|
+
"error: verify --codegen requires --out (the committed output dir).",
|
|
247
|
+
file=sys.stderr,
|
|
248
|
+
)
|
|
249
|
+
return 2
|
|
250
|
+
|
|
251
|
+
# Reuse the exact gen code path — regenerate into a throwaway temp dir.
|
|
252
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
253
|
+
written, errors = _generate(args.metadata_dir, tmp)
|
|
254
|
+
if errors:
|
|
255
|
+
print("error: failed to load metadata:", file=sys.stderr)
|
|
256
|
+
for msg in errors:
|
|
257
|
+
print(f" {msg}", file=sys.stderr)
|
|
258
|
+
return 1
|
|
259
|
+
|
|
260
|
+
expected = _relative_set(Path(tmp))
|
|
261
|
+
committed = _relative_set(Path(args.out))
|
|
262
|
+
|
|
263
|
+
changed = sorted(
|
|
264
|
+
k for k in expected if k in committed and expected[k] != committed[k]
|
|
265
|
+
)
|
|
266
|
+
missing = sorted(k for k in expected if k not in committed) # not yet committed
|
|
267
|
+
extra = sorted(k for k in committed if k not in expected) # stale committed file
|
|
268
|
+
|
|
269
|
+
if not changed and not missing and not extra:
|
|
270
|
+
print(f"metaobjects verify: in sync ({len(expected)} file(s)).")
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
print("error: generated code is out of sync with metadata.", file=sys.stderr)
|
|
274
|
+
for k in changed:
|
|
275
|
+
print(f" drifted: {k}", file=sys.stderr)
|
|
276
|
+
for k in missing:
|
|
277
|
+
print(f" missing: {k}", file=sys.stderr)
|
|
278
|
+
for k in extra:
|
|
279
|
+
print(f" extra: {k}", file=sys.stderr)
|
|
280
|
+
print(
|
|
281
|
+
"regenerate (metaobjects gen) and commit the result.",
|
|
282
|
+
file=sys.stderr,
|
|
283
|
+
)
|
|
284
|
+
return 1
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
#: The text-ref attrs a ``template.*`` node may carry. Each is resolved through
|
|
288
|
+
#: the filesystem provider and run through the render ``verify()`` gate. A prompt
|
|
289
|
+
#: / document uses ``@textRef``; an email uses subject / html / (optional) text
|
|
290
|
+
#: part-refs. We check whichever are declared (matching the render-helper gate).
|
|
291
|
+
_TEMPLATE_TEXT_REF_ATTRS = (
|
|
292
|
+
tc.TEMPLATE_ATTR_TEXT_REF,
|
|
293
|
+
tc.TEMPLATE_ATTR_SUBJECT_REF,
|
|
294
|
+
tc.TEMPLATE_ATTR_HTML_BODY_REF,
|
|
295
|
+
tc.TEMPLATE_ATTR_TEXT_BODY_REF,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _verify_templates(args: argparse.Namespace) -> int:
|
|
300
|
+
"""``verify --templates`` — the template/prompt ``{{field}}`` ↔ payload drift gate.
|
|
301
|
+
|
|
302
|
+
For each ``template.*`` node: resolve its ``@payloadRef`` to an
|
|
303
|
+
``object.value`` + derive its field tree (reusing the render-helper
|
|
304
|
+
generator's derivation — nested ``@objectRef`` resolved by short-name,
|
|
305
|
+
cycle-guarded), resolve each declared text-ref through a
|
|
306
|
+
:class:`FilesystemProvider` rooted at ``--templates-root``, and run the render
|
|
307
|
+
``verify()`` engine ({{field}} ↔ payload-field drift). Any drift — an
|
|
308
|
+
unresolvable ref or a non-warning ``verify`` error — is reported and fails
|
|
309
|
+
(exit 1). Clean → 0. Reuses the render engine + field-tree walk; nothing is
|
|
310
|
+
reimplemented here.
|
|
311
|
+
"""
|
|
312
|
+
template_root = getattr(args, "templates_root", None)
|
|
313
|
+
if not template_root:
|
|
314
|
+
print(
|
|
315
|
+
"error: verify --templates requires --templates-root (the on-disk "
|
|
316
|
+
"template/prompt dir).",
|
|
317
|
+
file=sys.stderr,
|
|
318
|
+
)
|
|
319
|
+
return 2
|
|
320
|
+
|
|
321
|
+
root, errors = _load_root(args.metadata_dir)
|
|
322
|
+
if root is None:
|
|
323
|
+
print("error: failed to load metadata:", file=sys.stderr)
|
|
324
|
+
for msg in errors:
|
|
325
|
+
print(f" {msg}", file=sys.stderr)
|
|
326
|
+
return 1
|
|
327
|
+
|
|
328
|
+
provider = FilesystemProvider(template_root)
|
|
329
|
+
# Toolcall templates have no renderable text body (the body IS the schema) —
|
|
330
|
+
# skip them; there is no {{field}} drift to check.
|
|
331
|
+
templates = [
|
|
332
|
+
c
|
|
333
|
+
for c in root.own_children()
|
|
334
|
+
if c.type == TYPE_TEMPLATE and c.sub_type != tc.TEMPLATE_SUBTYPE_TOOLCALL
|
|
335
|
+
]
|
|
336
|
+
|
|
337
|
+
if not templates:
|
|
338
|
+
print("metaobjects verify --templates: no template.* nodes found.")
|
|
339
|
+
return 0
|
|
340
|
+
|
|
341
|
+
error_count = 0
|
|
342
|
+
checked = 0
|
|
343
|
+
for tmpl in sorted(templates, key=lambda c: c.name):
|
|
344
|
+
payload_ref = tmpl.attr(tc.TEMPLATE_ATTR_PAYLOAD_REF)
|
|
345
|
+
if not isinstance(payload_ref, str) or not payload_ref:
|
|
346
|
+
print(f"error: [{tmpl.name}] missing @payloadRef.", file=sys.stderr)
|
|
347
|
+
error_count += 1
|
|
348
|
+
continue
|
|
349
|
+
vo = _resolve_payload_vo(root, payload_ref)
|
|
350
|
+
if vo is None:
|
|
351
|
+
print(
|
|
352
|
+
f"error: [{tmpl.name}] @payloadRef '{payload_ref}' did not "
|
|
353
|
+
"resolve to an object.value.",
|
|
354
|
+
file=sys.stderr,
|
|
355
|
+
)
|
|
356
|
+
error_count += 1
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
fields: list[PayloadField] = _derive_payload_field_tree(root, vo, frozenset())
|
|
360
|
+
|
|
361
|
+
refs = [
|
|
362
|
+
val
|
|
363
|
+
for a in _TEMPLATE_TEXT_REF_ATTRS
|
|
364
|
+
if isinstance(val := tmpl.attr(a), str) and val
|
|
365
|
+
]
|
|
366
|
+
if not refs:
|
|
367
|
+
print(
|
|
368
|
+
f"error: [{tmpl.name}] declares no resolvable text-ref "
|
|
369
|
+
"(@textRef / @subjectRef / @htmlBodyRef / @textBodyRef).",
|
|
370
|
+
file=sys.stderr,
|
|
371
|
+
)
|
|
372
|
+
error_count += 1
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
for ref in refs:
|
|
376
|
+
text = provider.resolve(ref)
|
|
377
|
+
if text is None:
|
|
378
|
+
print(
|
|
379
|
+
f"error: [{tmpl.name}] ref '{ref}' did not resolve under "
|
|
380
|
+
f"{template_root} (missing template).",
|
|
381
|
+
file=sys.stderr,
|
|
382
|
+
)
|
|
383
|
+
error_count += 1
|
|
384
|
+
continue
|
|
385
|
+
checked += 1
|
|
386
|
+
for e in render_verify(text, fields, provider=provider):
|
|
387
|
+
if e.code == ERR_REQUIRED_SLOT_UNUSED:
|
|
388
|
+
continue # warning, not drift
|
|
389
|
+
print(
|
|
390
|
+
f"error: [{tmpl.name}] ref '{ref}' — {e.code}: "
|
|
391
|
+
f"{{{{{e.path}}}}} not on payload VO '{payload_ref}'.",
|
|
392
|
+
file=sys.stderr,
|
|
393
|
+
)
|
|
394
|
+
error_count += 1
|
|
395
|
+
|
|
396
|
+
if error_count > 0:
|
|
397
|
+
print(
|
|
398
|
+
f"metaobjects verify --templates: {error_count} drift error(s).",
|
|
399
|
+
file=sys.stderr,
|
|
400
|
+
)
|
|
401
|
+
return 1
|
|
402
|
+
print(f"metaobjects verify --templates: {checked} template ref(s) clean.")
|
|
403
|
+
return 0
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _verify_db(_args: argparse.Namespace) -> int:
|
|
407
|
+
"""``verify --db`` — REJECTED in the Python port (ADR-0021 D2).
|
|
408
|
+
|
|
409
|
+
Schema drift is owned by the migrate engine (ADR-0015), not this codegen CLI.
|
|
410
|
+
"""
|
|
411
|
+
print(
|
|
412
|
+
"error: verify --db is not supported in the Python port; schema verify "
|
|
413
|
+
"is the migrate engine (the Node `meta` CLI, ADR-0015).",
|
|
414
|
+
file=sys.stderr,
|
|
415
|
+
)
|
|
416
|
+
return 2
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _cmd_verify(args: argparse.Namespace) -> int:
|
|
420
|
+
"""Subverb dispatch (ADR-0021 D2). Run each requested mode; aggregate exit =
|
|
421
|
+
max (non-zero if ANY mode drifts). Bare ``verify`` (no subverb) keeps the
|
|
422
|
+
historical default = ``--codegen`` + a one-line note advertising the subverbs.
|
|
423
|
+
"""
|
|
424
|
+
_warn_if_agent_context_stale()
|
|
425
|
+
|
|
426
|
+
run_db = args.db is not None
|
|
427
|
+
run_templates = bool(args.templates)
|
|
428
|
+
run_codegen = bool(args.codegen)
|
|
429
|
+
any_explicit = run_db or run_templates or run_codegen
|
|
430
|
+
|
|
431
|
+
if not any_explicit:
|
|
432
|
+
print(
|
|
433
|
+
"metaobjects verify — running --codegen (default). Explicit "
|
|
434
|
+
"subverbs: --codegen (codegen drift), --templates (template/prompt "
|
|
435
|
+
"drift), --db (schema drift — not supported in Python).",
|
|
436
|
+
file=sys.stderr,
|
|
437
|
+
)
|
|
438
|
+
run_codegen = True
|
|
439
|
+
|
|
440
|
+
exit_code = 0
|
|
441
|
+
if run_db:
|
|
442
|
+
exit_code = max(exit_code, _verify_db(args))
|
|
443
|
+
if run_codegen:
|
|
444
|
+
exit_code = max(exit_code, _verify_codegen(args))
|
|
445
|
+
if run_templates:
|
|
446
|
+
exit_code = max(exit_code, _verify_templates(args))
|
|
447
|
+
return exit_code
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
#: The root-doc filenames we look for / create, and the line we append so a
|
|
451
|
+
#: Claude Code agent always picks up the slim always-on context.
|
|
452
|
+
_ROOT_DOC_CANDIDATES = ("CLAUDE.md", "AGENTS.md")
|
|
453
|
+
_ROOT_DOC_IMPORT_LINE = "@.metaobjects/AGENTS.md"
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _detect_python_server(out_dir: Path) -> bool:
|
|
457
|
+
"""Simple stack detection: a ``pyproject.toml`` in --out marks a Python project."""
|
|
458
|
+
return (out_dir / "pyproject.toml").is_file()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _wire_root_doc(out_dir: Path) -> str | None:
|
|
462
|
+
"""Append ``@.metaobjects/AGENTS.md`` to the root CLAUDE.md/AGENTS.md.
|
|
463
|
+
|
|
464
|
+
Idempotent — if the import line is already present in either doc, do nothing.
|
|
465
|
+
If neither doc exists, create ``CLAUDE.md`` with the import line. Returns the
|
|
466
|
+
doc filename that was created/updated, or ``None`` if it was already wired.
|
|
467
|
+
"""
|
|
468
|
+
existing = [name for name in _ROOT_DOC_CANDIDATES if (out_dir / name).is_file()]
|
|
469
|
+
# Already wired in any existing doc → idempotent no-op.
|
|
470
|
+
for name in existing:
|
|
471
|
+
text = (out_dir / name).read_text(encoding="utf-8")
|
|
472
|
+
if _ROOT_DOC_IMPORT_LINE in text:
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
if existing:
|
|
476
|
+
target = out_dir / existing[0]
|
|
477
|
+
text = target.read_text(encoding="utf-8")
|
|
478
|
+
sep = "" if text.endswith("\n") or text == "" else "\n"
|
|
479
|
+
target.write_text(f"{text}{sep}{_ROOT_DOC_IMPORT_LINE}\n", encoding="utf-8")
|
|
480
|
+
return existing[0]
|
|
481
|
+
|
|
482
|
+
target = out_dir / "CLAUDE.md"
|
|
483
|
+
target.write_text(f"{_ROOT_DOC_IMPORT_LINE}\n", encoding="utf-8")
|
|
484
|
+
return "CLAUDE.md"
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _cmd_agent_docs(args: argparse.Namespace) -> int:
|
|
488
|
+
"""``agent-docs`` — scaffold the slim MetaObjects Claude Code agent context.
|
|
489
|
+
|
|
490
|
+
Resolve the stack from ``--server`` / ``--client`` (repeatable). With neither,
|
|
491
|
+
detect ``pyproject.toml`` → a python server. Assemble against the bundled
|
|
492
|
+
content tree, then write the files into ``--out`` (default cwd): each new or
|
|
493
|
+
manifest-unmodified file is written at its path; a hand-edited file's fresh
|
|
494
|
+
contents go to ``<path>.new``. Append the always-on import to a root
|
|
495
|
+
CLAUDE.md/AGENTS.md (idempotent), and persist the sidecar manifest.
|
|
496
|
+
"""
|
|
497
|
+
out_dir = Path(args.out).resolve() if args.out else Path.cwd()
|
|
498
|
+
|
|
499
|
+
servers = list(args.server or [])
|
|
500
|
+
clients = list(args.client or [])
|
|
501
|
+
if not servers and not clients:
|
|
502
|
+
if _detect_python_server(out_dir):
|
|
503
|
+
servers = ["python"]
|
|
504
|
+
else:
|
|
505
|
+
print(
|
|
506
|
+
"error: no --server/--client given and no pyproject.toml found to "
|
|
507
|
+
"detect a stack. Pass at least one --server or --client.",
|
|
508
|
+
file=sys.stderr,
|
|
509
|
+
)
|
|
510
|
+
return 2
|
|
511
|
+
|
|
512
|
+
try:
|
|
513
|
+
content_root = resolve_agent_context_root()
|
|
514
|
+
except FileNotFoundError as e:
|
|
515
|
+
print(f"error: {e}", file=sys.stderr)
|
|
516
|
+
return 1
|
|
517
|
+
|
|
518
|
+
stack = make_stack(servers, clients)
|
|
519
|
+
assembled = assemble(content_root, stack)
|
|
520
|
+
|
|
521
|
+
# Load the prior manifest, if any, so hand-edits are preserved on re-run.
|
|
522
|
+
manifest_path = out_dir / AGENT_CONTEXT_MANIFEST_PATH
|
|
523
|
+
prior: Manifest | None = None
|
|
524
|
+
if manifest_path.is_file():
|
|
525
|
+
try:
|
|
526
|
+
prior = Manifest.from_json(
|
|
527
|
+
json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
528
|
+
)
|
|
529
|
+
except (json.JSONDecodeError, ValueError, KeyError):
|
|
530
|
+
prior = None # corrupt sidecar → treat as a fresh scaffold
|
|
531
|
+
|
|
532
|
+
def _read_current(rel: str) -> str | None:
|
|
533
|
+
p = out_dir / rel
|
|
534
|
+
return p.read_bytes().decode("utf-8") if p.is_file() else None
|
|
535
|
+
|
|
536
|
+
decision = plan_scaffold(
|
|
537
|
+
stack,
|
|
538
|
+
assembled,
|
|
539
|
+
prior,
|
|
540
|
+
_read_current,
|
|
541
|
+
generated_by=installed_metaobjects_version(),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
for w in decision.writes:
|
|
545
|
+
dest = out_dir / w.path
|
|
546
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
547
|
+
dest.write_bytes(w.contents.encode("utf-8"))
|
|
548
|
+
print(f"wrote {w.path}")
|
|
549
|
+
for c in decision.conflicts:
|
|
550
|
+
dest = out_dir / c.new_path
|
|
551
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
552
|
+
dest.write_bytes(c.contents.encode("utf-8"))
|
|
553
|
+
print(f"hand-edited; wrote fresh copy to {c.new_path} (kept your {c.path})")
|
|
554
|
+
for rel in decision.removed:
|
|
555
|
+
print(f"note: {rel} no longer applies to this stack (not deleted)")
|
|
556
|
+
|
|
557
|
+
# Persist the manifest.
|
|
558
|
+
assert decision.manifest is not None
|
|
559
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
560
|
+
manifest_path.write_text(
|
|
561
|
+
json.dumps(decision.manifest.to_json(), indent=2) + "\n", encoding="utf-8"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
wired = _wire_root_doc(out_dir)
|
|
565
|
+
if wired is not None:
|
|
566
|
+
print(f"wired {_ROOT_DOC_IMPORT_LINE} into {wired}")
|
|
567
|
+
|
|
568
|
+
print(
|
|
569
|
+
f"metaobjects agent-docs: scaffolded {len(assembled)} file(s) for stack "
|
|
570
|
+
f"servers={list(stack.servers)} clients={list(stack.clients)} under {out_dir}"
|
|
571
|
+
)
|
|
572
|
+
return 0
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
576
|
+
parser = argparse.ArgumentParser(
|
|
577
|
+
prog="metaobjects",
|
|
578
|
+
description=(
|
|
579
|
+
"MetaObjects Python codegen CLI. Generate idiomatic Python from "
|
|
580
|
+
"metadata and verify it has not drifted. Schema migrations are "
|
|
581
|
+
"owned by the Node `meta` CLI (ADR-0015) — there is no `migrate` "
|
|
582
|
+
"subcommand here."
|
|
583
|
+
),
|
|
584
|
+
)
|
|
585
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
586
|
+
|
|
587
|
+
gen = sub.add_parser("gen", help="run codegen, writing files under --out")
|
|
588
|
+
# metadata_dir / --out are optional so `gen --list` works without them.
|
|
589
|
+
gen.add_argument(
|
|
590
|
+
"metadata_dir",
|
|
591
|
+
nargs="?",
|
|
592
|
+
default=None,
|
|
593
|
+
help="directory of metadata JSON/YAML files",
|
|
594
|
+
)
|
|
595
|
+
gen.add_argument("--out", default=None, help="output directory for generated code")
|
|
596
|
+
gen.add_argument(
|
|
597
|
+
"--generators",
|
|
598
|
+
default=None,
|
|
599
|
+
help=(
|
|
600
|
+
"comma-separated STABLE generator names to run (e.g. entity,routes). "
|
|
601
|
+
"Resolved via the registry; omit to run the default suite. "
|
|
602
|
+
"See `gen --list`."
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
gen.add_argument(
|
|
606
|
+
"--list",
|
|
607
|
+
action="store_true",
|
|
608
|
+
help="list registered generators (stable name + description) and exit",
|
|
609
|
+
)
|
|
610
|
+
gen.add_argument(
|
|
611
|
+
"--package",
|
|
612
|
+
default=None,
|
|
613
|
+
help="(reserved) package hint; Python derives package from metadata",
|
|
614
|
+
)
|
|
615
|
+
gen.set_defaults(func=_cmd_gen)
|
|
616
|
+
|
|
617
|
+
verify = sub.add_parser(
|
|
618
|
+
"verify",
|
|
619
|
+
help=(
|
|
620
|
+
"drift gate — explicit subverbs --codegen / --templates / --db "
|
|
621
|
+
"(ADR-0021 D2); bare verify defaults to --codegen"
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
verify.add_argument("metadata_dir", help="directory of metadata JSON/YAML files")
|
|
625
|
+
verify.add_argument(
|
|
626
|
+
"--codegen",
|
|
627
|
+
action="store_true",
|
|
628
|
+
help="codegen drift: regenerate to a temp dir + diff vs --out (default)",
|
|
629
|
+
)
|
|
630
|
+
verify.add_argument(
|
|
631
|
+
"--templates",
|
|
632
|
+
action="store_true",
|
|
633
|
+
help=(
|
|
634
|
+
"template drift: each template.* node's {{field}} ↔ payload-VO "
|
|
635
|
+
"field tree (render verify); requires --templates-root"
|
|
636
|
+
),
|
|
637
|
+
)
|
|
638
|
+
verify.add_argument(
|
|
639
|
+
"--db",
|
|
640
|
+
default=None,
|
|
641
|
+
metavar="URL",
|
|
642
|
+
help="schema drift — NOT supported in the Python port (exit 2)",
|
|
643
|
+
)
|
|
644
|
+
verify.add_argument(
|
|
645
|
+
"--out",
|
|
646
|
+
default=None,
|
|
647
|
+
help="committed output directory to diff against (for --codegen)",
|
|
648
|
+
)
|
|
649
|
+
verify.add_argument(
|
|
650
|
+
"--templates-root",
|
|
651
|
+
dest="templates_root",
|
|
652
|
+
default=None,
|
|
653
|
+
help="on-disk template/prompt dir the --templates gate resolves refs against",
|
|
654
|
+
)
|
|
655
|
+
verify.set_defaults(func=_cmd_verify)
|
|
656
|
+
|
|
657
|
+
agent_docs = sub.add_parser(
|
|
658
|
+
"agent-docs",
|
|
659
|
+
help=(
|
|
660
|
+
"scaffold the slim MetaObjects Claude Code agent context "
|
|
661
|
+
"(.metaobjects/AGENTS.md + CLAUDE.md + the metaobjects-* skills)"
|
|
662
|
+
),
|
|
663
|
+
)
|
|
664
|
+
agent_docs.add_argument(
|
|
665
|
+
"--server",
|
|
666
|
+
action="append",
|
|
667
|
+
default=None,
|
|
668
|
+
metavar="LANG",
|
|
669
|
+
help="server language (repeatable): typescript|java|kotlin|csharp|python",
|
|
670
|
+
)
|
|
671
|
+
agent_docs.add_argument(
|
|
672
|
+
"--client",
|
|
673
|
+
action="append",
|
|
674
|
+
default=None,
|
|
675
|
+
metavar="FRAMEWORK",
|
|
676
|
+
help="client framework (repeatable): react|tanstack|angular",
|
|
677
|
+
)
|
|
678
|
+
agent_docs.add_argument(
|
|
679
|
+
"--out",
|
|
680
|
+
default=None,
|
|
681
|
+
help="output project root (default: cwd)",
|
|
682
|
+
)
|
|
683
|
+
agent_docs.set_defaults(func=_cmd_agent_docs)
|
|
684
|
+
|
|
685
|
+
return parser
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def main(argv: list[str] | None = None) -> int:
|
|
689
|
+
"""Entry point. Returns the process exit code (does not call ``sys.exit``)."""
|
|
690
|
+
parser = _build_parser()
|
|
691
|
+
args = parser.parse_args(argv)
|
|
692
|
+
return int(args.func(args))
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
if __name__ == "__main__": # pragma: no cover
|
|
696
|
+
sys.exit(main())
|
|
File without changes
|