Skip to content

feat(skills): carry devfive-frontend and let a skill be more than one file - #63

Merged
owjs3901 merged 3 commits into
mainfrom
feat/multi-document-skills
Sep 15, 2026
Merged

owjs3901 merged 3 commits into
mainfrom
feat/multi-document-skills

Conversation

@owjs3901

Copy link
Copy Markdown
Contributor

Why

devup_figma_export returns one screen's TSX, but a project is a tree of routes, and knowing devup-ui does not tell an agent which file that TSX becomes.

The gap is not hypothetical. A vinext project created from service-template was implemented as a Vite SPA — a main.tsx swapping screens on local state, with authored .css files beside code that has globalCss() — by an agent that had been given the devup-ui skill and nothing else.

devup-ui is a styling skill and correctly says nothing about project structure. The rules that would have caught it live in devfive-frontend, which no bare machine had. And devfive-frontend is five files, so carrying it meant a skill could no longer be one document.

What changed

A third origin: own

origin source of truth pins a commit refresh-skills
embedded another DevFive repo yes fetches and moves it forward
own (new) this repository no reseals the manifest from disk
external someone else's n/a reports the install command only

An own skill pins no commit because it has no second copy to drift from — printing at unknown would read as a lost revision rather than one that never existed. Making it an origin rather than a special case keeps both carried origins on the same install path.

Multi-document skills

  • manifest.json records documents[{path, bytes, sha256}] per skill instead of one digest per skill, so a five-document skill whose fourth reference was edited fails on that reference, naming it.
  • Layout is now skills/<name>/<relpath> for every origin, so a document's source path and its install path are the same string and cannot disagree. The three existing skills were moved with git mv (0 content change).
  • install() stages a skill's documents in one OutputTransaction. A SKILL.md that survived while its four references did not is the failure this exists to avoid: it looks installed and its links go nowhere.
  • Only the entry document is annotated with provenance, so the manifest digest stays true of every reference that lands on disk.

Two things this surfaced

.gitattributes silently stopped applying. It matched skills/*.md, and a git pattern containing a slash does not let * cross one. Moving the documents into per-skill directories dropped the -text attribute that the file's own comment says exists to stop a Windows checkout from failing the integrity check on that platform alone. Now skills/**/*.md, confirmed with git check-attr.

refresh-skills.mjs had no way to reseal an own skill. It skipped them entirely, so editing a document this repository authors broke the integrity test with no tool offering to fix it — the trap that makes someone write a SHA-256 by hand. For an own skill the direction reverses: the file on disk is the truth and the script reseals the manifest from it. Demonstrated:

~ devfive-frontend/SKILL.md: 13130 -> 18354 bytes   → exit 1
Reconciled 1 document(s) and rewrote the manifest    → exit 0

Skill content

devfive-frontend gained the two rules the SPA incident needed:

  • vite.config.ts does not mean Vite. vinext runs Next App Router on Vite, so a normal App Router project has a vite.config.ts and no next.config.ts. Read package.json. There is no main.tsx and no index.html in such a project.
  • No .css/.scss in application source. resetCss() + globalCss() + style props, with the @devup-ui/reset-css build recipe (include / optimizeDeps.exclude / ssr.noExternal) that previously had to be reverse-engineered out of node_modules.

It also records the extraction rule from dev-five-git/devup-ui#663, checked against the extractor rather than assumed (extract_style_from_member_expression.rs):

mem.object result
ArrayExpression / ObjectExpression (inline literal) static classes
Identifier (external object by name) dynamic_style() → CSS variable

…and the exception that resolves an internal contradiction in the skill: an external object of css() results is fine, because css() already extracted at its own call site and className is never a style-extraction source (it appears nowhere in extract_style_from_jsx.rs; prop_modify_utils.rs only merges it).

Tests

The integration tests spelled out how many skills exist, so adding one failed four tests that had nothing to say about it. They derive the counts from the report now. Added coverage for the multi-document case: every reference SKILL.md links to must be a file the install writes.

Verification

gate result
cargo clippy -p devup-mcp --all-targets --all-features -- -D warnings exit 0, 0 warnings
cargo test -p devup-mcp --all-features exit 0, 0 failures
node scripts/refresh-skills.mjs --check exit 0
git check-attr text on all three depths text: unset

Follow-up (not in this PR)

