Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to the claude-plugins project will be documented in this fil

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Entries are listed newest-first; each plugin section is treated as released when merged to `main`.

### vibe v1.0.34

#### Changed
- Compile-only repairs to frozen v1.0 story files are allowed (design ruling, 2026-10-15). The freeze protects what a story shows and its link, not the code wording, so a rename elsewhere may repair a v1.0 story file with only mechanical changes (prop renames, imports, types): no story added or removed, no title, story ID or tag change and no visual change. Sessions no longer avoid a rename to protect a v1.0 story file.
- `handoff-inventory.mjs` lists an in-place edit of a v1.0 story file whose meta title, meta tags and set of exported story names (and so its story IDs) match the base under a new non-blocking `compileOnlyV1Repairs`. Any change to the title, tags or exported stories, or a move, stays in `v1Stories` with the reason `frozen` and blocks. A visual change cannot be proven mechanically and is left to the reviewer and the engineer.
- The ticket worker puts each `compileOnlyV1Repairs` file on the live ticket's Engineering checklist as `- [ ] Check compile-only repair to v1.0: <file>; confirm only prop, import or type changes and no visual change` (`ticket-template.md`, "Compile-only v1.0 repairs"). `vibe-storybook-decomposer` states the exception; `guardrails.md`, `design-pass.md`, handoff and the README carry it; the writer repairs and logs it; `vibe-guardrails-reviewer` check 10 confirms each repair is purely mechanical with no visual change and blocks anything else.
- Coverage: a node test for a v1.0 file where a prop rename passes into `compileOnlyV1Repairs` while an added, removed or renamed story export, a title change, a tag change and a move each block; a new contract test file for the prose and the ticket placement. Both fail on 1.0.33.

### vibe v1.0.33

#### Fixed
Expand Down
2 changes: 1 addition & 1 deletion plugins/vibe/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vibe",
"description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows",
"version": "1.0.33",
"version": "1.0.34",
"dependencies": ["closedloop-core"],
"author": {"name": "ClosedLoop", "email": "support@closedloop.ai"}
}
2 changes: 1 addition & 1 deletion plugins/vibe/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vibe",
"version": "1.0.33",
"version": "1.0.34",
"description": "Vibe sessions, handoff, seed refresh, and ClosedLoop ticket workflows",
"author": {"name": "ClosedLoop", "email": "support@closedloop.ai"},
"homepage": "https://github.com/closedloop-ai/claude-plugins",
Expand Down
7 changes: 4 additions & 3 deletions plugins/vibe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ and Storybook skill (`.claude/skills/storybook/`, whose placement workflows
and index scripts it uses) where they say how and mirroring that precedent's
structure, never its root or version, where they do not; the story files the
writer matches are the same capability's, not the same folder's. Stories cover the components consumers
render, not internal render pieces. A v1.0 story file is never edited, so new
states of its component go in a new v2.0 entry holding only them. New states
render, not internal render pieces. A v1.0 story file is never edited beyond
a compile-only repair, so new states of its component go in a new v2.0 entry
holding only them. New states
of any other existing component go in its existing story file under the same
title and tag, since a retitle breaks every link to its stories; handoff
blocks an edited v1.0 file, and a retitle unless it is a sibling replacement's
blocks any other edit of a v1.0 file, and a retitle unless it is a sibling replacement's
entry. The writer records it in its local plan;
a conflict goes on the live ticket by audience, design conflicts under
Design Review and everything else on the Engineering checklist, never to the
Expand Down
10 changes: 7 additions & 3 deletions plugins/vibe/agents/vibe-change-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,13 @@ Read the repo's `apps/storybook/TAXONOMY.md` and follow it: every new story
goes in a v2.0 root, in its page tree, tagged `v2.0`, with the Storybook
release bump it requires, never in a frozen v1.0 root; new states of an
existing component go in a new v2.0 entry holding only them when its story
file is v1.0, which you never edit, and otherwise in its existing story file,
title and tag unchanged
(`../skills/vibe/references/guardrails.md`, "Storybook taxonomy"). The story
file is v1.0, which you never edit beyond a compile-only repair, and otherwise
in its existing story file, title and tag unchanged
(`../skills/vibe/references/guardrails.md`, "Storybook taxonomy"). Never avoid
a rename to protect a v1.0 story file; when a rename breaks one, repair it with
only prop renames, imports and types, no story added or removed and no title,
story ID, tag or visual change, and log `Compile-only repair to v1.0: <file>`
in the change log (design ruling, 2026-10-15). The story
files you match are the same capability's, not the same folder's (same
section).

