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
2 changes: 2 additions & 0 deletions .claude/skills/create-thesis-sim-student/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: create-thesis-sim-student
description: Create new repo-local thesis-finder simulation student commands. Use when asked to add, scaffold, draft, or generate a new simulated thesis-finder student persona for Claude and Codex.
metadata:
internal: true
---

# Create Thesis Simulation Student
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/run-thesis-simulations/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: run-thesis-simulations
description: Run and evaluate all repo-local thesis-finder simulation commands. Use when asked to run the thesis simulation suite, evaluate thesis-finder personas, execute all thesis-sim commands, or produce conversation and rating artifacts.
metadata:
internal: true
---

# Run Thesis Simulations
Expand Down
2 changes: 2 additions & 0 deletions .codex/skills/create-thesis-sim-student/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: create-thesis-sim-student
description: Create new repo-local thesis-finder simulation student commands. Use when asked to add, scaffold, draft, or generate a new simulated thesis-finder student persona for Claude and Codex.
metadata:
internal: true
---

# Create Thesis Simulation Student
Expand Down
2 changes: 2 additions & 0 deletions .codex/skills/run-thesis-simulations/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: run-thesis-simulations
description: Run and evaluate all repo-local thesis-finder simulation commands. Use when asked to run the thesis simulation suite, evaluate thesis-finder personas, execute all thesis-sim commands, or produce conversation and rating artifacts.
metadata:
internal: true
---

# Run Thesis Simulations
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codex-multiturn-evals.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
python-version: "3.13"

- name: Install deterministic test dependencies
run: python -m pip install pytest
run: python -m pip install pytest pyyaml

- name: Install optional DeepEval dependency
if: ${{ inputs.run_deepeval }}
Expand Down
17 changes: 11 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,29 @@ This project follows Semantic Versioning for the released skill package:

### Added

- ...
- One-command install for skills-directory clients: `npx skills add
Tue-StudyOS/study-os-thesis --skill '*'` (third-party Vercel `skills` CLI), documented as
Route C Step 0 in INSTALL.md. The release archives and the manual copy route are unchanged.
Pinning a release requires the tag as a URL path (`/tree/skills-vX.Y.Z`); the `repo@tag`
shorthand is accepted but ignored.
- Test: every shipped `SKILL.md` frontmatter must parse under a strict YAML reader.

### Changed

- ...

### Fixed

- ...
- `thesis-finder`'s frontmatter description contained an unquoted `: `, which made it
invalid YAML. Strict parsers — including the `skills` CLI — skipped the entry-point skill
silently, leaving an install that dangles at the first hand-off.
- Repo-local maintainer skills (`create-thesis-sim-student`, `run-thesis-simulations`) are
marked `metadata.internal`, so external installers no longer offer them to students.

### Removed

- ...

### Breaking Changes

- None.

## [2.1.0] - 2026-08-20

**This release adds two new ways to install the same skills.** Until now the only shape was
Expand Down
37 changes: 36 additions & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ outside the skills folder.
| | Route A — Claude app | Route B — ChatGPT, Gemini, or any chat | Route C — Claude Code, Codex, Gemini CLI |
|---|---|---|---|
| For | Claude Pro/Max users | Anyone else, including free ChatGPT and Gemini | People already working in a terminal agent |
| Install | Upload **one zip** | Attach **one file**, paste one text block | Copy **ten folders** into a directory |
| Install | Upload **one zip** | Attach **one file**, paste one text block | One command, or copy **ten folders** into a directory |
| Terminal needed | No | No | Yes |

All three run the same searches and produce the same output. Whichever you pick,
Expand Down Expand Up @@ -93,6 +93,40 @@ works, but nothing is remembered once that chat ends.
they call each other. A partial install fails at the first hand-off. (Routes A and B have no
such trap — everything travels in one artifact.)

## Step 0 — The one-command shortcut (optional)

If you have **Node 22.20 or newer**, one command installs all ten skills into the client you
use — Claude Code, Codex, Cursor, OpenCode and some seventy others:

```bash
npx skills@latest add Tue-StudyOS/study-os-thesis --skill '*' --agent claude-code -g
```

- `--skill '*'` is **not optional**. Without it you are asked to pick from a list, and any
pick short of all ten hits the hand-off trap above.
- `--agent` names your client — `claude-code`, `codex`, `cursor`, … Leave the flag out and it
asks you.
- `-g` installs for your whole account (`~/.claude/skills/`). Drop it and the skills land in
the current folder only (`./.claude/skills/`), which is what you want if you keep one
directory per thesis search.
- The command tracks `main`. To pin a release instead, give the full URL with the tag in it:

