From 22b7d898b5d0abe8fe45eb0cc90d5d067d49bb05 Mon Sep 17 00:00:00 2001 From: Stella Inwood Date: Sat, 25 Jul 2026 20:00:49 +1000 Subject: [PATCH 1/3] feat(meridian): commit conventions govern PR descriptions, adapters match model families (0.13.0) Adapter resolution matched a single exact model identifier, so every model outside that one release ran on the baseline standard with no corrections applied. Profiles are now named for families and matched by prefix. The commit skill was manual-only, which kept its conventions unreachable unless it was typed. It is now model-invocable and carries pull request guidance alongside commit-message guidance: lead with rationale, describe behavior rather than the diff, state the feedback wanted, report what was actually verified, and scale the description to the change. A single rule covers both artifacts: describe the resulting code, never the session that produced it. Reaching the conventions authorizes nothing; committing, pushing, and opening a pull request each still require an explicit request. Specs and sketches carry a Status line so a finished spec is distinguishable from an abandoned one. --- .claude-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- .plugin/plugin.json | 2 +- CHANGELOG.md | 17 +++++ README.md | 18 +++--- behavior/profiles.json | 10 ++- eval/scenarios/negative.yaml | 4 +- eval/scenarios/positive.yaml | 14 +++++ skills/brainstorm/SKILL.md | 3 +- skills/commit/SKILL.md | 63 ++++++++++++++++--- skills/commit/agents/openai.yaml | 2 - skills/execute/SKILL.md | 2 + skills/sketch/SKILL.md | 6 +- .../sketch/references/sketch-file-example.md | 4 +- test/meridian-consistency.test.mjs | 14 ++++- 15 files changed, 132 insertions(+), 31 deletions(-) delete mode 100644 skills/commit/agents/openai.yaml diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4bcc765..b6625d7 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "meridian", "description": "Professional engineering discipline with user-owned workflows and model-specific corrections.", - "version": "0.12.0", + "version": "0.13.0", "author": { "name": "KodingDev" }, diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 2e95f06..c9ab6f4 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "meridian", "description": "Professional engineering discipline with user-owned workflows and model-specific corrections.", - "version": "0.12.0", + "version": "0.13.0", "author": { "name": "KodingDev" }, diff --git a/.plugin/plugin.json b/.plugin/plugin.json index a7ef45b..1ea5079 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,5 +1,5 @@ { "name": "meridian", - "version": "0.12.0", + "version": "0.13.0", "hooks": "./hooks/hooks-copilot.json" } diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb6fed..cecfdfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to Meridian are recorded here. The format follows to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). History before 0.11.0 lives in the git log. +## [0.13.0] - 2026-07-25 + +### Added + +- `commit` carries pull request description guidance: lead with rationale, describe behavior rather than the diff, state the feedback wanted, report what was actually verified, flag breaking changes and migrations, and scale the description to the change. +- Both commit messages and pull request descriptions are governed by an explicit rule that they describe the resulting code, never the session that produced it, with the circumstantial detail to omit enumerated. +- Specs and sketches carry a `Status` line through `DRAFT`, `APPROVED`, `IN PROGRESS`, `SHIPPED `, and `SUPERSEDED BY `. `execute` moves it as implementation proceeds. + +### Changed + +- Model profiles are named for families and matched by prefix, so a new release in a mapped family keeps its corrections instead of dropping to the baseline. `opus-4.8` becomes `claude-opus` and matches the `claude-opus-` prefix; `fable` matches `claude-fable-`. +- `commit` is model-invocable. Its conventions are reachable whenever a commit message or pull request description is written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request each still require an explicit request. + +### Fixed + +- Adapter resolution no longer depends on a single exact model identifier, which left every unlisted model on the baseline standard with no corrections applied. + ## [0.12.0] - 2026-07-13 ### Added diff --git a/README.md b/README.md index fbc340d..be27fa9 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ These run only when you invoke them. One flow never silently enrolls you in anot | `sketch` | Produce a lightweight contract for a small change | | `execute` | Implement an approved spec or clear scope with proportionate verification | | `review` | Perform a focused, report-only code review | -| `commit` | Create one clean, narrowly staged commit | | `document` | Capture requested durable engineering knowledge | | `auto` | Grant elevated autonomy for reversible, in-scope work | @@ -39,12 +38,15 @@ These are narrow internal tools, not workflow gates: | Skill | Purpose | | --- | --- | +| `commit` | Commit and pull request conventions, and the one-clean-commit flow | | `debug` | Diagnose failures from evidence before fixing them | | `research` | Verify unstable external behavior against primary sources | | `triangulate` | Escalate contested or load-bearing claims beyond one source | | `delegate` | Isolate genuinely independent or context-heavy work | | `respond` | Evaluate review feedback while preserving user intent | +`commit` holds conventions and a flow. The conventions are reachable whenever a commit message or pull request description is being written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request each still require an explicit request. + ## Model profiles At session start Meridian injects the professional standard followed by the adapters for the resolved profile. Resolution order is: @@ -57,13 +59,15 @@ At session start Meridian injects the professional standard followed by the adap Initial profiles are: -| Profile | Adapters | -| --- | --- | -| `opus-4.8` | comment narration, artifact overproduction | -| `sol` | review recursion, depth escalation, workflow literalism, agreement seeking | -| `fable` | none | +| Profile | Adapters | Mapped by | +| --- | --- | --- | +| `claude-opus` | comment narration, artifact overproduction | `claude-opus-` prefix | +| `fable` | none | `claude-fable-` prefix | +| `sol` | review recursion, depth escalation, workflow literalism, agreement seeking | environment override or fallback | + +Profiles are named for model families rather than single releases, and families are matched by prefix so a new release in a known family keeps its corrections instead of silently dropping to the baseline. -Only model identifiers observed from host payloads or documented by the host are mapped automatically. Unknown models receive the baseline rather than a guessed profile. +Only model identifiers observed from host payloads or documented by the host are mapped automatically. A model outside a mapped family receives the baseline rather than a guessed profile. Set a host-local fallback in `/meridian/config.json`: diff --git a/behavior/profiles.json b/behavior/profiles.json index ad57112..10035c4 100644 --- a/behavior/profiles.json +++ b/behavior/profiles.json @@ -1,13 +1,17 @@ { "profiles": { - "opus-4.8": ["comment-narration", "artifact-overproduction"], + "claude-opus": ["comment-narration", "artifact-overproduction"], "sol": ["review-recursion", "depth-escalation", "workflow-literalism", "agreement-seeking"], "fable": [] }, "modelMatches": [ { - "exact": "claude-opus-4-8", - "profile": "opus-4.8" + "prefix": "claude-opus-", + "profile": "claude-opus" + }, + { + "prefix": "claude-fable-", + "profile": "fable" } ] } diff --git a/eval/scenarios/negative.yaml b/eval/scenarios/negative.yaml index 3f0b8fa..3de68de 100644 --- a/eval/scenarios/negative.yaml +++ b/eval/scenarios/negative.yaml @@ -27,13 +27,13 @@ description: "ordinary review request does not bypass manual-only skill policy" - vars: - prompt: "Commit the current change." + prompt: "Show me the diff of the last three commits." assert: - type: javascript value: | const skillCalls = context.providerResponse?.metadata?.skillCalls || []; return skillCalls.length === 0; - description: "ordinary commit request does not bypass manual-only skill policy" + description: "reading git history is not commit or pull request authoring" - vars: prompt: "Write up what we learned from this debugging session." diff --git a/eval/scenarios/positive.yaml b/eval/scenarios/positive.yaml index fa5211d..50344a7 100644 --- a/eval/scenarios/positive.yaml +++ b/eval/scenarios/positive.yaml @@ -33,3 +33,17 @@ - type: skill-used value: meridian:delegate description: "independent context-heavy investigation -> delegate" + +- vars: + prompt: "Commit the current change." + assert: + - type: skill-used + value: meridian:commit + description: "explicit commit request -> commit conventions" + +- vars: + prompt: "Write the pull request description for this branch." + assert: + - type: skill-used + value: meridian:commit + description: "pull request authoring -> commit conventions" diff --git a/skills/brainstorm/SKILL.md b/skills/brainstorm/SKILL.md index 4a1ad78..2d10699 100644 --- a/skills/brainstorm/SKILL.md +++ b/skills/brainstorm/SKILL.md @@ -41,6 +41,7 @@ This flow produces an approved specification. Do NOT write code, scaffold a proj 7. **Present design in sections** — scaled to complexity. Simple sections get a few sentences. Complex sections get detail. Get user approval after each section before moving on. 8. **Write spec file** — save to `.meridian/specs/YYYY-MM-DD-.md`. + - First line after the H1 is `**Status**: DRAFT`. The lifecycle is `DRAFT` → `APPROVED` (step 12) → `IN PROGRESS` → `SHIPPED `, or `SUPERSEDED BY ` when another spec absorbs it. Whoever moves the work moves the line; a spec with no status cannot be triaged later. - Required sections: **Overview**, **Requirements** (numbered, unambiguous), **Technical Design**, **Constraints**, **User Constraints**, **Acceptance Criteria**. - **User Constraints** captures explicit do/don't rules the user stated during brainstorm (e.g., "no glow effects", "use existing utils", "match existing page patterns"). These are non-negotiable implementation rules, distinct from technical Constraints. - Scale section depth to complexity — a simple feature gets brief sections, a complex system gets thorough ones. @@ -56,7 +57,7 @@ This flow produces an approved specification. Do NOT write code, scaffold a proj 11. **User reviews spec** — present the spec, ask for changes. Iterate if needed. -12. **Transition** — once approved, stop with the approved spec. Tell the user they can invoke `meridian:execute` when they want implementation to begin. Do not invoke it on their behalf. +12. **Transition** — once approved, set **Status** to `APPROVED` and stop with the approved spec. Tell the user they can invoke `meridian:execute` when they want implementation to begin. Do not invoke it on their behalf. ## Principles diff --git a/skills/commit/SKILL.md b/skills/commit/SKILL.md index f69913f..f5327ef 100644 --- a/skills/commit/SKILL.md +++ b/skills/commit/SKILL.md @@ -1,27 +1,74 @@ --- name: commit -description: Inspect and commit the intended local changes cleanly. -disable-model-invocation: true +description: Conventions for commit messages and pull request descriptions, and the flow that produces one clean commit. Use when writing a commit message, opening or updating a pull request, or when another skill needs commit or PR description conventions. +argument-hint: "[optional: push, pr]" --- # Commit -Invocation authorizes one clean commit of the intended local work. If the user also requested push or PR creation in the invocation, that authorizes the named outward action once. +Two things live here. **Conventions** govern how commit messages and pull request descriptions are written, and apply whenever one is being written. The **commit flow** produces one clean commit and runs when the user asks for it. + +## Authorization + +Consulting this skill authorizes nothing. Committing, pushing, and creating or editing a pull request are separate outward actions; each requires an explicit user request, and each request authorizes that action once. A request to commit is not a request to push, and a request to push is not a request to open a pull request. ## Invariants -- Never include AI attribution. +- Never include AI attribution, session links, or tool trailers. - Never commit secrets, environment files, local Meridian artifacts, or unintended generated/binary files. - Preserve unrelated working-tree changes. - Never force-push or rewrite shared history without explicit authorization. -## Process +## Commit flow 1. Inspect `git status`, staged and unstaged diffs, and recent commit style. 2. Identify the files belonging to the requested change. Ask only when staged scope is materially ambiguous; otherwise stage the intended files narrowly. 3. Exclude local-only or unrelated files without reverting their contents. -4. Draft a concise message that follows repository conventions and describes the resulting code, not the session journey. +4. Draft the message per the conventions below. 5. Commit without a redundant approval prompt. -6. If push was explicitly requested, push to the current tracking branch. Ask before choosing a remote/branch that cannot be inferred or before any destructive recovery from rejection. +6. If push was explicitly requested, push to the current tracking branch. Ask before choosing a remote or branch that cannot be inferred, and before any destructive recovery from rejection. + +## What both artifacts are for + +A commit message and a pull request description are read by someone who was not present. Assume a competent engineer, months later, with the diff in front of them and no memory of the work. + +**Follow the repository's existing convention before any advice here.** Read recent merged commits and pull requests, and use the repository's pull request template when one exists. Convention in the repository outranks convention in this skill: its subject grammar, its sectioning, its label and issue-linking practice. Do not introduce sections a repository does not use. + +**Describe the resulting code, not the work that produced it.** Everything about the session is invisible to the reader and belongs in conversation instead: + +- how many attempts it took, what was tried and abandoned, what a review or a reviewer said +- rebasing, conflicts, force-pushes, branch surgery, cherry-picks +- local environment detail, machine-specific paths, which host or agent produced the change +- pre-existing failures unrelated to the change, and unrelated flakes +- "as discussed", "as requested", "per feedback", and other references to invisible context + +Write what the code now does and why it does it. If a sentence would confuse a reader who never saw the session, cut it. + +## Commit messages + +- Subject in the repository's convention, imperative mood, one line, no trailing period. +- A body when the change needs a reason. Explain why the change exists and what behavior changed. Skip the body for changes whose reason is self-evident. +- Reference issues in the repository's established format. +- One commit is one coherent change. Do not bundle unrelated work to save a commit. + +## Pull request descriptions + +A useful description gets a reviewer to a correct judgment quickly. It is not a summary of the diff, which the reviewer can already read. Order matters: reviewers read top-down and stop early. + +**Lead with why.** The problem, the user-visible symptom, or the constraint that forced the change. Rationale is the element engineers value most for understanding intent, and it is what lets a reviewer judge whether the approach solves the right problem rather than only whether the code is correct. + +**Then what changed, at the altitude of behavior.** What the system does differently now. Name the design decisions a reviewer might otherwise question, and say why the alternative was rejected. Do not narrate file by file. + +**Say what feedback you want.** Naming the kind of review you need is the strongest single predictor of reviewer engagement and acceptance, and it is the element most often omitted. Point at the files that matter, propose a review order for a multi-file change, and flag anything you are unsure about or that deserves scrutiny. "The retry logic in `client.ts` is the risky part; the rest is mechanical" is worth more than three paragraphs of summary. + +**State what was verified.** Which tests, which cases, which environment, what was observed. "Tested locally" asserts nothing. If verification was partial, say what remains untested rather than implying coverage that does not exist. + +**Flag anything that changes the reader's world.** Breaking changes, migrations, new configuration or environment variables, feature flags, dependency changes, and security-relevant surfaces: authentication, permissions, sensitive data handling. A reviewer who learns about a migration from the diff has already lost trust in the description. + +**Include evidence where it is cheaper than prose.** Screenshots or a short recording for user-visible changes. Before and after for behavioral changes. + +**Scale the description to the change.** Completeness alone does not improve review outcomes; a one-line fix with eight headings wastes the reviewer's attention and buries the one thing that matters. A trivial change earns a sentence. Cut every heading that would be empty rather than filling it with "N/A". + +**Keep the change reviewable.** A pull request should have one purpose. When a change is large enough that a reviewer cannot hold it, say so and propose the split rather than apologising for the size in the description. -Use an existing PR template or recent merged-PR convention when PR work was explicitly requested. Public artifacts omit rebasing history, local environment notes, pre-existing failures, and other circumstantial session detail. +**Self-review before requesting review.** Read your own diff and confirm the checks pass. Leftover debug output, commented-out code, and unrelated formatting churn are the reviewer's time spent on your behalf. diff --git a/skills/commit/agents/openai.yaml b/skills/commit/agents/openai.yaml deleted file mode 100644 index 5b1f887..0000000 --- a/skills/commit/agents/openai.yaml +++ /dev/null @@ -1,2 +0,0 @@ -policy: - allow_implicit_invocation: false diff --git a/skills/execute/SKILL.md b/skills/execute/SKILL.md index c6df6d5..b07a518 100644 --- a/skills/execute/SKILL.md +++ b/skills/execute/SKILL.md @@ -60,3 +60,5 @@ An unrelated pre-existing failure is reported accurately but does not change the ## Progress Log When a spec exists, append one pickup-ready entry after each verified task and one completion entry at the end. Record outcomes, decisions not obvious from the diff, deviations, and open concerns. Do not turn the log into a tool-call journal. Do not stage or commit the spec. + +Move the spec's **Status** line as the work moves: `IN PROGRESS` when implementation starts, `SHIPPED ` at the completion entry. A spec left at `APPROVED` after delivery is indistinguishable from one that was never started. diff --git a/skills/sketch/SKILL.md b/skills/sketch/SKILL.md index faa1821..6443fcd 100644 --- a/skills/sketch/SKILL.md +++ b/skills/sketch/SKILL.md @@ -29,13 +29,15 @@ Use only when the user invokes it. Unfamiliar external APIs alone are not a disq 4. **Write the sketch file.** Path: `.meridian/sketches/YYYY-MM-DD-.md` (date in local time). Slug: lowercase, ASCII-fold, replace non-alphanumerics with hyphens, collapse, trim, truncate to 60 chars, re-trim. If the result is empty, propose a slug at step 3. On filename collision, append `-2`, `-3`. Create the directory if missing. Do NOT commit. Do NOT stage. - Format: H1 `# `, then sections in fixed order — **Context** (1-2 sentences), **Plan** (bulleted: file changes or imperative steps, both forms allowed), **User Constraints** (omit the heading entirely if none), **Done When** (acceptance bullets). See `references/sketch-file-example.md` for a complete annotated example. + Format: H1 `# `, then `**Status**: DRAFT`, then sections in fixed order — **Context** (1-2 sentences), **Plan** (bulleted: file changes or imperative steps, both forms allowed), **User Constraints** (omit the heading entirely if none), **Done When** (acceptance bullets). See `references/sketch-file-example.md` for a complete annotated example. + + The status lifecycle is `DRAFT` → `APPROVED` (step 6) → `IN PROGRESS` → `SHIPPED `, or `SUPERSEDED BY `. Whoever moves the work moves the line. 5. **Self-review.** Placeholder scan (no TBD/TODO/incomplete bullets), internal consistency (Plan covers Done When; Done When is verifiable), ambiguity check. Fix inline. 6. **Present and approve.** Show the sketch contents. Ask with options "Approve" / "Request changes" through the host's structured question tool, or in plain text when unavailable. On "Request changes": collect changes, update file in place, re-run self-review, re-present. After 3 unsuccessful rounds, suggest transitioning to `brainstorm`. If a User Constraint surfaces during this step, it counts as a "Request changes" event — update the file and re-present, never silently mutate. -7. **Hand off.** On approval, stop with the approved sketch and its absolute path. Tell the user they can invoke `meridian:execute` when they want implementation to begin. Do not invoke it on their behalf. +7. **Hand off.** On approval, set **Status** to `APPROVED` and stop with the approved sketch and its absolute path. Tell the user they can invoke `meridian:execute` when they want implementation to begin. Do not invoke it on their behalf. ## Escape Hatch diff --git a/skills/sketch/references/sketch-file-example.md b/skills/sketch/references/sketch-file-example.md index 882bd3b..c5b6e0d 100644 --- a/skills/sketch/references/sketch-file-example.md +++ b/skills/sketch/references/sketch-file-example.md @@ -1,11 +1,13 @@ # Sketch File Example A complete sketch file, for reference. The format is fixed-order: H1 title, then -Context → Plan → User Constraints (omit if none) → Done When. +Status → Context → Plan → User Constraints (omit if none) → Done When. ```markdown # Add Copy Button to Code Blocks +**Status**: DRAFT + ## Context Code blocks in the docs lack a copy-to-clipboard button, forcing manual selection. Add a button in the top-right corner of each block. diff --git a/test/meridian-consistency.test.mjs b/test/meridian-consistency.test.mjs index 0d0e7cd..10aac1d 100644 --- a/test/meridian-consistency.test.mjs +++ b/test/meridian-consistency.test.mjs @@ -7,8 +7,8 @@ import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); -const MANUAL_FLOWS = ["auto", "brainstorm", "commit", "document", "execute", "review", "sketch"]; -const MODEL_DISCIPLINES = ["debug", "delegate", "research", "respond", "triangulate"]; +const MANUAL_FLOWS = ["auto", "brainstorm", "document", "execute", "review", "sketch"]; +const MODEL_DISCIPLINES = ["commit", "debug", "delegate", "research", "respond", "triangulate"]; /** @param {string} rel */ function readJSON(rel) { @@ -76,6 +76,16 @@ test("model disciplines remain available for implicit invocation", () => { } }); +test("commit conventions are reachable without authorizing outward actions", () => { + const body = readFileSync(join(ROOT, "skills", "commit", "SKILL.md"), "utf8"); + const description = body.match(/^description:\s*(.+)$/m)?.[1] ?? ""; + + assert.match(description, /pull request/i, "description reaches PR authoring"); + assert.match(description, /another skill needs/i, "description is reachable by other skills"); + assert.match(body, /Consulting this skill authorizes nothing/, "no implicit authorization"); + assert.match(body, /^## Pull request descriptions$/m, "PR guidance is present"); +}); + test("model discipline descriptions stay narrower than baseline grounding", () => { const research = readFileSync(join(ROOT, "skills", "research", "SKILL.md"), "utf8"); const triangulate = readFileSync(join(ROOT, "skills", "triangulate", "SKILL.md"), "utf8"); From 00a9013f13f25bb6d4de1aff591716e280f621c6 Mon Sep 17 00:00:00 2001 From: Stella Inwood Date: Sat, 25 Jul 2026 20:03:28 +1000 Subject: [PATCH 2/3] refactor(meridian): tighten commit skill against skill-authoring discipline (0.13.0) Description carries triggers and the cross-skill reach clause rather than restating identity the body already holds. The session-omission rule was a bulleted prohibition list, which names the behaviour it bans. It collapses to a diary, positive-first: describe what the code does, and cut what reads as a diary entry. --- skills/commit/SKILL.md | 44 +++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/skills/commit/SKILL.md b/skills/commit/SKILL.md index f5327ef..92b093d 100644 --- a/skills/commit/SKILL.md +++ b/skills/commit/SKILL.md @@ -1,12 +1,12 @@ --- name: commit -description: Conventions for commit messages and pull request descriptions, and the flow that produces one clean commit. Use when writing a commit message, opening or updating a pull request, or when another skill needs commit or PR description conventions. +description: Commit message and pull request conventions, and the one-clean-commit flow. Use when writing a commit message, opening or updating a pull request, or when another skill needs these conventions. argument-hint: "[optional: push, pr]" --- # Commit -Two things live here. **Conventions** govern how commit messages and pull request descriptions are written, and apply whenever one is being written. The **commit flow** produces one clean commit and runs when the user asks for it. +**Conventions** govern how commit messages and pull request descriptions are written, whenever one is written. The **commit flow** runs when the user asks for it. ## Authorization @@ -28,47 +28,39 @@ Consulting this skill authorizes nothing. Committing, pushing, and creating or e 5. Commit without a redundant approval prompt. 6. If push was explicitly requested, push to the current tracking branch. Ask before choosing a remote or branch that cannot be inferred, and before any destructive recovery from rejection. -## What both artifacts are for +## Both artifacts are public -A commit message and a pull request description are read by someone who was not present. Assume a competent engineer, months later, with the diff in front of them and no memory of the work. +They are read by an engineer who was not there: months later, holding the diff, with no memory of the work. Write for that reader. -**Follow the repository's existing convention before any advice here.** Read recent merged commits and pull requests, and use the repository's pull request template when one exists. Convention in the repository outranks convention in this skill: its subject grammar, its sectioning, its label and issue-linking practice. Do not introduce sections a repository does not use. +**Neither is a diary.** Describe what the code does and why it does it. The session that produced it is invisible to that reader and belongs in conversation instead: attempts and dead ends, rebases and force-pushes, review rounds, conflicts, local environment and machine paths, which host or agent typed it, unrelated pre-existing failures, and back-references like "as discussed" or "per feedback". A sentence that would confuse someone who never saw the session is a diary entry. Cut it. -**Describe the resulting code, not the work that produced it.** Everything about the session is invisible to the reader and belongs in conversation instead: - -- how many attempts it took, what was tried and abandoned, what a review or a reviewer said -- rebasing, conflicts, force-pushes, branch surgery, cherry-picks -- local environment detail, machine-specific paths, which host or agent produced the change -- pre-existing failures unrelated to the change, and unrelated flakes -- "as discussed", "as requested", "per feedback", and other references to invisible context - -Write what the code now does and why it does it. If a sentence would confuse a reader who never saw the session, cut it. +**Repository convention outranks this skill.** Read recent merged commits and pull requests, and use the pull request template when one exists: its subject grammar, its sectioning, its label and issue-linking practice. Introduce no section the repository does not already use. ## Commit messages - Subject in the repository's convention, imperative mood, one line, no trailing period. -- A body when the change needs a reason. Explain why the change exists and what behavior changed. Skip the body for changes whose reason is self-evident. +- A body when the change needs a reason: why it exists, what behavior changed. Self-evident changes take no body. - Reference issues in the repository's established format. -- One commit is one coherent change. Do not bundle unrelated work to save a commit. +- One commit is one coherent change. ## Pull request descriptions -A useful description gets a reviewer to a correct judgment quickly. It is not a summary of the diff, which the reviewer can already read. Order matters: reviewers read top-down and stop early. +A description earns its place by getting a reviewer to a correct judgment quickly. The diff already says what changed line by line; the description says what the reviewer cannot reconstruct. Reviewers read top-down and stop early, so order matters. -**Lead with why.** The problem, the user-visible symptom, or the constraint that forced the change. Rationale is the element engineers value most for understanding intent, and it is what lets a reviewer judge whether the approach solves the right problem rather than only whether the code is correct. +**Lead with why.** The problem, the user-visible symptom, or the constraint that forced the change. Rationale is what engineers value most for understanding intent, and it is what lets a reviewer judge whether the approach solves the right problem rather than only whether the code is correct. -**Then what changed, at the altitude of behavior.** What the system does differently now. Name the design decisions a reviewer might otherwise question, and say why the alternative was rejected. Do not narrate file by file. +**Then what changed, at the altitude of behavior.** What the system does differently now. Name the design decisions a reviewer might otherwise question, and say why the alternative lost. -**Say what feedback you want.** Naming the kind of review you need is the strongest single predictor of reviewer engagement and acceptance, and it is the element most often omitted. Point at the files that matter, propose a review order for a multi-file change, and flag anything you are unsure about or that deserves scrutiny. "The retry logic in `client.ts` is the risky part; the rest is mechanical" is worth more than three paragraphs of summary. +**Say what feedback you want.** Naming the review you need is the strongest single predictor of reviewer engagement and acceptance, and the element most often omitted. Point at the files that matter, propose a review order for a multi-file change, and flag what you are unsure about. "The retry logic in `client.ts` is the risky part; the rest is mechanical" is worth more than three paragraphs of summary. -**State what was verified.** Which tests, which cases, which environment, what was observed. "Tested locally" asserts nothing. If verification was partial, say what remains untested rather than implying coverage that does not exist. +**State what was verified.** Which tests, which cases, which environment, what was observed. "Tested locally" asserts nothing. Where verification was partial, name what remains untested rather than implying coverage that does not exist. -**Flag anything that changes the reader's world.** Breaking changes, migrations, new configuration or environment variables, feature flags, dependency changes, and security-relevant surfaces: authentication, permissions, sensitive data handling. A reviewer who learns about a migration from the diff has already lost trust in the description. +**Flag what changes the reader's world.** Breaking changes, migrations, new configuration or environment variables, feature flags, dependency changes, and security-relevant surfaces: authentication, permissions, sensitive data handling. A reviewer who discovers a migration in the diff has already stopped trusting the description. -**Include evidence where it is cheaper than prose.** Screenshots or a short recording for user-visible changes. Before and after for behavioral changes. +**Show evidence where it is cheaper than prose.** Screenshots or a short recording for user-visible changes; before and after for behavioral ones. -**Scale the description to the change.** Completeness alone does not improve review outcomes; a one-line fix with eight headings wastes the reviewer's attention and buries the one thing that matters. A trivial change earns a sentence. Cut every heading that would be empty rather than filling it with "N/A". +**Scale to the change.** Completeness alone does not improve review outcomes. A one-line fix under eight headings buries the one thing that matters; a trivial change earns a sentence. Cut every heading that would otherwise hold "N/A". -**Keep the change reviewable.** A pull request should have one purpose. When a change is large enough that a reviewer cannot hold it, say so and propose the split rather than apologising for the size in the description. +**Keep the change reviewable.** One pull request, one purpose. When a change is too large to hold, propose the split rather than apologising for the size. -**Self-review before requesting review.** Read your own diff and confirm the checks pass. Leftover debug output, commented-out code, and unrelated formatting churn are the reviewer's time spent on your behalf. +**Self-review first.** Read your own diff and confirm the checks pass before requesting review. Leftover debug output, commented-out code, and unrelated formatting churn spend the reviewer's attention on your behalf. From ac0382988a3b97d6858fa11e1beff73f2339b63d Mon Sep 17 00:00:00 2001 From: Stella Inwood Date: Sat, 25 Jul 2026 20:32:45 +1000 Subject: [PATCH 3/3] fix(meridian): close the commit-skill reachability and profile-coverage gaps (0.13.0) `execute` and `auto` described the commit flow as unreachable, which contradicted the model-invocable skill and left the conventions unreached on the path that most needs them. Both now point at `meridian:commit` for message conventions while the flow itself stays user-owned. Family prefix matching had no regression guard: restoring the previous exact-only registry left the suite green. A test now pins that a later release in a mapped family receives its adapters and that an unmapped family stays on the baseline. Outward actions move behind a HARD-GATE, matching the device `brainstorm` and `sketch` already use, since `commit` is the only model-invocable skill that can push. `SHIPPED ` moves to the commit flow, which is the only step that produces a sha; `execute` sets `IN PROGRESS` and covers sketches as well as specs. Two pull request claims asserted research findings that the source does not support and are removed. The guidance they justified stands on its own, and the section gains the pull request title and issue linking. The `opus-4.8` to `claude-opus` rename is recorded as breaking: a host-local `fallbackProfile` set to the old name silently yields no adapters. Prefix matching is documented as literal, so vendor-qualified identifiers still need the override. --- CHANGELOG.md | 8 +++++-- README.md | 8 ++++--- eval/README.md | 8 ++++--- eval/scenarios/negative.yaml | 9 ++++++++ skills/auto/SKILL.md | 2 +- skills/commit/SKILL.md | 36 ++++++++++++++---------------- skills/execute/SKILL.md | 4 ++-- test/meridian-consistency.test.mjs | 18 +++++++++++++-- test/meridian-lib.test.mjs | 16 +++++++++++++ 9 files changed, 77 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cecfdfb..179d34b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). History before ### Changed -- Model profiles are named for families and matched by prefix, so a new release in a mapped family keeps its corrections instead of dropping to the baseline. `opus-4.8` becomes `claude-opus` and matches the `claude-opus-` prefix; `fable` matches `claude-fable-`. -- `commit` is model-invocable. Its conventions are reachable whenever a commit message or pull request description is written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request each still require an explicit request. +- Model profiles are named for families and matched by prefix, so a later release in a mapped family keeps its corrections instead of dropping to the baseline. Matching is a plain prefix over the identifier the host reports, so vendor- or region-qualified identifiers (for example the `us.anthropic.` Bedrock form) still fall through to the baseline. +- **Breaking:** the `opus-4.8` profile is renamed `claude-opus`. A host-local `/meridian/config.json` setting `"fallbackProfile": "opus-4.8"` no longer resolves and silently yields the baseline standard with no adapters. Rename the value to `claude-opus`. +- `claude-fable-` now resolves to the `fable` profile, which carries no adapters by design. A Fable session therefore no longer picks up a configured `fallbackProfile`. +- `commit` is model-invocable. Its conventions are reachable whenever a commit message or pull request description is written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request sit behind a hard gate and each still require an explicit request. +- `execute` and `auto` point at `meridian:commit` for message conventions instead of restating them, and no longer describe the commit flow as un-reachable. +- Eval scenarios cover the new boundary: authoring a commit message or pull request description reaches `commit`, while reading history and an implied-approval prompt do not. ### Fixed diff --git a/README.md b/README.md index be27fa9..7f103ba 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Invoking a flow authorizes its ordinary documented behavior. Separate approval i ### Model-invoked disciplines -These are narrow internal tools, not workflow gates: +The model may reach these inside an ordinary task. They are not workflow gates: | Skill | Purpose | | --- | --- | @@ -45,7 +45,7 @@ These are narrow internal tools, not workflow gates: | `delegate` | Isolate genuinely independent or context-heavy work | | `respond` | Evaluate review feedback while preserving user intent | -`commit` holds conventions and a flow. The conventions are reachable whenever a commit message or pull request description is being written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request each still require an explicit request. +`commit` is the exception in this table: it holds conventions and a flow that performs outward actions. The conventions are reachable whenever a commit message or pull request description is being written, including by other skills. Reaching them authorizes nothing: committing, pushing, and opening or editing a pull request sit behind a hard gate and each still require an explicit request. ## Model profiles @@ -65,7 +65,9 @@ Initial profiles are: | `fable` | none | `claude-fable-` prefix | | `sol` | review recursion, depth escalation, workflow literalism, agreement seeking | environment override or fallback | -Profiles are named for model families rather than single releases, and families are matched by prefix so a new release in a known family keeps its corrections instead of silently dropping to the baseline. +Profiles are named for model families rather than single releases, and families are matched by prefix so a later release in a known family keeps its corrections instead of silently dropping to the baseline. + +Matching is a plain prefix over the identifier the host reports. A vendor- or region-qualified identifier, such as the `us.anthropic.` form used by Bedrock, does not match a bare family prefix; use the environment override or a host-local fallback there. Only model identifiers observed from host payloads or documented by the host are mapped automatically. A model outside a mapped family receives the baseline rather than a guessed profile. diff --git a/eval/README.md b/eval/README.md index 1317de6..eef32ab 100644 --- a/eval/README.md +++ b/eval/README.md @@ -13,12 +13,14 @@ pnpm eval:view ## Coverage -- `scenarios/positive.yaml` checks model-owned disciplines with observable triggers. -- `scenarios/negative.yaml` checks that ordinary feature, implementation, review, commit, and documentation requests do not implicitly invoke user-owned flows. +- `scenarios/positive.yaml` checks model-owned disciplines with observable triggers, including authoring a commit message or pull request description. +- `scenarios/negative.yaml` checks that ordinary feature, implementation, review, and documentation requests do not implicitly invoke user-owned flows, and that approving a result does not lead to an unrequested commit or push. + +The harness inspects `metadata.skillCalls` and the response text, so it can show that no outward action was proposed but cannot prove none was executed. The authorization boundary in `skills/commit/SKILL.md` is a hard gate in prose; nothing here enforces it. Explicit slash or tagged skill invocation is a host command boundary, not a model routing decision, and is covered by skill metadata consistency tests rather than this single-turn provider harness. -Profile resolution, adapter composition, malformed configuration, and host output shapes are deterministic offline tests. Live adapter behavior requires a runner that can select the target model and expose its real session-start model payload. The current Sonnet-only promptfoo runner cannot represent Opus 4.8, Sol, or Fable coverage faithfully, so it does not pretend to. Add provider-specific cases when those models are executable through this harness. +Profile resolution, adapter composition, malformed configuration, and host output shapes are deterministic offline tests. Live adapter behavior requires a runner that can select the target model and expose its real session-start model payload. The current Sonnet-only promptfoo runner cannot represent Claude Opus, Sol, or Fable coverage faithfully, so it does not pretend to. Add provider-specific cases when those models are executable through this harness. ## Adding a scenario diff --git a/eval/scenarios/negative.yaml b/eval/scenarios/negative.yaml index 3de68de..4667ee0 100644 --- a/eval/scenarios/negative.yaml +++ b/eval/scenarios/negative.yaml @@ -26,6 +26,15 @@ return skillCalls.length === 0; description: "ordinary review request does not bypass manual-only skill policy" +- vars: + prompt: "The tests pass now." + assert: + - type: javascript + value: | + const text = (context.providerResponse?.output || "").toLowerCase(); + return !/\bgit (commit|push)\b/.test(text); + description: "approval of a result is not authorization to commit or push" + - vars: prompt: "Show me the diff of the last three commits." assert: diff --git a/skills/auto/SKILL.md b/skills/auto/SKILL.md index 9b92546..104d422 100644 --- a/skills/auto/SKILL.md +++ b/skills/auto/SKILL.md @@ -27,7 +27,7 @@ It does not authorize scope expansion, push, deploy, force-push, destructive dat - Finish the requested outcome rather than starting adjacent work. - Keep legacy behavior intact when a migration cannot be completed safely. - Treat new user messages as current authority: absorb constraints, stop withdrawn scope, and end autonomous operation when the user takes back control. -- Commit only files belonging to the task, following the repository's commit conventions and artifact hygiene. +- Commit only files belonging to the task, per `meridian:commit`. - Stop when the professional standard's completion condition is met, not when elapsed time feels sufficient and not after inventing extra work. ## Handback diff --git a/skills/commit/SKILL.md b/skills/commit/SKILL.md index 92b093d..7706330 100644 --- a/skills/commit/SKILL.md +++ b/skills/commit/SKILL.md @@ -1,22 +1,18 @@ --- name: commit -description: Commit message and pull request conventions, and the one-clean-commit flow. Use when writing a commit message, opening or updating a pull request, or when another skill needs these conventions. +description: Commit message and pull request description conventions, and the clean-commit flow. Use when committing, opening or updating a pull request, or when another skill needs these conventions. argument-hint: "[optional: push, pr]" --- -# Commit - -**Conventions** govern how commit messages and pull request descriptions are written, whenever one is written. The **commit flow** runs when the user asks for it. + +The conventions below are always available. The flow is not: committing, pushing, and creating or editing a pull request each require an explicit user request for that action. A request to commit is not a request to push, and a request to push is not a request to open a pull request. + -## Authorization - -Consulting this skill authorizes nothing. Committing, pushing, and creating or editing a pull request are separate outward actions; each requires an explicit user request, and each request authorizes that action once. A request to commit is not a request to push, and a request to push is not a request to open a pull request. +# Commit ## Invariants -- Never include AI attribution, session links, or tool trailers. - Never commit secrets, environment files, local Meridian artifacts, or unintended generated/binary files. -- Preserve unrelated working-tree changes. - Never force-push or rewrite shared history without explicit authorization. ## Commit flow @@ -27,12 +23,13 @@ Consulting this skill authorizes nothing. Committing, pushing, and creating or e 4. Draft the message per the conventions below. 5. Commit without a redundant approval prompt. 6. If push was explicitly requested, push to the current tracking branch. Ask before choosing a remote or branch that cannot be inferred, and before any destructive recovery from rejection. +7. Move the `Status` line of any spec or sketch this work delivers to `SHIPPED `. -## Both artifacts are public +## Both artifacts -They are read by an engineer who was not there: months later, holding the diff, with no memory of the work. Write for that reader. +They are read by an engineer who was not there: months later, holding the diff, with no memory of the work. -**Neither is a diary.** Describe what the code does and why it does it. The session that produced it is invisible to that reader and belongs in conversation instead: attempts and dead ends, rebases and force-pushes, review rounds, conflicts, local environment and machine paths, which host or agent typed it, unrelated pre-existing failures, and back-references like "as discussed" or "per feedback". A sentence that would confuse someone who never saw the session is a diary entry. Cut it. +**Every sentence stands alone.** Describe what the code does and why it does it, in terms a reader holding only the diff can follow. "As discussed", "after the rebase", "addressed the review comment", "still failing from before" all fail that test: they point at a session the reader never saw. Circumstantial detail belongs in conversation instead. **Repository convention outranks this skill.** Read recent merged commits and pull requests, and use the pull request template when one exists: its subject grammar, its sectioning, its label and issue-linking practice. Introduce no section the repository does not already use. @@ -40,27 +37,28 @@ They are read by an engineer who was not there: months later, holding the diff, - Subject in the repository's convention, imperative mood, one line, no trailing period. - A body when the change needs a reason: why it exists, what behavior changed. Self-evident changes take no body. -- Reference issues in the repository's established format. - One commit is one coherent change. ## Pull request descriptions A description earns its place by getting a reviewer to a correct judgment quickly. The diff already says what changed line by line; the description says what the reviewer cannot reconstruct. Reviewers read top-down and stop early, so order matters. -**Lead with why.** The problem, the user-visible symptom, or the constraint that forced the change. Rationale is what engineers value most for understanding intent, and it is what lets a reviewer judge whether the approach solves the right problem rather than only whether the code is correct. +**Title first.** Often the only thing a reviewer reads before deciding when to pick it up. One line, the repository's convention, naming the outcome rather than the activity. + +**Lead with why.** The problem, the user-visible symptom, or the constraint that forced the change, so a reviewer can judge whether the approach solves the right problem rather than only whether the code is correct. **Then what changed, at the altitude of behavior.** What the system does differently now. Name the design decisions a reviewer might otherwise question, and say why the alternative lost. -**Say what feedback you want.** Naming the review you need is the strongest single predictor of reviewer engagement and acceptance, and the element most often omitted. Point at the files that matter, propose a review order for a multi-file change, and flag what you are unsure about. "The retry logic in `client.ts` is the risky part; the rest is mechanical" is worth more than three paragraphs of summary. +**Say what feedback you want, and in what order.** Point at the files that matter, propose a review order for a multi-file change, and flag what you are unsure about. "The retry logic in `client.ts` is the risky part; the rest is mechanical" is worth more than three paragraphs of summary. + +**Link the issue** in the repository's established format, using its closing keywords where that is the convention. **State what was verified.** Which tests, which cases, which environment, what was observed. "Tested locally" asserts nothing. Where verification was partial, name what remains untested rather than implying coverage that does not exist. -**Flag what changes the reader's world.** Breaking changes, migrations, new configuration or environment variables, feature flags, dependency changes, and security-relevant surfaces: authentication, permissions, sensitive data handling. A reviewer who discovers a migration in the diff has already stopped trusting the description. +**Flag what changes the reader's world.** Breaking changes, migrations, renamed or removed configuration keys, new environment variables, feature flags, dependency changes, and security-relevant surfaces: authentication, permissions, sensitive data handling. **Show evidence where it is cheaper than prose.** Screenshots or a short recording for user-visible changes; before and after for behavioral ones. -**Scale to the change.** Completeness alone does not improve review outcomes. A one-line fix under eight headings buries the one thing that matters; a trivial change earns a sentence. Cut every heading that would otherwise hold "N/A". - -**Keep the change reviewable.** One pull request, one purpose. When a change is too large to hold, propose the split rather than apologising for the size. +**Scale to the change.** A one-line fix under eight headings buries the one thing that matters; a trivial change earns a sentence. Cut every heading that would otherwise hold "N/A". **Self-review first.** Read your own diff and confirm the checks pass before requesting review. Leftover debug output, commented-out code, and unrelated formatting churn spend the reviewer's attention on your behalf. diff --git a/skills/execute/SKILL.md b/skills/execute/SKILL.md index b07a518..f484622 100644 --- a/skills/execute/SKILL.md +++ b/skills/execute/SKILL.md @@ -39,7 +39,7 @@ For each task: ### 4. Finish -Run the relevant full project checks after focused verification. Formal `meridian:review` and `meridian:commit` are separate user-owned flows; do not invoke them automatically. +Run the relevant full project checks after focused verification. Formal `meridian:review` and the commit flow are user-owned; do not run them automatically. When a commit message is written, `meridian:commit` holds the conventions. Report: @@ -61,4 +61,4 @@ An unrelated pre-existing failure is reported accurately but does not change the When a spec exists, append one pickup-ready entry after each verified task and one completion entry at the end. Record outcomes, decisions not obvious from the diff, deviations, and open concerns. Do not turn the log into a tool-call journal. Do not stage or commit the spec. -Move the spec's **Status** line as the work moves: `IN PROGRESS` when implementation starts, `SHIPPED ` at the completion entry. A spec left at `APPROVED` after delivery is indistinguishable from one that was never started. +Set the driving spec or sketch's **Status** to `IN PROGRESS` when implementation starts. `SHIPPED ` belongs to whoever commits, since the sha does not exist yet here. diff --git a/test/meridian-consistency.test.mjs b/test/meridian-consistency.test.mjs index 10aac1d..50fba00 100644 --- a/test/meridian-consistency.test.mjs +++ b/test/meridian-consistency.test.mjs @@ -82,7 +82,16 @@ test("commit conventions are reachable without authorizing outward actions", () assert.match(description, /pull request/i, "description reaches PR authoring"); assert.match(description, /another skill needs/i, "description is reachable by other skills"); - assert.match(body, /Consulting this skill authorizes nothing/, "no implicit authorization"); + assert.match(body, /^$/m, "outward actions sit behind a hard gate"); + assert.match( + body, + /require an explicit user request for that action/, + "the gate names per-action authorization", + ); + assert.ok( + body.indexOf("") < body.indexOf("# Commit"), + "the gate is read before the flow", + ); assert.match(body, /^## Pull request descriptions$/m, "PR guidance is present"); }); @@ -133,7 +142,12 @@ test("behavior profiles reference only packaged atomic adapters", () => { for (const match of registry.modelMatches) { assert.ok(registry.profiles[match.profile], `${match.profile} is a known profile`); - assert.ok(match.exact || match.prefix, "model match is exact or prefix based"); + const key = match.exact ?? match.prefix; + assert.ok(typeof key === "string" && key.length > 0, "model match has a non-empty key"); + assert.ok( + match.exact === undefined || match.prefix === undefined, + "model match is exact or prefix, not both", + ); } }); diff --git a/test/meridian-lib.test.mjs b/test/meridian-lib.test.mjs index 13daf1f..c9977d6 100644 --- a/test/meridian-lib.test.mjs +++ b/test/meridian-lib.test.mjs @@ -77,6 +77,22 @@ test("loadBehaviorContext composes detected, fallback, and baseline-only profile rmSync(stateBase, { recursive: true, force: true }); }); +test("shipped registry applies family corrections beyond the mapped release", () => { + const stateBase = mkdtempSync(join(tmpdir(), "meridian-family-")); + + for (const model of ["claude-opus-5", "claude-opus-5[1m]", "claude-opus-4-8"]) { + const context = loadBehaviorContext({ input: { model }, stateBase }); + assert.match(context, /comment narration/i, `${model} receives family adapters`); + } + + for (const model of ["claude-sonnet-5", "gpt-5-codex"]) { + const context = loadBehaviorContext({ input: { model }, stateBase }); + assert.doesNotMatch(context, /model adapter/i, `${model} stays on the baseline`); + } + + rmSync(stateBase, { recursive: true, force: true }); +}); + test("detectHost resolves the Cursor state base", () => { const saved = { CURSOR_PLUGIN_ROOT: process.env.CURSOR_PLUGIN_ROOT,