@amsterdamdatalabs/enact-extensions 0.1.12 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +11 -12
  2. package/dist/create/enact.js +1 -1
  3. package/dist/create/enact.js.map +1 -1
  4. package/dist/create/index.d.ts +4 -3
  5. package/dist/create/index.d.ts.map +1 -1
  6. package/dist/create/index.js +9 -2
  7. package/dist/create/index.js.map +1 -1
  8. package/dist/index.d.ts +8 -6
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -3
  11. package/dist/index.js.map +1 -1
  12. package/dist/install.d.ts +5 -0
  13. package/dist/install.d.ts.map +1 -1
  14. package/dist/install.js +10 -3
  15. package/dist/install.js.map +1 -1
  16. package/dist/internal/agents.d.ts +6 -1
  17. package/dist/internal/agents.d.ts.map +1 -1
  18. package/dist/internal/agents.js +8 -4
  19. package/dist/internal/agents.js.map +1 -1
  20. package/dist/internal/claude.d.ts +24 -0
  21. package/dist/internal/claude.d.ts.map +1 -1
  22. package/dist/internal/claude.js +99 -0
  23. package/dist/internal/claude.js.map +1 -1
  24. package/dist/internal/platform.d.ts +3 -1
  25. package/dist/internal/platform.d.ts.map +1 -1
  26. package/dist/internal/platform.js +7 -1
  27. package/dist/internal/platform.js.map +1 -1
  28. package/dist/internal/types.d.ts +2 -1
  29. package/dist/internal/types.d.ts.map +1 -1
  30. package/dist/principles.d.ts +28 -0
  31. package/dist/principles.d.ts.map +1 -0
  32. package/dist/principles.js +159 -0
  33. package/dist/principles.js.map +1 -0
  34. package/extensions/dev-state/.agents/plugin.json +2 -1
  35. package/extensions/enact-context/.agents/plugin.json +2 -1
  36. package/extensions/enact-context/hooks/hooks.json +0 -10
  37. package/extensions/enact-context/skills/enact-context/SKILL.md +14 -12
  38. package/extensions/enact-context/skills/enact-context/scripts/install.sh +7 -7
  39. package/extensions/enact-core/.agents/plugin.json +2 -1
  40. package/extensions/enact-core/OPERATING-PRINCIPLES.md +7 -0
  41. package/extensions/enact-core/hooks/hooks.json +12 -0
  42. package/extensions/enact-evolve/.agents/plugin.json +47 -0
  43. package/extensions/enact-evolve/agents/evolve-session-analyst.toml +37 -0
  44. package/extensions/enact-evolve/skills/session-analysis/SKILL.md +98 -0
  45. package/extensions/enact-evolve/skills/session-analysis/scripts/run-evolve-analysis.sh +343 -0
  46. package/extensions/enact-factory/.agents/plugin.json +2 -2
  47. package/extensions/enact-factory/agents/architect.toml +9 -5
  48. package/extensions/enact-factory/agents/code-reviewer.toml +9 -5
  49. package/extensions/enact-factory/agents/critic.toml +9 -5
  50. package/extensions/enact-factory/agents/executor.toml +4 -1
  51. package/extensions/enact-factory/agents/explore.toml +4 -1
  52. package/extensions/enact-factory/agents/planner.toml +4 -1
  53. package/extensions/enact-factory/agents/verifier.toml +9 -5
  54. package/extensions/enact-factory/skills/advisor/SKILL.md +82 -0
  55. package/extensions/enact-factory/skills/ai-slop-cleaner/SKILL.md +6 -1
  56. package/extensions/enact-factory/skills/autonomous-runner/SKILL.md +347 -0
  57. package/extensions/enact-factory/skills/azdo-ci-strategy/SKILL.md +42 -15
  58. package/extensions/enact-factory/skills/committee/SKILL.md +80 -0
  59. package/extensions/enact-factory/skills/deep-interview/SKILL.md +9 -13
  60. package/extensions/enact-factory/skills/drive-loop/SKILL.md +161 -31
  61. package/extensions/enact-factory/skills/drive-loop/references/contract-schema.md +26 -6
  62. package/extensions/enact-factory/skills/handoff/SKILL.md +72 -0
  63. package/extensions/enact-factory/skills/hyperplan/SKILL.md +11 -3
  64. package/extensions/enact-factory/skills/looplan/SKILL.md +34 -17
  65. package/extensions/enact-factory/skills/plan/SKILL.md +40 -8
  66. package/extensions/enact-factory/skills/remove-deadcode/SKILL.md +6 -1
  67. package/extensions/enact-factory/skills/research/SKILL.md +14 -4
  68. package/extensions/enact-factory/skills/review/SKILL.md +21 -2
  69. package/extensions/enact-factory/skills/security-research/SKILL.md +5 -2
  70. package/extensions/enact-factory/skills/tdd/SKILL.md +7 -1
  71. package/extensions/enact-factory/skills/testing-strategy/SKILL.md +5 -0
  72. package/extensions/enact-factory/skills/trace/SKILL.md +5 -0
  73. package/extensions/enact-factory/skills/ultraqa/SKILL.md +21 -15
  74. package/extensions/enact-factory/skills/work-with-workitem/SKILL.md +5 -0
  75. package/extensions/enact-factory/skills/workitem-triage/SKILL.md +5 -0
  76. package/extensions/enact-loop/.agents/plugin.json +5 -4
  77. package/extensions/enact-loop/scripts/validate.mjs +123 -0
  78. package/extensions/enact-loop/skills/enact-loop/SKILL.md +189 -30
  79. package/extensions/enact-wiki/.agents/plugin.json +2 -1
  80. package/extensions/net-revenue-management/.agents/plugin.json +2 -1
  81. package/extensions/plugin-dev/.agents/plugin.json +2 -1
  82. package/extensions/plugin-dev/skills/start/SKILL.md +3 -3
  83. package/package.json +1 -1
  84. package/scripts/check-hooks.mjs +5 -5
  85. package/scripts/check-principles.mjs +19 -4
  86. package/scripts/enact-extensions.mjs +237 -90
  87. package/scripts/lib/hooks.mjs +61 -217
  88. package/scripts/lib/migrate-artifacts.mjs +144 -0
  89. package/scripts/lib/principles.mjs +109 -0
  90. package/scripts/lib/provision-mcp.mjs +1 -1
  91. package/scripts/lib/run-install.mjs +72 -2
  92. package/scripts/lib/run-prune.mjs +23 -2
  93. package/scripts/lib/run-sync.mjs +4 -1
  94. package/scripts/postinstall.mjs +6 -6
  95. package/scripts/setup-enact-context.sh +20 -15
  96. package/scripts/version-bump.sh +22 -1
  97. package/spec/codex.json +5 -0
  98. package/spec/enact.json +3 -3
  99. package/spec/enact.md +1 -4
  100. package/spec/index.json +1 -1
  101. package/extensions/enact-factory/hooks/hooks.json +0 -14
  102. package/extensions/enact-operator/.agents/plugin.json +0 -56
  103. package/extensions/enact-operator/.app.json +0 -3
  104. package/extensions/enact-operator/.mcp.json +0 -10
  105. package/extensions/enact-operator/_taxonomy.md +0 -86
  106. package/extensions/enact-operator/agents/README.md +0 -5
  107. package/extensions/enact-operator/agents/architect.toml +0 -25
  108. package/extensions/enact-operator/agents/code-reviewer.toml +0 -24
  109. package/extensions/enact-operator/agents/critic.toml +0 -30
  110. package/extensions/enact-operator/agents/executor.toml +0 -24
  111. package/extensions/enact-operator/agents/explore.toml +0 -23
  112. package/extensions/enact-operator/agents/planner.toml +0 -24
  113. package/extensions/enact-operator/agents/verifier.toml +0 -24
  114. package/extensions/enact-operator/docs/skill-variants.md +0 -44
  115. package/extensions/enact-operator/hooks/hooks.json +0 -91
  116. package/extensions/enact-operator/skills/ai-slop-cleaner/SKILL.md +0 -50
  117. package/extensions/enact-operator/skills/analyze/SKILL.md +0 -91
  118. package/extensions/enact-operator/skills/ask/SKILL.md +0 -47
  119. package/extensions/enact-operator/skills/autopilot/SKILL.md +0 -170
  120. package/extensions/enact-operator/skills/autoresearch-goal/SKILL.md +0 -79
  121. package/extensions/enact-operator/skills/cancel/SKILL.md +0 -99
  122. package/extensions/enact-operator/skills/configure-notifications/SKILL.md +0 -77
  123. package/extensions/enact-operator/skills/deep-interview/SKILL.md +0 -80
  124. package/extensions/enact-operator/skills/doctor/SKILL.md +0 -48
  125. package/extensions/enact-operator/skills/hud/SKILL.md +0 -49
  126. package/extensions/enact-operator/skills/hyperplan/SKILL.md +0 -47
  127. package/extensions/enact-operator/skills/plan/SKILL.md +0 -78
  128. package/extensions/enact-operator/skills/ralph/SKILL.md +0 -201
  129. package/extensions/enact-operator/skills/ralph/gemini.md +0 -18
  130. package/extensions/enact-operator/skills/ralplan/SKILL.md +0 -151
  131. package/extensions/enact-operator/skills/remove-deadcode/SKILL.md +0 -45
  132. package/extensions/enact-operator/skills/research/SKILL.md +0 -74
  133. package/extensions/enact-operator/skills/review/SKILL.md +0 -58
  134. package/extensions/enact-operator/skills/security-research/SKILL.md +0 -54
  135. package/extensions/enact-operator/skills/setup/SKILL.md +0 -91
  136. package/extensions/enact-operator/skills/setup/scripts/install.sh +0 -50
  137. package/extensions/enact-operator/skills/skill/SKILL.md +0 -82
  138. package/extensions/enact-operator/skills/tdd/SKILL.md +0 -59
  139. package/extensions/enact-operator/skills/team/SKILL.md +0 -199
  140. package/extensions/enact-operator/skills/trace/SKILL.md +0 -41
  141. package/extensions/enact-operator/skills/ultragoal/SKILL.md +0 -99
  142. package/extensions/enact-operator/skills/ultraqa/SKILL.md +0 -113
  143. package/extensions/enact-operator/skills/ultrawork/SKILL.md +0 -145
  144. package/extensions/enact-operator/skills/ultrawork/planner.md +0 -28
  145. package/extensions/enact-operator/skills/wiki/SKILL.md +0 -41
  146. package/extensions/enact-operator/skills/work-with-workitem/SKILL.md +0 -51
  147. /package/extensions/{enact-operator → enact-evolve}/assets/icon.png +0 -0
  148. /package/extensions/{enact-operator → enact-evolve}/assets/logo.png +0 -0