```bash
npx skills@latest add https://github.com/Tue-StudyOS/study-os-thesis/tree/skills-v2.1.0 --skill '*' --agent claude-code -g
```

The shorter `repo@tag` form is accepted but the tag is ignored, so you silently get `main`.

Restart your client afterwards and continue at **Run it**.

`skills` is a third-party installer made by Vercel, not part of this project. It downloads
this repository and copies the ten folders from `skills/` into your client's skills
directory — by hand, that is exactly Steps 1 and 2 below, and the result is the same files.
If you would rather not run an unfamiliar installer, or you have no Node, just start at
Step 1.

---

## Step 1 — Get the files

Download the latest release archive from
Expand Down Expand Up @@ -232,6 +266,7 @@ single step is the difference between continuing and starting over.
| **Routes A/B:** it asks the full interview again on a later visit | The chat was not started inside the project/GPT/Gem, or the session file was never added to it | Open a chat inside the container holding your session file and type `thesis-finder` again. If no session file was ever saved, the interview has to be redone. |
| **Route B:** it answers from general knowledge instead of following the instructions | The document was attached but the instructions block was never pasted in | Paste the block from `-----BEGIN INSTRUCTIONS-----` into the container's instructions field. Without it the assistant has no reason to open the document. |
| **Route C:** agent finds *some* skills but breaks partway ("I don't have a skill called `discover-university-candidates`"), or the folder listing looks wrong | Wrong folder level, or a partial install | The path must be `<skills-dir>/thesis-finder/SKILL.md`, **not** `<skills-dir>/study-os-thesis-skills-vX.Y.Z/thesis-finder/SKILL.md`. Move the contents up one level and make sure **all** skill folders are there. |
| **Route C:** `npx skills` refuses to start, or warns `EBADENGINE` | Node is older than 22.20, or missing entirely | Skip Step 0 and install by hand from Step 1 — same files, no Node needed. |
| Results are thin, generic, or the agent says it cannot verify anything | No web access, or search quota exhausted | Confirm web search is enabled in your client and that you have quota left. These skills cannot work offline — everything they output is verified live, by design. |

---
Expand Down
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ what the target client can load, not by preference.
| **ChatGPT, Gemini** — Projects, Custom GPTs, Gems | `thesis-finder-portable-vX.Y.Z.md` + `-instructions-vX.Y.Z.txt` | One document, named sections | Neither loads Agent Skills at all. Their containers hold an instructions box and a handful of files. |
| **Any chat, nothing installed** | the same portable `.md` | Attach and type `thesis-finder` | Works, but nothing is remembered after the chat ends. |