Expand Down
5 changes: 4 additions & 1 deletion plugins/vibe/agents/vibe-guardrails-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ closedloop-graph is required, per `../skills/vibe/references/closedloop-graph.md
under or tagged for a frozen v1.0 root, or any edit or move of a story
file that is v1.0 on main (the inventory's `v1Stories`, reason `frozen`),
is blocking; that component's new states belong in a new v2.0 entry
holding only them, with no version number in its name. So is a v2.0 story outside its page tree, without the `v2.0`
holding only them, with no version number in its name. Confirm each of
the inventory's `compileOnlyV1Repairs` is purely mechanical (prop
renames, imports, types) with no visual change; anything else in one is
blocking. So is a v2.0 story outside its page tree, without the `v2.0`
tag, or added, moved or renamed without the release bump the doc names.
An existing non-v1.0 story file (on main) retitled, re-tagged or moved breaks every
link to its stories and is blocking (the inventory's `retitledStories`
Expand Down
8 changes: 6 additions & 2 deletions plugins/vibe/agents/vibe-storybook-decomposer.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ Do this first, in both modes.
even to add a story: the new states go in a new v2.0 entry for that
component holding only the new states, its name descriptive, permanent,
per `apps/storybook/TAXONOMY.md` and without a version number, and the
v1.0 stories stay where they are. Any other existing story file takes the
v1.0 stories stay where they are. The one exception is a compile-only
repair after a rename elsewhere: only mechanical changes (prop renames,
imports, types), no story added or removed, no title, story ID or tag
change and no visual change, since the freeze protects what its stories
show and their links, not the code wording. Any other existing story file takes the
new states as extra stories, its title, tag and existing story names
unchanged. Otherwise only a new component, or a sibling replacement such
as a V3, gets a new
Expand Down Expand Up @@ -126,7 +130,7 @@ Placement:
- Capability: <named without the entity>
- Precedents: <each: entity, providing component path, how hosts consume it, each story title, story names, tag and file>, or none found
- Owner: <path to build or extend>, per <doc section> or mirroring <precedent path>
- Stories: <each story to add or extend: title, tag, file>, root and tag per <TAXONOMY.md section>, structure per <doc section> or mirroring <precedent>; an existing file's title and tag unchanged, a v1.0 file not edited at all (step 4)
- Stories: <each story to add or extend: title, tag, file>, root and tag per <TAXONOMY.md section>, structure per <doc section> or mirroring <precedent>; an existing file's title and tag unchanged, a v1.0 file not edited beyond a compile-only repair (step 4)
- Conflict: <each disagreement: audience `design` or `engineering`, both sides quoted with paths, and how it was decided>, or none
```

