{"_id":"@brignano/driftwood","_rev":"3-92a5dd5993bea02a9948176067f66e26","name":"@brignano/driftwood","dist-tags":{"latest":"0.1.0"},"versions":{"0.0.0":{"name":"@brignano/driftwood","version":"0.0.0","keywords":["architecture","architecture-as-code","diagram","graphviz","mermaid","terraform","drift","infrastructure","cli"],"license":"MIT","_id":"@brignano/driftwood@0.0.0","maintainers":[{"name":"brignano","email":"anthonybrignano@gmail.com"}],"homepage":"https://github.com/brignano/driftwood#readme","bugs":{"url":"https://github.com/brignano/driftwood/issues"},"bin":{"driftwood":"dist/cli.js"},"dist":{"shasum":"48ec7e28210ebe78a414339811ca9cb09454af50","tarball":"https://registry.npmjs.org/@brignano/driftwood/-/driftwood-0.0.0.tgz","fileCount":39,"integrity":"sha512-eXnJJpPos3iF9bv6d3bOpjNlP+fu/QSEebGPNeowuGtZZtIgDiBMLOdXRWCzMW/ulynYQN6ll4vMsJ4MuFEJfQ==","signatures":[{"sig":"MEUCIFskMSU3xwkNZJiOUmm84Yc0wZJpptoYvjaJpWZ7CqkzAiEA8F94W3xlLBLF3bp9xIMD9MRhmAwjCU7b5GIri+V8HzE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":101575},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"gitHead":"378886caf120992fa02a079a5940ef49967154e9","scripts":{"dev":"tsx src/cli.ts","test":"vitest run","build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"brignano","email":"anthonybrignano@gmail.com"},"repository":{"url":"git+https://github.com/brignano/driftwood.git","type":"git"},"_npmVersion":"11.19.0","description":"Architecture as code, reconciled with live infrastructure.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"zod":"^3.23.8","yaml":"^2.5.1","commander":"^12.1.0","@hpcc-js/wasm-graphviz":"^1.28.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.1","vitest":"^2.1.2","typescript":"^5.6.2","@types/node":"^22.7.4"},"_npmOperationalInternal":{"tmp":"tmp/driftwood_0.0.0_1787519198340_0.0009267269961348301","host":"s3://npm-registry-packages-npm-production"}},"0.0.1":{"name":"@brignano/driftwood","version":"0.0.1","keywords":["architecture","architecture-as-code","diagram","graphviz","mermaid","terraform","drift","infrastructure","cli"],"license":"MIT","_id":"@brignano/driftwood@0.0.1","maintainers":[{"name":"brignano","email":"anthonybrignano@gmail.com"}],"homepage":"https://github.com/brignano/driftwood#readme","bugs":{"url":"https://github.com/brignano/driftwood/issues"},"bin":{"driftwood":"dist/cli.js"},"dist":{"shasum":"a393fa610b3f62d6472e591f445e117bfa38a572","tarball":"https://registry.npmjs.org/@brignano/driftwood/-/driftwood-0.0.1.tgz","fileCount":39,"integrity":"sha512-f6tCL5aiVn1t4gN6qbz1x0vMZBfyRpSVz7gieBToYvXqfFoSZfZR/GyWAA1yW2xliX3shQ95Gnkau0Yd3P1wWw==","signatures":[{"sig":"MEUCIA2epRQpQrSMUgC+Eni9UMulF7Fl9yYKoRcjkDSWP1yBAiEAxBBy6DIjxRxH60tmZ3DBix6jGeoCVHxEJ+aVgQ172lc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":101573},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"gitHead":"86dc7bc5c5bf9b9628f5448202ee6c288f8703b1","scripts":{"dev":"tsx src/cli.ts","test":"vitest run","build":"tsc -p tsconfig.json","typecheck":"tsc -p tsconfig.json --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6885508f-74d7-4f01-8892-3e650f8ea738"}},"repository":{"url":"git+https://github.com/brignano/driftwood.git","type":"git"},"_npmVersion":"12.0.2","description":"Architecture as code, reconciled with live infrastructure.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"zod":"^3.23.8","yaml":"^2.5.1","commander":"^12.1.0","@hpcc-js/wasm-graphviz":"^1.28.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.1","vitest":"^2.1.2","typescript":"^5.6.2","@types/node":"^22.7.4"},"_npmOperationalInternal":{"tmp":"tmp/driftwood_0.0.1_1787521326219_0.65650999946768","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@brignano/driftwood","version":"0.1.0","description":"Architecture as code, reconciled with live infrastructure.","type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/brignano/driftwood.git"},"bugs":{"url":"https://github.com/brignano/driftwood/issues"},"homepage":"https://github.com/brignano/driftwood#readme","keywords":["architecture","architecture-as-code","diagram","graphviz","mermaid","terraform","drift","infrastructure","cli"],"publishConfig":{"access":"public"},"bin":{"driftwood":"dist/cli.js"},"main":"./dist/index.js","types":"./dist/index.d.ts","engines":{"node":">=20"},"scripts":{"build":"tsc -p tsconfig.json","prepublishOnly":"npm run build","dev":"tsx src/cli.ts","docs":"tsx scripts/render-docs.ts","test":"vitest run","typecheck":"tsc -p tsconfig.json --noEmit"},"dependencies":{"@hpcc-js/wasm-graphviz":"^1.28.0","commander":"^12.1.0","yaml":"^2.5.1","zod":"^3.23.8"},"devDependencies":{"@types/node":"^22.7.4","tsx":"^4.19.1","typescript":"^5.6.2","vitest":"^3.2.6"},"gitHead":"05f74da8c34693060a25183a22841cfedc463025","_id":"@brignano/driftwood@0.1.0","_nodeVersion":"22.23.2","_npmVersion":"12.0.2","dist":{"integrity":"sha512-QwDToncnfhSzx5nCGAXAhvUUe2I8qqw1rrAYAyPxt46jXx2TzcF0PXhZetn6DRfpONqIBYq4bk/R8gnPILV0DA==","shasum":"24fa5ef41d89c4c79f11b43afc81cdb1048d2858","tarball":"https://registry.npmjs.org/@brignano/driftwood/-/driftwood-0.1.0.tgz","fileCount":41,"unpackedSize":130445,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAOAVP3ANpgZ26EZ0QvZkFuoInYEJpD7RhoNZ8iNMicfAiBcXfSQjlrWZ2w8kKLDotyj1pwfal5PCRAi2zD07Z7tgg=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:6885508f-74d7-4f01-8892-3e650f8ea738"}},"directories":{},"maintainers":[{"name":"brignano","email":"anthonybrignano@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/driftwood_0.1.0_1787526397639_0.7627368512876653"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-23T21:06:38.139Z","modified":"2026-08-23T23:06:37.934Z","0.0.0":"2026-08-23T21:06:38.492Z","0.0.1":"2026-08-23T21:42:06.347Z","0.1.0":"2026-08-23T23:06:37.800Z"},"bugs":{"url":"https://github.com/brignano/driftwood/issues"},"license":"MIT","homepage":"https://github.com/brignano/driftwood#readme","keywords":["architecture","architecture-as-code","diagram","graphviz","mermaid","terraform","drift","infrastructure","cli"],"repository":{"type":"git","url":"git+https://github.com/brignano/driftwood.git"},"description":"Architecture as code, reconciled with live infrastructure.","maintainers":[{"name":"brignano","email":"anthonybrignano@gmail.com"}],"readme":"# driftwood\n\n**Architecture as code, reconciled with live infrastructure.**\n\nA versioned architecture *model* in git that is continuously checked against reality — Terraform state today, cloud APIs and telemetry later. When the model and the infrastructure diverge, that divergence becomes a pull request instead of a diagram nobody trusts.\n\n> Status: **proof of concept.** The core loop works end to end. See [Where this is going](#where-this-is-going) for what is deliberately not built yet.\n\n## Why\n\nTwo problems that are usually treated separately share one root cause:\n\n- **Diagrams-as-code tools** (Structurizr, C4, `mingrammer/diagrams`, D2) move the picture into git but don't stop it rotting. A hand-maintained model decays exactly like a Visio file, just with better blame.\n- **Live topology tools** (Dynatrace Smartscape, Datadog Service Map, Kiali) show real discovered topology but produce no artifact — nothing to version, nothing to review, no expression of design *intent*, and nothing a coding agent can edit.\n\nNobody owns the middle. driftwood is the middle.\n\n### Graphviz: bundled, not required\n\n`mingrammer/diagrams` requires the Graphviz **system binary**, which is often impossible to get approved inside a corporate environment. driftwood ships Graphviz instead of requiring it.\n\n`@hpcc-js/wasm-graphviz` is real Graphviz compiled to WebAssembly — same DOT semantics, same layouts, zero transitive dependencies, WASM inlined into the JS. It is a **regular dependency**, so a plain `npm install` gives you working Graphviz on any machine, with no system package and no admin rights.\n\n| Engine | Needs | Output |\n|---|---|---|\n| `graphviz` (native) | `dot` on PATH | SVG, icons and all. Preferred when present — faster on very large graphs, honours a site's own Graphviz build |\n| `graphviz` (WASM) | **nothing — bundled** | SVG with category icons. The default |\n| `dot` | nothing | DOT source (it's just text) |\n| `mermaid` | nothing | Mermaid, renders natively in GitHub |\n\nOut of the box:\n\n```\n$ driftwood engines\ngraphviz   available    bundled @hpcc-js/wasm-graphviz\nmermaid    available    built-in\ndot        available    built-in\n\nauto would use: graphviz (bundled @hpcc-js/wasm-graphviz)\n```\n\nThe one environment where Graphviz still can't run is a runtime with WebAssembly switched off — a hardened container, or `node --jitless`. There `auto` degrades to Mermaid rather than failing:\n\n```\n$ node --jitless dist/cli.js engines\ngraphviz   unavailable  WebAssembly is disabled in this runtime, so the bundled Graphviz cannot load - install the `dot` binary or use --engine mermaid\n...\nauto would use: mermaid (built-in)\n```\n\nRare, but real — which is why the fallback isn't vestigial. Both paths are asserted in CI.\n\nFor size context, the bundled Graphviz is **smaller than `zod`**, which driftwood already depends on:\n\n| Package | Size | Transitive deps |\n|---|---|---|\n| `zod` | 5.2 MB | — |\n| `@hpcc-js/wasm-graphviz` | 2.1 MB (804 KB runtime) | none |\n| `yaml` | 1.4 MB | — |\n\n## How it fits together\n\n```mermaid\nflowchart LR\n    subgraph Providers[\"Providers — observe\"]\n        TF[\"Terraform state\"]\n        SOON[\"cloud APIs · OTel<br/>(not yet)\"]\n    end\n    subgraph Core[\"Core\"]\n        MODEL[\"architecture.yaml<br/>entities · edges · views<br/>(versioned in git)\"]\n        REC[\"Reconciler<br/>declared vs observed\"]\n    end\n    subgraph Renderers[\"Renderers — present\"]\n        GV[\"Graphviz SVG\"]\n        MMD[\"Mermaid · DOT\"]\n        LATER[\"2D live · 3D<br/>(not yet)\"]\n    end\n    TF --> REC\n    SOON -.-> REC\n    MODEL --> REC\n    REC -->|\"divergence\"| MODEL\n    MODEL --> GV\n    MODEL --> MMD\n    MODEL -.-> LATER\n```\n\nThe model is the only thing in git. Providers write into it, renderers read from it, the reconciler diffs it. Because everything routes through one model, \"static diagram vs. live health\" and \"2D vs. 3D\" are rendering modes rather than rewrites — health is just an attribute on a node, and blast radius is a traversal over edges that already exist.\n\n## Install\n\n```bash\nnpm install\nnpm run build\n```\n\nRequires Node 20+. Runtime dependencies are `commander`, `yaml`, `zod`, and `@hpcc-js/wasm-graphviz` — all pure JavaScript/WebAssembly, no native builds and no system packages.\n\n## Usage\n\n### Import a model from Terraform state\n\n```bash\nnpx tsx src/cli.ts import terraform examples/orders-platform.tfstate.json \\\n  --name orders-platform -o examples/architecture.yaml\n```\n\nEntity ids are Terraform addresses (`aws_s3_bucket.assets`). That's deliberate: the address is stable across plans, readable in a diff, and sidesteps the identity-resolution problem that kills CMDBs. Edges come from Terraform's own `dependencies`.\n\nThe worked example in `examples/` is a fictional e-commerce platform — CloudFront and Route 53 at the edge, an ALB in front of two ECS services, Postgres, Redis, DynamoDB and S3 behind them, and an SQS/SNS order pipeline with a Lambda worker. 45 resources: enough that the view mechanism is doing real work rather than decorating a diagram that fits on a page anyway.\n\n### Validate\n\n```bash\nnpx tsx src/cli.ts validate examples/architecture.yaml\n```\n\nCatches schema errors, duplicate ids, edges pointing at entities that don't exist, and views that match nothing. This is what makes agent edits safe to accept — a coding agent can rewrite the model and CI proves it's still coherent.\n\n### Render\n\n```bash\nnpx tsx src/cli.ts render examples/architecture.yaml --view app -o app.svg\n```\n\n![The app view: an ALB listener and target group feeding two ECS services and their task definitions](docs/app.svg)\n\nNodes are drawn with a category icon, the entity name, and its `kind` underneath. The icons are **drawn in this repo and chosen by category** — database, queue, load balancer — not by vendor: no icon pack to install, nothing to license, and one glyph that fits an RDS instance, a Cloud SQL instance and an on-prem Postgres alike. Colour groups the categories into families (edge, compute, data, messaging, security), so a diagram reads as a few zones instead of thirty unrelated boxes. Use `--no-icons` for plain boxes.\n\nThe icons are inlined into the SVG rather than handed to Graphviz as `image=` attributes, because the native `dot` binary resolves those as filesystem paths — a `data:` URI would work on the WASM tier and break on the native one. Post-processing the SVG means both tiers draw the same picture, and the result stays self-contained — no external references, no base64 bloat.\n\nEvery image in this README is generated from `examples/architecture.yaml` by `npm run docs`, and CI re-runs it and fails if the result differs from what is committed. A stale picture of your own output is worse than no picture, so the renders are checked against their source rather than trusted — the drift gate's own argument, one level up.\n\nThe `async` view through Mermaid, which renders natively in a pull request:\n\n<!-- generated:async -->\n```mermaid\nflowchart LR\n    subgraph n_lambda[\"lambda\"]\n        n_aws_lambda_event_source_mapping_orders[/\"orders<br/>aws_lambda_event_source_mapping\"/]\n        n_aws_lambda_function_order_worker[\"shop-order-worker<br/>aws_lambda_function\"]\n    end\n    subgraph n_sns[\"sns\"]\n        n_aws_sns_topic_subscription_orders[/\"orders<br/>aws_sns_topic_subscription\"/]\n        n_aws_sns_topic_order_events[/\"shop-order-events<br/>aws_sns_topic\"/]\n    end\n    subgraph n_sqs[\"sqs\"]\n        n_aws_sqs_queue_orders[/\"shop-orders<br/>aws_sqs_queue\"/]\n        n_aws_sqs_queue_orders_dlq[/\"shop-orders-dlq<br/>aws_sqs_queue\"/]\n    end\n    n_aws_lambda_event_source_mapping_orders --> n_aws_lambda_function_order_worker\n    n_aws_lambda_event_source_mapping_orders --> n_aws_sqs_queue_orders\n    n_aws_lambda_function_order_worker --> n_aws_sqs_queue_orders\n    n_aws_sns_topic_subscription_orders --> n_aws_sns_topic_order_events\n    n_aws_sns_topic_subscription_orders --> n_aws_sqs_queue_orders\n    n_aws_sqs_queue_orders --> n_aws_sqs_queue_orders_dlq\n    class n_aws_lambda_event_source_mapping_orders f_messaging;\n    class n_aws_lambda_function_order_worker f_compute;\n    class n_aws_sns_topic_subscription_orders f_messaging;\n    class n_aws_sns_topic_order_events f_messaging;\n    class n_aws_sqs_queue_orders f_messaging;\n    class n_aws_sqs_queue_orders_dlq f_messaging;\n    classDef f_compute fill:#fffbeb,stroke:#d97706,color:#0f172a;\n    classDef f_messaging fill:#f5f3ff,stroke:#7c3aed,color:#0f172a;\n```\n<!-- /generated:async -->\n\nMermaid has no icon primitive, so it maps the *same* categorisation onto shapes and colours — a queue must not be a queue in one engine and a cylinder in the other, or the two pictures stop describing the same system.\n\n#### The rest of the views\n\n| View | What it scopes to | File |\n|---|---|---|\n| `edge` | Route 53, CloudFront, ACM, WAF, the ALB | [svg](docs/edge.svg) |\n| `app` | Load balancer through to the ECS services | [svg](docs/app.svg) |\n| `data` | Postgres, Redis, DynamoDB, S3 | [svg](docs/data.svg) |\n| `async` | The SQS/SNS/Lambda order pipeline | [svg](docs/async.svg) |\n| `network` | VPC, subnets, security groups, NAT | [svg](docs/network.svg) |\n| `context` | Everything, minus IAM and CloudWatch noise | [svg](docs/context.svg) |\n\n**Views exist from v1, not as a later optimization.** Flat Mermaid becomes unreadable past roughly 150 nodes, and any real enterprise graph blows through that immediately. A view is a scoped slice matching entity ids or groups, with a trailing `*` wildcard. The example model ships six — `context`, `edge`, `app`, `data`, `async`, `network` — and even at 45 entities the difference between a view and the whole graph is the difference between a diagram and a wall.\n\n### Reconcile — the point of the whole thing\n\n```bash\nnpx tsx src/cli.ts reconcile examples/architecture.yaml \\\n  --terraform examples/orders-platform.drifted.tfstate.json\n```\n\nTwo resources created by hand during an incident, one bucket deleted, one database renamed:\n\n```markdown\n## Architecture drift detected\n\n### Present in infrastructure, missing from the model (2)\n\n- `aws_elasticache_replication_group.sessions_failover` - aws_elasticache_replication_group (shop-sessions-ha)\n- `aws_sqs_queue.payments` - aws_sqs_queue (shop-payments)\n\n### Declared in the model, not found in infrastructure (1)\n\n- `aws_s3_bucket.uploads` - aws_s3_bucket (shop-example-com-uploads)\n\n### Changed (1)\n\n| Entity | Field | Declared | Observed |\n|---|---|---|---|\n| `aws_db_instance.orders` | name | shop-orders-prod | shop-orders-prod-v2 |\n\n### Relationships\n\n- **added** `aws_elasticache_replication_group.sessions_failover` -> `aws_security_group.data`\n- **added** `aws_elasticache_replication_group.sessions_failover` -> `aws_subnet.private_a`\n- **added** `aws_sqs_queue.payments` -> `aws_kms_key.data`\n- **removed** `aws_iam_role_policy.order_worker` -> `aws_s3_bucket.uploads`\n- **removed** `aws_lambda_function.order_worker` -> `aws_s3_bucket.uploads`\n- **removed** `aws_s3_bucket.uploads` -> `aws_kms_key.data`\n\n_Ignored by policy: 1 entities, 0 edges._\n```\n\nExits **1** on drift and **0** when clean, so it works directly as a CI gate. Output is markdown because its destination is a pull request body.\n\n## Extensible by design\n\nProviders and renderers are both registries. A third-party plugin registers exactly the way a built-in does — there is no separate plugin API.\n\n### Providers — where facts come from\n\n| Provider | Kind | Platforms | Status |\n|---|---|---|---|\n| `terraform` | declarative | **any** (AWS, GCP, Azure, vSphere, on-prem) | built in |\n| `dynatrace` | runtime | aws, gcp, azure, onprem, kubernetes | built in |\n| Splunk, cloud APIs, OTel, Kubernetes | — | — | extension point ready |\n\nTerraform is platform-agnostic on purpose: one provider covers every target, because the platform is whatever the state file declares.\n\nThe `declarative` / `runtime` split matters. Terraform says what *should* exist; Dynatrace says what is *actually running*. A service Terraform declares but Dynatrace has never seen is a very different finding from one neither knows about. When both describe the same entity, the declarative source wins on naming and grouping — IaC resource names beat monitoring display names.\n\nAdding one is a small, well-defined job: see [`.claude/skills/add-provider/SKILL.md`](.claude/skills/add-provider/SKILL.md).\n\n### Configuration\n\nWiring is declarative, so adding a provider or switching engines is a config edit rather than a code change:\n\n```yaml\n# driftwood.config.yaml\nmodel: architecture.yaml\n\nproviders:\n  - use: terraform\n    with:\n      statePath: ./terraform.tfstate\n  - use: dynatrace\n    with:\n      url: https://abc12345.live.dynatrace.com\n      tokenEnv: DYNATRACE_API_TOKEN   # the env var NAME, never the token\n\nrender:\n  - view: context\n    to: docs/context.mmd\n    engine: auto\n```\n\n```bash\ndriftwood reconcile architecture.yaml -c driftwood.config.yaml\n```\n\n### Cross-source identity is explicit, never guessed\n\nTerraform calls it `aws_lambda_function.forwarder`; Dynatrace calls it `SERVICE-A1B2`. Nothing in either payload proves they are the same thing, so driftwood **does not guess**:\n\n```yaml\naliases:\n  SERVICE-A1B2: aws_lambda_function.forwarder\n```\n\nWithout an alias the two stay separate nodes. That is deliberate — a duplicated node is visible and fixable, whereas a wrongly merged node silently corrupts the graph. Where providers disagree about a merged entity, the disagreement is resolved *and reported*, never hidden.\n\n## The drift policy\n\nThis is the design decision most likely to sink the project in practice. Report too much and every run becomes noise that gets muted; report too little and the model rots anyway.\n\n**Default: structural facts count, metadata doesn't.**\n\n| Change | Drift? |\n|---|---|\n| A resource appears or disappears | **Yes** |\n| An edge appears or disappears | **Yes** |\n| `kind`, `name`, or `group` changes | **Yes** |\n| A tag is added or changed | No |\n| Anything matching an `ignore` rule | No |\n\n`ignore` holds intentional divergence, reviewed like code. An edge touching an ignored entity is ignored by implication — otherwise ignoring one noisy resource would still surface all of its edges.\n\n## Coverage gaps are explicit\n\nRead-only credentials never see everything, and **unknown must never be silently reported as absent**. The model declares its own blind spots:\n\n```yaml\ncoverage:\n  - scope: aws_secretsmanager_*\n    reason: the read-only role used by CI cannot list secrets\n```\n\nThese are printed with every drift report. \"Always matches live platform data\" is a promise no tool can keep; reconciliation with declared blind spots is one it can.\n\n## Project layout\n\n```\nsrc/\n  registry.ts            shared name -> implementation registry\n  model/schema.ts        the model — entities, edges, views, aliases, ignore, coverage\n  model/validate.ts      schema + referential integrity\n  model/merge.ts         multi-provider merge, provenance, conflict reporting\n  providers/types.ts     the provider extension point\n  providers/terraform.ts declarative — any platform Terraform manages\n  providers/dynatrace.ts runtime — Smartscape topology, read-only\n  render/types.ts        the renderer extension point (probe + render)\n  render/select.ts       shared view scoping\n  render/icons.ts        category icons, the kind -> icon/colour table, SVG injection\n  render/mermaid.ts      always available\n  render/dot.ts          DOT source, always available\n  render/graphviz.ts     SVG via native dot or WASM, with tier detection\n  reconcile/index.ts     declared vs observed -> drift report\n  config.ts              driftwood.config.yaml\n  cli.ts                 validate · render · engines · providers · import · reconcile\nexamples/                a worked 45-resource AWS platform, a drifted copy, and a config\nscripts/render-docs.ts   regenerates docs/ and the README's embedded render\ndocs/                    committed renders of the example, kept current by CI\n.claude/skills/          add-provider and add-renderer walkthroughs for agents\n```\n\n## Development\n\n```bash\nnpm test        # 98 tests\nnpm run typecheck\nnpm run build\n```\n\n## Where this is going\n\nBuilt:\n\n- [x] The model, with a schema and a real validator\n- [x] Pluggable provider registry — Terraform (any platform) and Dynatrace built in\n- [x] Pluggable renderer registry — Graphviz bundled and working out of the box, with Mermaid/DOT fallback\n- [x] Category icons and a family palette, shared by every engine\n- [x] Multi-provider merge with provenance, explicit aliases, and conflict reporting\n- [x] Declarative `driftwood.config.yaml` wiring\n- [x] Reconciler with an explicit drift policy, wired as a CI gate\n\nDeliberately not built yet, roughly in order:\n\n- [ ] Open the drift report as an actual pull request, not just a CI failure\n- [ ] Live cloud API providers (AWS, GCP) to catch resources no IaC owns\n- [ ] A Splunk provider (the extension point is ready; no implementation shipped yet)\n- [ ] Preserve human/agent annotations across regeneration\n- [ ] Health overlay on the 2D graph (the renderer already accepts it)\n- [ ] Interactive viewer, blast-radius traversal\n- [ ] 3D — last, optional, and only if someone actually asks\n\n**On 3D:** it's the reward, not the plan. Netflix's Vizceral was the flagship of exactly this concept and is effectively abandoned; Cloudcraft deliberately stopped at 2.5D isometric because it stays readable and screenshot-able. 3D topology demos brilliantly and then goes unused during incidents — it occludes, doesn't diff, doesn't paste into a postmortem, and needs a mouse. Build the model first and a 3D view stays cheap to add later.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}