Fetch-first with embedded fallback for devup_skills install. Two design points to settle first: the provenance note a fetched document carries (it has no commit), and whether the CLI path (--install-skills, which writes to $HOME) unifies with the MCP path (which writes project-locally). Keeping the CLI contract unchanged is the smaller correct change.

… file

devup_figma_export returns one screen's TSX, but a project is a tree of
routes, and knowing devup-ui does not tell an agent which file that TSX
becomes.

The gap is not hypothetical. A vinext project created from service-template
was implemented as a Vite SPA - a main.tsx swapping screens on local state,
with authored CSS files beside code that has globalCss() - by an agent that
had been given the devup-ui skill and nothing else. devup-ui is a styling
skill and correctly says nothing about project structure, so the rules that
would have caught it live in devfive-frontend, which no bare machine had.

devfive-frontend is five files, so carrying it meant a skill could no longer
be one document.

A third origin carries it. An `embedded` skill is copied from another DevFive
repository and pins the commit it copied, so refresh-skills.mjs can move it
forward. An `own` skill is authored here, has no second copy to drift from,
and pins no commit - printing "at unknown" would read as a lost revision
rather than one that never existed. Making that an origin rather than a
special case keeps both on the same install path.

The manifest now records documents[{path,bytes,sha256}] per skill instead of
one digest per skill, and every origin uses skills/<name>/<relpath>, so a
document's source path and its install path are the same string and cannot
disagree. install() stages a skill's documents in one OutputTransaction: a
SKILL.md that survived while its four references did not is the failure this
exists to avoid, because it looks installed and its links go nowhere. Only
the entry document is annotated, so the manifest digest stays true of every
reference that lands on disk.

Two things surfaced while building it.

.gitattributes matched skills/*.md, and a git pattern containing a slash does
not let * cross one. Moving the documents into per-skill directories silently
dropped the -text attribute that the file's own comment says exists to stop a
Windows checkout from failing the integrity check on that platform alone. It
now matches skills/**/*.md, confirmed with git check-attr.

refresh-skills.mjs skipped own skills entirely, which left no way to update a
digest after editing a document this repository authors - the trap that makes
someone write a SHA-256 by hand. For an own skill the direction reverses: the
file on disk is the truth and the script reseals the manifest from it.

The integration tests spelled out how many skills exist, so adding one failed
four tests that had nothing to say about it. They derive the counts now.

devfive-frontend also gained the two rules the SPA incident needed - that
vite.config.ts does not mean Vite, because vinext runs Next App Router on
Vite, and that no .css or .scss belongs in application source - plus the
extraction rule from devup-ui issue 663, checked against the extractor
itself: an inline object literal indexed at a style prop extracts to static
classes, an external object referenced by name becomes a CSS variable, and an
external object of css() results is neither, because css() already extracted
at its own call site and className is never a style-extraction source.
#64)

A binary's skill copy ages independently of the repository it describes.
Installing that copy without saying where it came from leaves the agent
applying old rules with no reason to question them. Fetch current documents
on an explicit MCP install, but keep offline delivery a first-class path:
the bare machine this feature exists for is also where a download is least
likely to work.

Reuse the CLI's ETag-aware upstream through a URL-taking trait. MCP URLs
come from each manifest repository and document path; the CLI still fetches
devup-ui and writes its existing HOME targets. A four-second budget covers
all fetching in one MCP install, and every write still goes through the
server's OutputPolicy and one OutputTransaction. If any reference fails,
use the whole embedded skill rather than mixing documents from two copies.

Report fetched or embedded for every installed skill, with a fallback
reason. A 404 additionally warns that the manifest may point at a moved
file. Fetched documents have no pinned commit, so their entry note reuses
the CLI provenance fields: URL, ETag, fetched time and SHA-256 of upstream
bytes. Keep that note after frontmatter so the skill loader still sees it.
Own skills stay in the binary; external skills are never fetched or written.

Read DEVUP_MCP_SKILLS_OFFLINE once and leave HTTP initialization on the
install path, unreachable from self-check, status or resource reads. Run
MCP integration tests in child processes with offline configuration set
before startup. Injected upstreams cover fresh documents, every fetch
failure, 404 warnings, nested references, the shared deadline, and rollback
without opening a socket. Preserve all four CLI behavior tests.
@owjs3901
owjs3901 merged commit 9441c51 into main Sep 15, 2026
8 checks passed
@owjs3901
owjs3901 deleted the feat/multi-document-skills branch September 15, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant