Skip to content

feat(skills): prefer current upstream documents with embedded fallback - #64

Merged
owjs3901 merged 1 commit into
feat/multi-document-skillsfrom
owjs3901/skills-fetch-first
Sep 15, 2026
Merged

owjs3901 merged 1 commit into
feat/multi-document-skillsfrom
owjs3901/skills-fetch-first

Conversation

@owjs3901

Copy link
Copy Markdown
Contributor

devup_skills install previously always wrote the copy compiled into the binary, even when the upstream skill had changed. It now prefers current documents from manifest-derived raw GitHub URLs and falls back to the complete embedded skill when any document cannot be fetched. Every installed skill reports source: fetched | embedded and a reason for using the binary; a 404 also warns that the manifest path may have moved.

This PR stacks on #63 and targets feat/multi-document-skills.

The existing CLI ETag-aware fetcher now accepts a URL. MCP fetching has one four-second budget for the entire call, while writes retain the server's project-local OutputPolicy and atomic transaction. Fetched entry documents record URL, ETag, fetch time and SHA-256 instead of claiming the vendored commit, with the note placed after YAML frontmatter. Own skills remain binary-only; external skills are never fetched or written. The CLI's devup-ui/HOME install contract is unchanged.

DEVUP_MCP_SKILLS_OFFLINE=1 disables fetching and is read once. Integration tests set it in child servers before startup; injected upstreams test fetched documents, all four error classes, 404 warnings, nested references, the shared deadline and rollback without network access. Self-check, status and resource reads cannot enter the fetch path. Includes the required changepack and README/tool-output updates.

Local Windows verification, actual final exit codes:

Command Exit
cargo metadata --locked --format-version 1 0
cargo fmt --all -- --check 0
node --test crates/devup-mcp-figma/tests/explore_script_behavior.mjs 0
cargo test --locked -p devup-mcp --test stdio_smoke 0
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings 0
cargo test -p devup-mcp --all-features 0
cargo insta test --workspace --all-features --check 0
cargo build --locked --workspace --release 0
python .github/scripts/test_release_lock.py 0

The all-features devup-mcp run passed 579 tests; all four existing CLI tests remain meaningful and pass. The workspace snapshot check reported no snapshots to review. Independent code review found no critical or important issues.

Initial broad verification attempts exhausted local disk space (OS error 112). After cleaning only this run's Cargo artifacts, Clippy and the devup-mcp tests passed with CARGO_PROFILE_DEV_DEBUG=0, CARGO_PROFILE_TEST_DEBUG=0, and CARGO_INCREMENTAL=0. The final workspace snapshot check and release build additionally used RUSTFLAGS=-C link-arg=/DEBUG:NONE to avoid Windows linker symbol files. No tracked build configuration was changed. Linux-only CI steps that install packages were not run locally.

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 8d66643 into feat/multi-document-skills Sep 15, 2026
8 checks passed
@owjs3901
owjs3901 deleted the owjs3901/skills-fetch-first branch September 15, 2026 00:29
owjs3901 added a commit that referenced this pull request Sep 15, 2026
… file (#63)

* feat(skills): carry devfive-frontend and let a skill be more than one 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.

* style: apply cargo fmt

* feat(skills): prefer current upstream documents with embedded fallback (#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.
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