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
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mikoshi-construct",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"packageManager": "pnpm@12.4.2",
|
|
6
|
+
"description": "Bootstrap for AI-native software projects. Start with a proven engineering workflow instead of an empty repository.",
|
|
7
|
+
"author": "Eli Tabrisov",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://github.com/e1i/mikoshi-construct#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/e1i/mikoshi-construct.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/e1i/mikoshi-construct/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude-code",
|
|
17
|
+
"cursor",
|
|
18
|
+
"ai-agents",
|
|
19
|
+
"scaffold",
|
|
20
|
+
"bootstrap",
|
|
21
|
+
"openapi",
|
|
22
|
+
"harness",
|
|
23
|
+
"architecture",
|
|
24
|
+
"cli",
|
|
25
|
+
"ai-dlc"
|
|
26
|
+
],
|
|
27
|
+
"bin": {
|
|
28
|
+
"construct": "dist/cli.js",
|
|
29
|
+
"miko": "dist/cli.js",
|
|
30
|
+
"mikoshi-construct": "dist/cli.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"templates"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsx src/cli.ts",
|
|
42
|
+
"lint": "eslint .",
|
|
43
|
+
"lint:fix": "eslint --fix .",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"quality": "pnpm composition:check && pnpm lint && pnpm typecheck && pnpm test",
|
|
48
|
+
"prepublishOnly": "pnpm build",
|
|
49
|
+
"composition:render": "tsx scripts/composition/sync-docs.ts",
|
|
50
|
+
"composition:check": "tsx scripts/composition/check.ts",
|
|
51
|
+
"ci": "pnpm run quality",
|
|
52
|
+
"changeset": "changeset",
|
|
53
|
+
"version-packages": "changeset version",
|
|
54
|
+
"release": "pnpm build && npm publish --provenance --access public"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@clack/prompts": "^1.8.0",
|
|
58
|
+
"citty": "^0.2.2",
|
|
59
|
+
"picocolors": "^1.1.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
63
|
+
"@changesets/changelog-github": "^1.0.0",
|
|
64
|
+
"@changesets/cli": "^3.0.0",
|
|
65
|
+
"@types/node": "^22.18.0",
|
|
66
|
+
"eslint": "^10.10.0",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"tsx": "^4.23.13",
|
|
69
|
+
"typescript": "^5.9.3",
|
|
70
|
+
"vitest": "^5.0.0",
|
|
71
|
+
"yaml": "^2.9.1"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
@AGENTS.md
|
|
4
|
+
|
|
5
|
+
Everything an agent needs to know about this repository — product, module map, commands, harness,
|
|
6
|
+
composition roots, dependency policy, high-effort areas, defects vs accepted variance — lives in
|
|
7
|
+
[AGENTS.md](AGENTS.md), imported above. Repository-wide code rules are in `.claude/rules/`; the
|
|
8
|
+
architecture, security and reasoning-budget rules in
|
|
9
|
+
[architecture/principles.md](architecture/principles.md).
|
|
10
|
+
|
|
11
|
+
## Working here with Claude Code
|
|
12
|
+
|
|
13
|
+
- `/construct-discover` — fill or refresh the discovery blocks in AGENTS.md and `architecture/`.
|
|
14
|
+
- `/plan <feature>` — decompose a feature into tasks with acceptance criteria and an effort class.
|
|
15
|
+
- `/implement <task>` — run the reasoning-budget ladder: classify, implement at the lowest rung,
|
|
16
|
+
verify with `{{harnessCommand}}`, escalate only when verification proves it was not enough.
|
|
17
|
+
- `construct doctor` (or `npx mikoshi-construct doctor`) — check that the baseline and discovery are
|
|
18
|
+
intact.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Construct
|
|
2
|
+
|
|
3
|
+
This repository runs on a construct materialized by `mikoshi-construct` v{{constructVersion}}. The
|
|
4
|
+
discovery blocks — product, module map, commands, composition roots, dependency policy, high-effort
|
|
5
|
+
areas, defects vs accepted variance, open questions — live in [AGENTS.md](AGENTS.md); the
|
|
6
|
+
architecture, security and reasoning-budget rules in
|
|
7
|
+
[architecture/principles.md](architecture/principles.md); repository-wide code rules in
|
|
8
|
+
`.claude/rules/`.
|
|
9
|
+
|
|
10
|
+
- `/construct-discover` — fill or refresh the discovery blocks.
|
|
11
|
+
- `/plan <feature>` — decompose a feature into tasks with acceptance criteria and an effort class.
|
|
12
|
+
- `/implement <task>` — the reasoning-budget ladder: classify, implement at the lowest rung, verify
|
|
13
|
+
with `{{harnessCommand}}`, escalate only when verification proves it was not enough.
|
|
14
|
+
- `construct doctor` (or `npx mikoshi-construct doctor`) — check that the baseline and discovery are
|
|
15
|
+
intact.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: High-effort design for an architecture, contract, composition or security change, or for a task an implementer reported as blocked or repeatedly failed. Reads and reasons; does not edit.
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: xhigh
|
|
6
|
+
tools: Read, Grep, Glob, Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You design; you do not implement. You may read anything and run read-only commands (`git log`,
|
|
10
|
+
`git diff`, tests), but you never write files. Run `git` only when `git rev-parse --show-toplevel`
|
|
11
|
+
is the directory that holds `construct.json`; a parent directory's repository tells you nothing about
|
|
12
|
+
this one.
|
|
13
|
+
|
|
14
|
+
Input you receive: the task, and on escalation either the implementer's question or the failure
|
|
15
|
+
excerpts of the attempts so far.
|
|
16
|
+
|
|
17
|
+
Work through, in this order, and write nothing down until you have:
|
|
18
|
+
|
|
19
|
+
1. Which contract(s), domain boundaries, dependencies and composition the change touches.
|
|
20
|
+
2. Whether an existing artifact already describes the area (API contract, composition model,
|
|
21
|
+
invariants table) and what in it must change first.
|
|
22
|
+
3. The alternatives you considered and why the chosen one keeps the change local.
|
|
23
|
+
4. Whether the API contract or a composition model must change; if so, that is part of the spec.
|
|
24
|
+
5. The explicit constraints the implementer must respect, and the acceptance criteria that make
|
|
25
|
+
the task statable.
|
|
26
|
+
|
|
27
|
+
Your final message is data, not prose. Return exactly one JSON object:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"decision": "the design in a few sentences, including what stays unchanged and why",
|
|
32
|
+
"contractChanges": "operations or schemas to add or change in the API contract, or empty",
|
|
33
|
+
"compositionChanges": "composition model nodes or edges to change, or empty",
|
|
34
|
+
"constraints": ["one constraint per entry"],
|
|
35
|
+
"acceptance": ["one verifiable criterion per entry"],
|
|
36
|
+
"files": ["files to create or change"]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: harness
|
|
3
|
+
description: Runs the repository's quality gate against the working tree and reports a structured verdict. Never edits. Use after an implementer so a pass is never self-reported.
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: low
|
|
6
|
+
tools: Bash, Read, Grep, Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You verify; you do not fix. Run the harness command(s) you are given against the current working
|
|
10
|
+
tree and report what happened. If no command is given, run the harness command named in
|
|
11
|
+
`construct.json` (`harness.command`), and fall back to `pnpm run quality`. If the repository's CLAUDE.md
|
|
12
|
+
names extra commands for the area the diff touches, run those too.
|
|
13
|
+
|
|
14
|
+
Git is usable only when `git rev-parse --show-toplevel` is the directory that holds `construct.json`.
|
|
15
|
+
When it is not — the repository has no `.git`, or a parent directory's repository would answer — set
|
|
16
|
+
`diffStat` to `not a git repository at <dir>`, and decide `contractChanged` and `testsWeakened` by
|
|
17
|
+
reading the files the implementer's report names, never by a `git diff` that sees a different tree.
|
|
18
|
+
|
|
19
|
+
When git is usable, also inspect `git diff` (staged and unstaged) for:
|
|
20
|
+
|
|
21
|
+
- a deleted or renamed file under any `tests/` directory;
|
|
22
|
+
- `.skip(` or `.only(` added to a test;
|
|
23
|
+
- a change to an API contract file under `contracts/` (report it; the caller decides whether it was
|
|
24
|
+
expected).
|
|
25
|
+
|
|
26
|
+
Report the security leg separately from the rest when a failure comes from a security lint rule,
|
|
27
|
+
a contract security test or the secret scan, so the reader sees the invariant, not just the tool.
|
|
28
|
+
|
|
29
|
+
Your final message is data, not prose. Return exactly one JSON object:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"passed": true,
|
|
34
|
+
"failureExcerpt": "the failing command and its last relevant lines, empty when passed",
|
|
35
|
+
"securityFinding": "the invariant that failed, empty when none",
|
|
36
|
+
"diffStat": "output of git diff --stat",
|
|
37
|
+
"testsWeakened": false,
|
|
38
|
+
"contractChanged": false
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer
|
|
3
|
+
description: Low-effort implementer for a task with a statable acceptance criterion. Follows the neighbouring pattern under strict constraints, runs the harness, and returns a structured report instead of a guess.
|
|
4
|
+
model: inherit
|
|
5
|
+
effort: low
|
|
6
|
+
tools: Read, Edit, Write, Grep, Glob, Bash
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You implement exactly the task you are given, nothing more. The repository's CLAUDE.md files are
|
|
10
|
+
already in your context; follow them.
|
|
11
|
+
|
|
12
|
+
Input you receive: the task, the acceptance criteria, the harness command(s), optionally a design
|
|
13
|
+
spec from the architect, and optionally the failure excerpt from the previous attempt.
|
|
14
|
+
|
|
15
|
+
Constraints at this effort level:
|
|
16
|
+
|
|
17
|
+
- Implement only the requested change. Follow the pattern of the neighbouring code.
|
|
18
|
+
- Add no abstraction, no dependency, no layer. Touch no unrelated module.
|
|
19
|
+
- When the task touches an HTTP route, change the API contract first, regenerate the shared
|
|
20
|
+
types, then implement.
|
|
21
|
+
- Never delete, skip or weaken a test. A changed logic module ships its test in the same change.
|
|
22
|
+
- Read the previous failure excerpt, if any, before touching code; fix the cause, not the symptom.
|
|
23
|
+
- Run the harness before reporting. If it fails and you cannot see why within two attempts, report
|
|
24
|
+
`failed` with the excerpt rather than looping.
|
|
25
|
+
- Do not commit.
|
|
26
|
+
|
|
27
|
+
If the task is ambiguous about a contract, a boundary or which of two designs is meant, stop and
|
|
28
|
+
report `blocked` with one precise question. Do not pick one.
|
|
29
|
+
|
|
30
|
+
Your final message is data, not prose. Return exactly one JSON object:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"status": "done | failed | blocked",
|
|
35
|
+
"summary": "one or two sentences on what changed",
|
|
36
|
+
"files": ["paths you changed"],
|
|
37
|
+
"harnessTail": "last lines of the harness output, empty when blocked",
|
|
38
|
+
"question": "the single question when blocked, otherwise empty"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Turn a feature or issue into tasks that /implement can run — each with a statable acceptance criterion and an effort class.
|
|
3
|
+
argument-hint: <feature, issue text or link>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Decompose `$ARGUMENTS` into tasks for the reasoning-budget ladder. Use the `architect` agent for the
|
|
7
|
+
decomposition when the feature touches a contract, a composition model, a high-effort area from
|
|
8
|
+
CLAUDE.md or a security invariant; otherwise reason it out directly.
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
|
|
12
|
+
- Two to six tasks. Each task is independently verifiable by the harness and leaves the tree green.
|
|
13
|
+
- Each task has two to four acceptance criteria that a harness run or a test can confirm. "Works" is
|
|
14
|
+
not a criterion; "GET /v1/things returns 200 with the `Thing` schema and the contract test passes" is.
|
|
15
|
+
- Order tasks so the contract and composition changes come first, then implementation, then anything
|
|
16
|
+
that consumes the new behaviour.
|
|
17
|
+
- Classify each task `low`, `medium` or `high` with the rules in `architecture/principles.md`
|
|
18
|
+
(Reasoning budget); a task touching a high-effort area, the contract, a composition model, the
|
|
19
|
+
dependency policy or a security invariant is `high`.
|
|
20
|
+
- If the feature is ambiguous about a contract or a boundary, put one precise question first and stop
|
|
21
|
+
after it; do not plan on a guess.
|
|
22
|
+
|
|
23
|
+
Output the tasks as a numbered list; for each, one line `/implement <task> — acceptance: …; effort:
|
|
24
|
+
<class>` that can be pasted as-is. Do not implement anything.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implement
|
|
3
|
+
description: Implement a task through the reasoning-budget ladder — classify effort, run a low-effort implementer under constraints, verify with the harness, escalate only on repeated failure or ambiguity.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
argument-hint: <task with an acceptance criterion>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Run the reasoning-budget ladder for the task in `$ARGUMENTS`. The rules are in the Reasoning budget
|
|
10
|
+
section of `architecture/principles.md`; repo specifics (harness command, high-effort areas) are in the
|
|
11
|
+
repository's CLAUDE.md and `construct.json`.
|
|
12
|
+
|
|
13
|
+
1. Classify the effort class and tell the user the class and the one-line reason before anything
|
|
14
|
+
else. Read the repository's CLAUDE.md for its harness command and its high-effort areas. A task
|
|
15
|
+
that names or must touch a high-effort area, the API contract, a composition model, the
|
|
16
|
+
dependency policy or the security invariants is `high`. A new endpoint, a new integration or a
|
|
17
|
+
change across several modules is `medium`. Everything with an existing pattern to copy and a
|
|
18
|
+
contract already defined is `low`.
|
|
19
|
+
2. Write the acceptance criteria in two to four verifiable lines. If the task has no statable
|
|
20
|
+
criterion, say so and stop; the ladder is not for one-line edits or open-ended exploration.
|
|
21
|
+
3. Call the Workflow tool with `scriptPath` set to `scripts/construct/implement.workflow.mjs` (the ladder
|
|
22
|
+
script lives with the project's scripts, not under `.claude/`) and `args` as
|
|
23
|
+
a JSON object:
|
|
24
|
+
`{ "task": ..., "acceptance": [...], "effort": "low|medium|high", "harness": { "command": ..., "extra": [...] } }`
|
|
25
|
+
where `harness.command` comes from `construct.json` and `harness.extra` lists any area-specific
|
|
26
|
+
commands CLAUDE.md names for the files the task touches (usually empty).
|
|
27
|
+
The user's `/implement` invocation is the opt-in the tool requires.
|
|
28
|
+
4. Record the run: append one JSON line to `.construct/runs.jsonl` (create the directory if needed)
|
|
29
|
+
with `at` (ISO time), `task` (first 120 characters), `effort` (the class you chose), `status`,
|
|
30
|
+
the rung that finished (`effort` from the result), `attempts` from the result, and the Workflow
|
|
31
|
+
tool's own accounting for the run — `agents`, `tokens`, `toolUses`, `seconds` — exactly as it
|
|
32
|
+
reported them. This log is what tunes the ladder later; workflow scripts have no filesystem
|
|
33
|
+
access, so it is written here, not by the script. `.construct/` is gitignored.
|
|
34
|
+
5. Relay the result: status, the effort rung that succeeded and how many attempts it took, the
|
|
35
|
+
files changed, and the harness tail. When the status is `blocked`, put the architect's or
|
|
36
|
+
implementer's question to the user verbatim. When `failed`, give the last failure excerpt.
|
|
37
|
+
Unless `construct.json` sets `report.usage` to `false`, end with one usage line for this run,
|
|
38
|
+
from the Workflow tool's own accounting: agents, subagent tokens, tool uses, wall time — so the
|
|
39
|
+
cost of the rung that succeeded is on record next to the result. When `construct` is on the PATH,
|
|
40
|
+
add the per-agent split from `construct cost --last --json` (implementer, harness, architect);
|
|
41
|
+
when it is not, the Workflow accounting alone is the line — never guess numbers.
|
|
42
|
+
6. Never commit. The user reviews the working tree first.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: 'implement',
|
|
3
|
+
description: 'Implement a task at low effort, verify with the harness, escalate on repeated failure or ambiguity',
|
|
4
|
+
phases: [
|
|
5
|
+
{ title: 'Design', detail: 'architect, only for high effort or after a blocked or failed attempt' },
|
|
6
|
+
{ title: 'Implement', detail: 'implementer at the current rung' },
|
|
7
|
+
{ title: 'Verify', detail: 'harness against the working tree' },
|
|
8
|
+
],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const LADDERS = {
|
|
12
|
+
low: ['low', 'low', 'medium', 'high'],
|
|
13
|
+
medium: ['medium', 'medium', 'high'],
|
|
14
|
+
high: ['high', 'high', 'xhigh'],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const REPORT = {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['status', 'summary', 'files', 'harnessTail', 'question'],
|
|
20
|
+
properties: {
|
|
21
|
+
status: { type: 'string', enum: ['done', 'failed', 'blocked'] },
|
|
22
|
+
summary: { type: 'string' },
|
|
23
|
+
files: { type: 'array', items: { type: 'string' } },
|
|
24
|
+
harnessTail: { type: 'string' },
|
|
25
|
+
question: { type: 'string' },
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const VERDICT = {
|
|
30
|
+
type: 'object',
|
|
31
|
+
required: ['passed', 'failureExcerpt', 'securityFinding', 'diffStat', 'testsWeakened', 'contractChanged'],
|
|
32
|
+
properties: {
|
|
33
|
+
passed: { type: 'boolean' },
|
|
34
|
+
failureExcerpt: { type: 'string' },
|
|
35
|
+
securityFinding: { type: 'string' },
|
|
36
|
+
diffStat: { type: 'string' },
|
|
37
|
+
testsWeakened: { type: 'boolean' },
|
|
38
|
+
contractChanged: { type: 'boolean' },
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const SPEC = {
|
|
43
|
+
type: 'object',
|
|
44
|
+
required: ['decision', 'contractChanges', 'compositionChanges', 'constraints', 'acceptance', 'files'],
|
|
45
|
+
properties: {
|
|
46
|
+
decision: { type: 'string' },
|
|
47
|
+
contractChanges: { type: 'string' },
|
|
48
|
+
compositionChanges: { type: 'string' },
|
|
49
|
+
constraints: { type: 'array', items: { type: 'string' } },
|
|
50
|
+
acceptance: { type: 'array', items: { type: 'string' } },
|
|
51
|
+
files: { type: 'array', items: { type: 'string' } },
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const task = args.task
|
|
56
|
+
const acceptance = args.acceptance ?? []
|
|
57
|
+
const harness = { command: 'pnpm run quality', extra: [], ...(args.harness ?? {}) }
|
|
58
|
+
const rungs = LADDERS[args.effort] ?? LADDERS.low
|
|
59
|
+
|
|
60
|
+
function harnessPrompt() {
|
|
61
|
+
return [
|
|
62
|
+
`Harness command: ${harness.command}`,
|
|
63
|
+
harness.extra.length > 0 ? `Extra commands for the area this task touches: ${harness.extra.join(' && ')}` : '',
|
|
64
|
+
'Verify the current working tree and return the verdict object.',
|
|
65
|
+
].filter(Boolean).join('\n')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function architectPrompt(reason) {
|
|
69
|
+
return [
|
|
70
|
+
`Task: ${task}`,
|
|
71
|
+
acceptance.length > 0 ? `Acceptance criteria so far:\n- ${acceptance.join('\n- ')}` : '',
|
|
72
|
+
reason,
|
|
73
|
+
'Return the design spec object.',
|
|
74
|
+
].filter(Boolean).join('\n\n')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function implementerPrompt(spec, feedback) {
|
|
78
|
+
return [
|
|
79
|
+
`Task: ${task}`,
|
|
80
|
+
`Acceptance criteria:\n- ${(spec?.acceptance?.length ? spec.acceptance : acceptance).join('\n- ')}`,
|
|
81
|
+
`Harness: ${harness.command}${harness.extra.length > 0 ? ` (plus ${harness.extra.join(' && ')})` : ''}`,
|
|
82
|
+
spec == null
|
|
83
|
+
? ''
|
|
84
|
+
: `Design spec from the architect:\n${spec.decision}\n\nContract changes: ${spec.contractChanges || 'none'}\nComposition changes: ${spec.compositionChanges || 'none'}\nConstraints:\n- ${spec.constraints.join('\n- ')}\nFiles: ${spec.files.join(', ')}`,
|
|
85
|
+
feedback == null ? '' : `The previous attempt failed the harness. Fix the cause of this before anything else:\n${feedback}`,
|
|
86
|
+
'Return the report object.',
|
|
87
|
+
].filter(Boolean).join('\n\n')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let spec = null
|
|
91
|
+
let feedback = null
|
|
92
|
+
const attempts = []
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if (args.effort === 'high') {
|
|
96
|
+
phase('Design')
|
|
97
|
+
spec = await agent(architectPrompt('The task is classified as high effort; design it before any implementation.'), {
|
|
98
|
+
agentType: 'architect',
|
|
99
|
+
effort: 'xhigh',
|
|
100
|
+
phase: 'Design',
|
|
101
|
+
label: 'design',
|
|
102
|
+
schema: SPEC,
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const [index, effort] of rungs.entries()) {
|
|
107
|
+
const rung = index + 1
|
|
108
|
+
phase('Implement')
|
|
109
|
+
log(`rung ${rung}/${rungs.length} @ ${effort}: implementing`)
|
|
110
|
+
const report = await agent(implementerPrompt(spec, feedback), {
|
|
111
|
+
agentType: 'implementer',
|
|
112
|
+
effort,
|
|
113
|
+
phase: 'Implement',
|
|
114
|
+
label: `implement ${rung}/${rungs.length} @ ${effort}`,
|
|
115
|
+
schema: REPORT,
|
|
116
|
+
})
|
|
117
|
+
if (report == null) {
|
|
118
|
+
attempts.push({ rung, effort, outcome: 'no report' })
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (report.status === 'blocked') {
|
|
123
|
+
attempts.push({ rung, effort, outcome: 'blocked', question: report.question })
|
|
124
|
+
if (rung === rungs.length)
|
|
125
|
+
return { status: 'blocked', question: report.question, attempts }
|
|
126
|
+
spec = await agent(architectPrompt(`The implementer stopped on this question:\n${report.question}`), {
|
|
127
|
+
agentType: 'architect',
|
|
128
|
+
effort: 'xhigh',
|
|
129
|
+
phase: 'Design',
|
|
130
|
+
label: `design after blocked ${rung}`,
|
|
131
|
+
schema: SPEC,
|
|
132
|
+
})
|
|
133
|
+
feedback = null
|
|
134
|
+
log(`rung ${rung}/${rungs.length} @ ${effort}: blocked, architect answered`)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
phase('Verify')
|
|
139
|
+
log(`rung ${rung}/${rungs.length} @ ${effort}: running ${harness.command}`)
|
|
140
|
+
const verdict = await agent(harnessPrompt(), {
|
|
141
|
+
agentType: 'harness',
|
|
142
|
+
effort: 'low',
|
|
143
|
+
phase: 'Verify',
|
|
144
|
+
label: `verify ${rung}/${rungs.length}`,
|
|
145
|
+
schema: VERDICT,
|
|
146
|
+
})
|
|
147
|
+
const passed = verdict?.passed === true && verdict.testsWeakened === false
|
|
148
|
+
attempts.push({ rung, effort, outcome: passed ? 'passed' : 'failed', securityFinding: verdict?.securityFinding ?? '' })
|
|
149
|
+
log(`rung ${rung} @ ${effort}: ${passed ? 'harness passed' : 'harness failed'}`)
|
|
150
|
+
|
|
151
|
+
if (passed) {
|
|
152
|
+
return {
|
|
153
|
+
status: 'done',
|
|
154
|
+
effort,
|
|
155
|
+
attempts,
|
|
156
|
+
files: report.files,
|
|
157
|
+
summary: report.summary,
|
|
158
|
+
harnessTail: report.harnessTail,
|
|
159
|
+
contractChanged: verdict.contractChanged,
|
|
160
|
+
diffStat: verdict.diffStat,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
feedback = verdict == null
|
|
165
|
+
? 'The harness produced no verdict.'
|
|
166
|
+
: verdict.testsWeakened
|
|
167
|
+
? `A test was deleted, skipped or narrowed. Restore it and make the implementation pass it.\n${verdict.failureExcerpt}`
|
|
168
|
+
: verdict.failureExcerpt
|
|
169
|
+
if (verdict?.securityFinding)
|
|
170
|
+
feedback = `Security invariant failed: ${verdict.securityFinding}\n${feedback}`
|
|
171
|
+
|
|
172
|
+
if (rung === rungs.length - 1) {
|
|
173
|
+
phase('Design')
|
|
174
|
+
log(`rung ${rung}/${rungs.length} failed twice: architect redesigns before the last rung`)
|
|
175
|
+
spec = await agent(architectPrompt(`Two rungs have failed the harness. Latest failure:\n${feedback}\n\nDecide whether the approach, the contract or the boundary is wrong before the last attempt.`), {
|
|
176
|
+
agentType: 'architect',
|
|
177
|
+
effort: 'xhigh',
|
|
178
|
+
phase: 'Design',
|
|
179
|
+
label: 'design before last rung',
|
|
180
|
+
schema: SPEC,
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { status: 'failed', attempts, lastFailure: feedback }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Construct baseline — where the rules, contract and harness live
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This repository runs on a construct materialized by `mikoshi-construct`. Before changing code read, in
|
|
7
|
+
this order: `architecture/principles.md` (architecture, security, reasoning budget), `AGENTS.md`
|
|
8
|
+
(what the product does, module map, commands, composition roots, dependency policy, high-effort
|
|
9
|
+
areas, defects vs accepted variance), `architecture/security-invariants.md`.
|
|
10
|
+
|
|
11
|
+
The harness command is in `construct.json` (`harness.command`). A change is done only when it passes.
|
|
12
|
+
API changes start from the contract file named in `construct.json` (`contracts.path`); regenerate the
|
|
13
|
+
types with `pnpm contracts:types` before implementing. Composition models in the directory
|
|
14
|
+
`construct.json` names as `discovery.composition` are the source of the diagrams; edit the model and run
|
|
15
|
+
`pnpm composition:render`, never the rendered block.
|
|
16
|
+
|
|
17
|
+
Discovery: when asked to "run construct discovery", follow the `construct-discover` rule in this
|
|
18
|
+
directory step by step. It fills every `<!-- construct:discover:* -->` block in AGENTS.md and
|
|
19
|
+
architecture/ with what you verified by reading code.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Claude Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [labeled]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
pr:
|
|
9
|
+
description: Pull request number
|
|
10
|
+
required: true
|
|
11
|
+
type: string
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: claude-review-${{ github.event.pull_request.number || inputs.pr }}
|
|
15
|
+
cancel-in-progress: true
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
review:
|
|
19
|
+
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'claude-review'
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 60
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: write
|
|
25
|
+
issues: write
|
|
26
|
+
id-token: write
|
|
27
|
+
env:
|
|
28
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
REPO: ${{ github.repository }}
|
|
30
|
+
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr }}
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v7
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- name: Check out the pull request branch
|
|
37
|
+
run: gh pr checkout "$PR_NUMBER"
|
|
38
|
+
|
|
39
|
+
- uses: anthropics/claude-code-action@v1
|
|
40
|
+
with:
|
|
41
|
+
anthropic_api_key: ${{ secrets.CODE_REVIEW_API_KEY }}
|
|
42
|
+
plugin_marketplaces: https://github.com/anthropics/claude-code.git
|
|
43
|
+
plugins: code-review@claude-code-plugins
|
|
44
|
+
prompt: /code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number || inputs.pr }}
|
|
45
|
+
claude_args: |
|
|
46
|
+
--model {{reviewModel}}
|
|
47
|
+
--allowedTools "Read,Grep,Glob,Bash,Task,Agent,TodoWrite,WebFetch,mcp__github_inline_comment__create_inline_comment"
|
|
48
|
+
|
|
49
|
+
- name: Remove trigger label
|
|
50
|
+
if: always() && github.event_name == 'pull_request'
|
|
51
|
+
run: gh api -X DELETE "repos/$REPO/issues/$PR_NUMBER/labels/claude-review" --silent || true
|