mikoshi-construct 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mikoshi-construct",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Bootstrap for AI-native software projects. Start with a proven engineering workflow instead of an empty repository.",
6
6
  "author": "Eli Tabrisov",
7
7
  "license": "MIT",
8
- "homepage": "https://github.com/E1i/mikoshi-construct#readme",
8
+ "homepage": "https://e1i.github.io/mikoshi-construct/",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/E1i/mikoshi-construct.git"
@@ -49,24 +49,28 @@
49
49
  "tsup": "^8.5.1",
50
50
  "tsx": "^4.23.13",
51
51
  "typescript": "^5.9.3",
52
+ "vitepress": "2.0.0-alpha.20",
52
53
  "vitest": "^5.0.0",
53
54
  "yaml": "^2.9.1"
54
55
  },
55
56
  "scripts": {
56
57
  "build": "tsup",
58
+ "changeset": "changeset",
59
+ "ci": "pnpm run quality",
60
+ "composition:check": "tsx scripts/composition/check.ts",
61
+ "composition:render": "tsx scripts/composition/sync-docs.ts",
57
62
  "dev": "tsx src/cli.ts",
63
+ "docs:build": "vitepress build docs",
64
+ "docs:dev": "vitepress dev docs",
65
+ "docs:preview": "vitepress preview docs",
58
66
  "lint": "eslint .",
59
67
  "lint:fix": "eslint --fix .",
60
- "typecheck": "tsc --noEmit",
68
+ "privacy:check": "tsx scripts/privacy/check.ts",
69
+ "quality": "pnpm composition:check && pnpm privacy:check && pnpm lint && pnpm typecheck && pnpm test",
70
+ "release": "pnpm build && changeset publish",
61
71
  "test": "vitest run",
62
72
  "test:watch": "vitest",
63
- "quality": "pnpm composition:check && pnpm privacy:check && pnpm lint && pnpm typecheck && pnpm test",
64
- "composition:render": "tsx scripts/composition/sync-docs.ts",
65
- "composition:check": "tsx scripts/composition/check.ts",
66
- "privacy:check": "tsx scripts/privacy/check.ts",
67
- "ci": "pnpm run quality",
68
- "changeset": "changeset",
69
- "version-packages": "changeset version",
70
- "release": "pnpm build && changeset publish"
73
+ "typecheck": "tsc --noEmit",
74
+ "version-packages": "changeset version"
71
75
  }
72
76
  }
@@ -12,6 +12,8 @@ Rules:
12
12
  - Two to six tasks. Each task is independently verifiable by the harness and leaves the tree green.
13
13
  - Each task has two to four acceptance criteria that a harness run or a test can confirm. "Works" is
14
14
  not a criterion; "GET /v1/things returns 200 with the `Thing` schema and the contract test passes" is.
15
+ - A criterion is verified by what the task changes itself. If satisfying it needs an action outside the
16
+ task, it belongs to that task, not this one.
15
17
  - Order tasks so the contract and composition changes come first, then implementation, then anything
16
18
  that consumes the new behaviour.
17
19
  - Classify each task `low`, `medium` or `high` with the rules in `architecture/principles.md`
@@ -31,6 +31,15 @@ repository's CLAUDE.md and `construct.json`.
31
31
  only when a rejected response is expected to be a transient shape error rather than a bad brief.
32
32
  The user's `/implement` invocation is the opt-in the tool requires. Note the run identifier the
33
33
  Workflow tool reports when it launches the run and again when it completes; step 4 records it.
34
+ The design step runs inside the ladder, not before it, and its outcome is one of the `attempts`
35
+ like any other. The statuses a run can return are:
36
+ - `done` — a rung passed the harness and every design step the run took completed.
37
+ - `degraded` — a rung passed the harness, but a design step was rejected by the schema and the
38
+ run continued without it. The result's `effort` is the class that actually executed.
39
+ - `design incomplete` — a high-effort run whose architect was rejected by the schema. No
40
+ implementer ran without a spec; the result carries the validator's text in `validationError`.
41
+ - `failed` — every rung ran and the harness stayed red; `lastFailure` carries the excerpt.
42
+ - `blocked` — the last rung stopped on a question; `question` carries it verbatim.
34
43
  4. Record the run: append one JSON line to `.construct/runs.jsonl` (create the directory if needed)
35
44
  with exactly these fields and no others:
36
45
  - `run` — the Workflow run identifier from step 3. It is the key `construct cost` joins the entry
@@ -38,13 +47,15 @@ repository's CLAUDE.md and `construct.json`.
38
47
  unjoinable, which is the truth about it.
39
48
  - `at` — the ISO timestamp.
40
49
  - `task` — the task text, first 120 characters.
