@mutmutco/codex-plugin 3.139.0 → 3.139.1
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/.codex-plugin/plugin.json +1 -1
- package/hooks/codex-hooks.json +1 -12
- package/package.json +2 -1
- package/prompts/soul.md +71 -0
- package/scripts/command-ladder-core.mjs +2 -2
- package/scripts/hook-policy.mjs +19 -14
- package/scripts/hook-run.mjs +6 -27
- package/scripts/pretooluse-shell-gates.mjs +155 -15
- package/scripts/secret-echo-lint.mjs +1 -1
- package/skills/bootstrap/SKILL.md +2 -2
- package/skills/mmi/SKILL.md +11 -19
- package/skills/mmi-doctor/SKILL.md +9 -13
- package/skills/stage/SKILL.md +1 -1
- package/scripts/secret-redact.mjs +0 -552
- package/scripts/validate-hook.mjs +0 -156
- package/skills/worktree/SKILL.md +0 -151
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// PreToolUse validate-hook (#2691): catch a bad mmi-cli WRITE before it spends a failed round-trip.
|
|
2
|
-
// When a Bash/PowerShell tool call is a simple `mmi-cli <verb> ...` invocation, this re-runs the SAME
|
|
3
|
-
// command with `--validate-only` (C3 middleware — a PURE LOCAL parse + flag/enum/ref check that writes
|
|
4
|
-
// NOTHING and never hits the network) against the shipped bundle. A validation failure surfaces as an
|
|
5
|
-
// ADVISORY (warn to stderr, never a deny) so the agent fixes the flags before the real write.
|
|
6
|
-
//
|
|
7
|
-
// FAST + FAIL-OPEN by contract: it only spawns for a detected simple mmi-cli command (never for general
|
|
8
|
-
// shell), skips any compound/piped/redirected command (too risky to reconstruct), bounds the spawn with a
|
|
9
|
-
// short timeout, and swallows every error — a validate probe must never block or slow a tool call it can't
|
|
10
|
-
// help. Disabled by default (opt-in): enable with MMI_VALIDATE_HOOK=on.
|
|
11
|
-
import { execFileSync } from 'node:child_process';
|
|
12
|
-
import { existsSync } from 'node:fs';
|
|
13
|
-
import { dirname, join, resolve } from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
import { tokenizeShell } from './command-ladder-core.mjs';
|
|
16
|
-
import { appendHookActivity } from './hook-trace.mjs';
|
|
17
|
-
|
|
18
|
-
const SPAWN_TIMEOUT_MS = 6000;
|
|
19
|
-
const MMI_BINARIES = new Set(['mmi-cli', 'mmi', 'mmi-cli.cmd', 'mmi.cmd']);
|
|
20
|
-
const WRAPPER_TOKENS = new Set(['command', 'sudo', 'npx']);
|
|
21
|
-
// Any of these in the raw command means it is not a single simple invocation — skip (do not reconstruct).
|
|
22
|
-
const SHELL_META_RE = /[|&;`]|\$\(|[<>]|\|\||&&/;
|
|
23
|
-
|
|
24
|
-
/** Strip one layer of matching surrounding quotes from a token so it becomes a real argv value. */
|
|
25
|
-
function unquote(tok) {
|
|
26
|
-
if (tok.length >= 2 && ((tok[0] === '"' && tok.at(-1) === '"') || (tok[0] === "'" && tok.at(-1) === "'"))) {
|
|
27
|
-
return tok.slice(1, -1);
|
|
28
|
-
}
|
|
29
|
-
return tok;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Detect a single, simple `mmi-cli <args...>` invocation and return its argv (quote-stripped), or null.
|
|
34
|
-
* Conservative by design: rejects any command carrying shell metacharacters (pipes, redirects, compounds,
|
|
35
|
-
* substitutions) so we never mis-reconstruct a complex command. Tolerates leading `command`/`sudo`/`npx`
|
|
36
|
-
* wrappers and inline `VAR=val` assignments. Exported (pure) for tests.
|
|
37
|
-
* @param {string} command
|
|
38
|
-
* @returns {{ argv: string[] } | null}
|
|
39
|
-
*/
|
|
40
|
-
export function detectMmiCliCommand(command) {
|
|
41
|
-
if (!command || typeof command !== 'string') return null;
|
|
42
|
-
if (SHELL_META_RE.test(command)) return null;
|
|
43
|
-
const tokens = tokenizeShell(command);
|
|
44
|
-
let i = 0;
|
|
45
|
-
while (i < tokens.length && (WRAPPER_TOKENS.has(tokens[i]) || /^[A-Za-z_][\w]*=/.test(tokens[i]))) i += 1;
|
|
46
|
-
const bin = tokens[i];
|
|
47
|
-
if (!bin || !MMI_BINARIES.has(bin.toLowerCase())) return null;
|
|
48
|
-
const argv = tokens.slice(i + 1).map(unquote);
|
|
49
|
-
if (!argv.length) return null; // bare `mmi-cli` with no verb — nothing to validate
|
|
50
|
-
// `--validate-only` already present ⇒ the agent is deliberately dry-running; don't double up.
|
|
51
|
-
if (argv.includes('--validate-only') || argv.includes('--dry-run')) return null;
|
|
52
|
-
return { argv };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Interpret a `--validate-only` spawn result into an advisory line, or null (silent). Pure + testable.
|
|
57
|
-
* - stdout `{ok:true,...}` → the command validates → silent.
|
|
58
|
-
* - stderr "unknown option/command" → not a mutating/known command (validate not applicable) → silent.
|
|
59
|
-
* - non-zero exit otherwise → a real validation problem the write would hit → advise with the message.
|
|
60
|
-
* @param {{ status: number|null, stdout: string, stderr: string }} res
|
|
61
|
-
* @returns {string | null}
|
|
62
|
-
*/
|
|
63
|
-
export function interpretValidateResult(res) {
|
|
64
|
-
const stdout = String(res?.stdout ?? '').trim();
|
|
65
|
-
const stderr = String(res?.stderr ?? '').trim();
|
|
66
|
-
if (stdout.startsWith('{')) {
|
|
67
|
-
try {
|
|
68
|
-
if (JSON.parse(stdout).ok === true) return null; // validates cleanly
|
|
69
|
-
} catch {
|
|
70
|
-
/* fall through — non-JSON stdout is not a success signal */
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (/unknown option|unknown command/i.test(stderr)) return null; // validate not applicable to this command
|
|
74
|
-
if (res?.status && res.status !== 0) {
|
|
75
|
-
const firstLine = (stderr.split(/\r?\n/).find((l) => l.trim()) ?? stdout.split(/\r?\n/)[0] ?? '').trim();
|
|
76
|
-
return firstLine || null;
|
|
77
|
-
}
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Resolve the shipped CLI bundle path: explicit override, then the plugin root, then a path relative to
|
|
82
|
-
* this script (repo layout: scripts/ and cli/ are siblings). Returns null when none exists. */
|
|
83
|
-
function resolveBundle(env) {
|
|
84
|
-
const candidates = [];
|
|
85
|
-
if (env.MMI_CLI_BUNDLE) candidates.push(env.MMI_CLI_BUNDLE);
|
|
86
|
-
if (env.CLAUDE_PLUGIN_ROOT) candidates.push(join(env.CLAUDE_PLUGIN_ROOT, 'cli', 'dist', 'index.cjs'));
|
|
87
|
-
candidates.push(resolve(dirname(fileURLToPath(import.meta.url)), '..', 'cli', 'dist', 'index.cjs'));
|
|
88
|
-
return candidates.find((p) => existsSync(p)) ?? null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Run the validate advisory for a PreToolUse Bash/PowerShell input. IO-thin: gating + one guarded spawn.
|
|
93
|
-
* Injected `spawnFn` and `bundleResolver` keep it unit-testable without the real bundle.
|
|
94
|
-
*/
|
|
95
|
-
export function runValidateAdvisory(input, { stderr = process.stderr, env = process.env, spawnFn, bundleResolver = resolveBundle } = {}) {
|
|
96
|
-
// Default OFF (opt-in): the advisory spawns a ~150-400ms bundle on write-shaped mmi-cli calls, above the
|
|
97
|
-
// "tens of ms" hook budget, so it stays out of the hot path unless explicitly enabled with MMI_VALIDATE_HOOK=on.
|
|
98
|
-
if (String(env.MMI_VALIDATE_HOOK ?? '').toLowerCase() !== 'on') return { ran: false };
|
|
99
|
-
const tool = input?.tool_name;
|
|
100
|
-
if (tool !== 'Bash' && tool !== 'PowerShell') return { ran: false };
|
|
101
|
-
|
|
102
|
-
const detected = detectMmiCliCommand(input?.tool_input?.command);
|
|
103
|
-
if (!detected) return { ran: false };
|
|
104
|
-
|
|
105
|
-
const bundle = bundleResolver(env);
|
|
106
|
-
if (!bundle) return { ran: false };
|
|
107
|
-
|
|
108
|
-
let res;
|
|
109
|
-
try {
|
|
110
|
-
const run = spawnFn ?? ((args) => {
|
|
111
|
-
const r = execFileSync(process.execPath, [bundle, ...args], {
|
|
112
|
-
encoding: 'utf8',
|
|
113
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
114
|
-
timeout: SPAWN_TIMEOUT_MS,
|
|
115
|
-
windowsHide: true,
|
|
116
|
-
env: { ...env, MMI_VALIDATE_HOOK: 'off' }, // guard against any re-entrant hook
|
|
117
|
-
});
|
|
118
|
-
return { status: 0, stdout: r, stderr: '' };
|
|
119
|
-
});
|
|
120
|
-
res = run([...detected.argv, '--validate-only']);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
// execFileSync throws on a non-zero exit — that carries the validation failure on stdout/stderr.
|
|
123
|
-
if (e && (e.stdout !== undefined || e.stderr !== undefined)) {
|
|
124
|
-
res = { status: e.status ?? 1, stdout: String(e.stdout ?? ''), stderr: String(e.stderr ?? '') };
|
|
125
|
-
} else {
|
|
126
|
-
appendHookActivity({ event: 'PreToolUse', script: 'validate-hook', outcome: 'failed', action: 'spawn error', tool });
|
|
127
|
-
return { ran: false }; // spawn/timeout error — fail-open, silent
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const advice = interpretValidateResult(res);
|
|
132
|
-
appendHookActivity({
|
|
133
|
-
event: 'PreToolUse',
|
|
134
|
-
script: 'validate-hook',
|
|
135
|
-
outcome: advice ? 'observe' : 'ran',
|
|
136
|
-
action: advice ? advice.slice(0, 200) : 'valid',
|
|
137
|
-
tool,
|
|
138
|
-
});
|
|
139
|
-
if (advice) {
|
|
140
|
-
stderr.write(`[mmi-validate] --validate-only flagged this command before it runs: ${advice}\n`);
|
|
141
|
-
stderr.write('[mmi-validate] advisory only — fix the flags/enums above, or proceed if this is intended.\n');
|
|
142
|
-
}
|
|
143
|
-
return { ran: true, advised: Boolean(advice) };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
process.argv[1] &&
|
|
148
|
-
(process.argv[1].endsWith('validate-hook.mjs') ||
|
|
149
|
-
process.argv[1].replace(/\\/g, '/').endsWith('scripts/validate-hook.mjs'))
|
|
150
|
-
) {
|
|
151
|
-
import('./hook-io.mjs')
|
|
152
|
-
.then(({ readHookInput }) => readHookInput())
|
|
153
|
-
.then((input) => runValidateAdvisory(input))
|
|
154
|
-
.catch(() => undefined)
|
|
155
|
-
.finally(() => process.exit(0));
|
|
156
|
-
}
|
package/skills/worktree/SKILL.md
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: worktree
|
|
3
|
-
description: Orchestrate a worktree from create to landed PR, tied to board status.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
**Host-native invocation:** Claude `/mmi:worktree` · Codex `$mmi:worktree` · jervcode/Kimi `/skill:worktree` · Kilo `skill` tool. A backticked `/name` in this doc names the matching workflow (this skill or a sibling), not a literal command.
|
|
7
|
-
|
|
8
|
-
# /worktree — create → work → land
|
|
9
|
-
|
|
10
|
-
Drive an item through an isolated worktree: cut it from latest `development`, claim the board item, do the
|
|
11
|
-
work, open and land the PR, and clean up — with the board moving automatically at each boundary. One
|
|
12
|
-
worktree per session; everything under `../mmi-worktrees/` is ephemeral and sweepable.
|
|
13
|
-
|
|
14
|
-
## Step 1 — create + provision
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
mmi-cli worktree create <owner/repo#N> --claim --from origin/development
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`worktree create` cuts the branch from `origin/development` (fetched first) and provisions it: installs
|
|
21
|
-
deps (`npm ci`) and copies local-only config (`.claude/settings.local.json`) a fresh checkout lacks. Use
|
|
22
|
-
`--from <ref>` for a non-default base, `--path <path>` to override the location. An existing worktree that
|
|
23
|
-
lost its deps re-provisions with `mmi-cli worktree setup [path]` (the SessionStart hook fires this
|
|
24
|
-
automatically).
|
|
25
|
-
|
|
26
|
-
The issue-ref form derives `<issue-number>-<short-slug>`, assigns the item, and moves it to In Progress.
|
|
27
|
-
|
|
28
|
-
`worktree create` also leases the tree to the **creating session** (#4328). A later `jerv-cli lane submit
|
|
29
|
-
--dir` (or any governed seat) refuses an already-held host lease and will not commandeer it. Before handing
|
|
30
|
-
the tree to a governed seat, release that lease first — safe once the session's own edits in that tree are
|
|
31
|
-
committed and pushed: `jerv-cli lease close --ref <worktree path>` (closes every lease on that ref; no id
|
|
32
|
-
lookup needed). To inspect what's held before closing, `jerv-cli lease list` shows id, ref, and owner.
|
|
33
|
-
|
|
34
|
-
## Step 2 — claim + work
|
|
35
|
-
|
|
36
|
-
Work in the provisioned worktree. Exercise the change with `/stage` (a local stage is bound to the worktree that
|
|
37
|
-
started it — stop it before switching worktrees). Sequential related items in one session reuse the active
|
|
38
|
-
worktree; do not churn one worktree per issue unless a true parallel or PR boundary needs it.
|
|
39
|
-
|
|
40
|
-
## Step 3 — open the PR
|
|
41
|
-
|
|
42
|
-
Push the branch and open the PR against `development`. The board moves to In Review automatically on PR
|
|
43
|
-
open — never move it by hand.
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
git push origin <branch>:<branch> # explicit feature refspec — see below
|
|
47
|
-
mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development
|
|
48
|
-
mmi-cli devops pr checks-wait <PR-number> # wait for required CI to go green
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Write the PR body under `.jerv/` (#4405), never at the worktree root. `.jerv/` is the agent-artifact
|
|
52
|
-
prefix `worktree land` treats as removable; a stray untracked `PR_BODY.md` anywhere else classifies the
|
|
53
|
-
tree as `untracked-files` and Step 4 then skips cleanup entirely.
|
|
54
|
-
|
|
55
|
-
The push must be the explicit `<branch>:<branch>` refspec (the branch Step 1 derived, e.g.
|
|
56
|
-
`git push origin 3795-my-slice:3795-my-slice`). The #1660 protected-push gate denies the
|
|
57
|
-
`HEAD`/remote-only form (`git push -u origin HEAD`) — its target cannot be proven safe. And never
|
|
58
|
-
share the push with staging or commit verbs in one compound command: the deny applies to the whole
|
|
59
|
-
command, so a refused `git add … && git commit … && git push …` chain discards the add and commit
|
|
60
|
-
with it. Stage, commit, and push as separate commands.
|
|
61
|
-
|
|
62
|
-
If the branch adds, removes, or renames any `docs/` file, `pr create` refuses with a stale
|
|
63
|
-
`docs/index.md` (#4092) — run `mmi-cli oracle docs index --write`, commit `docs/index.md`, and push again
|
|
64
|
-
before retrying.
|
|
65
|
-
|
|
66
|
-
## Step 4 — land + clean up
|
|
67
|
-
|
|
68
|
-
Under standing go (green CI, CI-gated PR) land to `development`. **Invoke `mmi-cli devops pr land` from the
|
|
69
|
-
primary checkout** (or any cwd that is not the PR worktree) (#4549). Landing while cwd is still inside
|
|
70
|
-
the worktree that cleanup removes can merge successfully and still exit 1 with
|
|
71
|
-
`cleanupError: … Unable to read current working directory` — post-merge `gh`/`git` follow-ups then have
|
|
72
|
-
no readable cwd even when removal itself chdir'd away (#4140).
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
cd <primary-checkout> # e.g. the main MMI-Hub clone — not the slice worktree
|
|
76
|
-
mmi-cli devops pr land <PR-number>
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
`pr land` waits for checks, squash-merges, and does the full cleanup at the branch boundary: removes the
|
|
80
|
-
worktree, deletes the merged branch (local + origin), and prunes tracking refs. A worktree the IDE has
|
|
81
|
-
locked is queued and retried by `mmi-cli worktree gc sweep-deferred`. Self-authored merges need an explicit
|
|
82
|
-
per-session merge grant — ask early if you don't have one.
|
|
83
|
-
|
|
84
|
-
**`not-attempted (untracked-files)` (#3500 / #4405):** the usual cause of a merged PR that left its
|
|
85
|
-
worktree behind. Any untracked path outside `.jerv/` — a hand-written `PR_BODY.md`, notes, a scratch
|
|
86
|
-
script — makes removal refuse, because an unadded file can be real work. Delete or move the file, then
|
|
87
|
-
re-run cleanup **from inside the worktree** (`worktree land` has no `--path`; it acts on the tree you
|
|
88
|
-
are standing in):
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
mmi-cli worktree land --apply
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Cwd-safe removal (#4140):** `worktree land --apply` (what `pr land` runs) releases the process cwd to
|
|
95
|
-
the primary checkout before deleting the tree (#1444/#2747) so Windows `rmdir` is not `EBUSY`. That does
|
|
96
|
-
**not** replace the agent rule above: still start `pr land` from the primary checkout (#4549). Prefer
|
|
97
|
-
primary cwd also when running `worktree land` manually outside `pr land`. If removal still fails (IDE
|
|
98
|
-
lock, antivirus), the deferred sweep retries from a safe cwd.
|
|
99
|
-
|
|
100
|
-
**No main lineage on a squashable development PR (#4365 / #4371):** never `git merge -s ours` (or otherwise
|
|
101
|
-
merge) a main-parented port commit into a development PR that will squash. Squash folds second-parent
|
|
102
|
-
trailers — including foreign `Closes #N` — into the development squash body, closes the wrong issues, and
|
|
103
|
-
trips the closing-keyword land guard. Keep the cherry-pick / main-clean source on a separate branch or ref;
|
|
104
|
-
land only development-tree changes on the development PR. To record a pushed main-clean SHA for a later
|
|
105
|
-
`/hotfix --from` without polluting squash parents, prefer a marker-only commit on the development branch
|
|
106
|
-
that references that SHA in the message — do not pull the main commit into the squashable parent list.
|
|
107
|
-
This does not weaken the `#3167` merge floor on `/hotfix` and `/release` (never squash a tagged commit;
|
|
108
|
-
alignment / roll-forward stays a true merge).
|
|
109
|
-
|
|
110
|
-
**Stay-open phrasing (JC#495 / #3718):** GitHub's closing-keyword parser is negation-blind. `Does not
|
|
111
|
-
close #N` still contains `close #N` and **closes the issue** on merge/squash (measured: Jerv-JervCode
|
|
112
|
-
PR #493 closed #487). When an issue must stay open, never put `close`/`closes`/`fix`/`resolve` + `#N`
|
|
113
|
-
in the PR body, commit message, or merge message — use `Part of #N`, `Refs #N`, or `leaves #N open` only.
|
|
114
|
-
`mmi-cli devops pr create` rewrites common negated phrases; `pr land` / `pr merge` still refuse any remaining
|
|
115
|
-
negation-blind close. `--force` only when those targets should actually close.
|
|
116
|
-
|
|
117
|
-
If a PR already inherited closed-issue `Closes` keywords from that anti-pattern and `pr land` / `pr merge`
|
|
118
|
-
refuses, land with `--force` only when those inherited targets are already closed — prefer preventing the
|
|
119
|
-
pollution above over relying on `--force`.
|
|
120
|
-
|
|
121
|
-
## Step 5 — record + next
|
|
122
|
-
|
|
123
|
-
Record durable decisions on the issue or PR, then check the board again:
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
mmi-cli oracle wave status # remaining worktrees, open PRs, local stages at a glance
|
|
127
|
-
mmi-cli oracle next # the next actionable item
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Notes
|
|
131
|
-
|
|
132
|
-
- Cut worktrees only at `../mmi-worktrees/<RepoName>/<branch>` (#3471) — generic helpers that force `.claude/worktrees/` or
|
|
133
|
-
`.worktrees/` are not the MMI path.
|
|
134
|
-
- Multiple independent items → one worktree each, run in parallel, one PR per item (`/mmi` Leverage).
|
|
135
|
-
- Never land to a protected/release branch here — that is `/rcand` and `/release`.
|
|
136
|
-
- **Keep Cursor on the primary checkout (#4489 / #4901).** Edit the worktree without
|
|
137
|
-
`move_agent_to_root` into the slice — moving the workspace root pins the session so land/gc
|
|
138
|
-
refuse forever. If Cursor’s agent workspace is still rooted at the slice, `worktree land` /
|
|
139
|
-
`worktree gc --apply` / doctor gc **refuse** to delete that directory (and queue deferred
|
|
140
|
-
removal). On a Cursor agent host, land/gc fail closed when the active root cannot be resolved:
|
|
141
|
-
open the primary first, or set `MMI_ACTIVE_WORKSPACE_ROOT` to the primary path. After Cursor is
|
|
142
|
-
on the primary, `mmi-cli worktree gc sweep-deferred` finishes cleanup.
|
|
143
|
-
|
|
144
|
-
## Retro — one check before you finish
|
|
145
|
-
Before your final report, answer one question honestly: did **this skill's own instructions** misfire
|
|
146
|
-
this run — ambiguous wording, a misleading message, or an environment failure it should have warned
|
|
147
|
-
about? (Process only — never the user's code or task; e.g. a create that branched from a stale base, or a
|
|
148
|
-
land that left a worktree behind.) If yes, file **one** lesson and move on; a clean run is silent (hard
|
|
149
|
-
cap: one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never edit
|
|
150
|
-
the skill live; the retro is advisory, so if the call fails, note it and continue:
|
|
151
|
-
`mmi-cli learning skill-lesson --skill worktree --title "<what misfired>" --body "<what; evidence; proposed amendment>"`
|