Expand Down
4 changes: 4 additions & 0 deletions plugins/vibe/agents/vibe-ticket-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ historical inspections accurately. Preserve the existing grading block.
entry in the change log once, as an unchecked item at the end of the
Engineering checklist in its logged shape
(`../skills/vibe/references/ticket-template.md`, "Engineering needed").
Place each file in the inventory's `compileOnlyV1Repairs` once, as an
unchecked item at the end of the Engineering checklist in the template's
shape (`../skills/vibe/references/ticket-template.md`, "Compile-only v1.0
repairs").
Fill Grading with the
template's Grading section copied unchanged, adding the section after
Handoff when the ticket has none.
Expand Down
7 changes: 4 additions & 3 deletions plugins/vibe/skills/handoff/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,11 @@ Storybook placement line; the SAME writer logs each Conflict with its
audience, and the ticket worker places it by that audience
(`../vibe/references/ticket-template.md`, "Conflicts"), never to the person.
Every new story follows the repo's `apps/storybook/TAXONOMY.md`, a v1.0
story file is never edited and any other existing story file keeps its title
and tag
story file is never edited beyond a compile-only repair and any other existing
story file keeps its title and tag
(`../vibe/references/guardrails.md`, "Storybook taxonomy"); the inventory's
`v1Stories` and `retitledStories` block handoff. Resume the SAME writer to perform every extraction, story and catalog
`v1Stories` and `retitledStories` block handoff. Its `compileOnlyV1Repairs`
do not; the ticket worker puts each on the Engineering checklist. Resume the SAME writer to perform every extraction, story and catalog
source change. Read-only verify footprint mode measures the sidebar and
reports remaining problems; they return to the writer, never another author.
Collect Design Review evidence from the verified final diff and existing
Expand Down
96 changes: 72 additions & 24 deletions plugins/vibe/skills/handoff/scripts/handoff-inventory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
// root, or an existing one newly given either, is listed under `v1Stories` and
// blocks. A story file that was v1.0 at the base is frozen: any edit or move
// of it is listed under `v1Stories` with the reason `frozen` and blocks, so new
// states of its component go in a new v2.0 entry instead. Any other existing
// states of its component go in a new v2.0 entry instead. The one exception is
// a compile-only repair (design ruling, 2026-10-15): an in-place edit that
// keeps the meta title, the meta tags and the set of exported story names, and
// so every story ID, is listed under `compileOnlyV1Repairs` and does not block;
// the ticket asks engineering to confirm it changed no visuals. Any other existing
// story file keeps its meta title, because Storybook builds story IDs from it:
// a title changed from the base, moves included, is listed under
// `retitledStories` and blocks unless the session record lists the file as a
Expand Down Expand Up @@ -96,12 +100,18 @@ const STORY_PATTERN = /\.stories\.tsx?$/;
const COMPONENT_PATTERN = /\/components\/.+\.tsx$/;
// v1.0 is the frozen baseline; new work goes under `<Level> v2.0/`
// (apps/storybook/TAXONOMY.md; Daniel Ochoa's ruling, 2026-10-14).
// A v1.0 story file is never edited (design ruling, 2026-10-15).
// A v1.0 story file is never edited except for a compile-only repair
// (design ruling, 2026-10-15).
const V1_STORY_TITLE = /\btitle:\s*["'`](?:Foundations|Primitives|Composites|Surfaces)\//;
const V1_STORY_TAG = /\btags:\s*\[[^\]]*["'`]v1\.0["'`]/;
// The meta object's title, not a `title:` arg or prop elsewhere in the file.
const STORY_META_START = /\b(?:const\s+meta\b[^=]*=|export\s+default)\s*\{/;
const STORY_META_TITLE = /\btitle:\s*(["'`])((?:(?!\1).)+)\1/;
const STORY_META_TAGS = /\btags:\s*\[([^\]]*)\]/;
const STRING_LITERAL = /(["'`])((?:(?!\1).)*)\1/g;
const NAMED_EXPORT = /\bexport\s+(?:const|let|var|function\*?|class)\s+([\w$]+)/g;
const EXPORT_LIST = /\bexport\s*\{([^}]*)\}/g;
const EXPORT_ALIAS = /\s+as\s+/;
const STORY_PATHSPECS = ["*.stories.ts", "*.stories.tsx"];
// Tests are written only when the person explicitly asks for a pull request
// (Daniel Ochoa's ruling, 2026-10-13); earlier phases may only repair existing
Expand Down Expand Up @@ -165,7 +175,7 @@ const outsideAllowed = changedFiles.filter(
const backendFiles = changedFiles.filter((file) => isBackend(file.path));
const componentsWithoutStories = findComponentsWithoutStories(changedFiles);
const movedStories = findMovedStories();
const v1Stories = findV1Stories(changedFiles);
const { v1Stories, compileOnlyV1Repairs } = findV1Stories(changedFiles);
const retitledStories = findRetitledStories(changedFiles);

const blocking = {
Expand Down Expand Up @@ -204,6 +214,7 @@ const result = {
backendFiles,
componentsWithoutStories,
v1Stories,
compileOnlyV1Repairs,
retitledStories,
};
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
Expand Down Expand Up @@ -358,30 +369,48 @@ function isBackend(filePath) {
}

/**
* Story files whose meta title sits in a v1.0 root or whose tags include v1.0
* where the base had neither, and every modified or moved story file that was
* v1.0 at the base (`frozen`).
* `v1Stories`: story files whose meta title sits in a v1.0 root or whose tags
* include v1.0 where the base had neither, and every modified or moved story
* file that was v1.0 at the base (`frozen`). `compileOnlyV1Repairs`: the
* in-place edits of a v1.0 file that keep its story IDs and tags.
*/
function findV1Stories(files) {
const movedFrom = new Map(movedStories.map((story) => [story.path, story.from]));
return files
.filter((file) => file.status !== "D" && STORY_PATTERN.test(file.path))
.flatMap((file) => {
const source = readWorktreeFile(file.path);
if (source === undefined) {
return [];
}
const from = movedFrom.get(file.path);
const basePath = file.status === "M" ? file.path : from;
const base = basePath === undefined ? undefined : readBaseFile(basePath);
const gained = (pattern) => pattern.test(source) && !(base !== undefined && pattern.test(base));
const reasons = [
...(gained(V1_STORY_TITLE) ? ["title"] : []),
...(gained(V1_STORY_TAG) ? ["tag"] : []),
...(base !== undefined && isV1Story(base) ? ["frozen"] : []),
];
return reasons.length ? [{ ...file, ...(from === undefined ? {} : { from }), reasons }] : [];
});
const v1 = [];
const compileOnly = [];
for (const file of files) {
if (file.status === "D" || !STORY_PATTERN.test(file.path)) {
continue;
}
const source = readWorktreeFile(file.path);
if (source === undefined) {
continue;
}
const from = movedFrom.get(file.path);
const basePath = file.status === "M" ? file.path : from;
const base = basePath === undefined ? undefined : readBaseFile(basePath);
const gained = (pattern) => pattern.test(source) && !(base !== undefined && pattern.test(base));
const reasons = [
...(gained(V1_STORY_TITLE) ? ["title"] : []),
...(gained(V1_STORY_TAG) ? ["tag"] : []),
...(base !== undefined && isV1Story(base) ? ["frozen"] : []),
];
if (reasons.length === 1 && reasons[0] === "frozen" && file.status === "M" && keepsStoryIdentity(base, source)) {
compileOnly.push(file);
} else if (reasons.length) {
v1.push({ ...file, ...(from === undefined ? {} : { from }), reasons });
}
}
return { v1Stories: v1, compileOnlyV1Repairs: compileOnly };
}

/** Same meta title, same meta tags and same exported story names, so the same story IDs. */
function keepsStoryIdentity(base, source) {
return (
storyMetaTitle(base) === storyMetaTitle(source) &&
JSON.stringify(storyMetaTags(base)) === JSON.stringify(storyMetaTags(source)) &&
JSON.stringify(storyExportNames(base)) === JSON.stringify(storyExportNames(source))
);
}

function isV1Story(source) {
Expand Down Expand Up @@ -436,6 +465,25 @@ function storyMetaTitle(source) {
return STORY_META_TITLE.exec(start === -1 ? source : source.slice(start))?.[2] ?? null;
}

function storyMetaTags(source) {
const start = source.search(STORY_META_START);
const tags = STORY_META_TAGS.exec(start === -1 ? source : source.slice(start))?.[1];
return tags === undefined ? null : [...tags.matchAll(STRING_LITERAL)].map((match) => match[2]);
}

function storyExportNames(source) {
const names = new Set([...source.matchAll(NAMED_EXPORT)].map((match) => match[1]));
for (const [, list] of source.matchAll(EXPORT_LIST)) {
for (const entry of list.split(",")) {
const name = entry.trim().split(EXPORT_ALIAS).at(-1);
if (name && name !== "default") {
names.add(name);
}
}
}
return [...names].sort();
}

function readWorktreeFile(filePath) {
const absolute = path.join(worktree, filePath);
return existsSync(absolute) ? readFileSync(absolute, "utf8") : undefined;
Expand Down
Loading
Loading