@macedon-technologies/batman 1.17.3 → 1.18.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.
Files changed (47) hide show
  1. package/.jsii +124 -17
  2. package/.swamp.yaml +6 -0
  3. package/API.md +107 -0
  4. package/CLAUDE.md +44 -0
  5. package/architecture-rules/README.md +42 -0
  6. package/architecture-rules/check.ts +162 -0
  7. package/architecture-rules/rules.json +3 -0
  8. package/architecture-rules/tsconfig.json +14 -0
  9. package/attestation/manifest.ts +40 -0
  10. package/attestation/tsconfig.json +13 -0
  11. package/attestation/verify.ts +142 -0
  12. package/lib/BaseApplicationStackFile.js +4 -3
  13. package/lib/BatmanProject.js +8 -2
  14. package/lib/BootcampBaseApplicationStackFile.js +4 -3
  15. package/lib/BootcampProject.js +8 -2
  16. package/lib/CodeOwnersFile.js +1 -1
  17. package/lib/ProductionDeployWorkflow.js +1 -1
  18. package/lib/SoftwareFactoryArchitectureRules.d.ts +10 -0
  19. package/lib/SoftwareFactoryArchitectureRules.js +245 -0
  20. package/lib/SoftwareFactoryAttestation.d.ts +18 -0
  21. package/lib/SoftwareFactoryAttestation.js +221 -0
  22. package/lib/SoftwareFactoryFiles.d.ts +45 -0
  23. package/lib/SoftwareFactoryFiles.js +102 -0
  24. package/lib/SoftwareFactoryModels.d.ts +25 -0
  25. package/lib/SoftwareFactoryModels.js +306 -0
  26. package/lib/SoftwareFactoryOpenSpecGates.d.ts +8 -0
  27. package/lib/SoftwareFactoryOpenSpecGates.js +75 -0
  28. package/lib/SoftwareFactoryWorkflows.d.ts +28 -0
  29. package/lib/SoftwareFactoryWorkflows.js +326 -0
  30. package/lib/StagingDeployWorkflow.js +1 -1
  31. package/lib/StaticWebsiteConstructFile.js +2 -2
  32. package/lib/WorkflowAutoDiscover.js +1 -1
  33. package/lib/index.d.ts +1 -0
  34. package/lib/index.js +2 -1
  35. package/lib/utils.js +29 -1
  36. package/models/@goodcraft/github/platform-common-pr.yaml +10 -0
  37. package/models/@swamp/software-factory/platform-common-factory.yaml +240 -0
  38. package/models/@webframp/github/platform-common-issues.yaml +8 -0
  39. package/models/command/shell/platform-common-shell-architecture.yaml +8 -0
  40. package/models/command/shell/platform-common-shell-manifest.yaml +8 -0
  41. package/models/command/shell/platform-common-shell.yaml +8 -0
  42. package/openspec-gates/check-tasks-complete.ts +44 -0
  43. package/openspec-gates/tsconfig.json +13 -0
  44. package/package.json +6 -1
  45. package/workflows/workflow-openspec-plan-verify.yaml +51 -0
  46. package/workflows/workflow-openspec-tasks-verify.yaml +51 -0
  47. package/workflows/workflow-pre-pr-verify.yaml +118 -0
package/.jsii CHANGED
@@ -158,7 +158,7 @@
158
158
  },
159
159
  "locationInModule": {
160
160
  "filename": "src/BatmanProject.ts",
161
- "line": 63
161
+ "line": 64
162
162
  },
