@mutmutco/codex-plugin 4.3.6 → 4.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -541,7 +541,9 @@ mmi-cli devops train doctor --lane release --repo "$OWNER/$REPO" --json
|
|
|
541
541
|
|
|
542
542
|
`ready: true` is the bootstrap's train-readiness proof; every finding carries `code`, `severity`, `source`,
|
|
543
543
|
`remedy` and a `docs/Guides/train-troubleshooting.md#<code>` anchor. A full-track repo runs
|
|
544
|
-
`--lane rcand` as well. A `bootstrap-gap`
|
|
544
|
+
`--lane rcand` as well. A `bootstrap-gap` finding names the apply row still missing — a blocker for a
|
|
545
|
+
train branch absent on origin, a warning for a stage with no required status check (#6136): read the
|
|
546
|
+
warnings too, `ready: true` alone does not prove the product ruleset is active.
|
|
545
547
|
|
|
546
548
|
## Retro — one check before you finish
|
|
547
549
|
Before your final report, answer one question honestly: did **this skill's own instructions** misfire
|
package/skills/epic/SKILL.md
CHANGED
|
@@ -50,9 +50,9 @@ Write a JSON array (one object per child) to a scratch path under `.jerv/tmp/`.
|
|
|
50
50
|
|
|
51
51
|
- `type` — `bug` · `feature` · `task` (required; maps to the repo's issue-template label).
|
|
52
52
|
- `title` — required, non-empty; keep it a short, greppable deliverable.
|
|
53
|
-
- `parent` — the umbrella ref (
|
|
54
|
-
create, so you do not need a separate `link-child`
|
|
55
|
-
product-repo children).
|
|
53
|
+
- `parent` — the umbrella ref (`N` or `#N` as a string, a bare JSON number, `owner/repo#N`, or
|
|
54
|
+
URL); links each child as a native sub-issue on create, so you do not need a separate `link-child`
|
|
55
|
+
pass. Cross-repo is fine (a Hub umbrella can parent product-repo children).
|
|
56
56
|
- `priority` — `urgent|high|medium|low` (defaults to `medium`); sets the board Priority **field**.
|
|
57
57
|
- `repo` — optional per-row target repo (`owner/repo`); defaults to `--repo`, then the current repo.
|
|
58
58
|
This is how one batch fans children out across repos under a single umbrella.
|
package/skills/hotfix/SKILL.md
CHANGED
|
@@ -65,7 +65,7 @@ MMI-Hub adds `--announce-summary-file "$f"` with a fresh 3–6 line neutral summ
|
|
|
65
65
|
|
|
66
66
|
Read the receipts, never the exit code (exit `2` = a follow-up pending; exit `1` = a follow-up failed after the tag):
|
|
67
67
|
|
|
68
|
-
- `runs[]` — the release-triggered runs on the tagged SHA (workflow, conclusion, url). Watch each non-success entry to conclusion: `gh run watch <run-id> --repo {owner}/{repo} --exit-status`. Name which run is red: `deploy.yml` / `publish.yml` / `tenant-deploy.yml` is a deploy or publish verdict; a `gate` push run is ordinary CI sharing the SHA.
|
|
68
|
+
- `runs[]` — the release-triggered runs on the tagged SHA (workflow, conclusion, url). Watch each non-success entry to conclusion: `gh run watch <run-id> --repo {owner}/{repo} --exit-status`. Name which run is red: `deploy.yml` / `publish.yml` / `tenant-deploy.yml` / `jerv-gateway` is a deploy or publish verdict; a `gate` push run is ordinary CI sharing the SHA. `jerv-gateway` is the operator-host deploy leg: it has no run id to watch, so read its `deployNote` / `ledger.phases.deploy` — `pending` there means production was never deployed and blocks the next train run.
|
|
69
69
|
- `foldStatus` + `foldNote` — the `hotfix-fold/<tag>` development PR; land it by true merge (Merge floor) and say whether it landed.
|
|
70
70
|
- `announceNote` (Hub), `verifyNote`, `checkout`, `localSync`, `ledger` — the durable phase legs; a pending or failed leg is unresolved, never done.
|
|
71
71
|
|
|
@@ -91,7 +91,7 @@ Every refusal and every doctor blocker names its `code` and `anchor`: open `docs
|
|
|
91
91
|
|
|
92
92
|
## Report
|
|
93
93
|
|
|
94
|
-
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), every
|
|
94
|
+
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), and every workflow run row with its conclusion and evidence — use its URL when emitted, otherwise the receipt's explicit no-run-URL note — saying which run is red, and whether it is a deploy/publish verdict or ordinary CI — `announceNote`, the alignment note (`devRollForward`, `rcAlignment`, or the hotfix fold PR and whether it landed), the `checkout` clause, and the sanctioned dispatch mechanism you used. A pending follow-up is reported as unverified, never as success. Product names appear only when releasing that product's repo.
|
|
95
95
|
|
|
96
96
|
## Retro
|
|
97
97
|
|
package/skills/rcand/SKILL.md
CHANGED
|
@@ -69,7 +69,7 @@ Every refusal and every doctor blocker names its `code` and `anchor`: open `docs
|
|
|
69
69
|
|
|
70
70
|
## Report
|
|
71
71
|
|
|
72
|
-
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), every
|
|
72
|
+
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), and every workflow run row with its conclusion and evidence — use its URL when emitted, otherwise the receipt's explicit no-run-URL note — saying which run is red, and whether it is a deploy/publish verdict or ordinary CI — `announceNote`, the alignment note (`devRollForward`, `rcAlignment`, or the hotfix fold PR and whether it landed), the `checkout` clause, and the sanctioned dispatch mechanism you used. A pending follow-up is reported as unverified, never as success. Product names appear only when releasing that product's repo.
|
|
73
73
|
|
|
74
74
|
## Retro
|
|
75
75
|
|
package/skills/release/SKILL.md
CHANGED
|
@@ -76,7 +76,7 @@ Every refusal and every doctor blocker names its `code` and `anchor`: open `docs
|
|
|
76
76
|
|
|
77
77
|
## Report
|
|
78
78
|
|
|
79
|
-
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), every
|
|
79
|
+
Title first, verdict first. Name the resolved tag (never a placeholder like `vX.Y.0`) and the Release URL, the receipt's status fields (`releaseStatus`, `followUpStatus`, `deployStatus`, `promoted`), and every workflow run row with its conclusion and evidence — use its URL when emitted, otherwise the receipt's explicit no-run-URL note — saying which run is red, and whether it is a deploy/publish verdict or ordinary CI — `announceNote`, the alignment note (`devRollForward`, `rcAlignment`, or the hotfix fold PR and whether it landed), the `checkout` clause, and the sanctioned dispatch mechanism you used. A pending follow-up is reported as unverified, never as success. Product names appear only when releasing that product's repo.
|
|
80
80
|
|
|
81
81
|
## Retro
|
|
82
82
|
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// The files one edit-shaped tool call touches, across BOTH host contracts (#3563).
|
|
2
|
-
//
|
|
3
|
-
// Claude sends `Edit`/`Write` with `tool_input.file_path`. Codex also sends `apply_patch`, and puts the
|
|
4
|
-
// whole patch in `tool_input.command` — there is no `file_path` at all. A gate that reads only
|
|
5
|
-
// `file_path` therefore sees nothing to check and exits 0: on Codex the vault-edit gate FAILED OPEN and
|
|
6
|
-
// `apply_patch` could create a `.env`, while the guide claimed the path was protected (cross-vendor
|
|
7
|
-
// check, openai-sol). Matching `apply_patch` in a host manifest does not rewrite the payload.
|
|
8
|
-
//
|
|
9
|
-
// One extractor, used by every edit-shaped integration, so a new host contract is taught in one place.
|
|
10
|
-
|
|
11
|
-
/** Tool names that mean "this call writes files". `StrReplace` is Cursor's in-place edit tool: it
|
|
12
|
-
* carries its target in `tool_input.path` and was outside this set, so even once the adapter matched
|
|
13
|
-
* it the gate would have read a non-edit tool and allowed (#5852). */
|
|
14
|
-
export const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'apply_patch', 'ApplyPatch', 'StrReplace']);
|
|
15
|
-
|
|
16
|
-
export function isEditTool(toolName) {
|
|
17
|
-
return typeof toolName === 'string' && EDIT_TOOL_NAMES.has(toolName);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// `*** Add File: path`, `*** Update File: path`, `*** Delete File: path`, and the `*** Move to: path`
|
|
21
|
-
// destination of a rename. Case-insensitive on the verb; the path runs to end of line.
|
|
22
|
-
const PATCH_TOUCH = /^\*\*\*\s+(add|update|delete)\s+file:\s*(.+?)\s*$/gim;
|
|
23
|
-
const PATCH_MOVE = /^\*\*\*\s+move\s+to:\s*(.+?)\s*$/gim;
|
|
24
|
-
|
|
25
|
-
function scan(re, patch, onMatch) {
|
|
26
|
-
re.lastIndex = 0;
|
|
27
|
-
for (let m = re.exec(patch); m; m = re.exec(patch)) onMatch(m);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Every path an `apply_patch` body TOUCHES — created, updated, deleted, or renamed onto. */
|
|
31
|
-
export function applyPatchTargets(patch) {
|
|
32
|
-
if (typeof patch !== 'string' || !patch) return [];
|
|
33
|
-
const out = [];
|
|
34
|
-
scan(PATCH_TOUCH, patch, (m) => out.push(m[2]));
|
|
35
|
-
scan(PATCH_MOVE, patch, (m) => out.push(m[1]));
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Only the paths a patch CREATES or WRITES — `Add`, `Update`, and a rename destination. A `Delete File:`
|
|
40
|
-
* target is excluded: the no-env floor blocks creating a `.env`, never removing one, and treating every
|
|
41
|
-
* extracted target as a write turned a delete-only patch into a denial (cross-vendor check, openai-sol). */
|
|
42
|
-
export function applyPatchWriteTargets(patch) {
|
|
43
|
-
if (typeof patch !== 'string' || !patch) return [];
|
|
44
|
-
const out = [];
|
|
45
|
-
scan(PATCH_TOUCH, patch, (m) => { if (m[1].toLowerCase() !== 'delete') out.push(m[2]); });
|
|
46
|
-
scan(PATCH_MOVE, patch, (m) => out.push(m[1]));
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** True when a shell command actually INVOKES `apply_patch` — as the COMMAND of some segment, not merely
|
|
51
|
-
* as a word in it.
|
|
52
|
-
*
|
|
53
|
-
* Patch-looking text is not a patch: `cat <<EOF … *** Add File: .env … EOF` writes nothing. But a bare
|
|
54
|
-
* word-boundary test was wrong both ways — `"apply_patch" <<EOF` (quoted) and `/usr/bin/apply_patch`
|
|
55
|
-
* slipped through, while `echo apply_patch <<EOF` was denied for running `echo` (cross-vendor check,
|
|
56
|
-
* openai-sol). So: split into segments, take each segment's first token, strip quotes and any directory
|
|
57
|
-
* prefix, and skip the leading `env`/`command`/`exec`/`sudo` wrappers that only prefix a real command. */
|
|
58
|
-
const INVOKE_PREFIXES = new Set(['env', 'command', 'exec', 'sudo', 'nohup', 'time']);
|
|
59
|
-
// `bash -lc "apply_patch …"` is how Codex delivers a patch through a shell tool: the real command sits
|
|
60
|
-
// inside the wrapper's argument, so the wrapper and its flags are stepped over too.
|
|
61
|
-
const SHELL_WRAPPERS = new Set(['bash', 'sh', 'zsh', 'dash', 'pwsh', 'powershell', 'cmd', 'cmd.exe']);
|
|
62
|
-
|
|
63
|
-
export function invokesApplyPatch(command) {
|
|
64
|
-
if (typeof command !== 'string' || !command) return false;
|
|
65
|
-
// Redirects may abut the command word — `apply_patch<<'EOF'` and `apply_patch<<<"$P"` are both valid
|
|
66
|
-
// shell, and both hid the command name from a whitespace tokenizer (cross-vendor check).
|
|
67
|
-
// Separating the operators first is what makes the command word its own token in every form.
|
|
68
|
-
const spaced = command.replace(/([<>]+)/g, ' $1 ');
|
|
69
|
-
for (const segment of spaced.split(/\|\||&&|[;&|\n]|<<-?\s*['"]?\w+['"]?/)) {
|
|
70
|
-
let wrapped = false;
|
|
71
|
-
for (const raw of segment.trim().split(/\s+/).filter(Boolean)) {
|
|
72
|
-
const bare = raw.replace(/^['"]+|['"]+$/g, '').replace(/\\/g, '/').split('/').pop() ?? '';
|
|
73
|
-
if (bare === 'apply_patch') return true;
|
|
74
|
-
// Step over wrappers, their env assignments, and — once inside a shell wrapper — its flags.
|
|
75
|
-
// Anything else in command position means this segment runs something other than apply_patch.
|
|
76
|
-
if (SHELL_WRAPPERS.has(bare.toLowerCase())) { wrapped = true; continue; }
|
|
77
|
-
if (INVOKE_PREFIXES.has(bare) || /^[A-Za-z_][A-Za-z0-9_]*=/.test(raw)) continue;
|
|
78
|
-
if (wrapped && /^[-/]/.test(raw)) continue;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Every file path a tool call writes to. Returns `[]` for non-edit tools and unreadable payloads —
|
|
87
|
-
* callers treat an empty list as "nothing to check", exactly as they treated a missing `file_path`.
|
|
88
|
-
* @param {{ tool_name?: unknown, tool_input?: Record<string, unknown> }} input raw tool payload
|
|
89
|
-
* @returns {string[]}
|
|
90
|
-
*/
|
|
91
|
-
export function editedPaths(input) {
|
|
92
|
-
const toolName = input?.tool_name;
|
|
93
|
-
if (!isEditTool(toolName)) return [];
|
|
94
|
-
return collectEditedPaths(input?.tool_input ?? {});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Every write target in a tool_input, from a direct path AND from any patch body it carries.
|
|
98
|
-
*
|
|
99
|
-
* Union, never first-match. Returning early on `file_path` meant an `apply_patch` carrying a harmless
|
|
100
|
-
* `file_path: "safe.txt"` alongside `*** Add File: .env` reported only `safe.txt`, and the vault gate
|
|
101
|
-
* allowed it — a fail-open reachable by adding one benign field (cross-vendor check, openai-sol). A
|
|
102
|
-
* gate must see every path a call writes, not the first one it finds. */
|
|
103
|
-
export function collectEditedPaths(toolInput) {
|
|
104
|
-
const ti = toolInput ?? {};
|
|
105
|
-
const out = [];
|
|
106
|
-
for (const direct of [ti.file_path, ti.path]) {
|
|
107
|
-
if (typeof direct === 'string' && direct) out.push(direct);
|
|
108
|
-
}
|
|
109
|
-
// Codex `apply_patch`: the patch text arrives as `command` (or `patch`/`input` on some builds).
|
|
110
|
-
// WRITE targets only — the shell path already excluded a `*** Delete File:` and this one did not, so a
|
|
111
|
-
// delete-only patch was allowed through the shell and denied through the tool (cross-vendor check,
|
|
112
|
-
// openai-sol). One rule, both doors.
|
|
113
|
-
for (const field of ['command', 'patch', 'input']) out.push(...applyPatchWriteTargets(ti[field]));
|
|
114
|
-
return [...new Set(out)];
|
|
115
|
-
}
|