@amsterdamdatalabs/enact-extensions 0.1.37 → 0.1.42
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/dist/internal/agents.d.ts +1 -1
- package/dist/internal/agents.d.ts.map +1 -1
- package/extensions/enact-factory/agents/architect.toml +1 -1
- package/extensions/enact-factory/agents/code-reviewer.toml +1 -1
- package/extensions/enact-factory/agents/critic.toml +1 -1
- package/extensions/enact-factory/agents/executor.toml +1 -1
- package/extensions/enact-factory/agents/explore.toml +1 -1
- package/extensions/enact-factory/agents/planner.toml +2 -2
- package/extensions/enact-factory/agents/verifier.toml +1 -1
- package/extensions/enact-factory/skills/advisor/SKILL.md +2 -2
- package/extensions/enact-factory/skills/autonomous-runner/SKILL.md +16 -16
- package/extensions/enact-factory/skills/committee/SKILL.md +4 -4
- package/extensions/enact-factory/skills/deslop-handover/SKILL.md +97 -0
- package/extensions/enact-factory/skills/deslop-handover/deslop.py +181 -0
- package/extensions/enact-factory/skills/drive-loop/SKILL.md +11 -11
- package/extensions/enact-factory/skills/paseo-dispatch/SKILL.md +141 -0
- package/extensions/enact-loop/scripts/validate.mjs +29 -0
- package/extensions/enact-loop/skills/enact-loop/SKILL.md +11 -11
- package/extensions/enact-loop/skills/loop-budget/SKILL.md +40 -0
- package/extensions/enact-loop/skills/loop-intake/SKILL.md +47 -0
- package/package.json +1 -1
- package/scripts/enact-extensions.mjs +10 -5
- package/scripts/hooks/session-start-drift-check.mjs +4 -3
- package/scripts/lib/auto-update.mjs +16 -3
- package/scripts/lib/run-install.mjs +7 -0
- package/scripts/lib/run-update.mjs +26 -13
|
@@ -12,7 +12,7 @@ export interface AgentDef {
|
|
|
12
12
|
name: string;
|
|
13
13
|
description: string;
|
|
14
14
|
body: string;
|
|
15
|
-
/** Codex/enact canonical model id (e.g. `gpt-5.
|
|
15
|
+
/** Codex/enact canonical model id (e.g. `gpt-5.6-luna`). */
|
|
16
16
|
model?: string;
|
|
17
17
|
/** Claude model alias (codex `model` is a separate namespace). */
|
|
18
18
|
claudeModel?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/internal/agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;GAOG;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/internal/agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;;GAOG;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAYD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAcxD;AAgBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAczF;AAgCD,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAA;CAAE,CA0BlD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAWpF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAmB/F"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name = "planner"
|
|
2
2
|
description = "Structured implementation planner that never writes production code"
|
|
3
|
-
model = "gpt-5.
|
|
3
|
+
model = "gpt-5.6-terra"
|
|
4
4
|
claude_model = "opus"
|
|
5
5
|
cursor_model = "default"
|
|
6
|
-
model_reasoning_effort = "
|
|
6
|
+
model_reasoning_effort = "high"
|
|
7
7
|
sandbox_mode = "danger-full-access"
|
|
8
8
|
developer_instructions = """
|
|
9
9
|
Agent definition: structured implementation planner that never writes production code.
|
|
@@ -36,7 +36,7 @@ passed AND that cheap graders would likely have false-GO'd.
|
|
|
36
36
|
## Model selection
|
|
37
37
|
|
|
38
38
|
Advisor runs on a **strong cross-vendor** model:
|
|
39
|
-
- **gpt-5.
|
|
39
|
+
- **gpt-5.6-sol** with high thinking — preferred for Frontend / UI a11y / visual judgment (distinct from sonnet executor)
|
|
40
40
|
- **opus 4.8** — preferred when executor is codex / gpt lineage (cross-vendor from OpenAI)
|
|
41
41
|
|
|
42
42
|
If the strong escalation model is unavailable, say so and STOP — do not substitute a cheap or
|
|
@@ -60,7 +60,7 @@ same-lineage model and call it independent.
|
|
|
60
60
|
```
|
|
61
61
|
Agent({
|
|
62
62
|
subagent_type: "critic" | "architect" | "verifier", // fit the question
|
|
63
|
-
model: "gpt-5.
|
|
63
|
+
model: "gpt-5.6-sol" | "claude-opus-4-8", // strong cross-vendor; ≠ executor model
|
|
64
64
|
prompt: "<WorkItem context + the exact decision/artifact +
|
|
65
65
|
acceptance criteria + 'Return GO / CONCERNS / NO-GO with
|
|
66
66
|
rationale and the single most important risk.'>"
|
|
@@ -153,10 +153,10 @@ logic** and add a judgment grader. (Full tree:
|
|
|
153
153
|
|
|
154
154
|
| Type | Executor (surface) | Grader — default cheap | Escalate to strong grader when |
|
|
155
155
|
|---|---|---|---|
|
|
156
|
-
| Frontend / UI | `claude` / sonnet — UX, visual, copy | **minimax M3** / **kimi K2.7** | `advisor` (
|
|
157
|
-
| Backend / logic | `codex` / **
|
|
158
|
-
| Data-heavy | `codex` / **
|
|
159
|
-
| Infra / CI | `codex` / **
|
|
156
|
+
| Frontend / UI | `claude` / sonnet — UX, visual, copy | **minimax M3** / **kimi K2.7** | `advisor` (GPT-5.6 Sol High) for a11y / visual / UX judgment |
|
|
157
|
+
| Backend / logic | `codex` / **GPT-5.6 Terra** — heavyweight implementation | **minimax M3** / **kimi K2.7** | `committee` (opus + GPT-5.6 Terra) if architecture unclear |
|
|
158
|
+
| Data-heavy | `codex` / **GPT-5.6 Terra** high-effort + data-safety stages | **minimax M3** / **kimi K2.7** | `committee` for risky or irreversible migrations |
|
|
159
|
+
| Infra / CI | `codex` / **GPT-5.6 Terra** + pipeline validation | **minimax M3** / **kimi K2.7** | `committee` before branch policy; `opus 4.8` for security/public-API changes |
|
|
160
160
|
| Docs | `claude` / sonnet (writing) | **minimax M3** / **kimi K2.7** | rarely |
|
|
161
161
|
|
|
162
162
|
The **executor ≠ grader** rule is a hard gate everywhere (see contract above).
|
|
@@ -169,16 +169,16 @@ each role:
|
|
|
169
169
|
|
|
170
170
|
| Function tier | Models | Used for |
|
|
171
171
|
|---|---|---|
|
|
172
|
-
| Workers (execute) | **sonnet** (in-window) · **codex/
|
|
173
|
-
| Planning / architecture | **opus 4.8** · **
|
|
174
|
-
| Verify / review / grade (DEFAULT cheap) | **
|
|
172
|
+
| Workers (execute) | **sonnet** (in-window) · **codex/GPT-5.6 Luna** for focused fast execution · **GPT-5.6 Terra** for heavyweight work | implementation, diffs, refactors, bugfixes |
|
|
173
|
+
| Planning / architecture | **opus 4.8** · **GPT-5.6 Terra** (reasoning: high+) | plans, interface/architecture design, hard root-cause, committee |
|
|
174
|
+
| Verify / review / grade (DEFAULT cheap) | **MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) · **kimi K2.7** | first-pass judgment on routine code-review / verify / docs (~80% of grading) |
|
|
175
175
|
| Explorers (map, read-only) | **haiku** · **cursor composer 2.5** | codebase sweeps, naming maps, brownfield discovery — never grade |
|
|
176
176
|
|
|
177
|
-
**Grading = cheap default + risk-based escalation.**
|
|
178
|
-
~80%. Escalate to a strong cross-vendor grader for:
|
|
179
|
-
- Frontend / UI a11y or visual / UX judgment → **advisor** = **
|
|
177
|
+
**Grading = cheap default + risk-based escalation.** MiniMax M3 (`minimax-coding-plan/MiniMax-M3`, always
|
|
178
|
+
`--thinking on`) / kimi K2.7 grade the routine ~80%. Escalate to a strong cross-vendor grader for:
|
|
179
|
+
- Frontend / UI a11y or visual / UX judgment → **advisor** = **GPT-5.6 Sol High** (or opus if executor was codex)
|
|
180
180
|
- Security, public API / barrel / contract changes → **opus 4.8**
|
|
181
|
-
- Architecture decisions / risky or irreversible migrations → **committee** (opus +
|
|
181
|
+
- Architecture decisions / risky or irreversible migrations → **committee** (opus + GPT-5.6 Terra)
|
|
182
182
|
|
|
183
183
|
#### Contract shape per tier
|
|
184
184
|
|
|
@@ -201,11 +201,11 @@ When the executor is **`claude-sonnet-4-6`** (or any sonnet-tier model), the
|
|
|
201
201
|
`cursor` grader (`cursor/claude-sonnet-4-6`) is **NOT model-distinct** — it runs
|
|
202
202
|
the same underlying model and the independence rule is violated.
|
|
203
203
|
|
|
204
|
-
**Default cheap graders for sonnet work:** use **
|
|
205
|
-
are vendor-distinct from sonnet and satisfy the independence rule cleanly. Use:
|
|
204
|
+
**Default cheap graders for sonnet work:** use **MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always
|
|
205
|
+
`--thinking on`) or **kimi K2.7** — both are vendor-distinct from sonnet and satisfy the independence rule cleanly. Use:
|
|
206
206
|
|
|
207
|
-
- **
|
|
208
|
-
- **opus 4.8** / **
|
|
207
|
+
- **MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) / **kimi K2.7** — default graders for sonnet-executor work (cheap, independent)
|
|
208
|
+
- **opus 4.8** / **GPT-5.6 Sol High** — escalation graders for high-risk judgment (see escalation table above)
|
|
209
209
|
- `cursor` grader ONLY when the executor is opus-tier (cursor runs claude-sonnet, so it is distinct from opus)
|
|
210
210
|
|
|
211
211
|
#### Paseo permission-mode table
|
|
@@ -218,7 +218,7 @@ or return an empty session:
|
|
|
218
218
|
| `codex` | `--mode auto-review` | Routes the grader's eligible `on-request` approvals through codex's auto-reviewer subagent instead of BLOCKING — without it a grader can stall on an approval (e.g. a cargo/network fetch during build verification). `--mode bypass` is rejected by codex |
|
|
219
219
|
| `cursor` | default / agent | `--mode bypass` is a claude mode, NOT cursor's — previously documented wrong; cursor uses default or agent mode |
|
|
220
220
|
| `kimi` | `--mode auto` | Same as codex |
|
|
221
|
-
| `opencode` (
|
|
221
|
+
| `opencode` (MiniMax M3) | `--mode plan --thinking on` | Required for every MiniMax M3 dispatch |
|
|
222
222
|
|
|
223
223
|
Codex mode enum (authoritative): `read-only` · `auto` · `auto-review` · `full-access`. The grader
|
|
224
224
|
stays read-only BY PROMPT; `auto-review` only governs approval handling and composes with
|
|
@@ -34,14 +34,14 @@ two independent strong perspectives before proceeding.
|
|
|
34
34
|
|
|
35
35
|
## Model selection
|
|
36
36
|
|
|
37
|
-
Committee always uses **opus 4.8 + gpt-5.
|
|
37
|
+
Committee always uses **opus 4.8 + gpt-5.6-terra** — the two strongest available models from different
|
|
38
38
|
vendors. This pairing provides genuine diversity: one OpenAI-lineage, one Anthropic-lineage.
|
|
39
39
|
|
|
40
40
|
## Boundaries (hard)
|
|
41
41
|
|
|
42
42
|
- **Diagnose + plan only.** No code changes, no commits, no board mutation. The
|
|
43
43
|
output is a plan; implementation happens later via `drive-loop`.
|
|
44
|
-
- **Two distinct vendors: opus 4.8 + gpt-5.
|
|
44
|
+
- **Two distinct vendors: opus 4.8 + gpt-5.6-terra.** Both members run on models that are different
|
|
45
45
|
from each other and from the executor (genuine diversity). If two distinct models are
|
|
46
46
|
unavailable, run what you can and STATE the reduced diversity loudly — do not
|
|
47
47
|
pretend two same-model passes are a committee.
|
|
@@ -52,11 +52,11 @@ vendors. This pairing provides genuine diversity: one OpenAI-lineage, one Anthro
|
|
|
52
52
|
|
|
53
53
|
1. Assemble the dossier: `factory_workitem_get` (goal, acceptance, current
|
|
54
54
|
state, `blockedBy`), the loop status / stall reason, recent failing output.
|
|
55
|
-
2. Spawn TWO agents on **opus 4.8 + gpt-5.
|
|
55
|
+
2. Spawn TWO agents on **opus 4.8 + gpt-5.6-terra**, with contrasting lenses:
|
|
56
56
|
|
|
57
57
|
```
|
|
58
58
|
Agent({ subagent_type: "critic", model: "claude-opus-4-8", prompt: "<dossier> — root-cause why this is stuck; what is the real blocker?" })
|
|
59
|
-
Agent({ subagent_type: "architect", model: "gpt-5.
|
|
59
|
+
Agent({ subagent_type: "architect", model: "gpt-5.6-terra", prompt: "<dossier> — is the current approach right? propose the smallest viable path to closure." })
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
3. Reconcile their findings into ONE plan: the root cause, the chosen approach
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deslop-handover
|
|
3
|
+
description: "Strip AI-authoring tells (emoji, marketing tone, over-bolding, edit scars) from handover docs for a no-AI customer, while preserving every number, reference, code block, and finding verbatim."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Deslop Handover
|
|
7
|
+
|
|
8
|
+
Make a documentation handover read as plain, human-authored engineering notes — for a
|
|
9
|
+
customer that enforces a **strict no-AI policy** — without losing any of the hard-won
|
|
10
|
+
technical substance.
|
|
11
|
+
|
|
12
|
+
This is sharper than `ai-slop-cleaner` (which targets TODO/placeholder code residue) because
|
|
13
|
+
it adds the two things that matter for prose handovers:
|
|
14
|
+
|
|
15
|
+
1. **Preserve-rules** — numbers, percentages, file:line refs, code blocks, table data,
|
|
16
|
+
citations/DOIs stay byte-for-byte. The value is the substance; only the *voice* changes.
|
|
17
|
+
2. **Sanctioned-vs-violation distinction** — the delivered product running on the customer's
|
|
18
|
+
OWN infrastructure (Azure OpenAI, LLM parser, embeddings, BM25, vector search, ontology,
|
|
19
|
+
taxonomy) is **architecture, not a violation** — KEEP it. Only AI-*authoring* tells go.
|
|
20
|
+
|
|
21
|
+
## Use When
|
|
22
|
+
|
|
23
|
+
- Preparing any markdown/docs bundle for handover to a no-AI / compliance-sensitive customer.
|
|
24
|
+
- After an AI-assisted writing or redaction pass that left tone slop or edit scars.
|
|
25
|
+
- When docs must look human-authored but every finding/number must survive intact.
|
|
26
|
+
|
|
27
|
+
## What counts as slop (REMOVE / NEUTRALIZE)
|
|
28
|
+
|
|
29
|
+
- **Emoji & decorative symbols** (✅ ✔ ✓ ❌ ⚠️ 🎯 📊 🚀 ✨ ⭐). Badge labels like `✅ Good` →
|
|
30
|
+
the plain word (`Good`); decorative ones → delete. (Arrows `→`, dashes `—`, math `≤ ≥ ≠`
|
|
31
|
+
are notation — keep.)
|
|
32
|
+
- **Marketing / filler**: comprehensive, seamless, robust (only when decorative — keep
|
|
33
|
+
"robust to X" technical usage), powerful, leverage, elegant, best practices, closes the
|
|
34
|
+
gap, dive in, it's worth noting, it's important to note, "Note that" openers, "Overall,",
|
|
35
|
+
"In summary", "In conclusion", game-changer, unlock(s), elevate, supercharge, effortless,
|
|
36
|
+
cutting-edge.
|
|
37
|
+
- **Rule-of-three flourishes** and hype cadence ("X, Y, and Z — all real") → plain statement.
|
|
38
|
+
- **Over-bolding** — bold used for sentence emphasis; keep bold only on genuine key terms/values.
|
|
39
|
+
- **Self-referential tells** — "generated by an AI", "LLM-authored", "I analyzed", "this
|
|
40
|
+
session", "as an AI"; template title prefixes like `Learning:` / `Fix:`.
|
|
41
|
+
- **Edit scars** from prior passes — blank-date holes (`as of )`, `On , `, `( run, …)`),
|
|
42
|
+
empty parens `( )`, leading-comma parens `(, …)`, space-before-colon `Foo :`, dangling
|
|
43
|
+
heading dashes `# Title —`, duplicated words.
|
|
44
|
+
|
|
45
|
+
## What to PRESERVE (do not touch)
|
|
46
|
+
|
|
47
|
+
Every number, percentage, row count, threshold, column name, file path, file:line reference,
|
|
48
|
+
SQL/code block (verbatim), table data cell, citation, DOI, author, journal, year. Every
|
|
49
|
+
finding, root cause, decision, and deferred/open/superseded note. Section structure. The
|
|
50
|
+
sanctioned product-architecture terms listed above.
|
|
51
|
+
|
|
52
|
+
## Workflow
|
|
53
|
+
|
|
54
|
+
**Step 1 — mechanical pass (the ~80%).** Run the bundled script in report mode first, then fix:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python skills/deslop-handover/deslop.py --report <root-or-globs...> # see what's there
|
|
58
|
+
python skills/deslop-handover/deslop.py --fix <root-or-globs...> # apply mechanical fixes
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The script is **code-fence-aware** (never touches fenced ``` blocks, inline `code`, or digits)
|
|
62
|
+
and only does safe structural edits: emoji removal/badge-strip, edit-scar repair, trailing
|
|
63
|
+
whitespace. It does NOT auto-rewrite prose — it **reports** slop phrases, over-bold lines, and
|
|
64
|
+
blank-date holes for the judgment pass.
|
|
65
|
+
|
|
66
|
+
**Step 2 — judgment pass (the ~20%), fanned out.** Tone, over-bolding ("key term vs emphasis"),
|
|
67
|
+
rule-of-three, and ambiguous scars need a reader. Parallelize:
|
|
68
|
+
|
|
69
|
+
1. Write a shared rubric file (this SKILL's "What counts as slop" + "PRESERVE" sections).
|
|
70
|
+
2. Group the dirty files into batches of ~6.
|
|
71
|
+
3. Dispatch one sonnet executor per batch: *"Read the rubric at <path>. Deslop these files in
|
|
72
|
+
place: <list>. Preserve all numbers/refs/code verbatim. Report per file."*
|
|
73
|
+
4. Each executor reports per-file changes and confirms zero numbers/refs/code changed.
|
|
74
|
+
|
|
75
|
+
**Step 3 — verification gate.** Re-grep the whole bundle; the count must be zero:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
rg -lP '[\x{1F300}-\x{1FAFF}\x{2600}-\x{26FF}\x{2705}\x{274C}\x{2714}\x{2713}\x{2716}]' <root> # emoji
|
|
79
|
+
rg -ni -e seamless -e '\bleverage' -e '\bunlock' -e 'best practices' -e 'dive in' <root> # phrases
|
|
80
|
+
rg -n -e 'as of \)' -e '\( *\)' -e '\(, ' -e ' —$' <root> # scars
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A grep hit inside a fenced code block (e.g. `.collect()` matching empty-parens) is a
|
|
84
|
+
false positive — confirm by line before acting.
|
|
85
|
+
|
|
86
|
+
## State Contract
|
|
87
|
+
|
|
88
|
+
- Reads: the target docs (and a temp rubric file for the judgment pass).
|
|
89
|
+
- Writes: the same docs, in place. No new files in the handover bundle, no frontmatter added.
|
|
90
|
+
|
|
91
|
+
## Final Check
|
|
92
|
+
|
|
93
|
+
- Zero emoji, zero slop phrases, zero edit scars outside fenced code.
|
|
94
|
+
- Every number, file:line ref, code block, table cell, and citation is unchanged (diff to confirm).
|
|
95
|
+
- Product-architecture terms (Azure OpenAI / LLM / embeddings / BM25 / ontology) intact.
|
|
96
|
+
- Protected docs (e.g. a `STRATEGY.md`) excluded unless the user explicitly opts them in.
|
|
97
|
+
- Ambiguous scars (missing words, duplicated phrases, lost dates) surfaced to the user, not guessed.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""deslop-handover — mechanical AI-tone cleanup for markdown handover docs.
|
|
3
|
+
|
|
4
|
+
Code-fence-aware. Does the safe, deterministic ~80% of a deslop pass:
|
|
5
|
+
- strips emoji / badge symbols (keeps arrows, dashes, math notation)
|
|
6
|
+
- repairs edit scars (empty parens, leading-comma parens, space-before-colon,
|
|
7
|
+
space-before-comma, dangling heading dashes, trailing whitespace)
|
|
8
|
+
- REPORTS (never auto-rewrites) slop phrases, over-bold lines, and blank-date holes
|
|
9
|
+
that need the judgment/agent pass.
|
|
10
|
+
|
|
11
|
+
NEVER touches: fenced ``` code blocks, inline `code` spans, digits, or table data values.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
python deslop.py --report PATH [PATH ...] # default: show findings, change nothing
|
|
15
|
+
python deslop.py --fix PATH [PATH ...] # apply the mechanical fixes in place
|
|
16
|
+
|
|
17
|
+
PATH may be a file, a directory (recursed for *.md), or a glob. Vendored dirs
|
|
18
|
+
(.venv/.git/node_modules/dist-info/.pytest_cache) are skipped.
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import glob
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
_SKIP_DIRS = {".venv", ".git", "node_modules", ".pytest_cache", "__pycache__"}
|
|
29
|
+
|
|
30
|
+
# Emoji / decorative symbols. Deliberately EXCLUDES arrows (U+2190-21FF, →),
|
|
31
|
+
# dashes (—, –) and math (≤ ≥ ≠) which are technical notation.
|
|
32
|
+
EMOJI = re.compile(
|
|
33
|
+
"[\U0001F300-\U0001FAFF\U00002600-\U000027BF\U00002B00-\U00002BFF"
|
|
34
|
+
"✅❌✔✓✖✗✨️]"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
SLOP_PHRASES = [
|
|
38
|
+
"comprehensive", "seamless", "powerful", "leverage", "elegant", "best practices",
|
|
39
|
+
"closes the gap", "dive in", "worth noting", "important to note", "game-changer",
|
|
40
|
+
"game changer", "unlock", "elevate", "supercharge", "effortless", "cutting-edge",
|
|
41
|
+
"in conclusion", "in summary",
|
|
42
|
+
]
|
|
43
|
+
# matches that are technical, not slop — do not flag these
|
|
44
|
+
SLOP_ALLOW = re.compile(r"robust to|robustness|leverage point|highest leverage", re.I)
|
|
45
|
+
|
|
46
|
+
# Ambiguous scars that imply a lost word/date — REPORT, do not auto-fix.
|
|
47
|
+
HOLE = re.compile(r"as of \)|\( *run\b|On , |Effective ,|\bpre-\s*$")
|
|
48
|
+
|
|
49
|
+
_INLINE_CODE = re.compile(r"`[^`]*`")
|
|
50
|
+
_FENCE = re.compile(r"^\s*(```|~~~)")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _split_protecting_code(line: str):
|
|
54
|
+
"""Yield (text, is_code) chunks so transforms skip inline `code` spans."""
|
|
55
|
+
pos = 0
|
|
56
|
+
for m in _INLINE_CODE.finditer(line):
|
|
57
|
+
if m.start() > pos:
|
|
58
|
+
yield line[pos:m.start()], False
|
|
59
|
+
yield m.group(0), True
|
|
60
|
+
pos = m.end()
|
|
61
|
+
if pos < len(line):
|
|
62
|
+
yield line[pos:], False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _fix_text(text: str, is_heading: bool) -> str:
|
|
66
|
+
"""Apply mechanical fixes to a non-code text chunk."""
|
|
67
|
+
text = EMOJI.sub("", text)
|
|
68
|
+
text = text.replace("️", "")
|
|
69
|
+
text = re.sub(r"\(\s*\)", "", text) # empty parens
|
|
70
|
+
text = re.sub(r"\(,\s*", "(", text) # leading-comma paren
|
|
71
|
+
text = re.sub(r"\(\s+", "(", text) # leading space in paren
|
|
72
|
+
text = re.sub(r"\s+\)", ")", text) # space before close paren
|
|
73
|
+
text = re.sub(r"\s+,", ",", text) # space before comma
|
|
74
|
+
text = re.sub(r"\s+:\*\*", ":**", text) # space before bold-closing colon
|
|
75
|
+
text = re.sub(r"(\*\*[^*\n]+?) :", r"\1:", text) # "label :" inside bold
|
|
76
|
+
text = re.sub(r"\s+:\s*$", ":", text) # space before trailing label colon
|
|
77
|
+
if is_heading:
|
|
78
|
+
text = re.sub(r"\s*[—–-]\s*$", "", text) # dangling heading dash
|
|
79
|
+
text = re.sub(r"(?<=\S) +", " ", text) # collapse runs of inner spaces
|
|
80
|
+
return text
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def process(path: Path, fix: bool):
|
|
84
|
+
lines = path.read_text(encoding="utf-8").splitlines(keepends=False)
|
|
85
|
+
in_fence = False
|
|
86
|
+
out, report = [], {"emoji": 0, "scars": 0, "phrases": [], "bold": [], "holes": []}
|
|
87
|
+
for n, line in enumerate(lines, 1):
|
|
88
|
+
if _FENCE.match(line):
|
|
89
|
+
in_fence = not in_fence
|
|
90
|
+
out.append(line)
|
|
91
|
+
continue
|
|
92
|
+
if in_fence:
|
|
93
|
+
out.append(line)
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# ---- report-only signals (computed on the original line) ----
|
|
97
|
+
for p in SLOP_PHRASES:
|
|
98
|
+
if p in line.lower() and not SLOP_ALLOW.search(line):
|
|
99
|
+
report["phrases"].append((n, p))
|
|
100
|
+
if HOLE.search(line):
|
|
101
|
+
report["holes"].append((n, line.strip()[:80]))
|
|
102
|
+
for b in re.findall(r"\*\*([^*\n]+)\*\*", line):
|
|
103
|
+
if "." in b or len(b.split()) > 6: # bold wrapping a sentence/clause
|
|
104
|
+
report["bold"].append((n, b[:60]))
|
|
105
|
+
report["emoji"] += len(EMOJI.findall(line))
|
|
106
|
+
|
|
107
|
+
# ---- mechanical fix ----
|
|
108
|
+
lead = re.match(r"^\s*", line).group(0)
|
|
109
|
+
rest = line[len(lead):]
|
|
110
|
+
is_heading = rest.startswith("#") or rest.startswith(">")
|
|
111
|
+
rebuilt = "".join(
|
|
112
|
+
chunk if is_code else _fix_text(chunk, is_heading)
|
|
113
|
+
for chunk, is_code in _split_protecting_code(rest)
|
|
114
|
+
)
|
|
115
|
+
rebuilt = (lead + rebuilt).rstrip()
|
|
116
|
+
if rebuilt != line:
|
|
117
|
+
report["scars"] += 1
|
|
118
|
+
out.append(rebuilt)
|
|
119
|
+
|
|
120
|
+
changed = out != lines
|
|
121
|
+
if fix and changed:
|
|
122
|
+
path.write_text("\n".join(out) + "\n", encoding="utf-8")
|
|
123
|
+
return changed, report
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def collect(paths):
|
|
127
|
+
files = []
|
|
128
|
+
for p in paths:
|
|
129
|
+
for hit in glob.glob(p, recursive=True) if any(c in p for c in "*?[") else [p]:
|
|
130
|
+
hp = Path(hit)
|
|
131
|
+
if hp.is_dir():
|
|
132
|
+
files += [f for f in hp.rglob("*.md")
|
|
133
|
+
if not any(part in _SKIP_DIRS for part in f.parts)]
|
|
134
|
+
elif hp.suffix == ".md":
|
|
135
|
+
files.append(hp)
|
|
136
|
+
return sorted(set(files))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def main():
|
|
140
|
+
ap = argparse.ArgumentParser(description="Mechanical deslop for handover markdown.")
|
|
141
|
+
ap.add_argument("--fix", action="store_true", help="apply fixes (default: report only)")
|
|
142
|
+
ap.add_argument("--report", action="store_true", help="report only (default; no-op flag for clarity)")
|
|
143
|
+
ap.add_argument("paths", nargs="+", help="files, dirs, or globs")
|
|
144
|
+
args = ap.parse_args()
|
|
145
|
+
|
|
146
|
+
files = collect(args.paths)
|
|
147
|
+
if not files:
|
|
148
|
+
print("no markdown files found", file=sys.stderr)
|
|
149
|
+
return 1
|
|
150
|
+
|
|
151
|
+
total_changed = 0
|
|
152
|
+
for f in files:
|
|
153
|
+
changed, r = process(f, args.fix)
|
|
154
|
+
signal = r["emoji"] or r["scars"] or r["phrases"] or r["bold"] or r["holes"]
|
|
155
|
+
if not signal:
|
|
156
|
+
continue
|
|
157
|
+
total_changed += int(bool(changed))
|
|
158
|
+
tag = "FIXED " if (args.fix and changed) else "FOUND "
|
|
159
|
+
print(f"\n{tag}{f}")
|
|
160
|
+
if r["emoji"]:
|
|
161
|
+
print(f" emoji/badges: {r['emoji']}")
|
|
162
|
+
if r["scars"] and not args.fix:
|
|
163
|
+
print(f" mechanical scars: {r['scars']} line(s) (run --fix)")
|
|
164
|
+
if r["phrases"]:
|
|
165
|
+
print(" slop phrases (judgment): " +
|
|
166
|
+
", ".join(f"L{n}:{p}" for n, p in r["phrases"][:8]))
|
|
167
|
+
if r["bold"]:
|
|
168
|
+
print(f" over-bold lines (judgment): {len(r['bold'])} "
|
|
169
|
+
f"(e.g. L{r['bold'][0][0]})")
|
|
170
|
+
if r["holes"]:
|
|
171
|
+
print(" blank-date / lost-word HOLES (needs human): " +
|
|
172
|
+
"; ".join(f"L{n}" for n, _ in r["holes"]))
|
|
173
|
+
|
|
174
|
+
verb = "fixed" if args.fix else "with findings"
|
|
175
|
+
print(f"\n{total_changed if args.fix else len(files)} file(s) {verb}. "
|
|
176
|
+
f"Run the verification grep + judgment pass next (see SKILL.md).")
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
if __name__ == "__main__":
|
|
181
|
+
sys.exit(main())
|
|
@@ -161,7 +161,7 @@ For each judgment stage:
|
|
|
161
161
|
> the factory MCP tool `factory_paseo_grader_run` runs the grader with
|
|
162
162
|
> `paseo run --output-schema --json --wait-timeout` so **stdout is the validated `{verdict,
|
|
163
163
|
> evidence}` object** — no `paseo logs | grep`. It bakes in the per-provider `--mode`
|
|
164
|
-
> (codex `auto-review` so on-request approvals don't block, kimi `auto`, cursor
|
|
164
|
+
> (codex `auto-review` so on-request approvals don't block, kimi `auto`, cursor default, opencode MiniMax M3 `--mode plan --thinking on`) and recovers the actual resolved model via
|
|
165
165
|
> `paseo ls --label` / `inspect`, which you relay as `graderModelActual` on `loop_grader_verdict`.
|
|
166
166
|
> Full design: `enact-loop/docs/design/2026-06-22-paseo-verdict-capture.md`.
|
|
167
167
|
|
|
@@ -235,16 +235,16 @@ acceptance criteria in a real surface.
|
|
|
235
235
|
|
|
236
236
|
#### Grader selection — cheap default + risk-based escalation
|
|
237
237
|
|
|
238
|
-
**
|
|
238
|
+
**MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) and **kimi K2.7** are the default cheap graders for routine judgment
|
|
239
239
|
(~80% of grading). Escalate to a strong cross-vendor grader when:
|
|
240
|
-
- Frontend / UI a11y or visual / UX judgment → `advisor` = **
|
|
240
|
+
- Frontend / UI a11y or visual / UX judgment → `advisor` = **GPT-5.6 Sol High** (or opus 4.8 if executor was codex)
|
|
241
241
|
- Security, public API / barrel / contract changes → **opus 4.8**
|
|
242
|
-
- Architecture decisions / risky or irreversible migrations → `committee` (opus 4.8 +
|
|
242
|
+
- Architecture decisions / risky or irreversible migrations → `committee` (opus 4.8 + GPT-5.6 Terra)
|
|
243
243
|
|
|
244
|
-
**Independence (hard gate — model-distinct):** grader model ≠ executor model.
|
|
245
|
-
are vendor-distinct from both sonnet and codex workers, so default cheap graders are always
|
|
246
|
-
independent. On escalation keep cross-vendor: grade codex work with **opus** (not
|
|
247
|
-
OpenAI lineage); grade sonnet work with
|
|
244
|
+
**Independence (hard gate — model-distinct):** grader model ≠ executor model. MiniMax M3
|
|
245
|
+
(`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) / kimi K2.7 are vendor-distinct from both sonnet and codex workers, so default cheap graders are always
|
|
246
|
+
independent. On escalation keep cross-vendor: grade codex work with **opus** (not GPT-5.6 Sol — same
|
|
247
|
+
OpenAI lineage); grade sonnet work with GPT-5.6 Sol High / opus / minimax / kimi.
|
|
248
248
|
|
|
249
249
|
#### Permission modes (REQUIRED — non-default modes prevent stall/empty sessions)
|
|
250
250
|
|
|
@@ -253,7 +253,7 @@ OpenAI lineage); grade sonnet work with gpt-5.5 / opus / minimax / kimi.
|
|
|
253
253
|
| `codex` | `--mode auto-review` | Routes the grader's eligible `on-request` approvals through codex's auto-reviewer subagent instead of BLOCKING — without it a grader can stall on an approval (e.g. a cargo/network fetch during build verification). `--mode bypass` is rejected by codex |
|
|
254
254
|
| `cursor` | default / agent | `--mode bypass` is a claude mode, NOT cursor's — previously documented wrong |
|
|
255
255
|
| `kimi` | `--mode auto` | Same as codex |
|
|
256
|
-
| `opencode` (
|
|
256
|
+
| `opencode` (MiniMax M3) | `--mode plan --thinking on` | Required for every MiniMax M3 dispatch |
|
|
257
257
|
|
|
258
258
|
Codex mode enum (authoritative): `read-only` · `auto` · `auto-review` · `full-access`. The grader
|
|
259
259
|
stays read-only BY PROMPT; `auto-review` only governs approval handling and composes with
|
|
@@ -262,8 +262,8 @@ stays read-only BY PROMPT; `auto-review` only governs approval handling and comp
|
|
|
262
262
|
#### Sonnet-executor caveat
|
|
263
263
|
|
|
264
264
|
`cursor` (`cursor/claude-sonnet-4-6`) is NOT model-distinct from a sonnet executor.
|
|
265
|
-
Use **
|
|
266
|
-
escalate to opus 4.8 /
|
|
265
|
+
Use **MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) or **kimi K2.7** as the clean default cheap graders for sonnet-executor work;
|
|
266
|
+
escalate to opus 4.8 / GPT-5.6 Sol High for high-risk judgment. Reserve `cursor` for opus-tier executors.
|
|
267
267
|
|
|
268
268
|
#### Verdict capture — structured `--output-schema`, not log-scraped
|
|
269
269
|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: paseo-dispatch
|
|
3
|
+
description: >-
|
|
4
|
+
Canonical Enact Factory reference for launching Paseo agents, reviewers,
|
|
5
|
+
graders, advisors, committees, and handoffs with correct provider/model/mode
|
|
6
|
+
selection across Codex, Claude Code, Kimi, OpenCode/MiniMax, and Cursor. Use
|
|
7
|
+
when a skill invokes Paseo, chooses a grader/advisor/committee model, runs
|
|
8
|
+
factory_paseo_grader_run, needs auto-review/nonblocking review modes, or must
|
|
9
|
+
prove model-distinct review provenance.
|
|
10
|
+
metadata:
|
|
11
|
+
author: Amsterdam Data Labs
|
|
12
|
+
version: 1.0.0
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# paseo-dispatch
|
|
16
|
+
|
|
17
|
+
Paseo dispatch is fragile because provider names, model ids, and permission modes
|
|
18
|
+
are not interchangeable. Use this skill whenever factory work launches agents
|
|
19
|
+
through Paseo or records an independent review verdict.
|
|
20
|
+
|
|
21
|
+
## Discovery First
|
|
22
|
+
|
|
23
|
+
Before choosing a provider or mode, inspect the live surface:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
paseo provider ls --json
|
|
27
|
+
paseo provider models <provider> --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If `~/.paseo/orchestration-preferences.json` exists, read it and treat it as the
|
|
31
|
+
user's preferred role map unless the task explicitly requires a different model.
|
|
32
|
+
Do not assume a model id from old docs when live `paseo provider models` disagrees.
|
|
33
|
+
|
|
34
|
+
Prefer explicit provider and model flags:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
paseo run --provider <provider> --model <model-id> --mode <mode> --cwd <repo> "<prompt>"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This keeps provider, model, and permission mode separable. Record the actual
|
|
41
|
+
model from `paseo inspect <id> --json` when a verdict or committee depends on
|
|
42
|
+
independence.
|
|
43
|
+
|
|
44
|
+
## Review Modes
|
|
45
|
+
|
|
46
|
+
Use the provider's nonblocking review mode for graders, advisors, and committees.
|
|
47
|
+
The prompt still makes the reviewer read-only; the mode only controls whether
|
|
48
|
+
tool approvals stall the agent.
|
|
49
|
+
|
|
50
|
+
| Provider | Review mode | Common model ids | Notes |
|
|
51
|
+
|---|---|---|---|
|
|
52
|
+
| `codex` | `--mode auto-review` | `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna` | Use Sol with `--thinking high` for reviewers/graders; use Terra for heavyweight work and Luna for lightweight fast execution. Never use `bypass`. |
|
|
53
|
+
| `claude` | `--mode auto` | `claude-opus-4-8`, `claude-sonnet-4-6`, `claude-fable-5` | Claude Code's nonblocking review path is Auto mode. Use `--mode plan` only for pure planning where no tool approvals are expected. |
|
|
54
|
+
| `kimi` | `--mode auto` | `kimi-code/kimi-for-coding` | Kimi K2.7 Code supports `--thinking on`; use it for review unless speed matters more. |
|
|
55
|
+
| `opencode` | `--mode plan` for review, `--mode build` for implementation | `minimax-coding-plan/MiniMax-M3`, `opencode-go/kimi-k2.7-code` | MiniMax M3 is a MiniMax model, not an OpenCode model. Every OpenCode/MiniMax M3 dispatch MUST pass `--thinking on`. |
|
|
56
|
+
| `cursor` | `--mode ask` or `--mode plan` for review, `--mode agent` for implementation | provider defaults vary | Do not pass Claude `bypass` to Cursor. Verify modes live before use. |
|
|
57
|
+
|
|
58
|
+
## Standard Review Commands
|
|
59
|
+
|
|
60
|
+
Codex Sol High grader:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
paseo run --provider codex --model gpt-5.6-sol --mode auto-review \
|
|
64
|
+
--thinking high --cwd "$ROOT" --wait-timeout 10m \
|
|
65
|
+
--label role=reviewer "<review prompt>"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Claude strong reviewer:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
paseo run --provider claude --model claude-opus-4-8 --mode auto \
|
|
72
|
+
--thinking high --cwd "$ROOT" --wait-timeout 10m \
|
|
73
|
+
--label role=reviewer "<review prompt>"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Kimi K2.7 reviewer:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
paseo run --provider kimi --model kimi-code/kimi-for-coding --mode auto \
|
|
80
|
+
--thinking on --cwd "$ROOT" --wait-timeout 10m \
|
|
81
|
+
--label role=reviewer "<review prompt>"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
MiniMax M3 reviewer:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
paseo run --provider opencode --model minimax-coding-plan/MiniMax-M3 --mode plan \
|
|
88
|
+
--thinking on --cwd "$ROOT" --wait-timeout 10m \
|
|
89
|
+
--label role=reviewer "<review prompt>"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Structured Verdict Capture
|
|
93
|
+
|
|
94
|
+
For loop judgment stages, prefer `factory_paseo_grader_run`; it wraps
|
|
95
|
+
`paseo run --output-schema --json --wait-timeout`, labels the run with the
|
|
96
|
+
continuation id, and recovers the actual model for `loop_grader_verdict`.
|
|
97
|
+
|
|
98
|
+
When direct CLI dispatch is required:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
paseo run --provider <provider> --model <model-id> --mode <review-mode> \
|
|
102
|
+
--output-schema <verdict-schema.json> --json \
|
|
103
|
+
--label continuation=<continuationId> --cwd "$ROOT" "<grader prompt>"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Then recover provenance:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
paseo ls -a --label continuation=<continuationId> --json
|
|
110
|
+
paseo inspect <agent-id> --json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Relay `graderModelActual` exactly as resolved. A claimed GO without the actual
|
|
114
|
+
model is weak evidence.
|
|
115
|
+
|
|
116
|
+
## Model Choice
|
|
117
|
+
|
|
118
|
+
Use model-distinct reviewers. For risky decisions, prefer vendor-distinct too.
|
|
119
|
+
|
|
120
|
+
- Routine judgment: MiniMax M3 (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) or Kimi K2.7.
|
|
121
|
+
- Strong single advisor or grader: Claude Opus 4.8 when the executor is Codex;
|
|
122
|
+
GPT-5.6 Sol with `--thinking high` when the executor is Claude/Sonnet;
|
|
123
|
+
MiniMax/Kimi when cost or breadth matters.
|
|
124
|
+
- Heavyweight implementation and planning: GPT-5.6 Terra. Lightweight focused
|
|
125
|
+
execution and exploration: GPT-5.6 Luna.
|
|
126
|
+
- Committee: launch two contrasting models. Default strong pair is Claude Opus
|
|
127
|
+
4.8 plus GPT-5.6 Terra. For a cost-sensitive or extra-breadth committee, use Kimi
|
|
128
|
+
K2.7 plus MiniMax M3, or add them as extra reviewers.
|
|
129
|
+
- Never grade with the exact executor model/session. If distinct reviewers are
|
|
130
|
+
unavailable, stop or state reduced diversity loudly.
|
|
131
|
+
|
|
132
|
+
## Operating Rules
|
|
133
|
+
|
|
134
|
+
- Use `--label` for traceability: `role=...`, `workItem=...`, `continuation=...`.
|
|
135
|
+
- Use `--wait-timeout` for synchronous review requests; otherwise detach and let
|
|
136
|
+
Paseo notify on finish.
|
|
137
|
+
- Do not poll running agents. Use `paseo attach <id>` only when live output is
|
|
138
|
+
needed.
|
|
139
|
+
- Do not restart the Paseo daemon to unstick a run; it kills active agents.
|
|
140
|
+
- Do not confuse implementation modes with review modes. A read-only reviewer in
|
|
141
|
+
`auto-review` or `auto` is still read-only because of its prompt and role.
|
|
@@ -113,6 +113,35 @@ if (!existsSync(join(root, skillPath))) {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
const intakeSkillPath = "skills/loop-intake/SKILL.md";
|
|
117
|
+
if (!existsSync(join(root, intakeSkillPath))) {
|
|
118
|
+
fail(`${intakeSkillPath} is missing`);
|
|
119
|
+
} else {
|
|
120
|
+
const intake = readFileSync(join(root, intakeSkillPath), "utf8");
|
|
121
|
+
if (!/^name:\s*loop-intake\s*$/m.test(intake)) {
|
|
122
|
+
fail(`${intakeSkillPath} frontmatter must declare name: loop-intake`);
|
|
123
|
+
}
|
|
124
|
+
if (!/approvalRequired/i.test(intake) || !/MUST NOT call `loop_start`/.test(intake)) {
|
|
125
|
+
fail(`${intakeSkillPath} must require approval and prohibit loop_start`);
|
|
126
|
+
}
|
|
127
|
+
if (!/loop_operations_snapshot/.test(intake)) {
|
|
128
|
+
fail(`${intakeSkillPath} must use the read-only operations projection`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const budgetSkillPath = "skills/loop-budget/SKILL.md";
|
|
133
|
+
if (!existsSync(join(root, budgetSkillPath))) {
|
|
134
|
+
fail(`${budgetSkillPath} is missing`);
|
|
135
|
+
} else {
|
|
136
|
+
const budget = readFileSync(join(root, budgetSkillPath), "utf8");
|
|
137
|
+
if (!/^name:\s*loop-budget\s*$/m.test(budget)) {
|
|
138
|
+
fail(`${budgetSkillPath} frontmatter must declare name: loop-budget`);
|
|
139
|
+
}
|
|
140
|
+
if (!/loop_operations_snapshot/.test(budget) || !/unavailable/i.test(budget)) {
|
|
141
|
+
fail(`${budgetSkillPath} must use the operational projection and preserve unavailable host-token truth`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
if (failures.length > 0) {
|
|
117
146
|
for (const failure of failures) {
|
|
118
147
|
console.error(`[enact-loop validate] ${failure}`);
|
|
@@ -38,7 +38,7 @@ Stage {
|
|
|
38
38
|
name: string
|
|
39
39
|
type: 'mechanical' | 'judgment'
|
|
40
40
|
required: boolean
|
|
41
|
-
requires?: string[] # stage ids that must pass before this stage
|
|
41
|
+
requires?: string[] # stage ids that must pass before this stage may run or dispatch
|
|
42
42
|
command?: string # mechanical stages only
|
|
43
43
|
grader?: GraderSpec # judgment stages only
|
|
44
44
|
passCriteria?: string
|
|
@@ -111,13 +111,12 @@ idle → running → verifying → completed (terminal)
|
|
|
111
111
|
1. Read current state: `loop_status`
|
|
112
112
|
2. If no active loop, start one:
|
|
113
113
|
```
|
|
114
|
-
loop_start goal="<goal>"
|
|
114
|
+
loop_start goal="<goal>" contract=<stages JSON>
|
|
115
115
|
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
get the right judgment shape by default instead of hand-authoring stages.
|
|
116
|
+
If you want a named delivery shape such as `frontend-ui` or `docs-workflow`,
|
|
117
|
+
build an explicit contract from the factory-owned profile catalog first.
|
|
118
|
+
Legacy runtime preset compatibility may still exist for migration, but profile
|
|
119
|
+
ownership belongs to `enact-factory`, not `enact-loop`.
|
|
121
120
|
3. Implement the work while in phase `running`.
|
|
122
121
|
4. Advance to verification:
|
|
123
122
|
```
|
|
@@ -192,9 +191,9 @@ The independence rule is checked by model identity, not by vendor or surface nam
|
|
|
192
191
|
runs the same underlying model. The engine will emit `grader-independence-missing` and
|
|
193
192
|
block closure. Ensure the resolved `grader.model` differs from the executor's model string.
|
|
194
193
|
|
|
195
|
-
**Default cheap graders:** **
|
|
194
|
+
**Default cheap graders:** **MiniMax M3** (`minimax-coding-plan/MiniMax-M3`, always `--thinking on`) and **kimi K2.7** are vendor-distinct from
|
|
196
195
|
both sonnet and codex, making them always-independent defaults (~80% of grading). Escalate to
|
|
197
|
-
**opus 4.8** or **
|
|
196
|
+
**opus 4.8** or **GPT-5.6 Sol High** for high-risk judgment (a11y / visual / security / architecture).
|
|
198
197
|
`cursor` is only distinct when the executor is opus-tier (cursor runs claude-sonnet).
|
|
199
198
|
|
|
200
199
|
### fallbackModels rotation on spawn failure (529 / overload)
|
|
@@ -270,7 +269,8 @@ verdict fingerprints.
|
|
|
270
269
|
|
|
271
270
|
Each verdict is stamped with a worktree fingerprint. When the workspace changes (e.g. after
|
|
272
271
|
retarget or filesystem drift), stale `passed` verdicts are invalidated (`BLOCKER_FILESYSTEM_STATE_CHANGED`).
|
|
273
|
-
`loop_retarget` preserves verdicts only when `id + type +
|
|
272
|
+
`loop_retarget` preserves verdicts only when `id + type + stage definition + worktreeFingerprint`
|
|
273
|
+
still match unchanged stages.
|
|
274
274
|
|
|
275
275
|
## Workspace root resolution
|
|
276
276
|
|
|
@@ -353,7 +353,7 @@ last message) to decide the next action after the loop completes.
|
|
|
353
353
|
|
|
354
354
|
| Tool | Purpose |
|
|
355
355
|
|------|---------|
|
|
356
|
-
| `loop_start` | Start loop with goal + contract
|
|
356
|
+
| `loop_start` | Start loop with goal + explicit contract; stamps executorIdentity |
|
|
357
357
|
| `loop_status` | Read current loop state |
|
|
358
358
|
| `loop_advance` | Phase transition |
|
|
359
359
|
| `loop_grade` | Run a mechanical stage command in loop root; args: `stageId`, optional `timeoutMs` |
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-budget
|
|
3
|
+
description: This skill should be used when inspecting enact-loop budget status, execution limits, host token availability, 80 percent warnings, exhausted budgets, or why a loop will not run another command or grader.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Enact Loop Budget
|
|
7
|
+
|
|
8
|
+
Use this skill to inspect and explain an existing loop's execution budget. It is an operational-read path, not a loop driver.
|
|
9
|
+
|
|
10
|
+
## Safety boundary
|
|
11
|
+
|
|
12
|
+
- Read state only through `loop_operations_snapshot` or `enact-loop operations --json`.
|
|
13
|
+
- NEVER edit `~/.enact/` files directly.
|
|
14
|
+
- MUST NOT call `loop_start`, `loop_grade`, `loop_grader_dispatch`, `loop_advance`, `loop_retarget`, `loop_complete`, `loop_block`, `loop_abort`, `loop_pause`, or `loop_resume`.
|
|
15
|
+
- Do not invent host-reported token use. If `hostReportedTokens.availability` is `unavailable`, report exactly that a trusted host usage adapter is not configured.
|
|
16
|
+
|
|
17
|
+
## Read and report
|
|
18
|
+
|
|
19
|
+
1. Call `loop_operations_snapshot` from the loop workspace. If MCP is unavailable, run `enact-loop operations --json` from that workspace.
|
|
20
|
+
2. Report the loop id, phase, goal, ledger entry count, and latest event kind.
|
|
21
|
+
3. For each budget counter, preserve its status exactly:
|
|
22
|
+
- `unbounded`: no configured cap.
|
|
23
|
+
- `normal`: below 80% of a configured cap.
|
|
24
|
+
- `warning`: at least 80% used but not exhausted.
|
|
25
|
+
- `exhausted`: the cap is reached; the next matching mechanical command or grader dispatch is refused.
|
|
26
|
+
4. Separate engine-observed counters from host-owned data. Mechanical attempts and grader dispatches are durable runtime facts. Host tokens remain unavailable unless a future trusted adapter attests them.
|
|
27
|
+
5. Recommend an explicit, human-approved next action; do not execute it in this skill.
|
|
28
|
+
|
|
29
|
+
## Contract fields
|
|
30
|
+
|
|
31
|
+
An execution contract may declare:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
executionBudget:
|
|
35
|
+
maxMechanicalAttempts: 3 # per mechanical stage; 0 is an explicit no-op
|
|
36
|
+
maxTotalMechanicalAttempts: 8 # total engine-run mechanical commands; 0 is an explicit no-op
|
|
37
|
+
maxTotalGraderDispatches: 2 # total host grader launches; 0 is an explicit no-op
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
At 80% the projection warns. At 100% the runtime refuses the next matching action. A zero cap is a deliberate no-op, never an inferred lack of data.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop-intake
|
|
3
|
+
description: This skill should be used when triaging ambiguous work, an issue, CI failure, or PR into a proposed enact-loop contract and readiness report before any execution loop is approved.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Enact Loop Intake
|
|
7
|
+
|
|
8
|
+
Turn incoming work into a report-only readiness assessment and a proposed contract. This skill plans; it does not start, mutate, or drive a loop.
|
|
9
|
+
|
|
10
|
+
## Hard boundary
|
|
11
|
+
|
|
12
|
+
- MUST NOT call `loop_start`, `loop_grade`, `loop_grader_dispatch`, `loop_advance`, `loop_retarget`, `loop_complete`, `loop_block`, `loop_abort`, `loop_pause`, or `loop_resume`.
|
|
13
|
+
- MUST NOT edit `~/.enact/`, create a state file, change a factory board, modify a worktree, or run a mutating command.
|
|
14
|
+
- MAY call `loop_operations_snapshot`, `loop_status`, `enact-loop operations --json`, and other read-only inspection commands.
|
|
15
|
+
- Every result ends with `approvalRequired: true`. A proposed contract is not approval to execute it.
|
|
16
|
+
|
|
17
|
+
## Intake procedure
|
|
18
|
+
|
|
19
|
+
1. Capture the request verbatim: desired outcome, affected workspace, constraints, deadline/risk, and available evidence (issue, CI log, PR, or reproduction).
|
|
20
|
+
2. Inspect only the named workspace and any existing loop through `loop_operations_snapshot` or `loop_status`. If an active loop exists, report its id/phase and do not replace or retarget it.
|
|
21
|
+
3. Identify unknowns that could change scope, acceptance, risk, or ownership. Ask concise questions instead of assuming those facts.
|
|
22
|
+
4. Produce a proposed contract only when the goal and acceptance evidence are sufficient. Keep mechanical stages executable and evidence-based; use judgment stages only where an independent assessment is genuinely required.
|
|
23
|
+
5. State dependencies, blockers, expected evidence, and the suggested budget. Set every budget cap deliberately; use `0` only for an explicit no-op.
|
|
24
|
+
|
|
25
|
+
## Required report shape
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
intake:
|
|
29
|
+
outcome: ready | needs-clarification | blocked | active-loop-present
|
|
30
|
+
scope: <one sentence>
|
|
31
|
+
evidence: [<facts inspected>]
|
|
32
|
+
openQuestions: [<questions>]
|
|
33
|
+
risks: [<risks>]
|
|
34
|
+
proposedContract: <contract object or null>
|
|
35
|
+
approvalRequired: true
|
|
36
|
+
proposedNextAction: <human approval or clarification only>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
When `outcome` is not `ready`, set `proposedContract: null`. When it is ready, return the contract in the report only; do not write it to disk or invoke any loop lifecycle tool.
|
|
40
|
+
|
|
41
|
+
## Contract checklist
|
|
42
|
+
|
|
43
|
+
- Unique stage ids and an acyclic `requires` graph.
|
|
44
|
+
- Required mechanical stages have a resolvable command and a concrete pass criterion.
|
|
45
|
+
- Judgment stages name a model distinct from the executor and specify the needed number of rounds.
|
|
46
|
+
- `executionBudget` includes only deliberate limits: per-stage mechanical attempts, total mechanical attempts, and total grader dispatches.
|
|
47
|
+
- Host token data is not part of the proposal unless a trusted host adapter is explicitly available; otherwise mark it unavailable.
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import { listBundles } from "./lib/list-bundles.mjs";
|
|
|
5
5
|
import { buildIndex } from "./lib/build-index.mjs";
|
|
6
6
|
import { runValidate } from "./lib/run-validate.mjs";
|
|
7
7
|
import { runSync } from "./lib/run-sync.mjs";
|
|
8
|
-
import { runInstall } from "./lib/run-install.mjs";
|
|
8
|
+
import { INSTALL_DRY_RUN_UNSUPPORTED_MESSAGE, runInstall } from "./lib/run-install.mjs";
|
|
9
9
|
import { runUpdate } from "./lib/run-update.mjs";
|
|
10
10
|
import { runPrune } from "./lib/run-prune.mjs";
|
|
11
11
|
import { runUninstall } from "./lib/run-uninstall.mjs";
|
|
@@ -66,6 +66,7 @@ Usage:
|
|
|
66
66
|
enact-extensions install [path|name] --platform shared Install skills to host-neutral .agents/skills/
|
|
67
67
|
enact-extensions install [path|name] --platform all Install to every plugin platform (codex, claude, cursor, enact)
|
|
68
68
|
enact-extensions install [path|name] --platform claude,cursor Install to a subset of platforms
|
|
69
|
+
enact-extensions install [path|name] --dry-run Unsupported; exits non-zero without writing anything
|
|
69
70
|
enact-extensions install [path|name] --global Install into the default agent home (default)
|
|
70
71
|
enact-extensions install [path|name] --local Install into a project home under cwd (./.codex, ...)
|
|
71
72
|
enact-extensions uninstall [path|name] Uninstall plugin from Codex-compatible homes (default: enact)
|
|
@@ -932,8 +933,8 @@ if (command === "serve") {
|
|
|
932
933
|
//
|
|
933
934
|
// Currently supported sub-commands:
|
|
934
935
|
// enact-extensions hook session-start
|
|
935
|
-
// Runs the advisory drift-check and prints
|
|
936
|
-
// surfaces are outdated.
|
|
936
|
+
// Runs the advisory drift-check and prints valid hook JSON only when plugin
|
|
937
|
+
// surfaces are outdated or auto-updated.
|
|
937
938
|
// enact-extensions hook user-prompt-submit
|
|
938
939
|
// Reads a UserPromptSubmit hook payload from stdin, extracts the prompt,
|
|
939
940
|
// builds the skill catalog from the bundled extensions dir, routes the
|
|
@@ -962,12 +963,12 @@ if (command === "hook") {
|
|
|
962
963
|
(async () => {
|
|
963
964
|
if (subCommand === "session-start") {
|
|
964
965
|
try {
|
|
965
|
-
const { handleSessionStartDrift } = await import("./lib/auto-update.mjs");
|
|
966
|
+
const { handleSessionStartDrift, sessionStartHookJson } = await import("./lib/auto-update.mjs");
|
|
966
967
|
// Opt-in is the USER's: gate auto-apply on the env var they set.
|
|
967
968
|
const autoApply = !!process.env.ENACT_EXTENSIONS_AUTO_UPDATE;
|
|
968
969
|
const result = handleSessionStartDrift({ autoApply });
|
|
969
970
|
if (result.line) {
|
|
970
|
-
process.stdout.write(`${result.line}\n`);
|
|
971
|
+
process.stdout.write(`${sessionStartHookJson(result.line)}\n`);
|
|
971
972
|
}
|
|
972
973
|
} catch {
|
|
973
974
|
// Fail-silent for session-start hooks.
|
|
@@ -1085,6 +1086,10 @@ try {
|
|
|
1085
1086
|
}
|
|
1086
1087
|
|
|
1087
1088
|
if (command === "install") {
|
|
1089
|
+
if (options.dryRun) {
|
|
1090
|
+
console.error(INSTALL_DRY_RUN_UNSUPPORTED_MESSAGE);
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
1088
1093
|
// Single global ledger: every install (local or global scope) records to
|
|
1089
1094
|
// <homedir>/.enact/extensions/ledger.jsonl so `log` always finds it,
|
|
1090
1095
|
// regardless of which per-platform home the bundle was installed to. The
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* - FAIL-SILENT: wrap everything in try/catch; never throw; always exit 0.
|
|
13
13
|
* - FAST: imports the helper directly (no subprocess) for speed.
|
|
14
14
|
* - READ-ONLY by default: only mutates state when the user opts into auto-apply.
|
|
15
|
-
* - NO OUTPUT on success (or error): only print
|
|
15
|
+
* - NO OUTPUT on success (or error): only print valid hook JSON when there
|
|
16
|
+
* are outdated entries.
|
|
16
17
|
* - DEPENDENCY-FREE: only node builtins + enact-extensions own modules.
|
|
17
18
|
*/
|
|
18
19
|
|
|
@@ -25,7 +26,7 @@ const REPO_ROOT = resolve(__dirname, "..", "..");
|
|
|
25
26
|
|
|
26
27
|
try {
|
|
27
28
|
// Dynamically import so any module-load failure is caught by the outer try/catch.
|
|
28
|
-
const { handleSessionStartDrift } = await import(join(REPO_ROOT, "scripts", "lib", "auto-update.mjs"));
|
|
29
|
+
const { handleSessionStartDrift, sessionStartHookJson } = await import(join(REPO_ROOT, "scripts", "lib", "auto-update.mjs"));
|
|
29
30
|
|
|
30
31
|
// Opt-in is the USER's: gate auto-apply on the env var they set.
|
|
31
32
|
const autoApply = !!process.env.ENACT_EXTENSIONS_AUTO_UPDATE;
|
|
@@ -39,7 +40,7 @@ try {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
if (result.line) {
|
|
42
|
-
process.stdout.write(`${result.line}\n`);
|
|
43
|
+
process.stdout.write(`${sessionStartHookJson(result.line)}\n`);
|
|
43
44
|
}
|
|
44
45
|
} catch {
|
|
45
46
|
// Any top-level error (import failure, parse error, etc.) — silent exit 0.
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* auto-apply when opted in via ENACT_EXTENSIONS_AUTO_UPDATE.
|
|
4
4
|
*
|
|
5
5
|
* Single export:
|
|
6
|
-
* handleSessionStartDrift({ autoApply, home, cwd })
|
|
6
|
+
* handleSessionStartDrift({ autoApply, home, cwd }) -> { outdated, applied, updated, line }
|
|
7
|
+
* sessionStartHookResponse(line) -> valid hook JSON response object
|
|
7
8
|
*
|
|
8
9
|
* Behaviour:
|
|
9
10
|
* - Compute the outdated (drifted) surfaces from the ledger vs canonical source.
|
|
10
11
|
* - No outdated surfaces → { outdated: [], applied: false, line: null }.
|
|
11
12
|
* - Outdated + autoApply=false → advisory line (default; mutates nothing).
|
|
12
|
-
* - Outdated + autoApply=true → runUpdate({ all: true }); summary line naming
|
|
13
|
+
* - Outdated + autoApply=true → runUpdate({ all: true, silent: true }); summary line naming
|
|
13
14
|
* what was refreshed. If the refresh updated
|
|
14
15
|
* nothing actionable (e.g. all orphaned), fall
|
|
15
16
|
* back to the advisory line.
|
|
@@ -43,6 +44,18 @@ export function autoUpdateSummaryLine(updated) {
|
|
|
43
44
|
);
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/** Convert a drift advisory/update line into valid SessionStart hook JSON. */
|
|
48
|
+
export function sessionStartHookResponse(line) {
|
|
49
|
+
const response = { continue: true };
|
|
50
|
+
if (line) response.systemMessage = line;
|
|
51
|
+
return response;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** JSON-string form for command hooks that write their response to stdout. */
|
|
55
|
+
export function sessionStartHookJson(line) {
|
|
56
|
+
return JSON.stringify(sessionStartHookResponse(line));
|
|
57
|
+
}
|
|
58
|
+
|
|
46
59
|
/**
|
|
47
60
|
* Resolve SessionStart drift: advise by default, or auto-apply when opted in.
|
|
48
61
|
*
|
|
@@ -62,7 +75,7 @@ export function handleSessionStartDrift({ autoApply, home, cwd } = {}) {
|
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
if (autoApply) {
|
|
65
|
-
const summary = runUpdate({ all: true, home, cwd });
|
|
78
|
+
const summary = runUpdate({ all: true, silent: true, home, cwd });
|
|
66
79
|
const updated = summary?.updated ?? [];
|
|
67
80
|
if (updated.length === 0) {
|
|
68
81
|
// Nothing actionable was refreshed (e.g. orphaned source) — advise instead.
|
|
@@ -17,6 +17,9 @@ import { provisionMcp, summarizeProvision } from "./provision-mcp.mjs";
|
|
|
17
17
|
import { registerPluginHooks, removeHook, removePluginHooks } from "./hooks.mjs";
|
|
18
18
|
import { assertSupportedHookEvents } from "../../dist/index.js";
|
|
19
19
|
|
|
20
|
+
export const INSTALL_DRY_RUN_UNSUPPORTED_MESSAGE =
|
|
21
|
+
"install --dry-run is not supported. No changes were made.";
|
|
22
|
+
|
|
20
23
|
// scope: "global" (default) -> the agent's default home (~/.codex, ~/.enact/agent, ~/.claude, ~/.cursor, ~/.agents)
|
|
21
24
|
// "local" -> a project-scoped home under the current dir (./.codex, ./.enact/agent, ...)
|
|
22
25
|
// An explicit --<platform>-home always wins over the scope default.
|
|
@@ -439,6 +442,10 @@ function runProvision(pluginRoot, options) {
|
|
|
439
442
|
}
|
|
440
443
|
|
|
441
444
|
export function runInstall(pluginRoot, options = {}) {
|
|
445
|
+
if (options.dryRun) {
|
|
446
|
+
throw new Error(INSTALL_DRY_RUN_UNSUPPORTED_MESSAGE);
|
|
447
|
+
}
|
|
448
|
+
|
|
442
449
|
// Refuse to install a bundle that declares hook events outside the
|
|
443
450
|
// cross-surface intersection (claude, codex, cursor, enact).
|
|
444
451
|
assertSupportedHookEvents(pluginRoot);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* run-update.mjs — refresh installed plugins that have drifted from their
|
|
3
3
|
* canonical source bundle.
|
|
4
4
|
*
|
|
5
|
-
* runUpdate({ name?, all?, dryRun?, home?, cwd? }) → summary
|
|
5
|
+
* runUpdate({ name?, all?, dryRun?, home?, cwd?, silent? }) → summary
|
|
6
6
|
*
|
|
7
7
|
* The update is driven ENTIRELY by the ledger, per (plugin, surface, home):
|
|
8
8
|
*
|
|
@@ -80,11 +80,12 @@ function safeResolveBundlePath(name, cwd) {
|
|
|
80
80
|
* @param {string} [opts.name] — restrict to a single plugin name.
|
|
81
81
|
* @param {boolean} [opts.all] — update every outdated surface.
|
|
82
82
|
* @param {boolean} [opts.dryRun]— report only; make no changes.
|
|
83
|
+
* @param {boolean} [opts.silent]— suppress install stdout during refresh.
|
|
83
84
|
* @param {string} [opts.home] — ledger home override (defaults to os.homedir()).
|
|
84
85
|
* @param {string} [opts.cwd] — working dir for canonical bundle resolution.
|
|
85
86
|
* @returns {{ planned: object[], updated: object[], skipped: object[], failed: {entry: object, error: Error}[], dryRun: boolean }}
|
|
86
87
|
*/
|
|
87
|
-
export function runUpdate({ name, all, dryRun, home, cwd } = {}) {
|
|
88
|
+
export function runUpdate({ name, all, dryRun, silent, home, cwd } = {}) {
|
|
88
89
|
const resolvedHome = home ?? homedir();
|
|
89
90
|
const resolvedCwd = cwd ?? processCwd();
|
|
90
91
|
|
|
@@ -131,17 +132,29 @@ export function runUpdate({ name, all, dryRun, home, cwd } = {}) {
|
|
|
131
132
|
|
|
132
133
|
const homeOption = PLATFORM_HOME_OPTION[entry.platform] ?? "codexHome";
|
|
133
134
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
135
|
+
const install = () =>
|
|
136
|
+
runInstall(canonicalPath, {
|
|
137
|
+
platform: entry.platform,
|
|
138
|
+
scope: entry.scope,
|
|
139
|
+
[homeOption]: entry.home,
|
|
140
|
+
ledgerHome: resolvedHome,
|
|
141
|
+
// Refresh the already-installed surface only; do not regenerate source
|
|
142
|
+
// manifests, and skip MCP provisioning (a refresh re-installs identical
|
|
143
|
+
// package deps — provisioning is best-effort and not needed here).
|
|
144
|
+
sync: false,
|
|
145
|
+
noProvision: true,
|
|
146
|
+
});
|
|
147
|
+
if (silent) {
|
|
148
|
+
const originalLog = console.log;
|
|
149
|
+
try {
|
|
150
|
+
console.log = () => {};
|
|
151
|
+
install();
|
|
152
|
+
} finally {
|
|
153
|
+
console.log = originalLog;
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
install();
|
|
157
|
+
}
|
|
145
158
|
updated.push(entry);
|
|
146
159
|
} catch (err) {
|
|
147
160
|
failed.push({ entry, error: err instanceof Error ? err : new Error(String(err)) });
|