41
- - `effort` — the class you chose in step 1.
42
- - `status` `done`, `failed` or `blocked`, from the result.
43
- - `rung` — the effort of the rung that finished: `effort` from the result when it is `done`,
50
+ - `effort` — the class the run performed: the result's `effort` when it carries one, and only
51
+ then the class you chose in step 1. A run whose design step did not complete is never written
52
+ down as `high`; the result has already degraded it.
53
+ - `status` — the result's status verbatim, one of the five in step 3.
54
+ - `rung` — the effort of the rung that finished: `effort` from the result when it carries one,
44
55
  otherwise the `effort` of the last entry in `attempts`.
45
56
  - `attempts` — the result's `attempts` array verbatim; each entry carries its `rung`, `effort`,
46
- `outcome` and the `reason` that separates an invalid response shape from a red harness from a
47
- blocked report.
57
+ `outcome` and the `reason` that separates an invalid response shape from a red harness, from a
58
+ blocked report and from a design the schema rejected.
48
59
  - `agents`, `tokens`, `toolUses`, `seconds` — the Workflow tool's own accounting for the run,
49
60
  exactly as it reported it. Write `"unknown"` for a token figure it did not report, never `0`.
50
61
  The ledger carries counts and reasons only — never a prompt, a response or any other message
@@ -54,7 +65,10 @@ repository's CLAUDE.md and `construct.json`.
54
65
  gitignored.
55
66
  5. Relay the result: status, the effort rung that succeeded and how many attempts it took, the
56
67
  files changed, and the harness tail. When the status is `blocked`, put the architect's or
57
- implementer's question to the user verbatim. When `failed`, give the last failure excerpt.
68
+ implementer's question to the user verbatim. When `failed`, give the last failure excerpt. When
69
+ `design incomplete`, say that the design step did not complete and give `validationError` as the
70
+ runtime reported it; when `degraded`, say which design step was rejected and that the reported
71
+ class is the one that executed, not the one that was requested.
58
72
  Unless `construct.json` sets `report.usage` to `false`, end with one usage line for this run,
59
73
  from the Workflow tool's own accounting: agents, subagent tokens, tool uses, wall time — so the
60
74
  cost of the rung that succeeded is on record next to the result. When `construct` is on the PATH,
