mikoshi-construct 0.1.0

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 (96) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/dist/cli.js +1492 -0
  4. package/package.json +73 -0
  5. package/templates/ai/claude/CLAUDE.md.eta +18 -0
  6. package/templates/ai/claude/CLAUDE.md.existing.eta +15 -0
  7. package/templates/ai/claude/_claude/agents/architect.md +38 -0
  8. package/templates/ai/claude/_claude/agents/harness.md +40 -0
  9. package/templates/ai/claude/_claude/agents/implementer.md +40 -0
  10. package/templates/ai/claude/_claude/commands/plan.md +24 -0
  11. package/templates/ai/claude/_claude/skills/implement/SKILL.md +42 -0
  12. package/templates/ai/claude/scripts/construct/implement.workflow.mjs +185 -0
  13. package/templates/ai/cursor/_cursor/rules/construct.mdc +19 -0
  14. package/templates/ai/review/_github/workflows/claude-review.yml.eta +51 -0
  15. package/templates/ai/shared/AGENTS.md.eta +123 -0
  16. package/templates/ai/shared/AGENTS.md.existing.eta +50 -0
  17. package/templates/ai/shared/_claude/commands/construct-discover.md +75 -0
  18. package/templates/ai/shared/_claude/rules/conventions.md +63 -0
  19. package/templates/ai/shared/_claude/rules/secrets.md +8 -0
  20. package/templates/ai/shared/_claude/rules/tests.md +6 -0
  21. package/templates/base/_editorconfig +12 -0
  22. package/templates/base/_github/workflows/security.yml +41 -0
  23. package/templates/base/_gitignore +11 -0
  24. package/templates/base/_gitleaks.toml +2 -0
  25. package/templates/base/_nvmrc.eta +1 -0
  26. package/templates/base/_vscode/settings.json +22 -0
  27. package/templates/base/architecture/checklists.md +42 -0
  28. package/templates/base/architecture/principles.md +113 -0
  29. package/templates/base/architecture/security-invariants.md +17 -0
  30. package/templates/harness/_github/workflows/ci.yml.eta +31 -0
  31. package/templates/harness/eslint.config.mjs +13 -0
  32. package/templates/harness/package.json.eta +29 -0
  33. package/templates/harness/pnpm-workspace.yaml +11 -0
  34. package/templates/harness/scripts/composition/check.ts +19 -0
  35. package/templates/harness/scripts/composition/files.ts +34 -0
  36. package/templates/harness/scripts/composition/model.ts +137 -0
  37. package/templates/harness/scripts/composition/render.ts +72 -0
  38. package/templates/harness/scripts/composition/sync-docs.ts +11 -0
  39. package/templates/harness/scripts/tests/composition/files.test.ts +16 -0
  40. package/templates/harness/scripts/tests/composition/model.test.ts +41 -0
  41. package/templates/harness/scripts/tests/composition/render.test.ts +54 -0
  42. package/templates/harness/tsconfig.base.json +14 -0
  43. package/templates/harness/tsconfig.json +5 -0
  44. package/templates/harness/vitest.config.ts +8 -0
  45. package/templates/presets/monorepo/baseline/eslint.config.mjs.eta +100 -0
  46. package/templates/presets/monorepo/baseline/package.json.eta +18 -0
  47. package/templates/presets/monorepo/baseline/packages/shared/package.json.eta +22 -0
  48. package/templates/presets/monorepo/baseline/packages/shared/src/api/openapi.ts +81 -0
  49. package/templates/presets/monorepo/baseline/packages/shared/src/index.ts +1 -0
  50. package/templates/presets/monorepo/baseline/packages/shared/tsconfig.build.json +10 -0
  51. package/templates/presets/monorepo/baseline/packages/shared/tsconfig.json +4 -0
  52. package/templates/presets/monorepo/baseline/pnpm-workspace.yaml.eta +31 -0
  53. package/templates/presets/monorepo/baseline/tsconfig.json +12 -0
  54. package/templates/presets/monorepo/baseline/vitest.config.ts +8 -0
  55. package/templates/presets/monorepo/sample/apps/api/package.json.eta +23 -0
  56. package/templates/presets/monorepo/sample/apps/api/tsconfig.build.json +10 -0
  57. package/templates/presets/monorepo/sample/apps/api/tsconfig.json +4 -0
  58. package/templates/presets/monorepo/sample/scripts/tests/lint/syntax-policy.test.ts.eta +36 -0
  59. package/templates/presets/node-backend/baseline/eslint.config.mjs +59 -0
  60. package/templates/presets/node-backend/baseline/package.json.eta +20 -0
  61. package/templates/presets/node-backend/baseline/src/contracts/openapi.ts +81 -0
  62. package/templates/presets/node-backend/baseline/tsconfig.build.json +10 -0
  63. package/templates/presets/node-backend/sample/scripts/tests/lint/syntax-policy.test.ts +31 -0
  64. package/templates/presets/node-frontend/baseline/_claude/rules/css.md +158 -0
  65. package/templates/presets/node-frontend/baseline/eslint.config.mjs +44 -0
  66. package/templates/presets/node-frontend/baseline/package.json.eta +15 -0
  67. package/templates/presets/node-frontend/baseline/tsconfig.json +11 -0
  68. package/templates/presets/node-frontend/baseline/vitest.config.ts +10 -0
  69. package/templates/presets/node-frontend/sample/architecture/app.md +37 -0
  70. package/templates/presets/node-frontend/sample/architecture/composition/app.yaml +41 -0
  71. package/templates/presets/node-frontend/sample/index.html.eta +12 -0
  72. package/templates/presets/node-frontend/sample/src/app.ts +32 -0
  73. package/templates/presets/node-frontend/sample/src/main.ts +9 -0
  74. package/templates/presets/node-frontend/sample/src/styles/app.css +43 -0
  75. package/templates/presets/node-frontend/sample/src/styles/tokens.css +16 -0
  76. package/templates/presets/node-frontend/sample/tests/app.test.ts +21 -0
  77. package/templates/stacks/express-api/app/src/app.ts +27 -0
  78. package/templates/stacks/express-api/app/src/config.ts.eta +30 -0
  79. package/templates/stacks/express-api/app/src/contracts/types.ts.eta +1 -0
  80. package/templates/stacks/express-api/app/src/health/health.controller.ts +12 -0
  81. package/templates/stacks/express-api/app/src/health/health.service.ts +23 -0
  82. package/templates/stacks/express-api/app/src/http/api-contract.middleware.ts +13 -0
  83. package/templates/stacks/express-api/app/src/http/app-error.ts +14 -0
  84. package/templates/stacks/express-api/app/src/http/async-handler.middleware.ts +7 -0
  85. package/templates/stacks/express-api/app/src/http/error-handler.middleware.ts +50 -0
  86. package/templates/stacks/express-api/app/src/server.ts +9 -0
  87. package/templates/stacks/express-api/app/tests/health.test.ts +28 -0
  88. package/templates/stacks/express-api/app/tests/http/error-handler.middleware.test.ts +48 -0
  89. package/templates/stacks/express-api/repo/architecture/composition/http.yaml.eta +45 -0
  90. package/templates/stacks/express-api/repo/architecture/http.md +39 -0
  91. package/templates/stacks/http-contract/_github/workflows/api-contract.yml +36 -0
  92. package/templates/stacks/http-contract/contracts/api/openapi.yaml.eta +62 -0
  93. package/templates/stacks/http-contract/package.json.eta +13 -0
  94. package/templates/stacks/http-contract/redocly.yaml.eta +8 -0
  95. package/templates/stacks/http-contract/scripts/contracts/types.mjs.eta +20 -0
  96. package/templates/stacks/http-contract/scripts/tests/contracts/security.test.ts.eta +86 -0