163
163
  "parameters": [
164
164
  {
@@ -172,7 +172,7 @@
172
172
  "kind": "class",
173
173
  "locationInModule": {
174
174
  "filename": "src/BatmanProject.ts",
175
- "line": 62
175
+ "line": 63
176
176
  },
177
177
  "name": "BatmanProject",
178
178
  "symbolId": "src/BatmanProject:BatmanProject"
@@ -190,7 +190,7 @@
190
190
  "kind": "interface",
191
191
  "locationInModule": {
192
192
  "filename": "src/BatmanProject.ts",
193
- "line": 11
193
+ "line": 12
194
194
  },
195
195
  "name": "BatmanProjectOptions",
196
196
  "properties": [
@@ -203,7 +203,7 @@
203
203
  "immutable": true,
204
204
  "locationInModule": {
205
205
  "filename": "src/BatmanProject.ts",
206
- "line": 16
206
+ "line": 17
207
207
  },
208
208
  "name": "dev",
209
209
  "type": {
@@ -219,7 +219,7 @@
219
219
  "immutable": true,
220
220
  "locationInModule": {
221
221
  "filename": "src/BatmanProject.ts",
222
- "line": 26
222
+ "line": 27
223
223
  },
224
224
  "name": "prod",
225
225
  "type": {
@@ -235,7 +235,7 @@
235
235
  "immutable": true,
236
236
  "locationInModule": {
237
237
  "filename": "src/BatmanProject.ts",
238
- "line": 21
238
+ "line": 22
239
239
  },
240
240
  "name": "staging",
241
241
  "type": {
@@ -251,7 +251,7 @@
251
251
  "immutable": true,
252
252
  "locationInModule": {
253
253
  "filename": "src/BatmanProject.ts",
254
- "line": 51
254
+ "line": 52
255
255
  },
256
256
  "name": "actionVersions",
257
257
  "optional": true,
@@ -268,7 +268,7 @@
268
268
  "immutable": true,
269
269
  "locationInModule": {
270
270
  "filename": "src/BatmanProject.ts",
271
- "line": 41
271
+ "line": 42
272
272
  },
273
273
  "name": "additionalCodeOwners",
274
274
  "optional": true,
@@ -290,7 +290,7 @@
290
290
  "immutable": true,
291
291
  "locationInModule": {
292
292
  "filename": "src/BatmanProject.ts",
293
- "line": 31
293
+ "line": 32
294
294
  },
295
295
  "name": "additionalTags",
296
296
  "optional": true,
@@ -313,7 +313,7 @@
313
313
  "immutable": true,
314
314
  "locationInModule": {
315
315
  "filename": "src/BatmanProject.ts",
316
- "line": 58
316
+ "line": 59
317
317
  },
318
318
  "name": "authorizerObjectVersion",
319
319
  "optional": true,
@@ -330,7 +330,7 @@
330
330
  "immutable": true,
331
331
  "locationInModule": {
332
332
  "filename": "src/BatmanProject.ts",
333
- "line": 36
333
+ "line": 37
334
334
  },
335
335
  "name": "n8nWorkflows",
336
336
  "optional": true,
@@ -347,7 +347,7 @@
347
347
  "immutable": true,
348
348
  "locationInModule": {
349
349
  "filename": "src/BatmanProject.ts",
350
- "line": 46
350
+ "line": 47
351
351
  },
352
352
  "name": "websiteDeps",
353
353
  "optional": true,
@@ -376,7 +376,7 @@
376
376
  },
377
377
  "locationInModule": {
378
378
  "filename": "src/BootcampProject.ts",
379
- "line": 17
379
+ "line": 18
380
380
  },
381
381
  "parameters": [
382
382
  {
@@ -390,7 +390,7 @@
390
390
  "kind": "class",
391
391
  "locationInModule": {
392
392
  "filename": "src/BootcampProject.ts",
393
- "line": 16
393
+ "line": 17
394
394
  },
395
395
  "name": "BootcampProject",
396
396
  "symbolId": "src/BootcampProject:BootcampProject"
@@ -408,7 +408,7 @@
408
408
  "kind": "interface",
409
409
  "locationInModule": {
410
410
  "filename": "src/BootcampProject.ts",
411
- "line": 12
411
+ "line": 13
412
412
  },
413
413
  "name": "BootcampProjectOptions",
414
414
  "symbolId": "src/BootcampProject:BootcampProjectOptions"
@@ -780,6 +780,113 @@
780
780
  ],
781
781
  "symbolId": "src/ProductionDeployWorkflow:ProductionDeployWorkflowOptions"
782
782
  },
783
+ "@macedon-technologies/batman.SoftwareFactoryFiles": {
784
+ "assembly": "@macedon-technologies/batman",
785
+ "docs": {
786
+ "remarks": "What swamp itself would generate (extension docs pulled via\n`swamp extension pull`, `.swamp.yaml`, the vault, and the \"swamp managed\nsection\" in CLAUDE.md) is deliberately NOT hand-templated here — those are\nproduced by the real `swamp` CLI. `swamp init` is idempotent (a no-op if\nthe repo is already initialized), so it's wired into the project's\n`install`/`install:ci` tasks and runs automatically on every install,\nguarded so it's a silent no-op on a machine without `swamp` on PATH (e.g.\nCI). The extension pulls and vault creation are one-time, not idempotent\nin a way that's safe to repeat on every install, so they stay in the\n`swamp:bootstrap` task this construct adds. Only exman's own authored\nconfig (the factory's state machine, the architecture-rule engine, the\nattestation mechanism, the OpenSpec task-completion gate, and the 3 swamp\nworkflows that wire them together) is templated.",
787
+ "stability": "stable",
788
+ "summary": "Backports exman's swamp \"software factory\" pattern (macedon-technologies/exman#13) as generated scaffolding: a `@swamp/software-factory` state machine that drives a GitHub issue from intake through an opened PR, enforcing OpenSpec usage, an architecture rule registry, and a pre-PR verification gate whose attestation is re-checked at merge time."
789
+ },
790
+ "fqn": "@macedon-technologies/batman.SoftwareFactoryFiles",
791
+ "initializer": {
792
+ "docs": {
793
+ "stability": "stable"
794
+ },
795
+ "locationInModule": {
796
+ "filename": "src/SoftwareFactoryFiles.ts",
797
+ "line": 55
798
+ },
799
+ "parameters": [
800
+ {
801
+ "name": "project",
802
+ "type": {
803
+ "fqn": "projen.javascript.NodeProject"
804
+ }
805
+ },
806
+ {
807
+ "name": "options",
808
+ "type": {
809
+ "fqn": "@macedon-technologies/batman.SoftwareFactoryFilesOptions"
810
+ }
811
+ }
812
+ ]
813
+ },
814
+ "kind": "class",
815
+ "locationInModule": {
816
+ "filename": "src/SoftwareFactoryFiles.ts",
817
+ "line": 54
818
+ },
819
+ "name": "SoftwareFactoryFiles",
820
+ "symbolId": "src/SoftwareFactoryFiles:SoftwareFactoryFiles"
821
+ },
822
+ "@macedon-technologies/batman.SoftwareFactoryFilesOptions": {
823
+ "assembly": "@macedon-technologies/batman",
824
+ "datatype": true,
825
+ "docs": {
826
+ "stability": "stable"
827
+ },
828
+ "fqn": "@macedon-technologies/batman.SoftwareFactoryFilesOptions",
829
+ "kind": "interface",
830
+ "locationInModule": {
831
+ "filename": "src/SoftwareFactoryFiles.ts",
832
+ "line": 11
833
+ },
834
+ "name": "SoftwareFactoryFilesOptions",
835
+ "properties": [
836
+ {
837
+ "abstract": true,
838
+ "docs": {
839
+ "stability": "stable",
840
+ "summary": "The bare repo name (e.g. `expense-tracker`), used to derive the factory's model names (`<repoName>-factory`, `<repoName>-issues`, `<repoName>-pr`, `<repoName>-shell*`) and vault name (`<repoName>-secrets`)."
841
+ },
842
+ "immutable": true,
843
+ "locationInModule": {
844
+ "filename": "src/SoftwareFactoryFiles.ts",
845
+ "line": 17
846
+ },
847
+ "name": "repoName",
848
+ "type": {
849
+ "primitive": "string"
850
+ }
851
+ },
852
+ {
853
+ "abstract": true,
854
+ "docs": {
855
+ "stability": "stable",
856
+ "summary": "Overrides for the pinned action versions used by `verify-attestation.yml`."
857
+ },
858
+ "immutable": true,
859
+ "locationInModule": {
860
+ "filename": "src/SoftwareFactoryFiles.ts",
861
+ "line": 29
862
+ },
863
+ "name": "actionVersions",
864
+ "optional": true,
865
+ "type": {
866
+ "fqn": "@macedon-technologies/batman.WorkflowActionVersions"
867
+ }
868
+ },
869
+ {
870
+ "abstract": true,
871
+ "docs": {
872
+ "default": "'main'",
873
+ "stability": "stable",
874
+ "summary": "The branch the factory opens pull requests against."
875
+ },
876
+ "immutable": true,
877
+ "locationInModule": {
878
+ "filename": "src/SoftwareFactoryFiles.ts",
879
+ "line": 24
880
+ },
881
+ "name": "baseBranch",
882
+ "optional": true,
883
+ "type": {
884
+ "primitive": "string"
885
+ }
886
+ }
887
+ ],
888
+ "symbolId": "src/SoftwareFactoryFiles:SoftwareFactoryFilesOptions"
889
+ },
783
890
  "@macedon-technologies/batman.StagingDeployWorkflow": {
784
891
  "assembly": "@macedon-technologies/batman",
785
892
  "base": "projen.YamlFile",
@@ -1140,6 +1247,6 @@
1140
1247
  "symbolId": "src/WorkflowAutoDiscover:WorkflowAutoDiscover"
1141
1248
  }
1142
1249
  },
1143
- "version": "1.17.3",
1144
- "fingerprint": "P+oTgE5PvMlTAfLt/cPc3/R0/p+eE8VKJnnVlGRzP4Y="
1250
+ "version": "1.18.1",
1251
+ "fingerprint": "0vF+6JOckmNcOaTJ5G7JV4uJbejomXAtMaxvX0i6F4M="
1145
1252
  }
package/.swamp.yaml ADDED
@@ -0,0 +1,6 @@
1
+ swampVersion: 20260830.034252.0
2
+ initializedAt: "2026-08-31T20:50:20.528Z"
3
+ repoId: c00711dd-bd24-4a7a-ab61-38d554723f27
4
+ tools:
5
+ - claude
6
+ gitignoreManaged: true
package/API.md CHANGED
@@ -10572,6 +10572,63 @@ Overrides for the pinned action versions used by this workflow.
10572
10572
 
10573
10573
  ---
10574
10574
 
10575
+ ### SoftwareFactoryFilesOptions <a name="SoftwareFactoryFilesOptions" id="@macedon-technologies/batman.SoftwareFactoryFilesOptions"></a>
10576
+
10577
+ #### Initializer <a name="Initializer" id="@macedon-technologies/batman.SoftwareFactoryFilesOptions.Initializer"></a>
10578
+
10579
+ ```typescript
10580
+ import { SoftwareFactoryFilesOptions } from '@macedon-technologies/batman'
10581
+
10582
+ const softwareFactoryFilesOptions: SoftwareFactoryFilesOptions = { ... }
10583
+ ```
10584
+
10585
+ #### Properties <a name="Properties" id="Properties"></a>
10586
+
10587
+ | **Name** | **Type** | **Description** |
10588
+ | --- | --- | --- |
10589
+ | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.repoName">repoName</a></code> | <code>string</code> | The bare repo name (e.g. `expense-tracker`), used to derive the factory's model names (`<repoName>-factory`, `<repoName>-issues`, `<repoName>-pr`, `<repoName>-shell*`) and vault name (`<repoName>-secrets`). |
10590
+ | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.actionVersions">actionVersions</a></code> | <code><a href="#@macedon-technologies/batman.WorkflowActionVersions">WorkflowActionVersions</a></code> | Overrides for the pinned action versions used by `verify-attestation.yml`. |
10591
+ | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.baseBranch">baseBranch</a></code> | <code>string</code> | The branch the factory opens pull requests against. |
10592
+
10593
+ ---
10594
+
10595
+ ##### `repoName`<sup>Required</sup> <a name="repoName" id="@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.repoName"></a>
10596
+
10597
+ ```typescript
10598
+ public readonly repoName: string;
10599
+ ```
10600
+
10601
+ - *Type:* string
10602
+
10603
+ The bare repo name (e.g. `expense-tracker`), used to derive the factory's model names (`<repoName>-factory`, `<repoName>-issues`, `<repoName>-pr`, `<repoName>-shell*`) and vault name (`<repoName>-secrets`).
10604
+
10605
+ ---
10606
+
10607
+ ##### `actionVersions`<sup>Optional</sup> <a name="actionVersions" id="@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.actionVersions"></a>
10608
+
10609
+ ```typescript
10610
+ public readonly actionVersions: WorkflowActionVersions;
10611
+ ```
10612
+
10613
+ - *Type:* <a href="#@macedon-technologies/batman.WorkflowActionVersions">WorkflowActionVersions</a>
10614
+
10615
+ Overrides for the pinned action versions used by `verify-attestation.yml`.
10616
+
10617
+ ---
10618
+
10619
+ ##### `baseBranch`<sup>Optional</sup> <a name="baseBranch" id="@macedon-technologies/batman.SoftwareFactoryFilesOptions.property.baseBranch"></a>
10620
+
10621
+ ```typescript
10622
+ public readonly baseBranch: string;
10623
+ ```
10624
+
10625
+ - *Type:* string
10626
+ - *Default:* 'main'
10627
+
10628
+ The branch the factory opens pull requests against.
10629
+
10630
+ ---
10631
+
10575
10632
  ### StagingDeployWorkflowOptions <a name="StagingDeployWorkflowOptions" id="@macedon-technologies/batman.StagingDeployWorkflowOptions"></a>
10576
10633
 
10577
10634
  #### Initializer <a name="Initializer" id="@macedon-technologies/batman.StagingDeployWorkflowOptions.Initializer"></a>
@@ -10797,5 +10854,55 @@ The `actions/upload-artifact` action reference, used by projen's built-in depend
10797
10854
 
10798
10855
  ---
10799
10856
 
10857
+ ## Classes <a name="Classes" id="Classes"></a>
10858
+
10859
+ ### SoftwareFactoryFiles <a name="SoftwareFactoryFiles" id="@macedon-technologies/batman.SoftwareFactoryFiles"></a>
10860
+
10861
+ Backports exman's swamp "software factory" pattern (macedon-technologies/exman#13) as generated scaffolding: a `@swamp/software-factory` state machine that drives a GitHub issue from intake through an opened PR, enforcing OpenSpec usage, an architecture rule registry, and a pre-PR verification gate whose attestation is re-checked at merge time.
10862
+
10863
+ What swamp itself would generate (extension docs pulled via
10864
+ `swamp extension pull`, `.swamp.yaml`, the vault, and the "swamp managed
10865
+ section" in CLAUDE.md) is deliberately NOT hand-templated here — those are
10866
+ produced by the real `swamp` CLI. `swamp init` is idempotent (a no-op if
10867
+ the repo is already initialized), so it's wired into the project's
10868
+ `install`/`install:ci` tasks and runs automatically on every install,
10869
+ guarded so it's a silent no-op on a machine without `swamp` on PATH (e.g.
10870
+ CI). The extension pulls and vault creation are one-time, not idempotent
10871
+ in a way that's safe to repeat on every install, so they stay in the
10872
+ `swamp:bootstrap` task this construct adds. Only exman's own authored
10873
+ config (the factory's state machine, the architecture-rule engine, the
10874
+ attestation mechanism, the OpenSpec task-completion gate, and the 3 swamp
10875
+ workflows that wire them together) is templated.
10876
+
10877
+ #### Initializers <a name="Initializers" id="@macedon-technologies/batman.SoftwareFactoryFiles.Initializer"></a>
10878
+
10879
+ ```typescript
10880
+ import { SoftwareFactoryFiles } from '@macedon-technologies/batman'
10881
+
10882
+ new SoftwareFactoryFiles(project: NodeProject, options: SoftwareFactoryFilesOptions)
10883
+ ```
10884
+
10885
+ | **Name** | **Type** | **Description** |
10886
+ | --- | --- | --- |
10887
+ | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFiles.Initializer.parameter.project">project</a></code> | <code>projen.javascript.NodeProject</code> | *No description.* |
10888
+ | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFiles.Initializer.parameter.options">options</a></code> | <code><a href="#@macedon-technologies/batman.SoftwareFactoryFilesOptions">SoftwareFactoryFilesOptions</a></code> | *No description.* |
10889
+
10890
+ ---
10891
+
10892
+ ##### `project`<sup>Required</sup> <a name="project" id="@macedon-technologies/batman.SoftwareFactoryFiles.Initializer.parameter.project"></a>
10893
+
10894
+ - *Type:* projen.javascript.NodeProject
10895
+
10896
+ ---
10897
+
10898
+ ##### `options`<sup>Required</sup> <a name="options" id="@macedon-technologies/batman.SoftwareFactoryFiles.Initializer.parameter.options"></a>
10899
+
10900
+ - *Type:* <a href="#@macedon-technologies/batman.SoftwareFactoryFilesOptions">SoftwareFactoryFilesOptions</a>
10901
+
10902
+ ---
10903
+
10904
+
10905
+
10906
+
10800
10907
 
10801
10908
 
package/CLAUDE.md ADDED
@@ -0,0 +1,44 @@
1
+ <!-- BEGIN swamp managed section - DO NOT EDIT -->
2
+ # Project
3
+
4
+ This repository is managed with [swamp](https://github.com/swamp-club/swamp).
5
+
6
+ ## Rules
7
+
8
+ 1. **Search before you build.** When automating AWS, APIs, or any external service: (a) search community extensions with `swamp extension search <query>` — prefer `@swamp/*` official extensions first, (b) search local/installed types with `swamp model type search <query>`, (c) if a community extension exists, install it with `swamp extension pull <package>` instead of building from scratch, (d) extend an existing type if it covers the domain but lacks the method you need, (e) only create a custom extension model in `extensions/models/` as a last resort. Use the `swamp` skill for guidance. The `command/shell` model is ONLY for ad-hoc one-off shell commands, NEVER for wrapping CLI tools or building integrations.
9
+ 2. **Extend, don't be clever.** When a model covers the domain but lacks the method you need, extend it with `export const extension` — don't bypass it with shell scripts, CLI tools, or multi-step hacks. One method, one purpose. Use `swamp model type describe <type> --json` to check available methods.
10
+ 3. **Use the data model.** Once data exists in a model (via `lookup`, `start`, `sync`, etc.), reference it with CEL expressions. Don't re-fetch data that's already available.
11
+ 4. **CEL expressions everywhere.** Wire models together with CEL expressions. Always prefer `data.latest("<name>", "<dataName>").attributes.<field>` over the deprecated `model.<name>.resource.<spec>.<instance>.attributes.<field>` pattern.
12
+ 5. **Verify before destructive operations.** Always `swamp model get <name> --json` and verify resource IDs before running delete/stop/destroy methods.
13
+ 6. **Prefer fan-out methods over loops.** When operating on multiple targets, use a single method that handles all targets internally (factory pattern) rather than looping N separate `swamp model method run` calls against the same model. Multiple parallel calls against the same model contend on the per-model lock, causing timeouts. A single fan-out method acquires the lock once and produces all outputs in one execution. Check `swamp model type describe` for methods that accept filters or produce multiple outputs.
14
+ 7. **Extension npm deps are bundled, not lockfile-tracked.** Swamp's bundler inlines all npm packages (except zod) into extension bundles at bundle time. `deno.lock` and `package.json` do NOT cover extension model dependencies — this is by design. Always pin explicit versions in `npm:` import specifiers (e.g., `npm:lodash-es@4.17.21`).
15
+ 8. **Reports for reusable data pipelines.** When the task involves building a repeatable pipeline to transform, aggregate, or analyze model output (security reports, cost analysis, compliance checks, summaries), create a report extension. Use the `swamp` skill for guidance.
16
+ 9. **"Workflow" means a swamp workflow.** In this repository the word "workflow" (and "create/run/execute/validate/debug workflow", "automate", "orchestrate", "automated/nightly job") refers to a swamp workflow — a declarative YAML DAG of model-method steps authored via `swamp workflow create`. Load and follow the `swamp` skill for these requests. Do NOT interpret these as a request to build an agent task list, spin up worktrees, or schedule a cron/remote agent. Only use those orchestration mechanisms when the user explicitly names one (e.g. "task list", "subagent", "worktree", "cron", "remote agent") or explicitly asks you to do the work yourself step by step rather than author a swamp workflow.
17
+ 10. **Use swamp, don't bypass it.** Always work through swamp commands — don't go around them with raw shell tools. Use `swamp data query` to find data, not `grep`/`find` on `.swamp/` files. Use model methods to interact with resources, not `curl`/`aws`/`gcloud`/`kubectl` when a model type already wraps that API — check with `swamp model type search`. Use `swamp help` for CLI discovery, not guesswork. Composing with swamp output is fine (e.g. piping `--json` through `jq`) — the anti-pattern is bypassing swamp entirely.
18
+ 11. **Inspect reports after failures.** When a model method or workflow run fails, inspect its generated reports before retrying or changing definitions. Reports run even on failure and capture structured diagnostics — error messages, execution status, arguments, and data output pointers. Use `swamp report get @swamp/method-summary --model <model> --json` for method failures or `swamp report get @swamp/workflow-summary --workflow <workflow> --json` for workflow failures. Run `swamp help report get` to confirm current retrieval syntax.
19
+
20
+ ## Skills
21
+
22
+ **IMPORTANT:** Always load swamp skills, even when in plan mode. The skills provide
23
+ essential context for working with this repository.
24
+
25
+ - `swamp` - Swamp CLI — models, workflows, data, vaults, extensions, publishing, repos, reports, issues, and troubleshooting
26
+ - `swamp-getting-started` - Interactive onboarding for new swamp users
27
+
28
+ ## Getting Started
29
+
30
+ **IMPORTANT:** At the start of every conversation, run
31
+ `swamp model search --json`. If no models are returned (empty result), you MUST
32
+ immediately invoke the `swamp-getting-started` skill before doing anything else.
33
+ This walks new users through an interactive onboarding tutorial.
34
+
35
+ If models already exist, start by using the `swamp` skill to work with
36
+ swamp models.
37
+
38
+ ## Commands
39
+
40
+ Use `swamp --help` to see available commands. For a machine-readable JSON
41
+ schema of the CLI (commands, options, arguments) intended for agent
42
+ consumption, run `swamp help [<command>...]` — e.g. `swamp help` returns
43
+ the full tree, and `swamp help model method run` scopes to a subtree.
44
+ <!-- END swamp managed section -->
@@ -0,0 +1,42 @@
1
+ # Architecture rules
2
+
3
+ A data-driven registry of architectural constraints, enforced by
4
+ `pnpm validate:architecture` and run as part of the pre-PR verification
5
+ workflow (`workflows/workflow-pre-pr-verify.yaml`) before the swamp factory
6
+ opens a PR. New rules are new entries in `rules.json`, not new code — the
7
+ engine (`check.ts`) only needs a new case in `evaluateRule()` when a rule
8
+ needs a genuinely new kind of check.
9
+
10
+ ## Rule shape
11
+
12
+ ```json
13
+ {
14
+ "id": "unique-id",
15
+ "description": "What this enforces and why.",
16
+ "enabled": true,
17
+ "severity": "low" | "medium" | "high" | "critical",
18
+ "check": {
19
+ "type": "forbidden-import",
20
+ "from": ["glob patterns selecting files this rule inspects"],
21
+ "forbid": ["glob patterns those files may not import from"]
22
+ }
23
+ }
24
+ ```
25
+
26
+ `enabled: false` rules are skipped (reported as "deferred") rather than
27
+ evaluated — use this for a rule you've named and described but don't yet
28
+ have a concrete convention to check.
29
+
30
+ ## Current rules
31
+
32
+ None yet — `rules.json` ships empty. Add rules as your domain module
33
+ conventions solidify (e.g. an event-sourced/CQRS module layout under
34
+ `src/domains/<name>/{commands,events,aggregates,projections}/`).
35
+
36
+ ## Adding a check type
37
+
38
+ `forbidden-import` is the only implemented `check.type`. If a future rule
39
+ needs something it can't express (e.g. "every aggregate file must have a
40
+ matching events file"), add a new `type` value, a matching interface, and a
41
+ case in `evaluateRule()` in `check.ts` — keep the engine itself generic;
42
+ rule-specific knowledge belongs in `rules.json`.
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Generic architecture-rule engine. Rules live entirely in rules.json — this
3
+ * file only knows how to evaluate the check "types" that rules can declare.
4
+ * Add a new rule by adding a rules.json entry; add a new check type by adding
5
+ * a case in evaluateRule().
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import rulesFile from './rules.json';
10
+
11
+ interface ForbiddenImportCheck {
12
+ type: 'forbidden-import';
13
+ // Glob patterns (relative to repo root) selecting the files this rule inspects.
14
+ from: string[];
15
+ // Glob patterns (relative to repo root) that "from" files may not import from.
16
+ forbid: string[];
17
+ }
18
+
19
+ interface Rule {
20
+ id: string;
21
+ description: string;
22
+ enabled: boolean;
23
+ severity: 'low' | 'medium' | 'high' | 'critical';
24
+ check: ForbiddenImportCheck;
25
+ }
26
+
27
+ interface Violation {
28
+ ruleId: string;
29
+ severity: string;
30
+ message: string;
31
+ }
32
+
33
+ const REPO_ROOT = path.resolve(__dirname, '..');
34
+ const IGNORED_DIRS = new Set(['node_modules', '.git', '.swamp', 'cdk.out', 'lib', 'build', 'dist']);
35
+
36
+ // Minimal glob -> RegExp: supports "*", "**", "**/" and literal path segments.
37
+ // No new dependency for this — the only pattern shapes rules.json ever needs
38
+ // are directory-prefix globs like "src/domains/*/commands/**".
39
+ function globToRegExp(glob: string): RegExp {
40
+ let out = '';
41
+ for (let i = 0; i < glob.length; i++) {
42
+ const c = glob[i];
43
+ if (c === '*' && glob[i + 1] === '*') {
44
+ i++;
45
+ if (glob[i + 1] === '/') {
46
+ out += '(?:.*/)?';
47
+ i++;
48
+ } else {
49
+ out += '.*';
50
+ }
51
+ } else if (c === '*') {
52
+ out += '[^/]*';
53
+ } else if (c === '?') {
54
+ out += '[^/]';
55
+ } else if ('.+^${}()|[]\\'.includes(c)) {
56
+ out += `\\${c}`;
57
+ } else {
58
+ out += c;
59
+ }
60
+ }
61
+ return new RegExp(`^${out}$`);
62
+ }
63
+
64
+ function matchesAny(relFile: string, globs: string[]): boolean {
65
+ return globs.some((glob) => globToRegExp(glob).test(relFile));
66
+ }
67
+
68
+ function toRepoRelative(absPath: string): string {
69
+ return path.relative(REPO_ROOT, absPath).split(path.sep).join('/');
70
+ }
71
+
72
+ function walk(dir: string, acc: string[] = []): string[] {
73
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
74
+ if (IGNORED_DIRS.has(entry.name)) continue;
75
+ const full = path.join(dir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ walk(full, acc);
78
+ } else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))) {
79
+ acc.push(full);
80
+ }
81
+ }
82
+ return acc;
83
+ }
84
+
85
+ const RELATIVE_IMPORT_RE = /(?:import|export)\s+(?:[^'";]*?\bfrom\s+)?['"](\.[^'"]+)['"]/g;
86
+
87
+ function findRelativeImports(source: string): string[] {
88
+ const specs: string[] = [];
89
+ RELATIVE_IMPORT_RE.lastIndex = 0;
90
+ let match: RegExpExecArray | null;
91
+ while ((match = RELATIVE_IMPORT_RE.exec(source))) {
92
+ specs.push(match[1]);
93
+ }
94
+ return specs;
95
+ }
96
+
97
+ // An import spec resolves to a module, not a specific file extension, so a
98
+ // forbid glob is checked against the bare resolved path plus its .ts/index.ts
99
+ // variants.
100
+ function resolvedCandidates(fromFile: string, spec: string): string[] {
101
+ const resolved = toRepoRelative(path.normalize(path.join(path.dirname(fromFile), spec)));
102
+ return [resolved, `${resolved}.ts`, `${resolved}/index.ts`];
103
+ }
104
+
105
+ function evaluateForbiddenImport(rule: Rule): Violation[] {
106
+ const { from, forbid } = rule.check;
107
+ if (from.length === 0 || forbid.length === 0) return [];
108
+
109
+ const violations: Violation[] = [];
110
+ for (const file of walk(REPO_ROOT)) {
111
+ const relFile = toRepoRelative(file);
112
+ if (!matchesAny(relFile, from)) continue;
113
+
114
+ const source = fs.readFileSync(file, 'utf8');
115
+ for (const spec of findRelativeImports(source)) {
116
+ const candidates = resolvedCandidates(file, spec);
117
+ if (candidates.some((candidate) => matchesAny(candidate, forbid))) {
118
+ violations.push({
119
+ ruleId: rule.id,
120
+ severity: rule.severity,
121
+ message: `${relFile} imports "${spec}", which resolves under a path forbidden by rule "${rule.id}"`,
122
+ });
123
+ }
124
+ }
125
+ }
126
+ return violations;
127
+ }
128
+
129
+ function evaluateRule(rule: Rule): Violation[] {
130
+ switch (rule.check.type) {
131
+ case 'forbidden-import':
132
+ return evaluateForbiddenImport(rule);
133
+ default:
134
+ throw new Error(`architecture-rules: unknown check.type "${(rule.check as { type: string }).type}" on rule "${rule.id}"`);
135
+ }
136
+ }
137
+
138
+ function main(): number {
139
+ const rules = (rulesFile as { rules: Rule[] }).rules;
140
+ let evaluated = 0;
141
+ let deferred = 0;
142
+ const violations: Violation[] = [];
143
+
144
+ for (const rule of rules) {
145
+ if (!rule.enabled) {
146
+ deferred++;
147
+ console.log(`[deferred] ${rule.id}: ${rule.description}`);
148
+ continue;
149
+ }
150
+ evaluated++;
151
+ violations.push(...evaluateRule(rule));
152
+ }
153
+
154
+ console.log(`\narchitecture-rules: ${evaluated} rule(s) evaluated, ${deferred} deferred, ${violations.length} violation(s)`);
155
+ for (const violation of violations) {
156
+ console.error(` [${violation.severity}] ${violation.ruleId}: ${violation.message}`);
157
+ }
158
+
159
+ return violations.length > 0 ? 1 : 0;
160
+ }
161
+
162
+ process.exit(main());
@@ -0,0 +1,3 @@
1
+ {
2
+ "rules": []
3
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "rootDir": "."
6
+ },
7
+ "include": [
8
+ "*.ts",
9
+ "*.json"
10
+ ],
11
+ "exclude": [
12
+ "node_modules"
13
+ ]
14
+ }