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.
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/cli.js +1492 -0
- package/package.json +73 -0
- package/templates/ai/claude/CLAUDE.md.eta +18 -0
- package/templates/ai/claude/CLAUDE.md.existing.eta +15 -0
- package/templates/ai/claude/_claude/agents/architect.md +38 -0
- package/templates/ai/claude/_claude/agents/harness.md +40 -0
- package/templates/ai/claude/_claude/agents/implementer.md +40 -0
- package/templates/ai/claude/_claude/commands/plan.md +24 -0
- package/templates/ai/claude/_claude/skills/implement/SKILL.md +42 -0
- package/templates/ai/claude/scripts/construct/implement.workflow.mjs +185 -0
- package/templates/ai/cursor/_cursor/rules/construct.mdc +19 -0
- package/templates/ai/review/_github/workflows/claude-review.yml.eta +51 -0
- package/templates/ai/shared/AGENTS.md.eta +123 -0
- package/templates/ai/shared/AGENTS.md.existing.eta +50 -0
- package/templates/ai/shared/_claude/commands/construct-discover.md +75 -0
- package/templates/ai/shared/_claude/rules/conventions.md +63 -0
- package/templates/ai/shared/_claude/rules/secrets.md +8 -0
- package/templates/ai/shared/_claude/rules/tests.md +6 -0
- package/templates/base/_editorconfig +12 -0
- package/templates/base/_github/workflows/security.yml +41 -0
- package/templates/base/_gitignore +11 -0
- package/templates/base/_gitleaks.toml +2 -0
- package/templates/base/_nvmrc.eta +1 -0
- package/templates/base/_vscode/settings.json +22 -0
- package/templates/base/architecture/checklists.md +42 -0
- package/templates/base/architecture/principles.md +113 -0
- package/templates/base/architecture/security-invariants.md +17 -0
- package/templates/harness/_github/workflows/ci.yml.eta +31 -0
- package/templates/harness/eslint.config.mjs +13 -0
- package/templates/harness/package.json.eta +29 -0
- package/templates/harness/pnpm-workspace.yaml +11 -0
- package/templates/harness/scripts/composition/check.ts +19 -0
- package/templates/harness/scripts/composition/files.ts +34 -0
- package/templates/harness/scripts/composition/model.ts +137 -0
- package/templates/harness/scripts/composition/render.ts +72 -0
- package/templates/harness/scripts/composition/sync-docs.ts +11 -0
- package/templates/harness/scripts/tests/composition/files.test.ts +16 -0
- package/templates/harness/scripts/tests/composition/model.test.ts +41 -0
- package/templates/harness/scripts/tests/composition/render.test.ts +54 -0
- package/templates/harness/tsconfig.base.json +14 -0
- package/templates/harness/tsconfig.json +5 -0
- package/templates/harness/vitest.config.ts +8 -0
- package/templates/presets/monorepo/baseline/eslint.config.mjs.eta +100 -0
- package/templates/presets/monorepo/baseline/package.json.eta +18 -0
- package/templates/presets/monorepo/baseline/packages/shared/package.json.eta +22 -0
- package/templates/presets/monorepo/baseline/packages/shared/src/api/openapi.ts +81 -0
- package/templates/presets/monorepo/baseline/packages/shared/src/index.ts +1 -0
- package/templates/presets/monorepo/baseline/packages/shared/tsconfig.build.json +10 -0
- package/templates/presets/monorepo/baseline/packages/shared/tsconfig.json +4 -0
- package/templates/presets/monorepo/baseline/pnpm-workspace.yaml.eta +31 -0
- package/templates/presets/monorepo/baseline/tsconfig.json +12 -0
- package/templates/presets/monorepo/baseline/vitest.config.ts +8 -0
- package/templates/presets/monorepo/sample/apps/api/package.json.eta +23 -0
- package/templates/presets/monorepo/sample/apps/api/tsconfig.build.json +10 -0
- package/templates/presets/monorepo/sample/apps/api/tsconfig.json +4 -0
- package/templates/presets/monorepo/sample/scripts/tests/lint/syntax-policy.test.ts.eta +36 -0
- package/templates/presets/node-backend/baseline/eslint.config.mjs +59 -0
- package/templates/presets/node-backend/baseline/package.json.eta +20 -0
- package/templates/presets/node-backend/baseline/src/contracts/openapi.ts +81 -0
- package/templates/presets/node-backend/baseline/tsconfig.build.json +10 -0
- package/templates/presets/node-backend/sample/scripts/tests/lint/syntax-policy.test.ts +31 -0
- package/templates/presets/node-frontend/baseline/_claude/rules/css.md +158 -0
- package/templates/presets/node-frontend/baseline/eslint.config.mjs +44 -0
- package/templates/presets/node-frontend/baseline/package.json.eta +15 -0
- package/templates/presets/node-frontend/baseline/tsconfig.json +11 -0
- package/templates/presets/node-frontend/baseline/vitest.config.ts +10 -0
- package/templates/presets/node-frontend/sample/architecture/app.md +37 -0
- package/templates/presets/node-frontend/sample/architecture/composition/app.yaml +41 -0
- package/templates/presets/node-frontend/sample/index.html.eta +12 -0
- package/templates/presets/node-frontend/sample/src/app.ts +32 -0
- package/templates/presets/node-frontend/sample/src/main.ts +9 -0
- package/templates/presets/node-frontend/sample/src/styles/app.css +43 -0
- package/templates/presets/node-frontend/sample/src/styles/tokens.css +16 -0
- package/templates/presets/node-frontend/sample/tests/app.test.ts +21 -0
- package/templates/stacks/express-api/app/src/app.ts +27 -0
- package/templates/stacks/express-api/app/src/config.ts.eta +30 -0
- package/templates/stacks/express-api/app/src/contracts/types.ts.eta +1 -0
- package/templates/stacks/express-api/app/src/health/health.controller.ts +12 -0
- package/templates/stacks/express-api/app/src/health/health.service.ts +23 -0
- package/templates/stacks/express-api/app/src/http/api-contract.middleware.ts +13 -0
- package/templates/stacks/express-api/app/src/http/app-error.ts +14 -0
- package/templates/stacks/express-api/app/src/http/async-handler.middleware.ts +7 -0
- package/templates/stacks/express-api/app/src/http/error-handler.middleware.ts +50 -0
- package/templates/stacks/express-api/app/src/server.ts +9 -0
- package/templates/stacks/express-api/app/tests/health.test.ts +28 -0
- package/templates/stacks/express-api/app/tests/http/error-handler.middleware.test.ts +48 -0
- package/templates/stacks/express-api/repo/architecture/composition/http.yaml.eta +45 -0
- package/templates/stacks/express-api/repo/architecture/http.md +39 -0
- package/templates/stacks/http-contract/_github/workflows/api-contract.yml +36 -0
- package/templates/stacks/http-contract/contracts/api/openapi.yaml.eta +62 -0
- package/templates/stacks/http-contract/package.json.eta +13 -0
- package/templates/stacks/http-contract/redocly.yaml.eta +8 -0
- package/templates/stacks/http-contract/scripts/contracts/types.mjs.eta +20 -0
- 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,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 @@
|
|
|
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
|
+
)
|