**Or install straight from this repo.** Any client that reads a skills directory can also
skip the release archive and pull `skills/` directly with the third-party
[`skills`](https://github.com/vercel-labs/skills) CLI:

```bash
npx skills@latest add Tue-StudyOS/study-os-thesis --skill '*' --agent claude-code -g
```

It copies the same ten folders into that client's skills directory — the archive route by
other means. Two consequences: `--skill '*'` is required, because a partial pick breaks the
name-based hand-offs, and the CLI installs the **default branch** — pinning a release needs
the tag spelled out as a URL path
(`add https://github.com/Tue-StudyOS/study-os-thesis/tree/skills-v2.1.0`), since the shorter
`repo@tag` form parses but ignores the tag. The CLI parses frontmatter with a strict YAML reader and
silently drops any skill that fails it, so `skills/*/SKILL.md` frontmatter is
`yaml.safe_load`-clean and tested as such.

### Why three and not one

Each client breaks the previous form in a specific way:
Expand Down Expand Up @@ -184,7 +201,7 @@ independent reference list at all and says so to the student at run time.

## Quality gates

Tests are dependency-free (`pytest` only) and run from the repo root:
Tests need only `pytest` and `pyyaml`, and run from the repo root:

```bash
python -m pip install -e ".[dev]"
Expand Down
24 changes: 24 additions & 0 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,30 @@ architecture-tagged. W optional.

## Log

- **2026-08-30** — **One-command install via the Vercel `skills` CLI — and the frontmatter bug
it exposed.** Branch `feat/skills-cli-install`. `npx skills add Tue-StudyOS/study-os-thesis`
already worked against our layout without any change: the CLI scans `skills/<name>/SKILL.md`,
which is what we ship. Running it revealed that it found **11 skills and skipped
`thesis-finder`** — the entry point — with a YAML parse error. Cause: `Supports multi-session
continuity: detects prior searches` in the frontmatter description. An unquoted `: ` inside a
plain scalar is a nested mapping to a strict YAML reader. Claude Code's lenient parser and our
own regex-based bundlers never noticed; `yaml.safe_load` fails on it too. Every external
installer would therefore have handed students exactly the partial install INSTALL.md warns
about. Fixed with an em dash (no quoting, so the bundler regexes are untouched), guarded by a
new strict-YAML test over `skills/`, `.claude/skills/`, and `.codex/skills/` — verified to
fail when the colon is put back. `pyyaml` added to the `dev` extra for it, and the README's
"dependency-free (`pytest` only)" claim corrected. Also marked the two maintainer-only skills
(`create-thesis-sim-student`, `run-thesis-simulations`) `metadata.internal`, which the CLI
honours: the public listing went 12 → 10, and Claude Code still loads them locally. Documented
as Route C **Step 0** in INSTALL.md plus a README paragraph, with the two caveats that matter:
`--skill '*'` is mandatory (partial picks break the hand-offs) and the CLI installs the
**default branch**. The `repo@tag` shorthand parses but silently ignores the tag — caught
by testing the published branch rather than only the local tree; pinning needs the tag as
a URL path (`/tree/skills-v2.1.0`), and refs containing `/` are misparsed there. Verified
end-to-end over the network against the fixed tree: 10 skills, `thesis-finder` present,
no skip warning. Routes A, B, and the manual Route C copy are
untouched — no files moved, no frontmatter quoted, no build script changed.

- **2026-08-20 (d)** — **Task AP: degree programs resolved by rule, not by lookup. The
catalog was already lying.** Branch `feat/degree-program-discovery`, off `main`.

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT"
[project.optional-dependencies]
dev = [
"pytest>=8",
"pyyaml>=6",
]
eval = [
"deepeval",
Expand Down
22 changes: 22 additions & 0 deletions skills/tests/test_skill_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import re
from pathlib import Path

import pytest
import yaml


SKILLS_DIR = Path(__file__).resolve().parents[1]
REPO_ROOT = SKILLS_DIR.parent
Expand Down Expand Up @@ -85,6 +88,25 @@ def test_skill_frontmatter_is_portable_and_trigger_rich() -> None:
assert set(fields) == {"name", "description"}


def test_every_shipped_skill_frontmatter_parses_as_strict_yaml() -> None:
"""External installers (e.g. `npx skills add`) parse frontmatter with a strict YAML
reader and silently skip a skill whose frontmatter fails. An unquoted `: ` inside a
description is enough to drop the entry point from an install."""
skill_files = [skill_dir / "SKILL.md" for skill_dir in _skill_dirs()]
for agent_dir in (REPO_ROOT / ".claude" / "skills", REPO_ROOT / ".codex" / "skills"):
skill_files.extend(sorted(path for path in agent_dir.glob("*/SKILL.md") if not path.parent.is_symlink()))

for skill_md in skill_files:
_, _, rest = skill_md.read_text(encoding="utf-8").partition("---\n")
frontmatter, separator, _ = rest.partition("\n---")
assert separator, f"{skill_md} has no closing frontmatter delimiter"
try:
parsed = yaml.safe_load(frontmatter)
except yaml.YAMLError as error:
raise AssertionError(f"{skill_md} frontmatter is not valid YAML: {error}") from error
assert parsed["name"] and parsed["description"]


def test_referenced_skill_resources_exist() -> None:
reference_pattern = re.compile(r"`(references/[^`]+)`")

Expand Down
2 changes: 1 addition & 1 deletion skills/thesis-finder/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: thesis-finder
description: Single entry point for thesis discovery. Helps a student sharpen what kind of thesis fits them and then find where to write it. Builds the student profile through an inline interview if not yet present, then routes to university chair discovery (find-university-chairs), company thesis discovery (find-company-thesis-options), or both, based on student choice. Records the student's own thesis self-understanding before and after the search. Supports multi-session continuity: detects prior searches and resumes without re-interviewing. Use when a student wants to find where to write their thesis — no prior skill invocation needed.
description: Single entry point for thesis discovery. Helps a student sharpen what kind of thesis fits them and then find where to write it. Builds the student profile through an inline interview if not yet present, then routes to university chair discovery (find-university-chairs), company thesis discovery (find-company-thesis-options), or both, based on student choice. Records the student's own thesis self-understanding before and after the search. Supports multi-session continuity — detects prior searches and resumes without re-interviewing. Use when a student wants to find where to write their thesis — no prior skill invocation needed.
---

# Thesis Finder
Expand Down
Loading