Conversation
Rule 6 landed in the development skill as text. Text is skippable: a pull request that says nothing about MCP passes today. This makes the rule a check. surface.json holds the leaf command surface and a test keeps it current, so a stale snapshot cannot quietly stop the gate from firing. CI diffs it against the base, and when leaves were added it requires the pull request body to carry an MCP section naming one of five things. Diffing the committed snapshot rather than rebuilding the base tree means no second checkout and no second install, and the added leaves appear in the pull request diff where a reviewer sees them too. A base with no surface.json is the commit that introduces it, and counts as adding nothing. The accepted forms are enumerated on purpose. Free text would take "no MCP tool: later", which turns an open question into a closed record with a reason attached and is harder to reopen than an unanswered one. A real not-now points at an issue instead, which is the fifth form. src/index.ts exports program and guards its two top-level calls behind INSTA_DUMP_SURFACE, so the tree can be walked in process. That also sees the four hidden commands, which a recursive --help scrape cannot. Verified by simulation before opening: a new leaf with no MCP section fails, a linked MCP pull request passes, "later" fails, a product decision with an issue passes, and a change that adds no leaf passes.
There was a problem hiding this comment.
5 issues found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="test/mcp-declaration.test.ts">
<violation number="1" location="test/mcp-declaration.test.ts:69">
P3: `process.env.INSTA_DUMP_SURFACE = '1'` is never restored after the test. The module-level guard in src/index.ts makes every later evaluation of the CLI skip `selfUpdate.maybeUpdate` and `program.parseAsync`, so if the worker's process environment is shared or reused (threads pool, or `isolate: false`), the flag leaks into test files that spawn `tsx src/index.ts` (test/help-surface.test.ts, test/mcp-token-cli.test.ts, test/api-url-override.test.ts, test/domain-org-flag.test.ts, test/retired-policy.test.ts) — those children would print an empty surface and exit 0, silently breaking their assertions. Use `vi.stubEnv('INSTA_DUMP_SURFACE', '1')`, which restores the previous value automatically when the test completes.</violation>
</file>
<file name="scripts/dump-surface.mjs">
<violation number="1" location="scripts/dump-surface.mjs:11">
P3: `writeFileSync` overwrites the committed surface.json unconditionally, so if `leafPaths` ever returns an empty array (partially built `program`, broken commander setup), the gate's source of truth is silently replaced with `{"leaves": []}` and CI's added-leaf diff sees nothing. Fail loudly instead of writing a zero-leaf surface, since a CLI command tree with no leaves can only be a broken traversal, never a valid result.</violation>
</file>
<file name="package.json">
<violation number="1" location="package.json:39">
P3: `node --import tsx` requires Node >= 18.19.0 or >= 20.6.0, but `engines` declares `>=18`, so `npm run surface` fails with "bad option: --import" for developers on Node 18.0–18.18 or 20.0–20.5. Run the script through tsx directly (`tsx scripts/dump-surface.mjs`) to stay within the declared Node range, or bump `engines` to `>=18.19.0` (non-consecutive change).</violation>
</file>
<file name=".github/workflows/ci.yml">
<violation number="1" location=".github/workflows/ci.yml:37">
P1: `PR_BODY: ${{ github.event.pull_request.body }}` embeds the raw PR body into the workflow `env:`, and PR bodies are multi-line (a valid gate scenario requires a "## MCP" heading on its own line). GitHub Actions re-parses the workflow YAML after expression substitution, so a newline from the context value makes the workflow invalid, or the value is cut off at the first line. Either way the gate fails or reads a truncated body for exactly the multi-line bodies it exists to validate. Serialize with `${{ toJSON(github.event.pull_request.body) }}` and `JSON.parse(process.env.PR_BODY)` inside the script (``JSON.parse('null')`` accounts for a null body).</violation>
</file>
<file name="src/mcp-declaration.ts">
<violation number="1" location="src/mcp-declaration.ts:8">
P2: The first accepted form only matches the exact-case autolink text `InsForge/instacloud-mcp#<n>`. Common ways of pointing at the MCP pull request — a full URL (`https://github.com/InsForge/instacloud-mcp/pull/123`), a lowercase repo (`insforge/instacloud-mcp#123`), or a bare `#123` — are rejected, so a genuine "yes" declaration fails CI. The failure message enumerates the form without stating the exact required spelling; broaden the regex to the URL form and make it case-insensitive.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| - run: node --import tsx scripts/check-mcp-declaration.mjs | ||
| env: | ||
| BASE_REF: ${{ github.base_ref }} | ||
| PR_BODY: ${{ github.event.pull_request.body }} |
There was a problem hiding this comment.
P1: PR_BODY: ${{ github.event.pull_request.body }} embeds the raw PR body into the workflow env:, and PR bodies are multi-line (a valid gate scenario requires a "## MCP" heading on its own line). GitHub Actions re-parses the workflow YAML after expression substitution, so a newline from the context value makes the workflow invalid, or the value is cut off at the first line. Either way the gate fails or reads a truncated body for exactly the multi-line bodies it exists to validate. Serialize with ${{ toJSON(github.event.pull_request.body) }} and JSON.parse(process.env.PR_BODY) inside the script (JSON.parse('null') accounts for a null body).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci.yml, line 37:
<comment>`PR_BODY: ${{ github.event.pull_request.body }}` embeds the raw PR body into the workflow `env:`, and PR bodies are multi-line (a valid gate scenario requires a "## MCP" heading on its own line). GitHub Actions re-parses the workflow YAML after expression substitution, so a newline from the context value makes the workflow invalid, or the value is cut off at the first line. Either way the gate fails or reads a truncated body for exactly the multi-line bodies it exists to validate. Serialize with `${{ toJSON(github.event.pull_request.body) }}` and `JSON.parse(process.env.PR_BODY)` inside the script (``JSON.parse('null')`` accounts for a null body).</comment>
<file context>
@@ -18,6 +18,24 @@ jobs:
+ - run: node --import tsx scripts/check-mcp-declaration.mjs
+ env:
+ BASE_REF: ${{ github.base_ref }}
+ PR_BODY: ${{ github.event.pull_request.body }}
+
test-windows:
</file context>
| // with a reason attached, which is harder to reopen than an unanswered one. A real "not now" has | ||
| // an issue to point at, which is what the fourth form is for. | ||
| const FORMS = [ | ||
| { name: 'a pull request in the MCP repository', re: /InsForge\/instacloud-mcp#\d+/ }, |
There was a problem hiding this comment.
P2: The first accepted form only matches the exact-case autolink text InsForge/instacloud-mcp#<n>. Common ways of pointing at the MCP pull request — a full URL (https://github.com/InsForge/instacloud-mcp/pull/123), a lowercase repo (insforge/instacloud-mcp#123), or a bare #123 — are rejected, so a genuine "yes" declaration fails CI. The failure message enumerates the form without stating the exact required spelling; broaden the regex to the URL form and make it case-insensitive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp-declaration.ts, line 8:
<comment>The first accepted form only matches the exact-case autolink text `InsForge/instacloud-mcp#<n>`. Common ways of pointing at the MCP pull request — a full URL (`https://github.com/InsForge/instacloud-mcp/pull/123`), a lowercase repo (`insforge/instacloud-mcp#123`), or a bare `#123` — are rejected, so a genuine "yes" declaration fails CI. The failure message enumerates the form without stating the exact required spelling; broaden the regex to the URL form and make it case-insensitive.</comment>
<file context>
@@ -0,0 +1,44 @@
+// with a reason attached, which is harder to reopen than an unanswered one. A real "not now" has
+// an issue to point at, which is what the fourth form is for.
+const FORMS = [
+ { name: 'a pull request in the MCP repository', re: /InsForge\/instacloud-mcp#\d+/ },
+ { name: 'no MCP tool: credential minting', re: /no MCP tool:\s*credential minting/i },
+ { name: 'no MCP tool: needs this machine', re: /no MCP tool:\s*needs this machine/i },
</file context>
| describe('surface.json', () => { | ||
| // The gate diffs this file, so a stale one would silently stop the gate from ever firing. | ||
| it('matches the live command tree', async () => { | ||
| process.env.INSTA_DUMP_SURFACE = '1' |
There was a problem hiding this comment.
P3: process.env.INSTA_DUMP_SURFACE = '1' is never restored after the test. The module-level guard in src/index.ts makes every later evaluation of the CLI skip selfUpdate.maybeUpdate and program.parseAsync, so if the worker's process environment is shared or reused (threads pool, or isolate: false), the flag leaks into test files that spawn tsx src/index.ts (test/help-surface.test.ts, test/mcp-token-cli.test.ts, test/api-url-override.test.ts, test/domain-org-flag.test.ts, test/retired-policy.test.ts) — those children would print an empty surface and exit 0, silently breaking their assertions. Use vi.stubEnv('INSTA_DUMP_SURFACE', '1'), which restores the previous value automatically when the test completes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/mcp-declaration.test.ts, line 69:
<comment>`process.env.INSTA_DUMP_SURFACE = '1'` is never restored after the test. The module-level guard in src/index.ts makes every later evaluation of the CLI skip `selfUpdate.maybeUpdate` and `program.parseAsync`, so if the worker's process environment is shared or reused (threads pool, or `isolate: false`), the flag leaks into test files that spawn `tsx src/index.ts` (test/help-surface.test.ts, test/mcp-token-cli.test.ts, test/api-url-override.test.ts, test/domain-org-flag.test.ts, test/retired-policy.test.ts) — those children would print an empty surface and exit 0, silently breaking their assertions. Use `vi.stubEnv('INSTA_DUMP_SURFACE', '1')`, which restores the previous value automatically when the test completes.</comment>
<file context>
@@ -0,0 +1,74 @@
+describe('surface.json', () => {
+ // The gate diffs this file, so a stale one would silently stop the gate from ever firing.
+ it('matches the live command tree', async () => {
+ process.env.INSTA_DUMP_SURFACE = '1'
+ const { program } = await import('../src/index.js')
+ const committed = JSON.parse(readFileSync(fileURLToPath(new URL('../surface.json', import.meta.url)), 'utf8'))
</file context>
|
|
||
| const out = fileURLToPath(new URL('../surface.json', import.meta.url)) | ||
| const leaves = leafPaths(program) | ||
| writeFileSync(out, renderSurface(leaves)) |
There was a problem hiding this comment.
P3: writeFileSync overwrites the committed surface.json unconditionally, so if leafPaths ever returns an empty array (partially built program, broken commander setup), the gate's source of truth is silently replaced with {"leaves": []} and CI's added-leaf diff sees nothing. Fail loudly instead of writing a zero-leaf surface, since a CLI command tree with no leaves can only be a broken traversal, never a valid result.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/dump-surface.mjs, line 11:
<comment>`writeFileSync` overwrites the committed surface.json unconditionally, so if `leafPaths` ever returns an empty array (partially built `program`, broken commander setup), the gate's source of truth is silently replaced with `{"leaves": []}` and CI's added-leaf diff sees nothing. Fail loudly instead of writing a zero-leaf surface, since a CLI command tree with no leaves can only be a broken traversal, never a valid result.</comment>
<file context>
@@ -0,0 +1,12 @@
+
+const out = fileURLToPath(new URL('../surface.json', import.meta.url))
+const leaves = leafPaths(program)
+writeFileSync(out, renderSurface(leaves))
+console.log(`surface.json: ${leaves.length} leaf commands`)
</file context>
| "build": "tsc -p tsconfig.json", | ||
| "typecheck": "tsc -p tsconfig.json --noEmit", | ||
| "dev": "tsx src/index.ts", | ||
| "surface": "node --import tsx scripts/dump-surface.mjs", |
There was a problem hiding this comment.
P3: node --import tsx requires Node >= 18.19.0 or >= 20.6.0, but engines declares >=18, so npm run surface fails with "bad option: --import" for developers on Node 18.0–18.18 or 20.0–20.5. Run the script through tsx directly (tsx scripts/dump-surface.mjs) to stay within the declared Node range, or bump engines to >=18.19.0 (non-consecutive change).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 39:
<comment>`node --import tsx` requires Node >= 18.19.0 or >= 20.6.0, but `engines` declares `>=18`, so `npm run surface` fails with "bad option: --import" for developers on Node 18.0–18.18 or 20.0–20.5. Run the script through tsx directly (`tsx scripts/dump-surface.mjs`) to stay within the declared Node range, or bump `engines` to `>=18.19.0` (non-consecutive change).</comment>
<file context>
@@ -36,6 +36,7 @@
"build": "tsc -p tsconfig.json",
"typecheck": "tsc -p tsconfig.json --noEmit",
"dev": "tsx src/index.ts",
+ "surface": "node --import tsx scripts/dump-surface.mjs",
"test": "vitest run",
"compile": "bun build ./src/index.ts --compile --minify --outfile dist/bin/insta",
</file context>
jwfing
left a comment
There was a problem hiding this comment.
Summary
The snapshot approach is sound, but the declaration parser and base-snapshot error handling can both allow the new gate to pass without a valid MCP declaration.
Requirements context
I reviewed against the PR description and rule 6 in .claude/skills/developing-insta-cli/SKILL.md:65-73, which requires every new leaf command to identify a matching MCP tool or state an accepted reason why agents should not receive one. No linked issue or additional specification was provided.
Findings
Critical
-
Declaration matching accepts incidental or explicitly negated text. The accepted forms are unanchored substring regexes, so an MCP section containing
This is not no MCP tool: needs this machineorRelated issue InsForge/instacloud-mcp#123, but no matching tool existsreturns{ ok: true }. The section parser also accepts### MCP, despite the stated requirement that only a## MCPsection counts. This defeats the central guarantee that the body contains one of the enumerated declarations. Require an actual## MCPheading and validate a complete declaration line or link rather than searching arbitrary prose. Add negative tests for negated/incidental mentions and alternate heading levels. (src/mcp-declaration.ts:8-32,test/mcp-declaration.test.ts:15-58) -
Any failure reading the base snapshot disables the gate.
leavesAt()catches Git failures, malformed JSON, and invalid snapshot structure together with the intentional “file absent on the introducing base” case, returnsnull, and line 25 consequently treats the PR as adding no leaves. A transient checkout/ref problem or corrupt base snapshot therefore produces a green check. Handle only the confirmed missing-path case specially and let every other read or parse error fail CI; cover both cases in script-level tests. (scripts/check-mcp-declaration.mjs:13-25)
Suggestion
(none)
Information
- Software engineering: The implementation is focused, and the live-tree snapshot assertion is valuable.
git diff --checkand JavaScript syntax checks passed. I could not execute TypeScript or Vitest because dependencies are absent (tsc: not found), and this review was required to remain read-only. (test/mcp-declaration.test.ts:66-73,package.json:35-43) - Security: No new dependency, secret exposure, authorization change, or unsafe shell construction was found; Git is invoked with an argument array and the untrusted PR body is passed through an environment variable. (
scripts/check-mcp-declaration.mjs:7-28,.github/workflows/ci.yml:27-37) - Performance: No material concern. Surface traversal and set comparison are linear over the small command tree and run only during tests or CI. (
src/surface.ts:7-14,scripts/check-mcp-declaration.mjs:23-25)
Verdict
Request changes: the two Critical findings permit the load-bearing gate to pass without establishing the declaration required by rule 6.
…tion Both findings reproduced before fixing. The accepted forms were unanchored substrings, so a sentence denying one still matched it: "this is not no MCP tool: needs this machine" passed, and so did "InsForge/instacloud-mcp#123, but no matching tool exists". The heading match also took any depth, so a "### MCP" nested under another section counted. A declaration is now the first non-empty line of a heading that is exactly two hashes, and each form is anchored to the start of that line. The linked form may still say what the tool adds, since it only has to start with the link. Separately, reading the base snapshot caught every failure and returned null, which the caller read as "no leaves added". A bad ref, an unreadable blob or a malformed snapshot all produced a green gate. ls-tree now decides the one case that is not an error, the path being absent on a reachable ref, and everything else reaches the top and fails CI. Ten more tests: the negated forms, the deeper headings, prose above the declaration, an empty section, and each base-read failure asserted to throw rather than to pass.
jwfing
left a comment
There was a problem hiding this comment.
Summary
The implementation establishes the intended leaf-command snapshot gate, but workflow triggering and Markdown parsing gaps prevent it from reliably enforcing the declaration requirement.
Requirements context
The intent comes from the PR description and Rule 6 in .claude/skills/developing-insta-cli/SKILL.md:66-73: every new leaf command must identify a matching MCP PR or state an accepted reason why no MCP tool should exist. The PR further specifies that only a real ## MCP section counts.
Findings
Critical
-
.github/workflows/ci.yml:3-5— The workflow uses the defaultpull_requestactivity types, which are onlyopened,synchronize, andreopened; editing the PR description does not trigger a new run. Consequently, when this gate rejects a missing declaration, following its instruction to edit the body cannot produce a passing check without another commit or close/reopen cycle. Configurepull_request.typesto includeedited(along with the current default activities), and ideally cover this workflow-level behavior. -
src/mcp-declaration.ts:32-40— The raw line-based search treats headings inside fenced code blocks or HTML comments as real Markdown sections. For example, a body containing<!--\n## MCP\nno MCP tool: needs this machine\n-->passes despite having no visible MCP section, contradicting the stated “only the## MCPsection counts” requirement. Parse enough Markdown structure to ignore comments/code fences and add regression cases intest/mcp-declaration.test.ts:9-94.
Suggestion
src/mcp-declaration.ts:12— The linked-PR expression accepts arbitrary text after the issue number, includingInsForge/instacloud-mcp#123, but no matching tool exists, even though its label says the optional suffix describes what the PR adds. Consider constraining the suffix or documenting that semantic validation remains a reviewer responsibility.
Information
test/mcp-declaration.test.ts:9-131— Unit coverage is otherwise strong: accepted declarations, rejection paths, malformed snapshots, removals, and live-tree snapshot drift are covered.scripts/check-mcp-declaration.mjs:7-25— No security finding: Git is invoked with argument arrays, PR-body content is not passed to a shell, no dependencies were added, and authentication or secret handling is unchanged.src/surface.ts:7-15andsrc/index.ts:683-689— No runtime-performance finding: traversal is linear over the small command tree and is used only by tests/generation; normal CLI startup does not perform snapshot work.- Verification could not be reproduced locally because dependencies are absent (
tsc: not found), and the read-only review constraint precluded installing them.
Verdict
Request changes. The two Critical findings allow the central CI gate either to remain stale after the prescribed remediation or to pass without a genuine MCP declaration.
The gate told authors to add a section to the pull request body, and editing the body does not re-run a workflow on the default pull_request activity types. Following its own instruction left the check red until an unrelated commit landed. It moves to its own workflow file so it can take `edited` without putting that trigger on ci.yml, where it would re-run 1900 tests on every typo fix in a description. Separately, headings were found by a raw line search, so one inside an HTML comment or a code fence counted as a section. A pull request template carrying a commented-out example would have declared on every author behalf. Comments and fences are stripped before the search, and a real section after either one is still found. Also retitles the linked form. Its old label promised the suffix described what the pull request adds, which the check has no way to know: anything after the link is for a reader, and whether that pull request really adds the tool is a question only a reviewer can answer.
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
jwfing
left a comment
There was a problem hiding this comment.
Summary
The implementation is technically sound overall, but the repository’s required merge-process documentation must be updated before merging.
Requirements context
I reviewed this against the PR description, Rule 6 in .claude/skills/developing-insta-cli/SKILL.md, and that skill’s explicit instruction to document new required checks in the same PR. No separate linked issue or design document was provided beyond the referenced Rule 6 change in #305.
Findings
Critical
.github/workflows/mcp-declaration.yml:6-24,.claude/skills/developing-insta-cli/SKILL.md:84-89,.claude/skills/developing-insta-cli/SKILL.md:126-131— This PR adds a new check that is intended to gate command additions, but the documented merge flow still says the expected checks are only the twocijobs andcubic. The skill explicitly requires new checks and changed flows to be documented in the same PR. As written, a future agent following the authoritative merge instructions may disregard a redmcp declarationresult, undermining the policy this PR introduces. Add this workflow to the “Checks expected green” guidance and explain that editing the PR body reruns it.
Suggestion
.github/workflows/mcp-declaration.yml:18— Passgithub.base_refthrough an environment variable and quote it in the shell command, as is already done for the checker step. Direct interpolation intorunis less robust for unusual ref names and is best avoided for GitHub context values.scripts/check-mcp-declaration.mjs:11-29,test/mcp-declaration.test.ts:9-148— The tests thoroughly exercise the helper functions, but not the CI entry point’s Git/ref/environment integration. Consider adding a small temporary-repository integration test for absent, valid, and malformed base snapshots; the PR currently relies on manual end-to-end verification for that boundary.
Information
src/mcp-declaration.ts:11-64,src/surface.ts:7-40,test/mcp-declaration.test.ts:9-148— Software engineering and functionality are otherwise strong: responsibilities are separated cleanly, malformed snapshots fail closed, hidden commands are included, and the tests cover empty bodies, invalid declarations, comments, code fences, removals, and snapshot drift..github/workflows/mcp-declaration.yml:10-27— Security review found no runtime authentication, authorization, secrets, SQL, or HTTP changes. The workflow uses read-only repository permissions and treats the PR body as data rather than shell input.src/surface.ts:7-17,.github/workflows/mcp-declaration.yml:23-24— No material performance concern was found. Surface traversal is bounded by the small command tree; the heaviernpm cicost occurs only in CI and is not on the CLI runtime path.- Verification:
git diff --check origin/main...HEADpassed. Typecheck and tests could not be rerun because dependencies are not installed in the read-only review workspace (tsc: not found).
Verdict
Request changes because the PR violates the repository’s explicit same-PR process-documentation requirement. Once the development skill reflects the new check, the remaining observations are non-blocking.
A closing fence follows CommonMark now: same character, no shorter than the opening run, nothing after it but whitespace. A line reading ```not a closing fence used to end the block in the parser while a reader still saw code, which exposed any heading below it. The merge flow in the development skill listed only ci's two jobs and cubic. That skill's own closing rule says a new required check is documented in the same pull request, so the check is named there, with the reason an author edits the body rather than pushing: this workflow takes edited and ci does not. The git base reader moves out of the CI script into src, where a test can point it at a real repository. The distinction it draws, an absent path against an unreadable one, is git's rather than ours, and a stub cannot show it holds.
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The step read as though every failure were a missing declaration. The check also fails on an unreadable base snapshot, a bad ref and a malformed surface.json, and it fails loudly on those by design, so an author told to edit the body would be editing against a failure no body can fix.
jwfing
left a comment
There was a problem hiding this comment.
Summary
The PR correctly turns the documented MCP-declaration rule into a fail-closed CI gate, with one non-blocking Markdown parsing edge case.
Requirements context
The review is based on the PR description and Rule 6 in .claude/skills/developing-insta-cli/SKILL.md:65-73, which requires every new leaf command to have either a matching MCP tool or a stated reason why agents should not receive one. No linked issue or additional in-repository specification was provided; the PR description supplies the stricter requirements for the dedicated ## MCP section, enumerated forms, snapshot behavior, and edited-body reruns.
Findings
Critical
(none)
Suggestion
src/mcp-declaration.ts:38-51— The parser describes its handling as Markdown/CommonMark-aware, but its grammar differs at a few edges. It accepts##MCP, which CommonMark does not recognize as an ATX heading; rejects the valid heading## MCP ##; and treats fences indented four or more spaces as fenced blocks even though CommonMark permits at most three spaces. These can produce an unexpected pass or failure for unusual but plausible bodies. Consider enforcing the intended CommonMark subset explicitly and adding tests for spacing, optional closing hashes, and four-space-indented backticks.
Information
- Software engineering/functionality: coverage is thorough for accepted and rejected declarations, comments and fences, missing or malformed base snapshots, unknown refs, additions/removals, and snapshot drift (
test/mcp-declaration.test.ts:12-217). The snapshot comparison fails closed on unreadable data and includes hidden Commander leaves (src/surface.ts:6-15,src/surface.ts:20-51).git diff --checkwas clean. The test suite and typecheck could not be executed locally because the read-only checkout has no installed dependencies (tsc: not found); no dependency installation was attempted. - Security: no security-relevant defect found. PR-controlled body text is passed through the environment rather than interpolated into shell syntax, Git is invoked with argument arrays, and the workflow receives only read-only repository contents permission (
.github/workflows/mcp-declaration.yml:10-31,src/surface.ts:38-43). No dependencies, credentials, authentication behavior, SQL, or HTTP paths are added. - Performance: no material performance concern found. The synchronous Git reads and surface comparison run only in CI, while tree generation is a linear traversal used by the maintenance script/test (
src/surface.ts:8-18,scripts/check-mcp-declaration.mjs:12-17). Normal command execution retains its existing path (src/index.ts:683-689).
Verdict
Approved under the supplied severity rule: there are no Critical findings, and the Markdown grammar issue is non-blocking.
The heading needed a space after the hashes. ##MCP is a paragraph that renders as the literal text, so a body carrying only that showed a reader no section while the gate read one and passed. The optional closing run of hashes renders as the same heading and is accepted now rather than refused. A fence is indented at most three spaces. Four is an indented code block, which opens nothing, so a run of backticks that deep no longer swallows the heading under it. Any heading ends the section, not only one at two hashes.
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
## MCP### is a heading titled MCP###, not this section, and the gate read a declaration under it.
What
Turns rule 6 of
developing-insta-cli/SKILL.mdfrom text into a check.The rule landed in #305 and says a new leaf command ships with the matching MCP tool or with a stated reason. Text is skippable: a pull request that says nothing about MCP passes today, and saying nothing is exactly what produced eight capabilities lagging 14 to 41 days last quarter.
When this pull request adds a leaf command and the body carries no MCP declaration, CI fails with:
How
surface.jsonholds the 147 leaf command paths, and a test asserts it matches the live tree. That test is load bearing: a stale snapshot would quietly stop the gate from ever firing again.CI diffs the snapshot against the base. Diffing the committed file rather than rebuilding the base tree needs no second checkout and no second install, and the added leaves land in the pull request diff where a reviewer sees them too. A base with no
surface.jsonis the commit that introduces it, so it counts as adding nothing rather than as adding all 147.src/index.tsexportsprogramand guards its two top-level calls behindINSTA_DUMP_SURFACE. Both calls, not just the parse:selfUpdate.maybeUpdatereaches the network, and a surface dump must not. Walking the tree in process is instant and sees the four hidden commands, which a recursive--helpscrape cannot.The five accepted forms are enumerated rather than free text, and that is the load bearing decision. Free text accepts
no MCP tool: later, which turns an open question into a closed record with a reason attached, and that is harder to reopen than an unanswered one. A genuine not-now points at an issue instead, which is the fifth form, so the reason lives somewhere it can be argued with.Only the
## MCPsection counts. A link elsewhere in the body is prose about the change, not a declaration about the command.Verify
npx tsc --noEmitis clean andnpm testpasses 1920 tests across 94 files, none of them changed by this.The gate itself was driven end to end before opening, against a base that carries
surface.jsonand a working tree with a leaf added:no MCP tool: laterno MCP tool: product decision, <issue>Eleven unit tests cover the same shapes plus the cases a simulation does not reach: an empty body, a link outside the MCP section, and a section that ends at the next heading.
This pull request adds no leaf command, so the gate reports "no leaf command added" on itself.
MCP
no MCP tool: needs this machine
The gate is CI for this repository. Nothing here is a control-plane capability an agent could reach.
🤖 Generated with Claude Code
Summary by cubic
Turns rule 6 of
developing-insta-cli/SKILL.mdinto a CI gate: a pull request that adds a leaf command must declare in its body what MCP does with it, or CI fails. Before, a body with no MCP declaration passed, which is how eight capabilities fell 14 to 41 days behind last quarter.How it works
surface.jsonsnapshots the 147 leaf command paths; a test asserts it matches the live tree so a stale snapshot can't silently disable the gate.surface.jsonagainst the base, so added leaves land in the pull request diff where reviewers see them. A base that can't be read — a bad ref, an unreadable blob, or malformed JSON — fails CI instead of passing as "nothing was added."edited, so editing the body in response to a failure re-runs the check without re-running the whole suite on every typo fix.## MCPsection counts: a heading of exactly two hashes plus a space and a section open, since##MCPrenders as literal text and## MCP###is a heading titled "MCP###". The section's first non-empty line holds the declaration, with the five accepted forms anchored to the line start. That blocks negations ("this is not no MCP tool: needs this machine") andno MCP tool: later, which would turn an open question into a closed record; a real deferral points at an issue as the fifth form.##— ends the section.developing-insta-cli/SKILL.mdnow distinguishes the two red states: a missing declaration, fixed by editing the body, versus a broken check, which no body edit can fix.npm run surfaceregenerates the snapshot after adding commands.Written for commit f045726. Summary will update on new commits.