{"id":49234345,"url":"https://github.com/tdspora/tdm_rulesgen","last_synced_at":"2026-04-24T14:07:03.013Z","repository":{"id":352746383,"uuid":"1216439217","full_name":"tdspora/tdm_rulesgen","owner":"tdspora","description":"FastAPI service for safe rule parsing, compilation, preview execution, and sandbox-backed dataset generation.","archived":false,"fork":false,"pushed_at":"2026-04-21T01:01:11.000Z","size":278,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T01:28:53.584Z","etag":null,"topics":["fastapi","openapi","pydantic","python","rules-engine","synthetic-data"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tdspora.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T22:56:31.000Z","updated_at":"2026-04-21T01:01:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tdspora/tdm_rulesgen","commit_stats":null,"previous_names":["tdspora/tdm_rulesgen"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/tdspora/tdm_rulesgen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdspora%2Ftdm_rulesgen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdspora%2Ftdm_rulesgen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdspora%2Ftdm_rulesgen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdspora%2Ftdm_rulesgen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tdspora","download_url":"https://codeload.github.com/tdspora/tdm_rulesgen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tdspora%2Ftdm_rulesgen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32226461,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastapi","openapi","pydantic","python","rules-engine","synthetic-data"],"created_at":"2026-04-24T14:07:00.381Z","updated_at":"2026-04-24T14:07:03.003Z","avatar_url":"https://github.com/tdspora.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rulesgen\n\n`rulesgen` is a secure rule-processing service for synthetic data workflows.\n\nIt converts natural-language instructions into a restricted DSL, validates and compiles that DSL into an executable artifact, lets you preview rule behavior locally, and can generate datasets through either a local subprocess executor or an OpenSandbox-backed runtime.\n\nThe project is designed for teams that need:\n- natural-language rule authoring\n- deterministic validation and compilation\n- safe local previews\n- optional sandboxed execution for full dataset generation\n\n## What it does\n\n`rulesgen` supports a staged rule lifecycle:\n\n1. **Parse** natural language into an untrusted intermediate form (`semantic_frame`) and DSL candidate\n2. **Compile** the DSL into a validated executable artifact (`compiled_rule`)\n3. **Preview** rule execution against a sample row\n4. **Generate** datasets using a local or sandbox-backed execution path\n\nThis separation is intentional. Natural-language output is never trusted directly. A rule only becomes executable after validation and compilation succeed.\n\n---\n\n## Quick start\n\nThe fastest way to get started is with Docker Compose.\n\n### Prerequisites\n\n- Docker\n- `docker compose`\n- `curl`\n- `jq`\n\n### Start the stack\n\nThe default setup runs:\n- `rulesgen`\n- OpenSandbox\n- an LLM-backed translation path through LiteLLM when provider credentials are present\n- the built-in stub translation backend when provider credentials are absent\n\nOptional provider credentials for LiteLLM (examples):\n  - `OPENAI_API_KEY` (OpenAI / OpenAI-compatible)\n  - `ANTHROPIC_API_KEY`\n  - `GEMINI_API_KEY`\n  - `AZURE_API_KEY` (Azure OpenAI; often used with `AZURE_API_VERSION`)\n`./scripts/run_stack.sh` now falls back to `RULESGEN_LLM_GATEWAY_BACKEND=stub` when none of these credentials are set. Docker Compose still forwards the provider variables into the `rulesgen` container via `${VAR:-}` entries in the compose files.\n\n\nStart the stack:\n\n```bash\n./scripts/run_stack.sh\n```\n\nIf a provider key is not set, the stack still starts and uses the stub translation backend.\n\n### Service endpoints\n\nOnce the stack is up, the API is available at:\n\n* `http://127.0.0.1:8000`\n* `http://127.0.0.1:8000/docs` for OpenAPI documentation, when enabled\n\n### Verify readiness\n\n```bash\ncurl -s http://127.0.0.1:8000/health/ready\n```\n\n### Run the example workflows\n\nIn a new terminal:\n\n```bash\nexport BASE_URL=http://127.0.0.1:8000\n```\n\nThen continue with the two workflows below.\n\n---\n\n## Example workflows\n\n## 1. Parse → Compile → Preview\n\nThis workflow shows the safest path from natural-language input to executable rule behavior.\n\n### Step 1: Parse a natural-language instruction\n\nThe parse endpoint returns:\n\n* inferred intent\n* a candidate DSL expression\n* diagnostics\n* prompt-audit metadata\n* explainability and metrics data\n\nAt this stage, the output is still **untrusted**.\n\n```bash\ncurl -s \"$BASE_URL/rules/parse\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"table_name\": \"employees\",\n    \"schema\": [\n      {\"name\": \"salary\", \"type\": \"FLOAT\", \"nullable\": false, \"source\": \"syngen\"},\n      {\"name\": \"job_level\", \"type\": \"INT\", \"nullable\": false, \"source\": \"syngen\"},\n      {\n        \"name\": \"bonus\",\n        \"type\": \"FLOAT\",\n        \"nullable\": true,\n        \"source\": \"rule\",\n        \"source_text\": \"If job_level is 5 or higher, set bonus to 10 percent of salary.\",\n        \"source_type\": \"natural_language\"\n      }\n    ]\n  }'\n```\n\nImportant fields in the response:\n\n* `dsl_candidate`\n* `diagnostics`\n* `prompt_audit`\n* `metrics`\n* `explainability_trace`\n\nFor schema-embedded rule requests, the target column is inferred from the schema row `name`. Supported row-level `source_type` values are:\n\n* `natural_language`\n* `domain_specific_language`\n\n### Step 2: Compile the DSL into a validated rule artifact\n\nUse the `dsl_candidate` from the parse response, or submit a DSL expression directly.\n\n```bash\nCOMPILE_RESPONSE=\"$(\n  curl -s \"$BASE_URL/rules/compile\" \\\n    -H \"Content-Type: application/json\" \\\n    --data-binary @- \u003c\u003c'EOF'\n{\n  \"expression\": \"0.1 * col('salary') if col('job_level') \u003e= 5 else 0\",\n  \"target_column\": \"bonus\"\n}\nEOF\n)\"\n\nexport ARTIFACT_ID=\"$(echo \"$COMPILE_RESPONSE\" | jq -r '.artifact_id')\"\necho \"ARTIFACT_ID=$ARTIFACT_ID\"\n```\n\nSave the returned `artifact_id`. You will use it in the preview step.\n\n### Step 3: Preview the rule against a sample row\n\nThe preview endpoint executes the compiled rule locally and supports row-phase helpers only.\n\n```bash\ncurl -s \"$BASE_URL/rules/preview\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- \u003c\u003cEOF\n{\n  \"artifact_id\": \"$ARTIFACT_ID\",\n  \"row\": {\n    \"salary\": 120000,\n    \"job_level\": 6\n  },\n  \"seed\": 99\n}\nEOF\n```\n\nKey fields in the response:\n\n* `value`\n* `execution_mode`\n* `diagnostics`\n\n---\n\n## 2. Upload a dataset file → Generate a dataset → Poll the job → Inspect artifacts\n\nThis workflow shows full dataset generation, including staged file upload, job tracking, and artifact retrieval.\n\n### Step 1: Upload a source file\n\n```bash\nUPLOAD_RESPONSE=\"$(\n  curl -s \"$BASE_URL/datasets/uploads\" \\\n    -F \"file=@samples/orders.csv;type=text/csv\"\n)\"\n\nexport FILE_ID=\"$(echo \"$UPLOAD_RESPONSE\" | jq -r '.file_id')\"\necho \"FILE_ID=$FILE_ID\"\n```\n\nThe response includes:\n\n* `file_id`\n* `format`\n* `row_count`\n* `columns`\n\nSave the returned `file_id`. You will use it in the generation request.\n\n### Step 2: Submit a generation job\n\n```bash\nGENERATE_RESPONSE=\"$(\n  curl -s \"$BASE_URL/datasets/generate\" \\\n    -H \"Content-Type: application/json\" \\\n    --data-binary @- \u003c\u003cEOF\n{\n  \"file_id\": \"$FILE_ID\",\n  \"schema\": [\n    {\"name\": \"order_id\", \"type\": \"STRING\", \"nullable\": false, \"source\": \"syngen\"},\n    {\"name\": \"line_amount\", \"type\": \"INT\", \"nullable\": false, \"source\": \"syngen\"},\n    {\n      \"name\": \"order_total\",\n      \"type\": \"INT\",\n      \"nullable\": true,\n      \"source\": \"rule\",\n      \"source_text\": \"group_sum(key=col(\\\"order_id\\\"), value=col(\\\"line_amount\\\"))\",\n      \"source_type\": \"domain_specific_language\"\n    }\n  ],\n  \"seed\": 17\n}\nEOF\n)\"\n\nexport JOB_ID=\"$(echo \"$GENERATE_RESPONSE\" | jq -r '.job_id')\"\necho \"JOB_ID=$JOB_ID\"\n```\n\nExactly one of `base_rows` or `file_id` must be provided:\n\n* use `file_id` for staged CSV or JSON uploads\n* use `base_rows` plus `row_count` for inline JSON rows\n\nWhen `file_id` is used, `row_count` is derived from the uploaded file and must not be supplied.\n\nThe generation response includes:\n\n* `job_id`\n* `status`\n* `planned_column_sources`\n* `llm_metrics`, when natural-language translation is used\n* `diagnostics`\n\nThis response is metadata-only. It does not embed the generated row payload.\n\n### Step 3: Poll the job\n\n```bash\ncurl -s \"$BASE_URL/jobs/$JOB_ID\"\n```\n\nThe job response includes:\n\n* `result.output_path` for the generated dataset on the `rulesgen` host\n* `artifacts` for the dataset, manifest, diagnostics, and execution log\n* `llm_metrics` for the translation session, when applicable\n* `diagnostics` from the execution path\n\nThis JSON response remains metadata-only. Use the download endpoints below to retrieve file contents.\n\n### Step 4: Download the generated dataset\n\n```bash\ncurl -s \"$BASE_URL/jobs/$JOB_ID/dataset\" -o generated_rows.json\n```\n\nTo download a specific stored artifact from the same job, select an `artifact_id` from\n`GET /jobs/$JOB_ID` and export it (example: download the input manifest):\n\n```bash\nexport ARTIFACT_ID=\"$(\n  curl -s \"$BASE_URL/jobs/$JOB_ID\" \\\n    | jq -r '.artifacts[] | select(.kind == \"input_manifest\") | .artifact_id' \\\n    | head -n 1\n)\"\necho \"ARTIFACT_ID=$ARTIFACT_ID\"\n\ncurl -s \"$BASE_URL/jobs/$JOB_ID/artifacts/$ARTIFACT_ID\" -o artifact.bin\n```\n\nBy default, generated files are written under the local OSSFS root:\n\n```text\n.rulesgen-data/ossfs/\n```\n\nExecution backend behavior depends on configuration:\n\n* `RULESGEN_SANDBOX_BACKEND=subprocess`\n  Uses the local subprocess dataset executor\n\n* `RULESGEN_SANDBOX_BACKEND=opensandbox`\n  Uploads the same manifest to an Alibaba OpenSandbox-managed container, then downloads the generated dataset back into the local OSSFS root\n\n---\n\n## Configuration\n\n## Optional\n\n### `OPENAI_API_KEY`\n\nUsed when you want LiteLLM to call OpenAI or an OpenAI-compatible endpoint.\n\nIf you use `./scripts/run_stack.sh` without any provider credentials, the script starts the stack with the stub translation backend instead.\n\n## Optional\n\n### `RULESGEN_LLM_MODEL_NAME`\n\nOverrides the default model defined in Compose.\n\n### `RULESGEN_LLM_GATEWAY_URL`\n\nLeave unset to use the default OpenAI-compatible endpoint:\n\n```text\nhttps://api.openai.com/v1\n```\n\nSet this only if you are routing through an OpenAI-compatible proxy.\n\n## Where configuration is loaded from\n\n### Docker Compose\n\nConfiguration comes from:\n\n* `compose.yaml`\n* `compose.opensandbox.yaml`\n* your shell environment\n\n### Host-run mode\n\nConfiguration comes from:\n\n* `.env`\n* your shell environment\n\nSee `.env.example` for the supported `RULESGEN_*` settings.\n\n---\n\n## Run modes\n\n## Recommended: Docker Compose with OpenSandbox\n\nThis is the mode used by `./scripts/run_stack.sh`.\n\n```bash\nexport OPENAI_API_KEY=your-openai-key\ndocker compose -f compose.yaml -f compose.opensandbox.yaml up --build\n```\n\nWithout any provider credentials:\n\n```bash\n./scripts/run_stack.sh\n```\n\nStop the stack:\n\n```bash\n./scripts/run_stack.sh down\n```\n\n## Docker Compose without OpenSandbox\n\nThis mode uses the local subprocess executor only.\n\n```bash\ndocker compose up --build\n```\n\nIf you want Docker Compose to start without provider credentials, set `RULESGEN_LLM_GATEWAY_BACKEND=stub` in your shell before running `docker compose up --build`.\n\n## Host-run API with Compose-run OpenSandbox\n\nThis mode is useful for contributors who want to run the API locally while keeping OpenSandbox in Docker.\n\nStart OpenSandbox:\n\n```bash\ndocker compose -f compose.yaml -f compose.opensandbox.yaml up --build -d opensandbox-server\n```\n\nStart `rulesgen` on the host:\n\n```bash\nuv sync --extra api --extra dev\ndocker build -t rulesgen:local .\nexport OPENAI_API_KEY=your-openai-key  # omit this and set RULESGEN_LLM_GATEWAY_BACKEND=stub to use the stub backend\nRULESGEN_SANDBOX_BACKEND=opensandbox \\\nRULESGEN_OPENSANDBOX_DOMAIN=127.0.0.1:8090 \\\nRULESGEN_OPENSANDBOX_PROTOCOL=http \\\nRULESGEN_OPENSANDBOX_USE_SERVER_PROXY=false \\\nRULESGEN_OPENSANDBOX_IMAGE=rulesgen:local \\\nuv run uvicorn rulesgen.main:app --reload\n```\n\n---\n\n## Using rulesgen as a Python library\n\nYou can use `rulesgen` without running the API service.\n\nThe package exposes high-level entry points for parsing, compilation, preview, and in-process execution.\n\n### Compile and preview a rule locally\n\n```python\nfrom rulesgen import compile_rule, preview_rule\n\ncompiled = compile_rule(\n    '0.1 * col(\"salary\") if col(\"job_level\") \u003e= 5 else 0',\n    target_column=\"bonus\",\n)\n\npreview = preview_rule(\n    compiled,\n    row={\"salary\": 120000, \"job_level\": 6},\n    seed=99,\n)\n\nprint(preview.value)\n```\n\n### Parse a natural-language rule\n\n```python\nfrom rulesgen import Settings, SourceType, parse_rule\n\nsettings = Settings(\n    llm_gateway_backend=\"litellm\",  # or: \"http\" / \"stub\"\n    llm_model_name=\"gpt-4\",\n)\n\nframe = parse_rule(\n    \"If job_level is 5 or higher, set bonus to 10 percent of salary.\",\n    source_type=SourceType.NATURAL_LANGUAGE,\n    table_name=\"employees\",\n    schema_columns=[\"salary\", \"job_level\", \"bonus\"],\n    target_column=\"bonus\",\n    settings=settings,\n)\n\nprint(frame.dsl_candidate)\n```\n\n### Execute multiple compiled rules in-process\n\n```python\nfrom rulesgen import Settings, compile_rule, execute_generation_plan\n\nsettings = Settings()\n\nrows = [\n    {\"order_id\": \"A\", \"line_amount\": 10},\n    {\"order_id\": \"A\", \"line_amount\": 5},\n    {\"order_id\": \"B\", \"line_amount\": 7},\n]\n\ncompiled_rules = [\n    compile_rule(\n        'col(\"line_amount\") * 2',\n        target_column=\"line_amount_x2\",\n        settings=settings,\n    ),\n    compile_rule(\n        'group_sum(key=col(\"order_id\"), value=col(\"line_amount\"))',\n        target_column=\"order_total\",\n        settings=settings,\n    ),\n]\n\nrun = execute_generation_plan(\n    rows=rows,\n    compiled_rules=compiled_rules,\n    seed=17,\n    references={},\n    max_length=settings.dsl_max_length,\n    max_depth=settings.dsl_max_depth,\n    max_nodes=settings.dsl_max_nodes,\n)\n\nprint(run.rows)\nprint(run.column_sources)\n```\n\n### Copy a generated artifact for a completed job\n\nWhen the library shares the same local repositories as the API service, you can copy\ncompleted job artifacts to another local path:\n\n```python\nfrom rulesgen import Settings, download_job_artifact, download_job_dataset\n\nsettings = Settings(\n    jobs_repository_dir=\".rulesgen-data/jobs\",\n    artifacts_repository_dir=\".rulesgen-data/artifacts\",\n    ossfs_root_dir=\".rulesgen-data/ossfs\",\n)\n\ndataset_copy = download_job_dataset(\n    \"job-id\",\n    destination=\"downloads/generated_rows.json\",\n    settings=settings,\n)\n\nmanifest_copy = download_job_artifact(\n    \"job-id\",\n    \"artifact-id\",\n    destination=\"downloads/sandbox_manifest.json\",\n    settings=settings,\n)\n\nprint(dataset_copy)\nprint(manifest_copy)\n```\n\n---\n\n## API reference\n\n### Health\n\n* `GET /health/live`\n* `GET /health/ready`\n\n### Rules\n\n* `POST /rules/parse`\n* `POST /rules/compile`\n* `POST /rules/preview`\n* `POST /rules/execute`\n\n### Datasets and jobs\n\n* `POST /datasets/uploads`\n* `POST /datasets/generate`\n* `POST /jobs`\n* `GET /jobs/{job_id}`\n* `GET /jobs/{job_id}/dataset`\n* `GET /jobs/{job_id}/artifacts/{artifact_id}`\n\n---\n\n## Architecture summary\n\nThe HTTP layer is intentionally thin.\n\n* routers depend on services\n* services depend on compiler and repository interfaces\n* execution is limited to validated AST artifacts with a restricted runtime surface\n\nNatural-language parsing always produces an untrusted `semantic_frame` and DSL candidate first. Only validated DSL can be compiled into an executable artifact.\n\nExecution paths are separated by purpose:\n\n* **preview execution** uses the local preview executor and supports row-phase helpers\n* **dataset generation** uses either the subprocess executor or the OpenSandbox adapter\n* both generation modes preserve the same manifest and artifact contract under the configured OSSFS root\n\n---\n\n## What’s included\n\n* production-oriented ASGI application skeleton under `src/rulesgen/`\n* structured logging and request context propagation\n* RFC 9457 problem-details responses\n* versioned API modules for health, rules, datasets, and jobs\n* restricted DSL compilation based on Python AST validation\n* filesystem-backed repositories for rules, jobs, prompt audits, and artifacts\n* local preview execution\n* subprocess dataset execution\n* optional OpenSandbox integration for sandboxed dataset generation\n* tests, CI, and container build support\n\n---\n\n## Development\n\nInstall development dependencies:\n\n```bash\nuv sync --extra api --extra dev\n```\n\nUseful commands:\n\n```bash\nuv sync --extra api --extra dev\nuv run pytest\nuv run ruff check .\nuv run ruff format .\nuv run mypy src\nuv run pip-audit\n```\n\n---\n\n## Documentation\n\nThe project documentation site uses MkDocs with the Material theme and sources\nits published pages from `docs/`.\n\nFor a full contributor environment with the docs toolchain enabled:\n\n```bash\nuv sync --extra api --extra dev --extra docs --locked\nuv run mkdocs serve\n```\n\nTo build the site the same way as the GitHub Pages workflow:\n\n```bash\nuv run mkdocs build --strict\n```\n\nOnce GitHub Pages is configured to deploy from GitHub Actions, the published\nsite lives at `https://tdspora.github.io/tdm_rulesgen/`.\n\nThe Pages site is intentionally smaller than the full set of repository docs.\nLonger design and contributor references remain in the repository and are\nlinked from the published site.\n\n---\n\n## Release process\n\nPushes to `main` run CI, build the wheel and sdist, create the GitHub Release via semantic-release, attach release artifacts to that release, publish the same distributions to PyPI, and build/push a Docker image to Docker Hub.\n\nBefore enabling automated releases, configure these repository secrets:\n\n- `DEPLOY_KEY` for the SSH deploy key that semantic-release uses to push version bump commits and tags.\n- `PYPI_TOKEN` for a PyPI API token scoped to the `rulesgen` project.\n- `DOCKER_HUB_USER` for the Docker Hub namespace that owns the published `rulesgen` image.\n- `DOCKER_HUB_TOKEN` for a Docker Hub access token with permission to push images for that namespace.\n\nThe PyPI and Docker publishing jobs both check out the GitHub release tag created by semantic-release and verify that `project.version` matches the released semver before publishing.\n\nWhen a release is published, the workflow pushes `DOCKER_HUB_USER/rulesgen` with `latest`, `vX.Y.Z`, and `X.Y.Z` tags, while PyPI receives distributions built from that same tagged source.\n\nBefore the first automated release, create a baseline tag that matches `project.version` in `pyproject.toml`:\n\n```bash\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\nIf branch protection requires pull requests on `main`, allow the GitHub Actions app to bypass that requirement so semantic-release can push its version bump commit and publish release assets.\n\nThe script below applies the recommended repository and branch-protection settings and prints the one remaining manual UI step:\n\n```text\nscripts/configure-github-repo-oss.sh\n```\n\n---\n\n## Project guidance\n\n* Domain vocabulary for contributors and agents lives in `docs/domain-dictionary.md`\n* Cursor rules for this repository live in `.cursor/rules/`\n\n---\n\n## License\n\nThis project is licensed under the **Apache License 2.0**. See [`LICENSE`](LICENSE) for the full text and [`NOTICE`](NOTICE) for copyright attribution.\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md). This project follows the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). Report security issues according to [`SECURITY.md`](SECURITY.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdspora%2Ftdm_rulesgen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftdspora%2Ftdm_rulesgen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftdspora%2Ftdm_rulesgen/lists"}