@@ -5,13 +5,19 @@ description: "Contract-runner loop engine. Drives declared stages (mechanical an
5
5
 
6
6
  # enact-loop
7
7
 
8
+ > **Storage is GLOBAL — never write to a repo-local `.enact` dir.** Resolve the project's global dir:
9
+ > 1. `ROOT="$(git rev-parse --show-toplevel)"`
10
+ > 2. In `~/.enact/workspaces.json` → `workspaces`, find the entry whose `.root` == `$ROOT`; its object key is `<name>-<id>` (e.g. `enact-loop-b9762d`).
11
+ > 3. Loop runtime state lives under `~/.enact/loop/projects/<key>/…`.
12
+
8
13
  ## Purpose
9
14
 
10
15
  `$enact-loop` is the persistence wrapper for bounded work. Given a contract (an ordered set of
11
16
  stages), the loop drives each stage to closure and refuses to exit until every required stage has
12
- passed. Mechanical stages self-report via command evidence. Judgment stages require an **independent
13
- grader** running on a different model the engine rejects any verdict self-reported by the
14
- executor.
17
+ passed. Mechanical stages are graded by the engine: `loop_grade` runs the stage `command` in the
18
+ loop root and derives pass/fail from exit code (caller-supplied `status`/`evidence` are rejected).
19
+ Judgment stages require an **independent grader** on a different model — the executor must not
20
+ fabricate or self-report judgment verdicts.
15
21
 
