Skip to content

Commit f6b09b9

Browse files
authored
Introduce skill to help answering Perseus history questions (archaeology) (#3775)
## Summary: This PR introduces a skill that basically embeds the knowledge in our [Perseus Github History](https://khanacademy.atlassian.net/wiki/spaces/LC/pages/4370696155/Perseus+GitHub+History) document. It enables an AI agent to answer questions about why Perseus is built the way it is. The skill stitches together the Perseus git history timeline, which spans several repos, so that the agent can authoritatively answer these questions, complete with git commit IDs and pull request details. Although Phabricator was used earlier in Perseus development, this skill does not source Phabricator info into its research. If it finds related Phabricator Diff IDs, it will include them in the response, but the user will need to open those Phabricator urls. <details> <summary> Example usage </summary> ``` ❯ Why does the PerseusDependencies exist and what is it for? ⏺ Skill(archaeology) ⎿  Successfully loaded skill Searched for 3 patterns, read 2 files (ctrl+o to expand) ⏺ I have the current picture. Now let me trace the history. ... a bunch of AI research ... ⏺ I have the full trail. Here's the answer. Short answer PerseusDependencies exists to decouple Perseus from Khan Academy's webapp via dependency injection — it's the bag of host-provided implementations (KaTeX/TeX rendering, logging, JIPT translation, URL helpers, locale/env flags) that Perseus needs but cannot import directly, because those things live in webapp. It was created in 2021–2022 specifically to prepare Perseus for extraction from the webapp monorepo into its own standalone package, so Perseus code could stop reaching into webapp internals and instead receive them from whoever embeds it. That's also why dependencies.ts's error message still says "Make sure Perseus is being imported from javascript/perseus/perseus.js" — a fossil of its webapp home. It comes in two generations: the original PerseusDependencies (a global singleton set via setDependencies, used in non-React contexts) and the newer, preferred PerseusDependenciesV2 (delivered through React's DependenciesContext). The type's own doc-comment now tells you to stop adding to the V1 singleton and shrink its footprint. The trail (newest → oldest) - now · Khan/perseus · packages/perseus/src/dependencies.ts + types.ts:324 — V1 PerseusDependencies singleton (get/setDependencies) coexists with PerseusDependenciesV2 via DependenciesContext; V1's doc-comment warns "avoid adding new dependencies here… it was a quick fix… Aim to shrink the footprint." - 2024-Nov-04 · Khan/perseus · 486e4cd #1793 — "move useVideo to v2 deps" — migrating individual deps off the V1 singleton onto the V2 context, per the shrink-the-footprint goal. - 2023-Jul-06 · Khan/perseus · dd800c2 #600 — "[Analytics] 1. Introduce DependenciesContext" — births PerseusDependenciesV2/the React context as the modern replacement for the global singleton. - 2022-Apr-26 · Khan/perseus · afeec15 #136 — "FEI-3758.0: Copy over perseus-all-package…" — the file arrives in the current repo unchanged; the concept predates this repo entirely. - 2022-Feb-25 · Khan/webapp · a9c6b7e7e9 #3846 — "FEI-4234.2: Create dependencies singleton and use it for KaTeX deps" — establishes the singleton in earnest and adds an ESLint rule blocking katex-package imports inside the perseus packages, explicitly "prevent all imports from webapp inside of the perseus packages we're planning to extract." This is the "why." - 2021-Nov-08 · Khan/webapp · f497b8f9b5 #2136 (FEI-3863) — "Add a 'dependencies' attribute to APIOptions, to prepare the way for dependency injection" — the seed: creates the canonical webapp-dependencies.js (real impl) and test-dependencies.js (test impl) that get injected. Interpretation The two-tier design is a half-finished migration, not an intentional dual API. V1 was, in the author's own words, "a quick fix" for dependencies needed where neither APIOptions nor a React context reach (e.g. module-level singletons like the TeX component and JIPT label stores). V2 (React context) is the destination; deps are being moved over one at a time (e.g. useVideo in #1793), and staticUrl is already @deprecated in favor of PerseusDependenciesV2.generateUrl. So when you encounter it: prefer useDependencies()/PerseusDependenciesV2 for new work, and treat the V1 singleton as legacy surface to drain, not extend. ✻ Worked for 7m 16s · 2 shells still running ⏺ Background command "cd ~/khan/webapp echo "=== birth of webapp-dependencies.js (follow) ===" git -c core.hooksPath=/dev/null log --diff-filter=A --follow --date=short --format='%h %ad %an | %s' --all -- services/static/javascript/perseus/webapp-dependencies.js 2&gt;/dev/null | tail -3 git log --diff-filter=A --follow --date=short --format='%h %ad %an | %s' --all -- services/static/javascript/perseus-all-package/webapp-dependencies.js 2&gt;/dev/null | tail -3 echo echo "=== look for earlier 'perseus-dependencies' or setDependencies introduction ===" git log --all --date=short --format='%h %ad | %s' -S 'setDependencies' 2&gt;/dev/null | tail -5" completed (exit code 0) ⏺ The investigation is complete — that background task's output was already captured and incorporated into the answer above. No further action needed. ✻ Cogitated for 1s · 1 shell still running ⏺ Background command "cd ~/khan/webapp echo "=== prototype #3049 ===" git -c core.hooksPath=/dev/null log --all --oneline --grep='#3049' | head -3 git log --all --date=short --format='%h %ad %an | %s%n%b' -S 'getDependencies' 2&gt;/dev/null | grep -iE '3049|prototype|dependency injection|extract' | head -5" completed (exit code 0) ⏺ Both background tasks have completed and their output was already folded into the answer above. The archaeology is done — see the answer for why PerseusDependencies exists (decoupling Perseus from webapp for extraction) and its V1→V2 migration story. ✻ Churned for 2s ``` </details> Issue: "none" ## Test plan: Try the skill out! Ask it your hardest question! :D Author: jeremywiebe Reviewers: benchristel, Myranae, jeremywiebe, nishasy, mark-fitzgerald, handeyeco, catandthemachines, anakaren-rojas Required Reviewers: Approved By: benchristel, Myranae Checks: ✅ 11 checks were successful, ⏭️ 1 check has been skipped Pull Request URL: #3775
1 parent df41413 commit f6b09b9

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
name: archaeology
3+
description: >
4+
Investigates *why* Perseus code is the way it is by tracing its history across the
5+
several GitHub repos Perseus has lived in over its lifetime (khan-exercises →
6+
Khan/perseus → Khan/webapp → Khan/perseus). Use this skill whenever someone asks a
7+
code-archaeology or "why is it like this" question about Perseus — e.g. "why do we
8+
have both X and Y widgets?", "why is this implemented this way?", "when/why was this
9+
added?", "what's the history behind this file/function?", or hands you a commit, PR,
10+
or file and wants its origins explained. Also triggers on the explicit /archaeology
11+
command. This matters because Perseus's history bridges multiple repo moves (and
12+
predates React), so an ordinary `git blame` in the current checkout dead-ends at the
13+
2022 "initial commit" squash — this skill knows how to bridge those gaps. Do NOT use
14+
it for general web research (use deep-research) or for understanding what current
15+
code *does* (just read the code).
16+
---
17+
18+
# Perseus code archaeology
19+
20+
Perseus code is older than the repo it lives in. To answer "why is this the way it is?"
21+
you almost always have to cross one or more repo boundaries, because Perseus has moved
22+
homes several times and the current repo's history is squashed at the 2022 restart.
23+
24+
This skill is realistically **internal-only** (the repo is public, but Perseus does not
25+
accept external contributions — see `README.md`), so referencing Khan/webapp,
26+
Phabricator, and Jira here is fine.
27+
28+
## The timeline (embedded reference data)
29+
30+
| When | Event | Repo / pointer |
31+
| --- | --- | --- |
32+
| 2011-May-09 | `khan-exercises` prototype born | [`Khan/khan-exercises`](https://github.com/Khan/khan-exercises) |
33+
| 2013-Jan-25 | **Perseus** born | [`Khan/perseus`](https://github.com/Khan/perseus) |
34+
| 2018-Jan-04 | Perseus **moves into webapp** | [`Khan/webapp`](https://github.com/Khan/webapp) · `c4cc9587` |
35+
| 2018-Jan-19 | Last commit of old Perseus, tagged **`old-master`** | `Khan/perseus` · `0e82a089` |
36+
| 2018-Dec-04 | `khan-exercises` abandoned (last commit) | `Khan/khan-exercises` · `9cd98eef` |
37+
| 2022-Apr-20 | Perseus **restarts** in its own repo (current `main`) | `Khan/perseus` · `cb841024` "initial commit" |
38+
| 2022-May-25 | Perseus **fully removed from webapp** | `Khan/webapp` · `3a2d65d3` |
39+
40+
Source doc (humans, internal): [Perseus GitHub History](https://khanacademy.atlassian.net/wiki/spaces/LC/pages/4370696155/Perseus+GitHub+History).
41+
42+
### What this means in practice
43+
- The **current repo's history dead-ends** at `cb841024 "initial commit"`.
44+
`git log`/`git blame` in this checkout will not take you earlier than that.
45+
- The **`old-master` tag** (commit `0e82a08991`) is your gateway to the
46+
2013–2018 Perseus history. **Start most investigations from here** — most
47+
patterns were already established before the webapp move, so you can often
48+
skip the webapp era entirely.
49+
- The **2018–2022 webapp era** is a real gap the current repo can't see. Use
50+
the local `~/khan/webapp` repo (or `gh`) when the trail runs through those
51+
years.
52+
53+
## Repo access strategy
54+
55+
Prefer **local clones** at the canonical path `$HOME/khan/<repo>`; fall back to `gh` /
56+
the GitHub API when a repo isn't cloned.
57+
58+
Likely already present locally: `perseus` (this repo) and `webapp`. If `webapp` is missing, stop and ask the user to clone it! The legacy repos like `khan-exercises`, `mathquill`, `ka-perseus-graphie`, and `live-editor` are rarely cloned locally.
59+
60+
When a clone would meaningfully speed up the dig (e.g. deep blame-walking a
61+
sibling repo) and it isn't present, **offer to clone it** to the canonical location and,
62+
if the user agrees, **always use `ka-clone`**:
63+
64+
```bash
65+
ka-clone <repo> ~/khan/<repo> # bare name → git@github.com:Khan/<repo>.git
66+
```
67+
68+
(`ka-clone` is a `git clone` wrapper that configures the KA repo; it's on PATH.)
69+
70+
## The procedure
71+
72+
Work **newest → oldest**, crossing repo boundaries only as far back as the question needs.
73+
74+
1. **Locate the code in the current repo.** Find the file/symbol/widget. Note
75+
the current path, e.g. `packages/perseus/src/widgets/<name>/`. Read for
76+
explicit history signals: `@deprecated`, "legacy", and ticket refs
77+
(`LEMS-####`, `FEI-####`, `LP-####`).
78+
79+
2. **Walk the current repo** with `git log`/`git blame` until you hit the
80+
`cb841024 "initial commit"` floor. If you don't have your answer yet,
81+
look in the webapp git history.
82+
```
83+
84+
3. **Walk the webapp era (2018–2022)** if the trail runs through those
85+
years. In webapp, Perseus lived under `javascript/perseus-all-package/...`
86+
(and earlier `src/widgets/...`, later also `services/static/javascript/perseus-all-package/...`). Discover the historical path, then log within the
87+
era:
88+
```bash
89+
git -C ~/khan/webapp log --all --pretty=format: --name-only --diff-filter=AM \
90+
| grep -iE '<file>\.jsx?$' | sort -u
91+
git -C ~/khan/webapp log --all --date=short --format='%h %ad | %s' \
92+
--since=2018-01-01 --until=2022-06-01 -- '<historical-path>'
93+
```
94+
webapp PRs (`#NNNN`) are reachable via `gh pr view <n> --repo Khan/webapp`.
95+
96+
4. **Walk the `old-master` era** for the 2013–2018 Perseus history (the most
97+
common destination). Files are `.jsx`/`.js` here — **transpose extensions**
98+
(`.tsx``.jsx`, `.ts``.js`). Find when the file(s) originated and when
99+
specific lines changed:
100+
```bash
101+
git log --diff-filter=A --follow --date=short --format='%h %ad %an | %s' \
102+
old-master -- src/widgets/<name>.jsx # birth (follows renames)
103+
git log -S '<exact code string>' --date=short --format='%h %ad %an | %s' \
104+
old-master -- src/widgets/<name>.jsx # when a line appeared/changed
105+
```
106+
Watch for **renames**`--follow` traces through them, and the commit that
107+
"adds" a file is often a rename ("Rename X to Y", "Create Z (clone of W)").
108+
Re-run without `--follow` to see the true add vs. a rename.
109+
110+
5. **Check khan-exercises** if the trail points further back (Perseus predates
111+
React; many concepts came from `khan-exercises`). Look for a similarly named
112+
file/concept:
113+
```bash
114+
ls ~/khan/khan-exercises/utils/ | grep -i <concept>
115+
```
116+
117+
6. **Land on the originating change** and surface its review link (see Phabricator
118+
below). Extract whatever rationale the commit body / PR / Diff holds.
119+
120+
### Phabricator (pre-GitHub code review)
121+
Commits from roughly 2013–2017 may carry, in the body:
122+
`Differential Revision: http://phabricator.khanacademy.org/D####`, or
123+
`Reviewers:` / `Auditors:` / `Test Plan:` fields. There may also be code
124+
comments that reference these Phabricator diffs in a `D#####` format.
125+
Phabricator is still up at `https://phabricator.khanacademy.org/` but requires
126+
**a login** (new employees likely lack access). **Do not try to fetch it.**
127+
Instead, surface the `D####` URL in the answer and mine the commit body itself
128+
for the Summary / Test Plan / reviewers. Let the user decide whether to open
129+
it.
130+
131+
### Jira / Confluence enrichment (best-effort, internal)
132+
Git/GitHub is the core method and always works. If a commit/PR references a
133+
ticket (`LEMS-####`, `FEI-####`, `LP-####`) and the Atlassian MCP is available,
134+
look it up for the original motivation; otherwise just include the ID/URL in
135+
the answer. Slack rarely helps — most of this history predates KA's Slack use.
136+
137+
## Sibling packages — origin repos
138+
139+
Originally, many packages in the Perseus "ecosystem" were in their own repo. In
140+
2022, these packages were moved into the Perseus repo and it became a monorepo
141+
to ease development and testing. To trace their pre-monorepo history, dig into
142+
the origin repo (clone via `ka-clone` if needed). The origin repos were
143+
archived after they were moved, so their history is frozen today.
144+
145+
| Package | Origin repo | Notes |
146+
| --- | --- | --- |
147+
| `@khanacademy/kas` | [`Khan/KAS`](https://github.com/Khan/KAS) (archived) | Lightweight JS CAS for comparing expressions; Moved into Khan/perseus in `#173` (2022-May-03). |
148+
| `@khanacademy/kmath` | [`Khan/kmath`](https://github.com/Khan/kmath) (archived) | JS numeric math utils; Moved into Khan/perseus in `#126` (2022-Apr-25). |
149+
| `@khanacademy/math-input` | [`Khan/math-input`](https://github.com/Khan/math-input) (archived) | react + redux + mathquill keypad; Imported 2022-Apr-28 at math-input commit `c49d43a1`. |
150+
| `@khanacademy/simple-markdown` | [`Khan/simple-markdown`](https://github.com/Khan/simple-markdown) (public) | Standalone markdown parser; Moved into Khan/perseus in `#180` (2022-May-03). |
151+
| (dep) mathquill | [`Khan/mathquill`](https://github.com/Khan/mathquill) | KA fork of `mathquill/mathquill`; a dependency of `math-input`, not a package. |
152+
| graphie-to-png | [`Khan/internal-services`](https://github.com/Khan/internal-services) (`graphie-to-png/`) | A website for building graphie images (a multi-file image format that combines a base SVG with a JSON data file that defines overlay labels that can be translated). There is a copy of the graphie renderer code in this repo. Earlier graphie drawing code also appears in [`Khan/khan-exercises`](https://github.com/Khan/khan-exercises) (`utils/graphie*.js`). |
153+
| `perseus-core`, `perseus-editor`, `perseus-linter`, `perseus-score`, `perseus-utils`, `pure-markdown`, `keypad-context` | (none) | Split out *within* the monorepo from `perseus`; no pre-monorepo repo. Trace them in this repo. |
154+
155+
**Discovery recipe** for a package not in the table, first ASK for help, then
156+
try:
157+
```bash
158+
gh repo view Khan/<name> --json name,isArchived,createdAt # try casing variants, e.g. KAS
159+
git log --diff-filter=A --date=short --format='%h %ad | %s' \
160+
-- packages/<pkg>/package.json | tail -1 # import commit msg often names the source repo/SHA
161+
```
162+
163+
## Output format
164+
165+
Lead with the **answer**, then show the **trail** if it is relevant to the
166+
answer, then any **interpretation**.
167+
168+
1. **Short answer** — the "why", in a sentence or two. State up front if the
169+
premise is off (e.g. a misremembered widget name).
170+
2. **The trail** — newest → oldest, important commits that impact the answer.
171+
Give every entry the same shape:
172+
`<when> · <repo> · <SHA> <ref> — "<commit msg>" — <synthesis>`
173+
- `<when>`: an unambiguous date (`2018-Jun-20`, never a 2-digit numeric
174+
month) or era label (`now`, `khan-exercises`).
175+
- `<repo>`: name it on *every* line. A repo boundary is then just the line
176+
where the repo changes — no separate "crossing" entry needed.
177+
- `<SHA>`: short hash. Omit only on the `now` line, which points at live
178+
code via `path:line` instead.
179+
- `<ref>`: `#1234` (PR) or `D####` (Phabricator Diff). If neither exists,
180+
omit it (and its surrounding separators) — don't leave a dangling dash.
181+
- `<commit msg>`: the one-line commit subject, verbatim.
182+
- `<synthesis>`: a short gloss of what this commit actually did *in relation
183+
to the question* — not a restatement of the message.
184+
Include an era only if it bears on the answer — never pad with "no change
185+
during this era". Omission is the signal that nothing relevant happened.
186+
3. **Interpretation (unverified)** — when the record runs cold (a terse commit, a
187+
token-gated Diff, no rationale), offer a clearly-labeled best interpretation reasoned
188+
from the code and the era. Keep verified facts and inference visibly separate.
189+
190+
### Skeletal trace (shape, not a script)
191+
```
192+
Answer: <X> does <ABC> because <Y>.
193+
194+
Trail (newest → oldest; one line each; omit eras that don't bear on the answer):
195+
• now · Khan/perseus · path:line — <signal in current code>
196+
• 2021-Mar-09 · Khan/webapp · <SHA> #12345 — "<commit msg>" — <synthesis re: the question>
197+
• 2018-Jun-20 · Khan/perseus · <SHA> D45678 — "<commit msg>" — <synthesis re: the question>
198+
• 2014-Nov-02 · khan-exercises · <SHA> — "<commit msg>" — <synthesis re: the question>
199+
200+
Interpretation (unverified): <reasoning where evidence ran out>
201+
```

.changeset/beige-cobras-kneel.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

0 commit comments

Comments
 (0)