@@ -0,0 +1,123 @@
1
+ # {{projectName}}
2
+
3
+ Context for coding agents and automated reviewers working on {{projectName}}. This repository runs on a
4
+ construct materialized by `mikoshi-construct` v{{constructVersion}}: architecture policy in
5
+ [architecture/](architecture/), a harness that proves every change, and the rules that apply to all
6
+ code in [architecture/principles.md](architecture/principles.md). What follows is what is specific to
7
+ this repository. Sections marked "not discovered yet" are filled by construct discovery
8
+ (`/construct-discover` in Claude Code, "run construct discovery" in Cursor), then kept current by
9
+ whoever changes the thing they describe.
10
+
11
+ ## What this product does
12
+
13
+ <!-- construct:discover:product -->
14
+ _Not discovered yet — run `/construct-discover`._
15
+ <!-- /construct:discover:product -->
16
+
17
+ ## Module map
18
+
19
+ <!-- construct:discover:module-map -->
20
+ _Not discovered yet — run `/construct-discover`._
21
+ <!-- /construct:discover:module-map -->
22
+
23
+ ## Commands
24
+
25
+ ```bash
26
+ pnpm dev # run the service locally
27
+ pnpm run quality # the harness: contract + composition checks, lint, typecheck, tests — the CI gate and the agent gate
28
+ {{#if contracts}}
29
+ pnpm contracts:types # regenerate {{contractTypesOutput}} after editing the API contract
30
+ {{/if}}
31
+ pnpm composition:render # regenerate architecture diagrams after editing a composition model
32
+ pnpm lint:fix # ESLint with --fix; ESLint is the only formatter
33
+ ```
34
+
35
+ Use `pnpm run quality` / `pnpm run ci` — bare `pnpm ci` is a pnpm install builtin, not this script.
36
+
37
+ <!-- construct:discover:commands -->
38
+ _Not discovered yet — run `/construct-discover`._
39
+ <!-- /construct:discover:commands -->
40
+
41
+ ## Harness
42
+
43
+ `{{harnessCommand}}` is the gate for any change. A change is not done until it passes; a pass is
44
+ reported by the harness, never by the implementer. Security invariants and the check that enforces
45
+ each one: [architecture/security-invariants.md](architecture/security-invariants.md).
46
+
47
+ {{#if contracts}}
48
+ ## Contract
49
+
50
+ [{{contractPath}}]({{contractPath}}) is the HTTP contract. The service validates requests against it,
51
+ consumers read the types generated into [{{contractTypesOutput}}]({{contractTypesOutput}}), and
52
+ `oasdiff` flags breaking changes on pull requests. An API change edits the contract first, runs
53
+ `pnpm contracts:types`, then implements.
54
+
55
+ {{/if}}
56
+ ## Architecture
57
+
58
+ The principles in [architecture/principles.md](architecture/principles.md) apply; this is how they map
59
+ here.
60
+
61
+ - *Composition roots.*
62
+ <!-- construct:discover:composition-roots -->
63
+ _Not discovered yet — run `/construct-discover`._
64
+ <!-- /construct:discover:composition-roots -->
65
+ - *Dependency policy.*
66
+ <!-- construct:discover:dependency-policy -->
67
+ _Not discovered yet — run `/construct-discover`._
68
+ <!-- /construct:discover:dependency-policy -->
69
+ - *Composition models* and their rendered diagrams: [{{compositionDir}}/]({{compositionDir}}/).
70
+
71
+ ## Reasoning budget
72
+
73
+ High-effort areas — a task that touches one of these is classified `high` and designed before it is
74
+ implemented (see `/implement`):
75
+
76
+ <!-- construct:discover:high-effort-areas -->
77
+ _Not discovered yet — run `/construct-discover`._
78
+ <!-- /construct:discover:high-effort-areas -->
79
+
80
+ {{#if contracts}}
81
+ Always high, whatever discovery finds: [{{contractPath}}]({{contractPath}}),
82
+ `{{compositionDir}}/`, `eslint.config.mjs`, and every row of
83
+ [architecture/security-invariants.md](architecture/security-invariants.md).
84
+ {{/if}}
85
+ {{#unless contracts}}
86
+ Always high, whatever discovery finds: `{{compositionDir}}/`, `eslint.config.mjs`, and every
87
+ row of [architecture/security-invariants.md](architecture/security-invariants.md).
88
+ {{/unless}}
89
+
90
+ ## Conventions the harness does not enforce
91
+
92
+ No comments in source (functional pragmas such as `eslint-disable*` and `@ts-expect-error` are
93
+ compiler input and are never removed). `async`/`await` over promise chains where the enclosing context
94
+ can be async. Tests live in `tests/**` as `*.test.ts`, never beside the source, and every new or
95
+ changed logic module ships its test in the same change. ESLint is the only formatter.
96
+
97
+ ## Real defects vs accepted variance
98
+
99
+ Treat as real defects:
100
+
101
+ - A new or changed logic module with no test file.
102
+ - A response shape that drifts from the API contract, or a breaking change to a `/v1` endpoint that
103
+ was not identified deliberately.
104
+ - A secret, API key or connection string written into any file. Config references the environment.
105
+ - Any row of [architecture/security-invariants.md](architecture/security-invariants.md) that a change
106
+ weakens.
107
+
108
+ <!-- construct:discover:defects-vs-variance -->
109
+ _Not discovered yet — run `/construct-discover`._
110
+ <!-- /construct:discover:defects-vs-variance -->
111
+
112
+ Treat as accepted variance and do not report: formatting, quoting and import order (ESLint owns
113
+ them); the `.js` suffix on relative TypeScript imports (NodeNext ESM requires it); the absence of
114
+ comments or JSDoc.
115
+
116
+ ## Open questions
117
+
118
+ These look like conventions but the codebase is not consistent about them. Confirm before treating
119
+ them as rules.
120
+
121
+ <!-- construct:discover:open-questions -->
122
+ _Not discovered yet — run `/construct-discover`._
123
+ <!-- /construct:discover:open-questions -->
@@ -0,0 +1,50 @@
1
+ ## Construct
2
+
3
+ This repository runs on a construct materialized by `mikoshi-construct` v{{constructVersion}}:
4
+ architecture policy in [architecture/](architecture/), a harness (`{{harnessCommand}}`) that proves
5
+ every change, and the rules that apply to all code in
6
+ [architecture/principles.md](architecture/principles.md). The blocks below are filled by construct
7
+ discovery (`/construct-discover` in Claude Code, "run construct discovery" in Cursor). Where this
8
+ document, or another one in the repository, already covers a block, discovery records a pointer to it
9
+ rather than a copy — one source of truth per concern.
10
+
11
+ Product and the flow where a defect costs the most:
12
+ <!-- construct:discover:product -->
13
+ _Not discovered yet — run `/construct-discover`._
14
+ <!-- /construct:discover:product -->
15
+
16
+ Module map:
17
+ <!-- construct:discover:module-map -->
18
+ _Not discovered yet — run `/construct-discover`._
19
+ <!-- /construct:discover:module-map -->
20
+
21
+ Commands beyond the harness (`{{harnessCommand}}`):
22
+ <!-- construct:discover:commands -->
23
+ _Not discovered yet — run `/construct-discover`._
24
+ <!-- /construct:discover:commands -->
25
+
26
+ Composition roots:
27
+ <!-- construct:discover:composition-roots -->
28
+ _Not discovered yet — run `/construct-discover`._
29
+ <!-- /construct:discover:composition-roots -->
30
+
31
+ Dependency policy, and where lint enforces it:
32
+ <!-- construct:discover:dependency-policy -->
33
+ _Not discovered yet — run `/construct-discover`._
34
+ <!-- /construct:discover:dependency-policy -->
35
+
36
+ High-effort areas — a task touching one is classified `high` and designed before it is implemented
37
+ (always high: the API contract, `{{compositionDir}}/`, the lint policy, every security invariant):
38
+ <!-- construct:discover:high-effort-areas -->
39
+ _Not discovered yet — run `/construct-discover`._
40
+ <!-- /construct:discover:high-effort-areas -->
41
+
42
+ Real defects vs accepted variance, beyond what this document already lists:
43
+ <!-- construct:discover:defects-vs-variance -->
44
+ _Not discovered yet — run `/construct-discover`._
45
+ <!-- /construct:discover:defects-vs-variance -->
46
+
47
+ Open questions — things that look like conventions but are not applied consistently:
48
+ <!-- construct:discover:open-questions -->
49
+ _Not discovered yet — run `/construct-discover`._
50
+ <!-- /construct:discover:open-questions -->
@@ -0,0 +1,75 @@
1
+ ---
2
+ description: Discover this repository and fill the construct's discovery markers — the step that turns a materialized baseline into a project-specific workflow.
3
+ argument-hint: [area to (re)discover, or empty for everything]
4
+ ---
5
+
6
+ You are running construct discovery. The CLI detected facts (`construct.json`); your job is to
7
+ interpret the system and record what an agent must know to work here safely. Write nothing you did
8
+ not verify by reading code; where the codebase is inconsistent, record an open question instead of
9
+ inventing a rule.
10
+
11
+ Scope: `$ARGUMENTS` (empty means every marker). The markers, and the file each one lives in, are
12
+ listed under `discovery` in `construct.json` (the text markers in `AGENTS.md`, the composition models
13
+ in the directory `discovery.composition` names). Every marker is a block between
14
+ `<!-- construct:discover:<name> -->` and `<!-- /construct:discover:<name> -->`; replace the placeholder
15
+ line inside the block and nothing outside it. `construct doctor` reports any marker still holding the
16
+ placeholder.
17
+
18
+ Two rules for a repository that already documents itself:
19
+
20
+ - **One source of truth.** If a block's content already exists elsewhere — a module map in
21
+ `README.md`, review standards in `best_practices.md`, invariants a PR reviewer reads — the marker
22
+ body is a pointer to that place (path and heading), not a copy. Do not duplicate, and do not merge
23
+ the existing document into the construct's files unless the user asks.
24
+ - **Move nothing.** Existing composition models, docs and scripts stay where they are; `construct.json`
25
+ already records where the models live. If a construct file links to a path that differs from the
26
+ real one, say so in the report instead of renaming directories.
27
+
28
+ Work in this order:
29
+
30
+ 1. **Wire the harness.** In a repository that existed before the construct, `init` kept the
31
+ existing `eslint.config.mjs`, `tsconfig.json`, `vitest.config.ts` and `quality` script. Before
32
+ anything else make them cover what the construct added: ESLint ignores
33
+ `scripts/construct/*.workflow.mjs` (top-level `return`), TypeScript includes `scripts/**/*.ts`,
34
+ Vitest includes `scripts/tests/**/*.test.ts`, and the harness command runs `composition:check`
35
+ (and `contracts:check` when a contract exists). Then run the harness; it must be green before
36
+ discovery starts. Skip this step when the construct created those files itself.
37
+ 2. **Inventory.** Read `construct.json`, `package.json`, the directory tree two levels deep, the entry
38
+ points (servers, app factories, `main.ts`, CLI scripts, workers), the API contract if there is
39
+ one, and every `*.config.ts` / `config.ts`. Note the package manager, runtime, database and clients, CI, deployment
40
+ and existing conventions. Do not write yet.
41
+ 3. **`product`** (AGENTS.md): what the system does, in one paragraph, and the one flow where a
42
+ defect costs the most (money, identity, data). If the repository is empty apart from the baseline,
43
+ say so in one line.
44
+ 4. **`module-map`** (AGENTS.md): a table `Path | Purpose` of the top-level modules that exist. Only
45
+ what exists.
46
+ 5. **`commands`** (AGENTS.md): dev, build, test and operational scripts from `package.json` that the
47
+ baseline block above does not already list, one line each. Remove the placeholder if there are none.
48
+ 6. **`composition-roots`** (AGENTS.md): the files where services are constructed and routes, jobs or
49
+ handlers are mounted, and the rule for adding a new one.
50
+ 7. **`dependency-policy`** (AGENTS.md, and `eslint.config.mjs`): which modules or packages may import
51
+ which. Describe it in one paragraph and make sure `eslint.config.mjs` enforces it — extend the
52
+ policy blocks there (`ALLOWED_WORKSPACE_IMPORTS`, the `restrictSyntax` rules); a declared policy
53
+ that lint does not enforce is not a policy.
54
+ 8. **`high-effort-areas`** (AGENTS.md): the paths where a wrong low-effort guess is expensive —
55
+ attribution, authentication, money, schema, anything a shipped client depends on. This list is what
56
+ `/implement` uses to classify a task as `high`.
57
+ 9. **`composition`** (`<discovery.composition>/*.yaml` from `construct.json`): one model per real flow the code has today
58
+ (the HTTP app, a worker, a sync, a CLI, the browser bootstrap) — small, one per flow, every `path`
59
+ must exist. A baseline model, when the construct shipped one, is updated, not duplicated; a
60
+ repository that had code before the construct starts with no model and needs at least one for its
61
+ main entry point. Add a `doc:` markdown with the `<!-- composition:<id> -->` block, run
62
+ `pnpm composition:render`, and confirm `pnpm composition:check` passes.
63
+ 10. **`security-invariants`** (`architecture/security-invariants.md`): rows in the form
64
+ `Invariant | Enforced by` for the system-specific invariants — ownership checks, role middleware,
65
+ integer money, closed DTOs. Name the lint rule, test or scanner that enforces each; write `review`
66
+ only when nothing mechanical exists yet, and prefer adding the check to writing the word.
67
+ 11. **`defects-vs-variance`** and **`open-questions`** (AGENTS.md): what a reviewer must flag here
68
+ beyond the baseline list, and what looks like a convention but is not consistently applied.
69
+ 12. **Prove it.** Run the harness command from `construct.json`. Fix anything discovery broke (a
70
+ stale rendered diagram, a lint rule with no matching file). Then run `construct doctor`, or
71
+ `npx mikoshi-construct doctor` when the CLI is not installed; it names every marker that still
72
+ holds the placeholder.
73
+
74
+ Report: which markers you filled, which you left as open questions and why, and the harness result.
75
+ Do not commit.
@@ -0,0 +1,63 @@
1
+ # Code conventions
2
+
3
+ Repository-wide conventions for new code and the code you touch. Architecture, security and the reasoning
4
+ budget are in [architecture/principles.md](../../architecture/principles.md).
5
+
6
+ ## Comments
7
+
8
+ - **Write no comments.** Not per-line, not "why" comments, not file-header blocks, not JSDoc/TSDoc
9
+ prose — including on exported functions and on shared types other packages consume. The code is
10
+ read directly; it should explain itself.
11
+ - A comment that feels necessary is a signal to make the code self-describing: extract a named
12
+ function, introduce a named constant, rename a variable, or push the fact into a test name where
13
+ it is executable. Move the knowledge into an identifier — don't delete it along with the comment.
14
+ - **Pragmas are not comments and are never stripped**: `eslint-disable*`, `@ts-expect-error`,
15
+ `@ts-ignore`, `stylelint-disable*`, and triple-slash `/// <reference … />` directives, which are
16
+ compiler input. Where a lint config sets `reportUnusedDisableDirectives`, a refactor that removes
17
+ the need for a suppression must delete its directive in the same commit.
18
+
19
+ ## Async
20
+
21
+ - Prefer `async`/`await` to `.then()` / `.catch()` / `.finally()` chains **where the enclosing
22
+ context can be async**.
23
+ - That qualifier is load-bearing. Never run a blanket codemod: check the enclosing context first.
24
+ Chains legitimately survive in Vue `onMounted`, DOM and extension event listeners,
25
+ `MutationObserver` callbacks, and `init(): void` methods; and as behaviour, not style, in a
26
+ `.catch()` supplying a default inside a `??` chain, a `.catch()` on one member of `Promise.all`
27
+ so one failure doesn't sink the batch, a two-arg `.then(onOk, onErr)` settle-to-Result adapter,
28
+ and `new Promise(() => {})` sentinels that never resolve during navigation teardown.
29
+ - When a call must be fire-and-forget from a synchronous context, write
30
+ `void (async () => { … })()` with `try`/`catch` inside, not a `.catch()` tail.
31
+
32
+ ## Design
33
+
34
+ - When something owns state or varies in behaviour — services, repositories, gateways, cached-resource
35
+ wrappers, anything with injected dependencies — and the abstraction checklist in [architecture/checklists.md](../../architecture/checklists.md) passes, use a
36
+ class and the named pattern that fits: Template Method, Strategy, Repository, **Singleton** (one
37
+ owner per concept: a single client, config or cache instance, not module-level mutable state),
38
+ **Factory / Abstract Factory** (branching construction, so call sites stay free of `switch`).
39
+ - A pattern earns its place by removing duplication or a conditional, never as decoration. Prefer
40
+ composition and constructor injection to inheritance chains and to singletons reached by import.
41
+ - Pure transformations stay plain functions. A class of only static methods is a namespace with
42
+ ceremony — no polymorphism, harder to tree-shake, more awkward to test. Prefer classes that *call*
43
+ pure functions.
44
+ - DRY / SOLID / minimum surface: reuse before writing; one owner per concept, so state and derived
45
+ data are not recomputed per consumer; prefer deleting to adding. Dead exports, incomplete barrel
46
+ files, and pass-through wrappers that add nothing get removed, not migrated.
47
+ - A class that has grown several unrelated responsibilities is split into collaborators in their own
48
+ files, not sectioned off inside one file.
49
+
50
+ ## Lint and formatting
51
+
52
+ - If a repo has an ESLint config, it is the single source of truth for style. Don't add Prettier, don't
53
+ turn on `formatOnSave`, and don't hand-format code to taste.
54
+ - Fix style by running the repo's lint script with `--fix`, never by reformatting manually.
55
+ - Match the surrounding code's existing idiom rather than imposing a different one. This does **not**
56
+ extend to comments — existing comments in a file are never a reason to add more; see Comments above.
57
+
58
+ ## pnpm
59
+
60
+ - In a workspace with a `catalog:` block in `pnpm-workspace.yaml`, package manifests reference
61
+ `"catalog:"` instead of hardcoding a version.
62
+ - `pnpm ci` is pnpm's own install builtin. To run a script named `ci`, always write `pnpm run ci`
63
+ (same care for any script whose name collides with a pnpm builtin).
@@ -0,0 +1,8 @@
1
+ # Secrets and configuration
2
+
3
+ - Never write an API key, token or connection secret into a config file — including gitignored or
4
+ otherwise local-only files. Local config still gets backed up, synced and read by other tools.
5
+ - Config files reference secrets through placeholders that resolve at runtime from the process
6
+ environment (`${VAR}`, `${VAR:-default}`); the real values live only in the shell environment.
7
+ - When a plan depends on a specific config syntax — variable expansion, default fallbacks, a particular
8
+ frontmatter key — verify against the tool's official documentation before building on it.
@@ -0,0 +1,6 @@
1
+ # Tests
2
+
3
+ - Tests never sit next to source. `src/` stays product-only; unit tests live in `<app|package>/tests/**`
4
+ mirroring the `src/` layout, named `*.test.ts` (Vitest).
5
+ - End-to-end and API integration specs live in a top-level `e2e/` directory as `*.spec.ts` (Playwright).
6
+ - Every new or changed logic module ships with its matching test file in the same PR — not a follow-up.
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = space
5
+ indent_size = 2
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,41 @@
1
+ name: Security
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: '0 6 * * 1'
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ secrets:
15
+ name: Secret scan
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Scan the history for committed secrets
23
+ run: |
24
+ docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:latest \
25
+ detect --source /repo --no-banner --redact --exit-code 1
26
+
27
+ dependencies:
28
+ name: Dependency audit
29
+ runs-on: ubuntu-latest
30
+ continue-on-error: true
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+
34
+ - uses: pnpm/action-setup@v6
35
+
36
+ - uses: actions/setup-node@v7
37
+ with:
38
+ node-version-file: .nvmrc
39
+
40
+ - name: Report vulnerable dependencies of high severity or above
41
+ run: pnpm audit --audit-level=high
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ *.log
5
+ *.tsbuildinfo
6
+ .eslintcache
7
+ .DS_Store
8
+ .env
9
+ .env.*
10
+ !.env.example
11
+ .construct/
@@ -0,0 +1,2 @@
1
+ [extend]
2
+ useDefault = true
@@ -0,0 +1 @@
1
+ {{nodeMajor}}
@@ -0,0 +1,22 @@
1
+ {
2
+ "prettier.enable": false,
3
+ "editor.formatOnSave": false,
4
+ "eslint.useFlatConfig": true,
5
+ "editor.codeActionsOnSave": {
6
+ "source.fixAll.eslint": "explicit",
7
+ "source.organizeImports": "never"
8
+ },
9
+ "eslint.rules.customizations": [
10
+ { "rule": "style/*", "severity": "off", "fixable": true },
11
+ { "rule": "format/*", "severity": "off", "fixable": true },
12
+ { "rule": "*-indent", "severity": "off", "fixable": true },
13
+ { "rule": "*-spacing", "severity": "off", "fixable": true },
14
+ { "rule": "*-spaces", "severity": "off", "fixable": true },
15
+ { "rule": "*-order", "severity": "off", "fixable": true },
16
+ { "rule": "*-dangle", "severity": "off", "fixable": true },
17
+ { "rule": "*-newline", "severity": "off", "fixable": true },
18
+ { "rule": "*quotes", "severity": "off", "fixable": true },
19
+ { "rule": "*semi", "severity": "off", "fixable": true }
20
+ ],
21
+ "eslint.validate": ["javascript", "typescript", "json", "jsonc", "yaml", "markdown", "css"]
22
+ }
@@ -0,0 +1,42 @@
1
+ # Checklists
2
+
3
+ Answer these before the change, not after. They apply the principles in
4
+ [principles.md](principles.md); the harness proves the result.
5
+
6
+ Before introducing a new abstraction:
7
+
8
+ 1. What concrete problem does it solve?
9
+ 2. Is the complexity required by the domain, an NFR, or an external contract?
10
+ 3. Can the same requirement be satisfied with fewer components?
11
+ 4. Does the abstraction reduce coupling, or merely move it?
12
+
13
+ When changing execution flow:
14
+
15
+ 1. Update the composition root and, where the repo keeps one, the composition model.
16
+ 2. Make sequence, parallelism, routing and fan-out explicit there.
17
+ 3. Do not hide orchestration inside unrelated domain objects.
18
+ 4. Regenerate rendered diagrams from the model; never edit a rendered diagram by hand.
19
+
20
+ Before a substantial architectural change:
21
+
22
+ 1. Identify the affected contracts, domain boundaries, dependencies and composition.
23
+ 2. Check whether an existing artifact already describes the area; update that source of truth
24
+ before touching dependent implementation when a contract or composition changes.
25
+ 3. Implement.
26
+ 4. Validate that the implementation conforms to the contract and the dependency policy.
27
+ 5. Regenerate generated docs and diagrams from their sources.
28
+ 6. Delete obsolete abstractions and documentation instead of stacking compatibility layers.
29
+ Never create a second representation of a decision when an existing source of truth can be
30
+ updated.
31
+
32
+ When adding a dependency:
33
+
34
+ 1. Check the repo's Dependency Policy (or the default in principles.md).
35
+ 2. Verify the dependency direction.
36
+ 3. Prefer an existing capability over introducing another dependency.
37
+
38
+ When making a local change:
39
+
40
+ 1. Identify which boundary should contain the change.
41
+ 2. Do not leak implementation details across that boundary.
42
+ 3. If unrelated components must change, reconsider the decomposition.
@@ -0,0 +1,113 @@
1
+ # Architecture principles
2
+
3
+ These principles are the construct's core. They are stack-agnostic and apply to every change in this
4
+ repository, by humans and by AI agents alike. Repo-specific facts (paths, scripts, boundaries) live in
5
+ [CLAUDE.md](../CLAUDE.md) and [AGENTS.md](../AGENTS.md); where the two genuinely conflict, the repo wins.
6
+
7
+ **CLI detects facts. Agent interprets the system.** The construct baseline was materialized by
8
+ `mikoshi-construct`; everything that requires understanding this codebase was, or will be, written by
9
+ the agent during discovery (`/construct-discover`) and refined as the project evolves.
10
+
11
+ ## Before writing code
12
+
13
+ - Read the surrounding structure first — design tokens, utility and layout classes, existing services
14
+ and helpers, the neighbouring files' idiom — and reuse what is already there before writing anything
15
+ new. Most tasks are a call to something that exists, not a new abstraction.
16
+ - The smallest correct diff wins. Extend an existing abstraction rather than adding a parallel one;
17
+ prefer deleting to adding.
18
+ - Split heavy logic into small single-purpose files rather than letting a file grow past the point
19
+ where its responsibility can be named in one phrase. One concept, one owner, one home.
20
+ - **These rules govern new code and the code you touch.** Match a file's surrounding idiom; never
21
+ mass-convert its units, selectors or query style as a side effect of an unrelated change.
22
+
23
+ ## Architecture
24
+
25
+ «Localize complexity, make it explicit, and don't introduce complexity that the problem does not
26
+ require.» The goal is not to eliminate complexity but to keep the necessary complexity explicit,
27
+ isolated and manageable — and to treat accidental complexity as a defect.
28
+
29
+ - **Decompose by responsibility and domain boundaries.** Split a system into meaningful components,
30
+ never by mechanically adding layers, services or abstractions.
31
+ - **Isolate change and implementation details.** Components talk through explicit contracts and
32
+ stable interfaces. A change inside one component must not require understanding or editing
33
+ unrelated parts.
34
+ - **Keep the architecture simple.** No abstraction, pattern, service or dependency without a
35
+ concrete reason. Prefer the simplest design that meets the requirements and NFRs.
36
+ - **Make composition explicit.** Execution flow — sequencing, parallelism, routing, fan-out/fan-in,
37
+ retries and timeouts where they matter, external boundaries — lives in a composition root (an app
38
+ factory, a CLI/cron entry, an extension init), separate from the implementation it wires. A repo's
39
+ `CLAUDE.md` names its composition roots; where none is named, the app's entry/wiring file is the
40
+ root. Orchestration is never hidden inside a domain object. Where a repo keeps composition models
41
+ (small, one per flow, machine-readable), diagrams are rendered views of them, never the source:
42
+ edit the model and regenerate. No single global graph of every implementation detail.
43
+ - **One source of truth per concern.** API behaviour → the API contract (OpenAPI or equivalent).
44
+ External or event messages → event contracts. Execution and composition → the composition model.
45
+ Domain behaviour → domain code and its tests. Dependency boundaries → lint rules. NFRs → a
46
+ requirements document only where nothing mechanical can express them. Never duplicate the same
47
+ fact across README, contract, diagram and implementation; when a representation is generated,
48
+ change the source, never the output. Prefer an executable or validated artifact over a descriptive
49
+ one whenever the property can be checked mechanically — a file nobody validates against is just
50
+ another README.
51
+ - **API changes start from the contract.** The implementation conforms to it, consumers read the
52
+ types generated from it, and a breaking change is identified explicitly before it ships, never
53
+ discovered afterwards. The contract carries external behaviour only: no internal implementation
54
+ details and no business rules that are not part of what the API promises.
55
+ - **Separate concerns by level.** Domain (what the business means and which rules exist),
56
+ Application (how use cases are orchestrated), Infrastructure (how technical capabilities are
57
+ implemented), Contracts (what a boundary promises its consumers), Composition (how components
58
+ are connected and executed), Dependency Policy (which dependencies and directions are allowed).
59
+ These are names for *what a piece of code is*, used to decide where a change belongs — not folders
60
+ to create. Keep the repo's existing organisation (feature folders, role suffixes) and never add a
61
+ layer it does not already have.
62
+ - **Default dependency policy** when the repo declares none: apps depend on packages; packages never
63
+ depend on apps; shared/leaf packages depend on no internal package; no cycles; prefer an existing
64
+ capability over another dependency. Higher-level business logic never depends on infrastructure
65
+ implementation details. A declared policy lives in lint configuration so it is enforced, not read.
66
+ - **Accidental complexity is a design smell.** When a change needs unrelated components understood,
67
+ crosses a boundary it should not, or touches several layers for a local behaviour change,
68
+ reconsider the decomposition, coupling or abstraction instead of pushing through.
69
+
70
+ The checklists that turn these principles into questions to answer before a change are in
71
+ [checklists.md](checklists.md).
72
+
73
+ ## Security
74
+
75
+ Security is a validation dimension of its own, separate from functional correctness, and unit
76
+ tests alone do not cover it.
77
+
78
+ When a vulnerability or unsafe pattern is found:
79
+
80
+ 1. Fix it.
81
+ 2. Add a regression test when the behaviour is testable.
82
+ 3. Add or update a static-analysis rule when the issue is a reusable code pattern.
83
+ 4. Check whether a dependency caused it; update or replace the dependency when it did.
84
+ 5. Never suppress or ignore a finding without documenting why.
85
+
86
+ A finding that appears more than once is an architectural problem, not a symptom to fix again:
87
+ find the common root cause, move the protection to a shared boundary, add automated enforcement,
88
+ and update the architecture or dependency policy if needed. The durable form of a finding is a
89
+ named invariant with a named check — lint, a contract-level test, response validation, an
90
+ integration test or a scanner — recorded once where reviewers read it. `review` as the only check
91
+ is allowed, but it is the weakest and is listed as such.
92
+
93
+ ## Reasoning budget
94
+
95
+ Reasoning effort is a budget, not a default. Implementation work starts at low effort under explicit
96
+ constraints; the harness (the repo's quality gate: lint, types, tests, contract and composition
97
+ checks, security rules) proves the result; more effort is spent only when the harness fails
98
+ repeatedly or the task is ambiguous.
99
+
100
+ - **Classify before starting.** *low*: an existing pattern to copy, a contract already defined, no
101
+ architecture change, no new dependency, nothing security-sensitive. *medium*: a new endpoint or
102
+ integration, a change across several modules, a non-trivial refactor. *high*: an architecture or
103
+ domain-boundary change, a contract redesign, a security-model or dependency-policy change,
104
+ ambiguous requirements, or repeated medium failure.
105
+ - **Low effort means stronger constraints, not looser ones.** Implement only the requested change,
106
+ follow the neighbouring pattern, add no abstraction and no dependency, touch no unrelated module,
107
+ never delete, skip or weaken a test, ship the test with the logic, run the harness before
108
+ reporting.
109
+ - **High effort means design first.** Inspect the boundaries, weigh alternatives, update the
110
+ contract and composition model before the implementation, state the decision.
111
+ - **Escalate by evidence**: low → retry low with the failure in hand → medium → high. Ambiguity at
112
+ any rung goes to design, never to a guess. Reserve the ladder for tasks with a statable acceptance
113
+ criterion; one-line edits stay inline.
@@ -0,0 +1,17 @@
1
+ # Security invariants
2
+
3
+ Security is its own review dimension. Each invariant names what enforces it; `review` means the
4
+ reviewer is the only check, so weigh those the most. A finding that recurs is an architectural
5
+ problem: move the protection to a shared boundary and add a check rather than fixing the symptom
6
+ again. Method: [principles.md § Security](principles.md#security).
7
+
8
+ The baseline rows below are enforced by files this construct materialized. Discovery adds the rows
9
+ that are specific to this system — the ones where money, identity or data can be corrupted.
10
+
11
+ | Invariant | Enforced by |
12
+ |-----------|-------------|
13
+ | No secret, token or connection string in any file, including gitignored ones; config references `${VAR}` placeholders | `.github/workflows/security.yml` runs gitleaks over the history on every push and pull request (`.gitleaks.toml` keeps the default ruleset live); review |
14
+ | Dependencies with known high-severity vulnerabilities are visible | `security.yml` runs `pnpm audit` weekly and on pull requests, reporting only |
15
+ <!-- construct:discover:security-invariants -->
16
+ _Not discovered yet — run `/construct-discover`._
17
+ <!-- /construct:discover:security-invariants -->
@@ -0,0 +1,31 @@
1
+ # The harness on every pull request and push to main: `pnpm run quality` is the single gate,
2
+ # for humans and for AI agents alike. Keep it green; extend it in package.json, not here.
3
+
4
+ name: CI
5
+
6
+ on:
7
+ pull_request:
8
+ push:
9
+ branches: [main]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ quality:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+
20
+ - uses: pnpm/action-setup@v6
21
+
22
+ - uses: actions/setup-node@v7
23
+ with:
24
+ node-version-file: .nvmrc
25
+ cache: pnpm
26
+
27
+ - name: Install
28
+ run: pnpm install --frozen-lockfile
29
+
30
+ - name: Quality
31
+ run: {{harnessCommand}}
@@ -0,0 +1,13 @@
1
+ import antfu from '@antfu/eslint-config'
2
+
3
+ export default antfu(
4
+ {
5
+ isInEditor: false,
6
+ typescript: {
7
+ tsconfigPath: './tsconfig.json',
8
+ },
9
+ },
10
+ {
11
+ ignores: ['**/dist/**', '**/node_modules/**', 'pnpm-lock.yaml', 'scripts/construct/*.workflow.mjs'],
12
+ },
13
+ )