16
22
  ## Use When
17
23
 
@@ -32,6 +38,7 @@ Stage {
32
38
  name: string
33
39
  type: 'mechanical' | 'judgment'
34
40
  required: boolean
41
+ requires?: string[] # stage ids that must pass before this stage runs
35
42
  command?: string # mechanical stages only
36
43
  grader?: GraderSpec # judgment stages only
37
44
  passCriteria?: string
@@ -42,17 +49,24 @@ GraderSpec {
42
49
  model?: string # e.g. claude-opus-4-8 — always specify; omitting causes E_GRADER_MODEL_UNSET
43
50
  harness?: paseo | subagent # default: paseo
44
51
  rounds?: integer # max grading rounds before escalation (default: 3)
52
+ timeoutMs?: integer # grader session timeout (default: 30 min); timeout → grader-unavailable blocker
53
+ fallbackModels?: string[] # tried in order on re-dispatch after spawn failure (529/overload)
45
54
  }
46
55
  ```
47
56
 
48
57
  ## Stage Types
49
58
 
50
- **Mechanical** stages are automated: the executor runs the declared `command` and self-reports the
51
- result via `loop_grade`. No independent grader is needed.
59
+ **Mechanical** stages are engine-run: call `loop_grade({ stageId, timeoutMs? })` and the runtime
60
+ executes the stage `command` in the loop root cwd, capturing exit code and output. Caller-supplied
61
+ `status` or `evidence` are rejected. No independent grader is needed.
52
62
 
53
- **Judgment** stages require an independent grader. The executor dispatches via
54
- `loop_grader_dispatch`; the grader records its verdict via `loop_grader_verdict`. The engine
55
- rejects any verdict where grader identity overlaps with executor identity.
63
+ **Judgment** stages require an independent grader. The executor/driver dispatches via
64
+ `loop_grader_dispatch`, then records the verdict via `loop_grader_verdict` (default:
65
+ `captureSource: "relayed"` — grader returns a verdict envelope; driver records with the minted
66
+ `continuationId`). A grader child session may call `loop_grader_verdict` directly (`captureSource:
67
+ "direct"`) but only with a `continuationId` minted by dispatch — otherwise
68
+ `E_UNKNOWN_GRADER_SESSION`. The engine rejects verdicts where grader identity overlaps executor
69
+ identity.
56
70
 
57
71
  ## The Independence Rule
58
72
 
@@ -89,7 +103,7 @@ idle → running → verifying → completed (terminal)
89
103
  - ambiguous goal → clarify first, then start the loop with the refined goal
90
104
  - goal is clear → start immediately: `loop_start`
91
105
  - blocked on external dependency → `loop_block`, resolve, then `loop_advance` back to `running`
92
- - all required stages passed → write summary.md, then `loop_complete` (only from phase=verifying)
106
+ - all required stages passed → `enact-loop summary write`, then `loop_complete` (only from phase=verifying)
93
107
 
94
108
  ## Workflow
95
109
 
@@ -98,14 +112,19 @@ idle → running → verifying → completed (terminal)
98
112
  ```
99
113
  loop_start goal="<goal>" preset=<preset>|contract=<stages JSON>
100
114
  ```
115
+ Built-in presets: `frontend-ui` (5 mechanical: lint/typecheck/test/build/visual,
116
+ then **two** judgment stages — `code-review` **and** `verify` — the shape that
117
+ reliably catches both correctness and acceptance defects), `docs` (mechanical +
118
+ one lighter `review`), plus `docs-workflow` / `custom-pipeline`. Use a preset to
119
+ get the right judgment shape by default instead of hand-authoring stages.
101
120
  3. Implement the work while in phase `running`.
102
121
  4. Advance to verification:
103
122
  ```
104
123
  loop_advance toPhase=verifying
105
124
  ```
106
- 5. For each **mechanical** stage: run the command, then `loop_grade`.
107
- 6. For each **judgment** stage: call `loop_grader_dispatch`, await the grader's
108
- `loop_grader_verdict`.
125
+ 5. For each **mechanical** stage (respecting `requires` preconditions): `loop_grade({ stageId })`.
126
+ 6. For each **judgment** stage: `loop_grader_dispatch({ stageId })` → spawn grader → record via
127
+ `loop_grader_verdict` (`captureSource: "relayed"` from driver, or `"direct"` from grader child).
109
128
  7. When all required stages are `passed`: run `enact-loop summary write` (pipe body sections;
110
129
  CLI stamps the fingerprint header), then `loop_complete`.
111
130
  8. If a stage fails: fix the root cause, do not fabricate evidence, re-run and re-grade.
@@ -115,21 +134,42 @@ idle → running → verifying → completed (terminal)
115
134
  **Step 1 — Dispatch a grader:**
116
135
 
117
136
  ```
118
- loop_grader_dispatch stageId=<id>
137
+ loop_grader_dispatch stageId=<id> [model=<grader-model>]
119
138
  ```
120
139
 
121
140
  - mints an unforgeable `continuationId` and `graderSessionId` via the runtime
122
- - resolves the grader model from `stage.grader.model ?? loop.graderModel`
141
+ - resolves the grader model: an explicit `model` override is used **verbatim**
142
+ (after the `≠ executor` check); otherwise `stage.grader.model ?? loop.graderModel`,
143
+ advancing through `fallbackModels` on re-dispatch after a spawn failure
123
144
  - requires the resolved model to differ from the executor's (`E_NO_DIVERSITY` otherwise)
124
145
  - launches the grader agent (read-only, same cwd) via Paseo `--provider <graderModel>`
125
146
  - returns the `continuationId` you must pass to step 2
126
147
 
127
- **Step 2 — Record the grader's verdict** (grader calls this, not the executor):
148
+ **Step 2 — Record the grader's verdict** (driver-relayed is the default):
128
149
 
129
150
  ```
130
- loop_grader_verdict stageId=<id> verdict=GO|NO-GO evidence="<findings>" continuationId=<id>
151
+ loop_grader_verdict stageId=<id> continuationId=<id> verdict=GO|NO-GO evidence="<findings>"
152
+ graderAgentId=<id> graderSessionId=<id> captureSource=relayed
153
+ recordedByAgentId=<executor> recordedByModel=<model> recordedBySessionId=<session>
154
+ [graderModelActual=<model-that-actually-ran>]
131
155
  ```
132
156
 
157
+ `graderModelActual` records the model that *actually* ran the grader (e.g. the
158
+ Paseo-resolved provider), separate from the dispatch-resolved request — the
159
+ verdict stores it as the authoritative grader model (keeping the dispatched one
160
+ as `graderModelDispatched`) and the independence check runs against it. Supply it
161
+ when the host ran a different/fallback model than dispatched; omit it otherwise.
162
+
163
+ Grader child sessions may call `loop_grader_verdict` directly with `captureSource=direct` instead.
164
+ Direct calls without a dispatch-minted `continuationId` fail with `E_UNKNOWN_GRADER_SESSION`.
165
+ Grader MCP sessions are **runtime-gated**: only `loop_grader_verdict`, `loop_status`,
166
+ `loop_stop_evaluate` — pass `_meta.enactLoop` with `sessionRole: grader` and bound `continuationId`.
167
+ Incomplete grader metadata (e.g. `role=grader` without `sessionId`) fails closed with
168
+ `E_INCOMPLETE_GRADER_SESSION` rather than defaulting to executor.
169
+ Grader timeout surfaces as `grader-unavailable` blocker — re-dispatch; never assume GO.
170
+ Spawn failure (529): `loop_grader_report_spawn_failure` then re-dispatch (uses `fallbackModels`).
171
+ While grader is in flight, Stop **allows** exit without `loop_pause` churn.
172
+
133
173
  The engine rejects the verdict if grader identity overlaps executor identity or the
134
174
  `continuationId` was not minted by this runtime.
135
175
 
@@ -137,9 +177,68 @@ The engine rejects the verdict if grader identity overlaps executor identity or
137
177
  `grader-independence-missing` if you do. If a grader returns NO-GO: fix the issue, stay in
138
178
  `verifying`, and re-dispatch. Exceeding the round limit escalates with a blocker.
139
179
 
180
+ ## Operational notes — grader independence, fallback, and worktree discipline
181
+
182
+ > Context: in a validated 3-wave run, independent graders caught FIVE real defects that
183
+ > all mechanical gates passed — model-distinct cross-vendor grading is the mechanism that
184
+ > surfaces defects mechanical gates cannot see.
185
+
186
+ ### Grader independence is MODEL-DISTINCT
187
+
188
+ The independence rule is checked by model identity, not by vendor or surface name.
189
+ **A sonnet executor + a cursor-sonnet grader is NOT distinct** — `cursor/claude-sonnet-4-6`
190
+ runs the same underlying model. The engine will emit `grader-independence-missing` and
191
+ block closure. Ensure the resolved `grader.model` differs from the executor's model string.
192
+
193
+ **Default cheap graders:** **minimax M3** (opencode) and **kimi K2.7** are vendor-distinct from
194
+ both sonnet and codex, making them always-independent defaults (~80% of grading). Escalate to
195
+ **opus 4.8** or **gpt-5.5** for high-risk judgment (a11y / visual / security / architecture).
196
+ `cursor` is only distinct when the executor is opus-tier (cursor runs claude-sonnet).
197
+
198
+ ### fallbackModels rotation on spawn failure (529 / overload)
199
+
200
+ 1. Call `loop_grader_report_spawn_failure({ continuationId, error })` to record the failure.
201
+ 2. Re-call `loop_grader_dispatch` — with no `model`, the runtime auto-selects the next
202
+ `fallbackModels` entry; pass an explicit `model` to **pin a specific grader** (the override
203
+ is honored verbatim and is not consumed by the rotation pointer).
204
+ 3. Record the verdict under the minted `continuationId`; set `graderModelActual` to the model
205
+ that actually ran so provenance is accurate. Independence holds as long as the actual grader
206
+ model ≠ executor model.
207
+
208
+ ### Worktree fingerprint — when verdicts are invalidated vs. preserved
209
+
210
+ | Change type | Effect on verdicts |
211
+ |---|---|
212
+ | Edit a **tracked** source file | Fingerprint changes → all prior `passed` verdicts invalidated (`BLOCKER_FILESYSTEM_STATE_CHANGED`) → re-grade required |
213
+ | Add an **untracked** new file (e.g. a new test file) | Fingerprint unchanged → prior GO verdicts remain valid |
214
+
215
+ This means you can add new test files mid-loop without forcing re-grading of already-passed stages.
216
+ Never use `git stash` mid-loop — it mutates tracked-file state and invalidates all verdicts.
217
+
218
+ ### `loop_pause` / `loop_resume` around long async grading
219
+
220
+ While a grader is dispatched and in flight, the Stop boulder already allows exit without `loop_pause`.
221
+ Use `loop_pause` / `loop_resume` explicitly when you need to hand off control for extended human
222
+ review or an async wait that is not grader-dispatch-in-flight, to avoid unnecessary Stop churn.
223
+
224
+ ### Graders are read-only
225
+
226
+ Grader child sessions must review the artifact and emit a verdict — nothing else.
227
+ They MUST NOT call `loop_*` management tools (complete, pause, resume, retarget, block, abort)
228
+ or `factory_*` tools. The runtime rejects these calls from grader sessions.
229
+
230
+ **Verdict capture is STRUCTURED, not log-scraped.** Dispatch with:
231
+ `paseo run --output-schema <verdict.json> --json --label continuation=<cid>`
232
+ — stdout is the validated `{verdict, evidence}` object (verified on codex / cursor / opencode / kimi;
233
+ coexists with tool use). Recover actual grader model + cost via
234
+ `paseo ls -a --label continuation=<cid> --json` + `paseo inspect <id> --json`.
235
+ Watch live: recover the id by label then `paseo attach <id>`.
236
+ Fallback only: if structured capture is unavailable, embed evidence inline in the `evidence` field.
237
+ Design doc: `enact-loop/docs/design/2026-06-22-paseo-verdict-capture.md`.
238
+
140
239
  ## Access Control — `.enact/` is CLI-mediated
141
240
 
142
- The agent **NEVER edits `.enact/` files directly**. All loop state mutation goes through the
241
+ The agent **NEVER edits `~/.enact/` files directly**. All loop state mutation goes through the
143
242
  `enact-loop` CLI or `loop_*` MCP tools — never raw file edits. Read state via:
144
243
 
145
244
  ```bash
@@ -147,9 +246,41 @@ enact-loop status --json # current loop state as JSON
147
246
  enact-loop summary show # display the current closure summary
148
247
  ```
149
248
 
249
+ ## Session roles and closure authority
250
+
251
+ | Role | May call | Must NOT call |
252
+ |------|----------|---------------|
253
+ | **Executor/driver** | `loop_grade`, `loop_grader_dispatch`, `loop_grader_verdict` (relayed), `loop_pause`, `loop_resume`, `loop_retarget`, `loop_complete`, `enact-loop summary write` | — |
254
+ | **Grader child** | `loop_grader_verdict` (direct), `loop_status`, `loop_stop_evaluate` | `loop_complete`, summary write, `loop_pause`/`loop_resume`/`loop_retarget`, closure tools |
255
+
256
+ Grader child sessions: review, emit verdict, stop cleanly. They have **no closure authority**.
257
+
258
+ **No `git stash` during an active loop** — stash mid-loop invalidates worktree state and stale
259
+ verdict fingerprints.
260
+
261
+ ## Worktree fingerprint
262
+
263
+ Each verdict is stamped with a worktree fingerprint. When the workspace changes (e.g. after
264
+ retarget or filesystem drift), stale `passed` verdicts are invalidated (`BLOCKER_FILESYSTEM_STATE_CHANGED`).
265
+ `loop_retarget` preserves verdicts only when `id + type + worktreeFingerprint` match unchanged stages.
266
+
267
+ ## Workspace root resolution
268
+
269
+ Loop state is keyed to the **launch workspace** — no ancestor `.git` walk:
270
+
271
+ | Surface | Root source |
272
+ |---------|-------------|
273
+ | CLI subcommands | `process.cwd()` — run from the driven project |
274
+ | MCP | `enact-loop mcp <workspace>` positional arg (bundle uses `"."` = host MCP cwd) |
275
+ | Hooks | `payload.cwd` from hook JSON |
276
+
277
+ Wrong cwd → different project store → "no active loop".
278
+
150
279
  ## Closure
151
280
 
152
- Closure requires a written, CURRENT `.enact/loop/state/summary.md`. When all required stages pass
281
+ Closure requires a written, CURRENT timestamped summary under
282
+ `~/.enact/loop/projects/<key>/state/<YYMMDDHH>-loop-summary.md`. The runtime records the active
283
+ path in `~/.enact/loop/projects/<key>/state/summary-current.json`. When all required stages pass
153
284
  (and every judgment stage has an independent verdict), the loop is closure-ready and enters the
154
285
  `verifying` phase. The Stop boulder then **BLOCKS exit** and injects a generic markdown
155
286
  **TEMPLATE** whose first line is a fingerprint header:
@@ -195,6 +326,12 @@ EOF
195
326
  Once a current summary exists, the boulder allows exit. `completeLoop` / `blockLoop` / `abortLoop`
196
327
  all **REQUIRE** a current summary and throw `E_CLOSURE_SUMMARY_MISSING` otherwise.
197
328
 
329
+ When a later `loop_start` begins a new loop, the prior terminal loop's active state is archived
330
+ under `~/.enact/loop/projects/<key>/archive/<loop-id>-<contract-id>/` before the new active
331
+ `state/loop.json` is written. That archive keeps the closed loop's `loop.json`,
332
+ `closure-manifest.json`, `grader-sessions.json`, `summary-current.json`, and every timestamped
333
+ closure summary for auditability.
334
+
198
335
  **This summary is the handoff**: enact-factory's brain reads it (together with the Paseo agent's
199
336
  last message) to decide the next action after the loop completes.
200
337
 
@@ -211,15 +348,15 @@ last message) to decide the next action after the loop completes.
211
348
  | `loop_start` | Start loop with goal + contract/preset; stamps executorIdentity |
212
349
  | `loop_status` | Read current loop state |
213
350
  | `loop_advance` | Phase transition |
214
- | `loop_grade` | Record a mechanical stage result (self-report; status + command evidence) |
351
+ | `loop_grade` | Run a mechanical stage command in loop root; args: `stageId`, optional `timeoutMs` |
215
352
  | `loop_grader_dispatch` | Dispatch an independent grader for a judgment stage (returns continuationId) |
216
- | `loop_grader_verdict` | Record an independent grader's GO/NO-GO (grader calls this, not executor) |
353
+ | `loop_grader_verdict` | Record GO/NO-GO; `captureSource`: `relayed` (driver default) or `direct` (grader child) |
217
354
  | `loop_pause` | Pause the loop for human review; Stop allows exit while paused |
218
355
  | `loop_resume` | Resume a paused loop; restores the pre-pause phase |
219
356
  | `loop_retarget` | Replace the active contract with a new one (same loop id) |
220
- | `loop_block` | Mark blocked with a reason; requires current summary.md |
221
- | `loop_complete` | Close the loop; requires current summary.md + phase=verifying + all stages passed |
222
- | `loop_abort` | Abort with a reason; requires current summary.md |
357
+ | `loop_block` | Mark blocked with a reason; requires current closure summary |
358
+ | `loop_complete` | Close the loop; requires current closure summary + phase=verifying + all stages passed |
359
+ | `loop_abort` | Abort with a reason; requires current closure summary |
223
360
  | `loop_contract_parity` | Run contract-parity check |
224
361
  | `loop_stop_evaluate` | Evaluate Stop boulder policy (called by the Stop hook) |
225
362
 
@@ -230,7 +367,7 @@ last message) to decide the next action after the loop completes.
230
367
  enact-loop status --json # current loop state as JSON
231
368
  enact-loop summary show # display the current closure summary
232
369
 
233
- # Write the closure summary (CLI stamps the fingerprint header; never write summary.md directly)
370
+ # Write the closure summary (CLI stamps fingerprint header into a new YYMMDDHH-loop-summary.md; never write summaries directly)
234
371
  enact-loop summary write <<'EOF'
235
372
  ## Summary
236
373
  ...
@@ -254,18 +391,21 @@ The Stop hook calls `enact-loop hook stop` → `loop_stop_evaluate`. The boulder
254
391
  - any `required` stage is still `pending` or `failed`
255
392
  - any judgment stage is `passed` but lacks a valid independent verdict (`grader-independence-missing`)
256
393
  - a required grader is unavailable (`grader-unavailable`)
257
- - the loop is closure-ready (all stages passed, independence satisfied) but `summary.md` is absent
258
- or stale (`closure-summary-missing`) — resolve by running `enact-loop summary write`
394
+ - the loop is closure-ready (all stages passed, independence satisfied) but no current closure summary exists
395
+ or the summary fingerprint is stale (`closure-summary-missing`) — resolve by running `enact-loop summary write`
259
396
  - the loop phase is not terminal (exception: `paused` allows exit)
260
397
 
261
398
  ## State
262
399
 
263
400
  `.enact/` is CLI-mediated — never read or write `.enact/` files directly.
264
401
 
265
- - Read state: `enact-loop status --json` (loop state) · `enact-loop summary show` (closure summary)
402
+ - Read state: `enact-loop status --json` (loop state) · `enact-loop summary show` (current closure summary)
266
403
  - Mutate state: `loop_*` MCP tools or `enact-loop` CLI subcommands only
267
- - Closure summary at `.enact/loop/state/summary.md` is written via `enact-loop summary write`
268
- (CLI stamps the fingerprint header); it is read by enact-factory's brain after loop completion.
404
+ - Active loop state stays in `~/.enact/loop/projects/<key>/state/`; closed loops move to
405
+ `~/.enact/loop/projects/<key>/archive/<loop-id>-<contract-id>/` on the next `loop_start`
406
+ - Closure summaries are append-only timestamped files at
407
+ `~/.enact/loop/projects/<key>/state/<YYMMDDHH>-loop-summary.md`, written via `enact-loop summary write`
408
+ (CLI stamps the fingerprint header and updates `summary-current.json`); read by enact-factory's brain after loop completion.
269
409
  - SessionStart hook auto-resumes an active (`running` or `verifying`) loop when the prompt
270
410
  contains `$enact-loop`. Paused loops are NOT auto-resumed.
271
411
 
@@ -281,9 +421,16 @@ stages:
281
421
  name: Tests passing
282
422
  type: mechanical
283
423
  required: true
284
- command: npm test
424
+ command: bash scripts/ci-local.sh
285
425
  passCriteria: All tests exit 0
286
426
 
427
+ - id: typecheck
428
+ name: Type check clean
429
+ type: mechanical
430
+ required: true
431
+ command: npm run typecheck && npm run typecheck:tests
432
+ passCriteria: No type errors in src or test files
433
+
287
434
  - id: lint
288
435
  name: Lint clean
289
436
  type: mechanical
@@ -303,6 +450,18 @@ stages:
303
450
  passCriteria: Reviewer approves with no blocking issues
304
451
  ```
305
452
 
453
+ **Tests stage must mirror CI (hard requirement #9).** A bare `npm test` on the
454
+ host is not acceptable — the host suite can pass green while the same deliverable
455
+ fails the Linux CI agent due to `HOME`/path divergence. The `command` for the
456
+ `tests` stage must invoke a Linux-parity / containerized runner (e.g.
457
+ `bash scripts/ci-local.sh`) that reproduces the CI environment locally.
458
+
459
+ **Include `typecheck:tests` (rule #7).** When the project has a separate
460
+ test-file typecheck script, include it — either as its own mechanical stage or
461
+ folded into the typecheck stage command (`npm run typecheck && npm run
462
+ typecheck:tests`). A src-only `typecheck` lets test-file type regressions slip
463
+ past undetected.
464
+
306
465
  ### Docs Workflow
307
466
 
308
467
  ```yaml
@@ -15,7 +15,8 @@
15
15
  "targets": [
16
16
  "claude",
17
17
  "codex",
18
- "cursor"
18
+ "cursor",
19
+ "enact"
19
20
  ],
20
21
  "skills": "./skills/",
21
22
  "mcpServers": "./.mcp.json",
@@ -17,7 +17,8 @@
17
17
  "targets": [
18
18
  "claude",
19
19
  "codex",
20
- "cursor"
20
+ "cursor",
21
+ "enact"
21
22
  ],
22
23
  "skills": "./skills/",
23
24
  "mcpServers": "./.mcp.json",
@@ -17,7 +17,8 @@
17
17
  "targets": [
18
18
  "claude",
19
19
  "codex",
20
- "cursor"
20
+ "cursor",
21
+ "enact"
21
22
  ],
22
23
  "skills": "./skills/",
23
24
  "agents": "./agents/",
@@ -37,7 +37,7 @@ Welcome to the Enact Plugin Manager!
37
37
  - Canonical manifest: .agents/plugin.json
38
38
  - Shared components: skills/ (passive + invocable), agents/, hooks/, .mcp.json
39
39
  - Host copies: .claude-plugin/, .codex-plugin/, .cursor-plugin/
40
- - Example: ../enact-operator/extensions/ (sibling top-level submodule of enact-os)
40
+ - Example: extensions/plugin-dev/
41
41
 
42
42
  Load the plugin-structure skill, then run create-plugin for the full workflow.
43
43
  ```
@@ -61,13 +61,13 @@ Option 1:
61
61
  Option 2:
62
62
 
63
63
  - label: "Validate an existing plugin"
64
- - description: "Run plugin-validator on an existing bundle (e.g. ../enact-operator/extensions)."
64
+ - description: "Run plugin-validator on an existing bundle (e.g. extensions/plugin-dev)."
65
65
 
66
66
  ## Step 4: Route Based on Choice
67
67
 
68
68
  **Create a plugin** → invoke `create-plugin` with `$ARGUMENTS`.
69
69
 
70
- **Validate an existing plugin** → invoke `plugin-validator` agent on the path the user provides (default: `../enact-operator/extensions`).
70
+ **Validate an existing plugin** → invoke `plugin-validator` agent on the path the user provides (default: `extensions/plugin-dev`).
71
71
 
72
72
  ---
73
73
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/enact-extensions",
3
- "version": "0.1.12",
3
+ "version": "0.1.25",
4
4
  "description": "Create and validate Enact multi-platform plugin manifests",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -2,11 +2,11 @@
2
2
  /**
3
3
  * Repo guard: validate every plugin's hooks before they ship.
4
4
  *
5
- * Born from real breakage: enact-operator shipped hook entries for a
6
- * doom-loop CLI that had been deleted (module-not-found at runtime), and
7
- * enact-factory's npm package omitted `extensions/` from its files whitelist
8
- * so `enact-factory hook ...` could never load. Both passed every static check
9
- * yet failed the moment Claude Code fired the hook. plugin-dev meanwhile
5
+ * Born from real breakage: a bundle shipped hook entries for a deleted
6
+ * CLI (module-not-found at runtime), and an npm package omitted `extensions/`
7
+ * from its files whitelist so its hook command could never load. Both passed
8
+ * every static check yet failed the moment Claude Code fired the hook.
9
+ * plugin-dev meanwhile
10
10
  * declared a `hooks` component whose hooks.json was `{ "hooks": {} }` — a
11
11
  * component that installs nothing.
12
12
  *
@@ -13,7 +13,8 @@
13
13
  * install).
14
14
  * E3 Skill hygiene. Each `skills/<dir>/` needs a `SKILL.md` with `name` +
15
15
  * `description` frontmatter, and no `README.md`.
16
- * E4 `targets` must not include "enact" (enact is the canonical source).
16
+ * E4 Surface targets are normalized repo-wide:
17
+ * - every bundled plugin targets claude/codex/cursor/enact
17
18
  */
18
19
  import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
19
20
  import { join, dirname } from "node:path";
@@ -26,6 +27,7 @@ const errors = [];
26
27
  const warnings = [];
27
28
  const err = (p, m) => errors.push(`[ERROR] ${p}: ${m}`);
28
29
  const warn = (p, m) => warnings.push(`[warn] ${p}: ${m}`);
30
+ const ALL_SURFACE_TARGETS = ["claude", "codex", "cursor", "enact"];
29
31
 
30
32
  function frontmatter(file) {
31
33
  const t = readFileSync(file, "utf8");
@@ -43,6 +45,11 @@ function isDir(p) {
43
45
  try { return statSync(p).isDirectory(); } catch { return false; }
44
46
  }
45
47
 
48
+ function normalizedTargets(value) {
49
+ if (!Array.isArray(value)) return null;
50
+ return value.filter((entry) => typeof entry === "string");
51
+ }
52
+
46
53
  if (!existsSync(EXT)) {
47
54
  console.error(`No extensions dir at ${EXT}`);
48
55
  process.exit(1);
@@ -62,9 +69,17 @@ for (const name of readdirSync(EXT)) {
62
69
  if ("commands" in m) err(id, `manifest declares "commands" — commands are skills; remove it`);
63
70
  if (isDir(join(root, "commands"))) err(id, `has a commands/ dir — commands are skills; migrate to skills/`);
64
71
 
65
- // E4 — targets must not include enact
66
- if (Array.isArray(m.targets) && m.targets.includes("enact"))
67
- err(id, `targets includes "enact" — enact is the canonical source, never a target`);
72
+ // E4 — repo-wide surface targeting contract
73
+ const expectedTargets = ALL_SURFACE_TARGETS;
74
+ const actualTargets = normalizedTargets(m.targets);
75
+ if (!actualTargets) {
76
+ err(id, `manifest must declare "targets": ${JSON.stringify(expectedTargets)}`);
77
+ } else if (JSON.stringify(actualTargets) !== JSON.stringify(expectedTargets)) {
78
+ err(
79
+ id,
80
+ `targets must be ${JSON.stringify(expectedTargets)}; found ${JSON.stringify(actualTargets)}`,
81
+ );
82
+ }
68
83
 
69
84
  // E2 — declare components that exist on disk
70
85
  for (const comp of ["skills", "agents", "hooks"]) {