@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.
- package/README.md +11 -12
- package/dist/create/enact.js +1 -1
- package/dist/create/enact.js.map +1 -1
- package/dist/create/index.d.ts +4 -3
- package/dist/create/index.d.ts.map +1 -1
- package/dist/create/index.js +9 -2
- package/dist/create/index.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +5 -0
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +10 -3
- package/dist/install.js.map +1 -1
- package/dist/internal/agents.d.ts +6 -1
- package/dist/internal/agents.d.ts.map +1 -1
- package/dist/internal/agents.js +8 -4
- package/dist/internal/agents.js.map +1 -1
- package/dist/internal/claude.d.ts +24 -0
- package/dist/internal/claude.d.ts.map +1 -1
- package/dist/internal/claude.js +99 -0
- package/dist/internal/claude.js.map +1 -1
- package/dist/internal/platform.d.ts +3 -1
- package/dist/internal/platform.d.ts.map +1 -1
- package/dist/internal/platform.js +7 -1
- package/dist/internal/platform.js.map +1 -1
- package/dist/internal/types.d.ts +2 -1
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/principles.d.ts +28 -0
- package/dist/principles.d.ts.map +1 -0
- package/dist/principles.js +159 -0
- package/dist/principles.js.map +1 -0
- package/extensions/dev-state/.agents/plugin.json +2 -1
- package/extensions/enact-context/.agents/plugin.json +2 -1
- package/extensions/enact-context/hooks/hooks.json +0 -10
- package/extensions/enact-context/skills/enact-context/SKILL.md +14 -12
- package/extensions/enact-context/skills/enact-context/scripts/install.sh +7 -7
- package/extensions/enact-core/.agents/plugin.json +2 -1
- package/extensions/enact-core/OPERATING-PRINCIPLES.md +7 -0
- package/extensions/enact-core/hooks/hooks.json +12 -0
- package/extensions/enact-evolve/.agents/plugin.json +47 -0
- package/extensions/enact-evolve/agents/evolve-session-analyst.toml +37 -0
- package/extensions/enact-evolve/skills/session-analysis/SKILL.md +98 -0
- package/extensions/enact-evolve/skills/session-analysis/scripts/run-evolve-analysis.sh +343 -0
- package/extensions/enact-factory/.agents/plugin.json +2 -2
- package/extensions/enact-factory/agents/architect.toml +9 -5
- package/extensions/enact-factory/agents/code-reviewer.toml +9 -5
- package/extensions/enact-factory/agents/critic.toml +9 -5
- package/extensions/enact-factory/agents/executor.toml +4 -1
- package/extensions/enact-factory/agents/explore.toml +4 -1
- package/extensions/enact-factory/agents/planner.toml +4 -1
- package/extensions/enact-factory/agents/verifier.toml +9 -5
- package/extensions/enact-factory/skills/advisor/SKILL.md +82 -0
- package/extensions/enact-factory/skills/ai-slop-cleaner/SKILL.md +6 -1
- package/extensions/enact-factory/skills/autonomous-runner/SKILL.md +347 -0
- package/extensions/enact-factory/skills/azdo-ci-strategy/SKILL.md +42 -15
- package/extensions/enact-factory/skills/committee/SKILL.md +80 -0
- package/extensions/enact-factory/skills/deep-interview/SKILL.md +9 -13
- package/extensions/enact-factory/skills/drive-loop/SKILL.md +161 -31
- package/extensions/enact-factory/skills/drive-loop/references/contract-schema.md +26 -6
- package/extensions/enact-factory/skills/handoff/SKILL.md +72 -0
- package/extensions/enact-factory/skills/hyperplan/SKILL.md +11 -3
- package/extensions/enact-factory/skills/looplan/SKILL.md +34 -17
- package/extensions/enact-factory/skills/plan/SKILL.md +40 -8
- package/extensions/enact-factory/skills/remove-deadcode/SKILL.md +6 -1
- package/extensions/enact-factory/skills/research/SKILL.md +14 -4
- package/extensions/enact-factory/skills/review/SKILL.md +21 -2
- package/extensions/enact-factory/skills/security-research/SKILL.md +5 -2
- package/extensions/enact-factory/skills/tdd/SKILL.md +7 -1
- package/extensions/enact-factory/skills/testing-strategy/SKILL.md +5 -0
- package/extensions/enact-factory/skills/trace/SKILL.md +5 -0
- package/extensions/enact-factory/skills/ultraqa/SKILL.md +21 -15
- package/extensions/enact-factory/skills/work-with-workitem/SKILL.md +5 -0
- package/extensions/enact-factory/skills/workitem-triage/SKILL.md +5 -0
- package/extensions/enact-loop/.agents/plugin.json +5 -4
- package/extensions/enact-loop/scripts/validate.mjs +123 -0
- package/extensions/enact-loop/skills/enact-loop/SKILL.md +189 -30
- package/extensions/enact-wiki/.agents/plugin.json +2 -1
- package/extensions/net-revenue-management/.agents/plugin.json +2 -1
- package/extensions/plugin-dev/.agents/plugin.json +2 -1
- package/extensions/plugin-dev/skills/start/SKILL.md +3 -3
- package/package.json +1 -1
- package/scripts/check-hooks.mjs +5 -5
- package/scripts/check-principles.mjs +19 -4
- package/scripts/enact-extensions.mjs +237 -90
- package/scripts/lib/hooks.mjs +61 -217
- package/scripts/lib/migrate-artifacts.mjs +144 -0
- package/scripts/lib/principles.mjs +109 -0
- package/scripts/lib/provision-mcp.mjs +1 -1
- package/scripts/lib/run-install.mjs +72 -2
- package/scripts/lib/run-prune.mjs +23 -2
- package/scripts/lib/run-sync.mjs +4 -1
- package/scripts/postinstall.mjs +6 -6
- package/scripts/setup-enact-context.sh +20 -15
- package/scripts/version-bump.sh +22 -1
- package/spec/codex.json +5 -0
- package/spec/enact.json +3 -3
- package/spec/enact.md +1 -4
- package/spec/index.json +1 -1
- package/extensions/enact-factory/hooks/hooks.json +0 -14
- package/extensions/enact-operator/.agents/plugin.json +0 -56
- package/extensions/enact-operator/.app.json +0 -3
- package/extensions/enact-operator/.mcp.json +0 -10
- package/extensions/enact-operator/_taxonomy.md +0 -86
- package/extensions/enact-operator/agents/README.md +0 -5
- package/extensions/enact-operator/agents/architect.toml +0 -25
- package/extensions/enact-operator/agents/code-reviewer.toml +0 -24
- package/extensions/enact-operator/agents/critic.toml +0 -30
- package/extensions/enact-operator/agents/executor.toml +0 -24
- package/extensions/enact-operator/agents/explore.toml +0 -23
- package/extensions/enact-operator/agents/planner.toml +0 -24
- package/extensions/enact-operator/agents/verifier.toml +0 -24
- package/extensions/enact-operator/docs/skill-variants.md +0 -44
- package/extensions/enact-operator/hooks/hooks.json +0 -91
- package/extensions/enact-operator/skills/ai-slop-cleaner/SKILL.md +0 -50
- package/extensions/enact-operator/skills/analyze/SKILL.md +0 -91
- package/extensions/enact-operator/skills/ask/SKILL.md +0 -47
- package/extensions/enact-operator/skills/autopilot/SKILL.md +0 -170
- package/extensions/enact-operator/skills/autoresearch-goal/SKILL.md +0 -79
- package/extensions/enact-operator/skills/cancel/SKILL.md +0 -99
- package/extensions/enact-operator/skills/configure-notifications/SKILL.md +0 -77
- package/extensions/enact-operator/skills/deep-interview/SKILL.md +0 -80
- package/extensions/enact-operator/skills/doctor/SKILL.md +0 -48
- package/extensions/enact-operator/skills/hud/SKILL.md +0 -49
- package/extensions/enact-operator/skills/hyperplan/SKILL.md +0 -47
- package/extensions/enact-operator/skills/plan/SKILL.md +0 -78
- package/extensions/enact-operator/skills/ralph/SKILL.md +0 -201
- package/extensions/enact-operator/skills/ralph/gemini.md +0 -18
- package/extensions/enact-operator/skills/ralplan/SKILL.md +0 -151
- package/extensions/enact-operator/skills/remove-deadcode/SKILL.md +0 -45
- package/extensions/enact-operator/skills/research/SKILL.md +0 -74
- package/extensions/enact-operator/skills/review/SKILL.md +0 -58
- package/extensions/enact-operator/skills/security-research/SKILL.md +0 -54
- package/extensions/enact-operator/skills/setup/SKILL.md +0 -91
- package/extensions/enact-operator/skills/setup/scripts/install.sh +0 -50
- package/extensions/enact-operator/skills/skill/SKILL.md +0 -82
- package/extensions/enact-operator/skills/tdd/SKILL.md +0 -59
- package/extensions/enact-operator/skills/team/SKILL.md +0 -199
- package/extensions/enact-operator/skills/trace/SKILL.md +0 -41
- package/extensions/enact-operator/skills/ultragoal/SKILL.md +0 -99
- package/extensions/enact-operator/skills/ultraqa/SKILL.md +0 -113
- package/extensions/enact-operator/skills/ultrawork/SKILL.md +0 -145
- package/extensions/enact-operator/skills/ultrawork/planner.md +0 -28
- package/extensions/enact-operator/skills/wiki/SKILL.md +0 -41
- package/extensions/enact-operator/skills/work-with-workitem/SKILL.md +0 -51
- /package/extensions/{enact-operator → enact-evolve}/assets/icon.png +0 -0
- /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
|
|
13
|
-
|
|
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
|
|
51
|
-
|
|
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
|
|
55
|
-
|
|
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 →
|
|
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
|
|
107
|
-
6. For each **judgment** stage:
|
|
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
|
|
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** (
|
|
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>"
|
|
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
|
|
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
|
|
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` |
|
|
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
|
|
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
|
|
221
|
-
| `loop_complete` | Close the loop; requires current summary
|
|
222
|
-
| `loop_abort` | Abort with a reason; requires current summary
|
|
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
|
|
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
|
|
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
|
-
-
|
|
268
|
-
|
|
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:
|
|
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
|
|
@@ -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:
|
|
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.
|
|
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:
|
|
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
package/scripts/check-hooks.mjs
CHANGED
|
@@ -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:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
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 —
|
|
66
|
-
|
|
67
|
-
|
|
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"]) {
|