@@ -2,7 +2,7 @@ export const meta = {
2
2
  name: 'implement',
3
3
  description: 'Implement a task at low effort, verify with the harness, escalate on repeated failure or ambiguity',
4
4
  phases: [
5
- { title: 'Design', detail: 'architect, only for high effort or after a blocked or failed attempt' },
5
+ { title: 'Design', detail: 'architect inside the run, for high effort before the first rung and after a blocked or failed attempt' },
6
6
  { title: 'Implement', detail: 'implementer at the current rung' },
7
7
  { title: 'Verify', detail: 'harness against the working tree' },
8
8
  ],
@@ -53,6 +53,8 @@ const SPEC = {
53
53
  }
54
54
 
55
55
  const DEFAULT_RETRY_LIMIT = 0
56
+ const DESIGN_EFFORT = 'xhigh'
57
+ const EFFORT_WITHOUT_DESIGN = { high: 'medium', xhigh: 'medium' }
56
58
 
57
59
  const task = args.task
58
60
  const acceptance = args.acceptance ?? []
@@ -125,22 +127,58 @@ function implementerPrompt(spec, feedback) {
125
127
 
126
128
  let spec = null
127
129
  let feedback = null
130
+ let designComplete = false
131
+ let designFailed = false
132
+ let designError = ''
128
133
  const attempts = []
129
134
 
135
+ function performedEffort(effort) {
136
+ return designComplete ? effort : (EFFORT_WITHOUT_DESIGN[effort] ?? effort)
137
+ }
130
138
 
131
- if (args.effort === 'high') {
139
+ async function design(rung, reason, label) {
132
140
  phase('Design')
133
- spec = await ask(architectPrompt('The task is classified as high effort; design it before any implementation.'), {
141
+ const result = await ask(architectPrompt(reason), {
134
142
  agentType: 'architect',
135
- effort: 'xhigh',
143
+ effort: DESIGN_EFFORT,
136
144
  phase: 'Design',
137
- label: 'design',
145
+ label,
138
146
  schema: SPEC,
139
147
  })
148
+ if (result == null) {
149
+ designComplete = false
150
+ designFailed = true
151
+ designError = lastValidationError ?? ''
152
+ attempts.push({ rung, effort: DESIGN_EFFORT, outcome: 'design schema invalid', reason: designError })
153
+ log(`${label}: the design step did not complete`)
154
+ return false
155
+ }
156
+ spec = result
157
+ designComplete = true
158
+ attempts.push({ rung, effort: DESIGN_EFFORT, outcome: 'designed', reason: '' })
159
+ return true
160
+ }
161
+
162
+ function designIncomplete(effort, question) {
163
+ return {
164
+ status: 'design incomplete',
165
+ effort: performedEffort(effort),
166
+ attempts,
167
+ validationError: designError,
168
+ question: question ?? '',
169
+ lastFailure: feedback ?? '',
170
+ }
140
171
  }
141
172
 
142
173
  for (const [index, effort] of rungs.entries()) {
143
174
  const rung = index + 1
175
+
176
+ if (rung === 1 && args.effort === 'high') {
177
+ const designed = await design(rung, 'The task is classified as high effort; design it before any implementation.', 'design')
178
+ if (!designed)
179
+ return designIncomplete(effort)
180
+ }
181
+
144
182
  phase('Implement')
145
183
  log(`rung ${rung}/${rungs.length} @ ${effort}: implementing`)
146
184
  const report = await ask(implementerPrompt(spec, feedback), {
@@ -159,15 +197,11 @@ for (const [index, effort] of rungs.entries()) {
159
197
  attempts.push({ rung, effort, outcome: 'blocked', reason: report.question, question: report.question })
160
198
  if (rung === rungs.length)
161
199
  return { status: 'blocked', question: report.question, attempts }
162
- spec = await ask(architectPrompt(`The implementer stopped on this question:\n${report.question}`), {
163
- agentType: 'architect',
164
- effort: 'xhigh',
165
- phase: 'Design',
166
- label: `design after blocked ${rung}`,
167
- schema: SPEC,
168
- })
200
+ const designed = await design(rung, `The implementer stopped on this question:\n${report.question}`, `design after blocked ${rung}`)
201
+ if (!designed && args.effort === 'high')
202
+ return designIncomplete(effort, report.question)
169
203
  feedback = null
170
- log(`rung ${rung}/${rungs.length} @ ${effort}: blocked, architect answered`)
204
+ log(`rung ${rung}/${rungs.length} @ ${effort}: blocked, architect ${designed ? 'answered' : 'did not answer'}`)
171
205
  continue
172
206
  }
173
207
 
@@ -194,8 +228,8 @@ for (const [index, effort] of rungs.entries()) {
194
228
 
195
229
  if (passed) {
196
230
  return {
197
- status: 'done',
198
- effort,
231
+ status: designFailed && !designComplete ? 'degraded' : 'done',
232
+ effort: performedEffort(effort),
199
233
  attempts,
200
234
  files: report.files,
201
235
  summary: report.summary,
@@ -214,16 +248,11 @@ for (const [index, effort] of rungs.entries()) {
214
248
  feedback = `Security invariant failed: ${verdict.securityFinding}\n${feedback}`
215
249
 
216
250
  if (rung === rungs.length - 1) {
217
- phase('Design')
218
251
  log(`rung ${rung}/${rungs.length} failed twice: architect redesigns before the last rung`)
219
- spec = await ask(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.`), {
220
- agentType: 'architect',
221
- effort: 'xhigh',
222
- phase: 'Design',
223
- label: 'design before last rung',
224
- schema: SPEC,
225
- })
252
+ const designed = await design(rung, `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.`, 'design before last rung')
253
+ if (!designed && args.effort === 'high')
254
+ return designIncomplete(effort)
226
255
  }
227
256
  }
228
257
 
229
- return { status: 'failed', attempts, lastFailure: feedback }
258
+ return { status: 'failed', attempts, lastFailure: feedback, effort: performedEffort(rungs[rungs.length - 1]) }
@@ -0,0 +1,26 @@
1
+ # Decisions
2
+
3
+ One file per decision that shapes what this project is allowed to claim. A decision lands here when
4
+ reversing it would cost more than making it did, or when a later reader would otherwise re-open it
5
+ from scratch. Read this directory before re-opening a question it already answers, and add a record
6
+ rather than restating a decision in a plan or a commit message.
7
+
8
+ Each record carries four sections: the context it was taken in, the decision itself, the
9
+ consequences it accepts, and how it is enforced.
10
+
11
+ Enforcement is named on a scale, so the strength of a decision is visible rather than implied:
12
+
13
+ | Level | Enforced by |
14
+ |---|---|
15
+ | L0 | text only — nothing checks it |
16
+ | L1 | review — a human is the check |
17
+ | L2 | a local hook — bypassable with `--no-verify` |
18
+ | L3 | CI — it runs on every push |
19
+ | L4 | CI that blocks the merge |
20
+
21
+ A decision enforced only by review says so; that is the honest answer, not a gap to hide.
22
+
23
+ Records are named `NNNN-a-short-title.md` and numbered in the order they were taken.
24
+
25
+ | # | Decision | Enforced at |
26
+ |---|---|---|
@@ -68,7 +68,9 @@ isolated and manageable — and to treat accidental complexity as a defect.
68
68
  reconsider the decomposition, coupling or abstraction instead of pushing through.
69
69
 
70
70
  The checklists that turn these principles into questions to answer before a change are in
71
- [checklists.md](checklists.md).
71
+ [checklists.md](checklists.md). A decision that shapes what this project may claim is recorded once in
72
+ [decisions/](decisions/README.md), with the level at which it is enforced, instead of being restated in
73
+ a plan or a commit message.
72
74
 
73
75
  ## Security
74
76