434 Commits

Author SHA1 Message Date
Vijay Janapa Reddi
713d719c3f merge origin/dev into yaml-audit
Brings in the dev-side prose / bib / math fixes that landed since the
yaml-audit branch was cut, and resolves three small conflicts:

* interviews/vault-cli/scripts/archive/split_corpus.py
    origin/dev deleted it (archive cleanup); we honor the deletion.
* interviews/vault-cli/scripts/validate_drafts.py
    origin/dev removed a leftover no-op statement; took theirs.
* interviews/vault-cli/scripts/summarize_proposed_chains.py
    origin/dev renamed loop var lvl→level; took theirs.

The two protected qmds (data_selection.qmd, model_compression.qmd)
are temp-stashed before the merge to honor the 'do not touch' rule;
restored after the merge commit lands.

After this commit, yaml-audit contains every commit on origin/dev as
an ancestor, so dev can fast-forward to yaml-audit's tip when the
maintainer is ready to merge.
2026-05-05 10:03:14 -04:00
Vijay Janapa Reddi
9aa6fefc9c fix(staffml,contribute): unique React keys in topic datalist
The /contribute page's topic datalist mapped allTopics with key={t.id},
but topic ids appear in multiple competency areas (54 topics shared
across 2-11 areas, e.g. 'mlops-lifecycle' spans 11 areas). Each
duplicate triggered the React 'two children with the same key' warning
— 326 of them per page load.

Fix: namespace the key by area, key={`${t.area}::${t.id}`}. The
'value' attribute stays as t.id since that's what the user picks.

Verified by walkthrough script: /contribute now renders with zero
console errors, like the other 18 routes.
2026-05-05 10:00:46 -04:00
Vijay Janapa Reddi
312f00eeca fix(staffml): napkin-math renderer polish
Three small renderer fixes that came out of inspecting how the
audit-corrected YAML content lands on /practice/?q=...:

1. Strip the redundant 'Conclusion & Interpretation:' / 'Result:'
   prefixes from result steps. The green callout already signals
   'this is the conclusion'; leaving the labels in produces noise
   like 'Conclusion & Interpretation: Result: Memory-Bound. ...'.
   Handles bold, unbold, and bold-wrapping-the-whole-phrase forms.

2. Teach the number-and-unit highlighter about scientific notation
   (Ne12, 1.2×10^14) so phrases like '120e12 FLOPs' render as a
   single number+unit chunk instead of '120' (bold) + 'e12' (plain)
   + 'FLOPs' (gray). Also broaden the unit vocabulary to include
   Hz/MHz/GHz, W/mW/μW/mJ/μJ/J, MACs, cycles, frames, samples, and
   common compound rates (FLOPs/byte, FLOP/cycle, etc.).

3. Distinguish a *section header* line ('**Conclusion & Interpretation:**'
   alone on its line) from a *result* line. Previously the parser
   marked the header as isResult=true, which then rendered an empty
   green callout because cleanStepText stripped the header to ''.
   Filter empty steps after cleaning as a belt-and-braces.

Verified across 10 sample questions covering different tracks
(cloud/edge/mobile/tinyml) and napkin-math shapes (sci notation,
multi-section structured, quantization-with-code, compute-bound,
memory-bound, I/O-bound). No regressions; the result blocks now
read directly with the verdict, not the section label.
2026-05-05 09:53:06 -04:00
Vijay Janapa Reddi
edcdba08da docs(staffml,vault-cli): document the local-dev corpus pipeline
Add interviews/staffml/README.md covering the local development
workflow that the prior commit's predev hook relies on:

- TL;DR install + run-dev steps
- explanation of the production-worker vs local-static data flow
- what the predev hook does (sync-periodic-table + vault build --local)
- env vars (NEXT_PUBLIC_VAULT_FALLBACK, NEXT_PUBLIC_VAULT_API,
  STAFFML_SKIP_LOCAL_CORPUS) and their effects
- troubleshooting the three failure modes that bit us during the YAML
  audit work (could-not-load, stale content, infinite loading)

Update interviews/vault-cli/README.md to surface `vault build --local`
in the Local-dev section with a pointer to the StaffML README.

The intent: a contributor who edits a YAML and doesn't see the change
in the dev server should now find the answer in the README before
they're forced to read the loader source.
2026-05-05 09:33:43 -04:00
Vijay Janapa Reddi
c7b42e41d8 fix(dev): make npm run dev serve full question content from local YAMLs
Before this change, the StaffML Next.js dev server fetched scenario and
details (including napkin_math) from the production Cloudflare Worker
even when contributors had local YAML edits — so changes weren't visible
without shipping. The opt-in static-fallback path existed but was wired
incorrectly: getStaticFullDetail used a Function-constructor dynamic
import of ../data/corpus.json, which Turbopack rewrote to a non-existent
/_next/static/data/corpus.json URL and 404'd at runtime.

Fix in three parts:

1. Loader (interviews/staffml/src/lib/corpus.ts): replace the broken
   dynamic import with fetch('/data/corpus.json'). On failure, throw a
   clear error pointing at `vault build --local`.

2. Build (interviews/vault-cli/src/vault_cli/commands/build.py): mirror
   the generated corpus.json into interviews/staffml/public/data/ so
   Next serves it as a static asset. Add --local as a clearer alias for
   --local-json and update the help text to spell out the dev workflow.

3. Wiring (interviews/staffml/package.json + scripts/build-local-corpus.mjs):
   predev now runs `vault build --local` automatically, with a soft-fail
   path if the vault CLI isn't installed (so first-time contributors
   still get a working dev server, just with the worker fallback). The
   committed .env.development sets NEXT_PUBLIC_VAULT_FALLBACK=static so
   the static path is the default in dev. Both copies of corpus.json are
   gitignored as build artifacts (the YAMLs are the source of truth).
2026-05-05 09:30:57 -04:00
Vijay Janapa Reddi
6bda543a33 chore(paper, staffml): refresh artifacts from vault 1.0.0
vault export-paper 1.0.0 regenerated paper/macros.tex and
paper/corpus_stats_export.json against the 1.0.0 release vault.db.
vault build --local-json refreshed staffml/src/data/{corpus-summary,
vault-manifest}.json. Numbers reflect the post-Phase-5 corpus state
(9,446 published, 89 topics, 843 chains, 30.1% chain coverage).
2026-05-04 08:52:01 -04:00
Vijay Janapa Reddi
be0408e28d fix(staffml): replace removed lucide-react Github icon with inline SVG
lucide-react v1.0 removed all brand icons (Github, Twitter, Facebook,
etc.) for trademark reasons, so the bundled Github symbol is no longer
exported. Add a local GithubIcon component using the standard GitHub
mark, bump lucide-react to ^1.14.0, and update the four consumers.

Closes #1667.
2026-05-04 08:30:19 -04:00
dependabot[bot]
23e8816269 deps(staffml): bump react-medium-image-zoom (#1650)
Bumps the next-react group with 1 update in the /interviews/staffml directory: [react-medium-image-zoom](https://github.com/rpearce/react-medium-image-zoom).


Updates `react-medium-image-zoom` from 5.4.3 to 5.4.5
- [Release notes](https://github.com/rpearce/react-medium-image-zoom/releases)
- [Changelog](https://github.com/rpearce/react-medium-image-zoom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rpearce/react-medium-image-zoom/compare/v5.4.3...v5.4.5)

---
updated-dependencies:
- dependency-name: react-medium-image-zoom
  dependency-version: 5.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: next-react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 08:18:06 -04:00
dependabot[bot]
1e59026cf9 deps(staffml-worker): bump wrangler in /interviews/staffml/worker (#1644)
Bumps [wrangler](https://github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler) from 4.85.0 to 4.87.0.
- [Release notes](https://github.com/cloudflare/workers-sdk/releases)
- [Commits](https://github.com/cloudflare/workers-sdk/commits/wrangler@4.87.0/packages/wrangler)

---
updated-dependencies:
- dependency-name: wrangler
  dependency-version: 4.87.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 07:18:59 -04:00
dependabot[bot]
b5e4bfb2d8 deps(staffml-worker): bump @cloudflare/workers-types (#1655)
Bumps [@cloudflare/workers-types](https://github.com/cloudflare/workerd) from 4.20260426.1 to 4.20260504.1.
- [Release notes](https://github.com/cloudflare/workerd/releases)
- [Changelog](https://github.com/cloudflare/workerd/blob/main/RELEASE.md)
- [Commits](https://github.com/cloudflare/workerd/commits)

---
updated-dependencies:
- dependency-name: "@cloudflare/workers-types"
  dependency-version: 4.20260504.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 07:18:28 -04:00
dependabot[bot]
3a6d5bfe0f deps(staffml): bump eslint from 10.2.1 to 10.3.0 in /interviews/staffml (#1657)
Bumps [eslint](https://github.com/eslint/eslint) from 10.2.1 to 10.3.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.2.1...v10.3.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 07:18:24 -04:00
dependabot[bot]
1f9ddb516b deps(staffml): bump jsdom from 29.1.0 to 29.1.1 in /interviews/staffml (#1670)
Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.1.0 to 29.1.1.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](https://github.com/jsdom/jsdom/compare/v29.1.0...v29.1.1)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 07:18:02 -04:00
dependabot[bot]
cd4fc7d948 deps(staffml): bump sigma from 3.0.2 to 3.0.3 in /interviews/staffml (#1671)
Bumps [sigma](https://github.com/jacomyal/sigma.js) from 3.0.2 to 3.0.3.
- [Release notes](https://github.com/jacomyal/sigma.js/releases)
- [Changelog](https://github.com/jacomyal/sigma.js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jacomyal/sigma.js/compare/sigma@3.0.2...sigma@3.0.3)

---
updated-dependencies:
- dependency-name: sigma
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-04 07:17:59 -04:00
Vijay Janapa Reddi
a74c98576e Merge origin/dev into yaml-audit
Sync the yaml-audit branch with the latest dev work since the previous
sync (5c5af75ed). Brings in 73 commits including:

  - CI security fixes: postcss XSS bump, uuid bounds bump, codeql
    paths-ignore for vendored bundles, read-only token on
    staffml-validate-vault workflow
  - kits/ dark mode polish: code-block readability, dropdown contrast
  - vault-cli/: pre-commit ruff hook + 20 ruff fixes, all-contributors
    auto-credit workflow change to pull_request_target
  - dev's earlier merge of yaml-audit (836d481b5) carrying the
    pre-trailer-strip Phase 1/2/3 history; this merge harmonises that
    with the current trailer-clean yaml-audit tip
  - misc bug fixes (tinytorch perceptron seed, infra workflows,
    socratiq vite dev injector)

Conflicts resolved (if any) preserve the yaml-audit-side authoritative
state for vault/* files (we own those) and the dev-side authoritative
state for .github/workflows/* and other shared infrastructure.

# Conflicts:
#	.github/workflows/all-contributors-auto-credit.yml
#	.github/workflows/staffml-preview-dev.yml
#	interviews/staffml/src/data/corpus-summary.json
#	interviews/staffml/src/data/vault-manifest.json
#	interviews/staffml/tests/chain-and-vault-smoke.mjs
#	interviews/vault-cli/README.md
#	interviews/vault-cli/docs/CHAIN_ROADMAP.md
#	interviews/vault-cli/scripts/build_chains_with_gemini.py
#	interviews/vault-cli/scripts/generate_question_for_gap.py
#	interviews/vault-cli/scripts/merge_chain_passes.py
#	interviews/vault-cli/scripts/validate_drafts.py
#	interviews/vault-cli/src/vault_cli/legacy_export.py
#	interviews/vault-cli/tests/test_chain_validation.py
#	interviews/vault/.gitignore
#	interviews/vault/ARCHITECTURE.md
#	interviews/vault/chains.json
#	interviews/vault/id-registry.yaml
#	interviews/vault/questions/edge/optimization/edge-2536.yaml
#	interviews/vault/questions/mobile/deployment/mobile-2147.yaml
#	tinytorch/src/03_layers/03_layers.py
2026-05-02 11:06:43 -04:00
Vijay Janapa Reddi
924363e2b7 feat(vault): Phase 3 batch — 6 questions published + chain rebuild
Second Phase 3 batch run (post-pre-filter and post-tightened-validator).
30 gaps fed in; 21 dropped by the gap pre-filter as hallucinated; 9
generated drafts; 6 cleared all gates and were published; 3 dropped on
level_fit (level inflation pattern).

Published (status=published, human_reviewed=verified by vj, all gates
pass + audit_math pass):
  edge-2540    L4  edge/real-time-deadlines
  mobile-2151  L4  mobile/kv-cache-management
  mobile-2152  L2  mobile/kv-cache-management
  mobile-2154  L4  mobile/model-serving-infrastructure
  mobile-2157  L4  mobile/roofline-analysis
  mobile-2161  L5  mobile/power-budgeting

Rejected (level_fit failures — Gemini stamped L3-L5 on questions whose
cognitive demand is L1/L2; same failure mode the audit caught on the
first pilot):
  edge-2537    edge/real-time-deadlines       (level inflation)
  edge-2543    edge/transformer-systems-cost  (level inflation + mixed
                                               base-2/base-10 conversions)
  mobile-2156  mobile/quantization-fundamentals (level inflation)

Targeted chain rebuilds on the 5 affected buckets (5 parallel
build_chains_with_gemini.py --bucket calls):
  edge/real-time-deadlines                7 chains → 9
  mobile/kv-cache-management              4 chains → 6
  mobile/model-serving-infrastructure     5 chains → 4
  mobile/roofline-analysis                3 chains → 4
  mobile/power-budgeting                  2 chains → 6
                                       21 dropped → 29 added
  net chain count: 835 → 843 (+8)

5 of 6 published questions land in clean primary chains:
  edge-2540    in [edge-0114(L1) → … → edge-2540(L4) → … → edge-0621(L6+)]
  mobile-2152  in [mobile-2152(L2) → mobile-1097(L3) → mobile-1185(L4)]
  mobile-2154  in [mobile-0244(L1) → mobile-0305(L2) → mobile-2154(L4) → mobile-0654(L6+)]
  mobile-2157  in [mobile-0364(L2) → mobile-0537(L3) → mobile-2157(L4) → mobile-0617(L5)]
  mobile-2161  in [mobile-0151(L2) → mobile-0103(L3) → mobile-0581(L4) → mobile-2161(L5) → mobile-1587(L6+)]
mobile-2151 didn't enter a chain — Gemini chose other L4 candidates for
that bucket; mobile-2152 covers the bridge work.

Drive-by: 24 chain_ids renumbered to bucket-tagged form
(<track>-chain-bucket-<topic-slug>-<NN>) to resolve collisions.
build_chains_with_gemini.py's chain_id format uses call_idx, which
restarts at 1 for each --bucket invocation — collides with the
original full-corpus run's IDs and across parallel bucket runs.
Filed as a follow-up to fix the generator (use a content-stable or
bucket-tagged ID scheme).

Verification trail (75 Gemini calls total this batch):
  pre-filter:    30 calls, 21 hallucinated, 9 real (70% hallucination
                 — matches audit-2 measurement exactly)
  generation:    9 calls, 9/9 schema-valid
  audit_math:    9 calls, 9/9 pass (independent re-derivation of all
                 napkin_math arithmetic; 4-way parallel via the new
                 ThreadPoolExecutor in audit_math.py)
  validate_drafts: 27 calls (3 LLM judges × 9 drafts), 6/9 pass
  bucket rebuild: 5 calls, 5 strict-mode chain sets

Validation:
  apply_proposed_chains.py --dry-run: clean (843 chains)
  vault check --strict: 10,709 loaded, 0 invariant failures
  vault build --local-json: published_count=9446, chainCount=843,
    releaseHash=5a4783e62d2ca8d…
2026-05-02 10:54:17 -04:00
Vijay Janapa Reddi
ac15ac2fd6 feat(vault): Phase 3.e — chain rebuild absorbing 2 new questions
After publishing mobile-2147 and edge-2536 in 9ab6bb85d (Phase 3.d
disposition), re-ran the strict-mode chain build on the two affected
buckets to absorb them into proper progressions.

Targeted rebuild (2 Gemini calls, ~1 min wall time vs ~25 min for
build_chains_with_gemini.py --all):

  build_chains_with_gemini.py --bucket mobile:model-format-conversion
  build_chains_with_gemini.py --bucket edge:pruning-sparsity

Results:
  mobile/model-format-conversion: 2 secondary chains → 12 primary chains.
    Notable: mobile-2147 lands in a clean L1→L2→L3→L4→L5→L6+ chain
    (mobile-0984 → mobile-2147 → mobile-1022 → mobile-1511 → mobile-0980
     → mobile-1662) — exactly the strict +1 progression the bridge was
    authored to enable.

  edge/pruning-sparsity:        3 secondary chains →  4 primary chains.
    Notable: edge-2536 lands in L1→L3→L4→L5 (edge-1784 → edge-1960 →
    edge-2536 → edge-1957) — slots between edge-1960 (L3) and edge-1957
    (L5) as designed, turning a Δ=2 jump into Δ=1 + Δ=1.

Both buckets transition from secondary-only to primary-only — strict
mode produced clean +1/+2 chains with the new bridges in place.

Net chain count: 824 → 835 (-5 old secondary, +16 new primary).

Validation:
  apply_proposed_chains.py --dry-run on merged chains.json: clean
  vault check --strict:                  10,703 loaded, 0 failures
  vault build --local-json:              chainCount=835, releaseHash 9b381a55…
2026-05-02 09:47:54 -04:00
Vijay Janapa Reddi
9ab6bb85d0 feat(vault): Phase 3 pilot disposition — 2 published, 3 rejected
Acting on the audit findings (independent Gemini audit, 2 runs converged
on the same per-draft verdicts). Of the 5 drafts in the Phase 3 pilot:

Published (status: published, human_reviewed: verified):
  mobile-2147  Model Format Conversion: Sizing the FP16 CoreML Payload
               Clean L2 / understand. FP32→FP16 storage halving on a
               15M-param iOS model. Realistic App Store framing,
               correct math, no fabrication.

  edge-2536    Diagnosing Zero Latency Gains from Unstructured Pruning
               on Coral TPU
               Canonical L4 / analyze lesson on dense systolic arrays
               + unstructured sparsity. Edited the scenario's baseline
               latency from 80ms → 15ms (more realistic for MobileNetV2
               on Coral USB TPU; audit flagged the 80ms figure as
               unrealistic). Pedagogical content unchanged.

Rejected (deleted):
  edge-2537    edge/tco-cost-modeling
               Audit (both runs) flagged "cognitive load too low for L3
               — basic arithmetic word problem with all parameters
               given". Real L3 TCO questions require judgement under
               uncertainty; this one is L1/L2.

  mobile-2146  mobile/duty-cycling
               Audit flagged a physically absurd 0.5s wake-up at 4W for
               a mobile NPU (real NPUs wake in milliseconds). Run 2
               additionally flagged the dashcam framing as broken (a
               dashcam idle 75% of the time would miss accidents).
               Premise is fiction; the lesson can't be salvaged.

  edge-2535    edge/latency-decomposition
               Failed validate_drafts.py originality gate at promotion
               (cosine 0.933 vs its own bridge anchor edge-1883). Was
               left as .yaml.draft pending review; content is fine on
               its own, but pedagogically duplicative with the lesson
               in the now-promoted edge-2536 (host-side bottleneck on
               Coral). Cleaner to drop than de-duplicate.

The 4 ID entries in id-registry.yaml stay (append-only ledger); the
removed YAMLs become dangling registry entries which is the intended
behaviour — the registry is "every ID ever assigned", not "every ID
currently active".

Validation:
  vault check --strict:    10,703 loaded, 0 invariant failures
  vault build --local-json: 9440 published (was 9438 + 2), chainCount=824,
                           releaseHash a9a601c2bf… (was 479811040b…)
2026-05-02 09:39:52 -04:00
Vijay Janapa Reddi
270b1a5bd2 fix(vault): drop 55 Δ=0 chains + remove Δ=0 from lenient mode
Action on the strongest finding from the 2026-05-01 independent audit:
54 of 55 Δ=0 chains had no shared scenario (the "two questions
sharing a scenario thread" constraint the lenient prompt was supposed
to enforce). Two independent audit fields agreed (verdict=bad and
shared_scenario=no), so this isn't a tuning question — the design
choice was wrong.

Why remove Δ=0 entirely rather than tighten the prompt:

  - The chain definition is "pedagogical progression through Bloom
    levels"; same-level edges contradict the definition.
  - The "shared scenario / different angle" carve-out is unenforceable
    by an LLM at corpus scale (audit confirmed).
  - Same-scenario same-level pairs are more honestly modeled as
    siblings of a chain anchor, not as chain members.

Changes:
  - chains.json: 879 → 824. Dropped: 55 chains (all tier=secondary,
    since Δ=0 was only ever produced by the lenient sweep).
    Per-track: edge -19, tinyml -12, mobile -10, cloud -7, global -7.
  - build_chains_with_gemini.py:
      MODE_CONFIG["lenient"]["allowed_deltas"]: {0,1,2,3} → {1,2,3}
      LENIENT_PROMPT_TEMPLATE: Δ=0 paragraph rewritten to explicitly
        REJECT same-level pairs (with rationale citing the audit).
      docstring + --mode help text updated.
  - tests/test_chain_validation.py:
      test_lenient_accepts_same_level_pair → test_lenient_rejects_same_level_pair
      header docstring updated to reflect the new rule.
  - vault-manifest.json: chainCount 879 → 824, releaseHash rolls to
    479811040b7a… (real content delta, not a timestamp churn).

Validation:
  - vault check --strict: 10,705 loaded, 0 failures
  - vault build --local-json: chainCount=824, releaseHash=479811040b…
  - pytest: 74/74
  - playwright chain-and-vault-smoke: 19/19 (fixtures cloud-0001 +
    cloud-0231 are still in their chains post-drop)

Audit findings #2 (gap detection ~50% noise) and #3 (4 pilot drafts
disposition) remain open — see CHAIN_ROADMAP.md Progress Log.
2026-05-02 08:51:49 -04:00
Vijay Janapa Reddi
bddac127bc feat(staffml/explore): Phase 2.3 deferred — Primary/All tier filter
The "Primary chains only / All" filter dropdown that was punted from
Phase 2.3 (ed2ddb51d) so the user could review the bigger UI surface.

Implementation:
  - new selectedTier state, default "primary"
  - filteredQuestions filter: when "primary", drop questions whose
    chain memberships are *all* secondary (questions not in any chain
    pass through unchanged — they're tier-irrelevant).
  - Tier FilterSelect dropdown next to the existing Level filter.

Default behaviour intentionally hides secondary-only questions —
matches the rest of the Phase 2 surfaces (practice prefers primary,
ChainBadge shows "alt path" pill on secondary, explore picks primary
chains for the related panel). Users opt into seeing the lenient-pass
questions by switching to "All chains".

Tests: new playwright case test8_explore_tier_filter:
  - tier filter dropdown rendered
  - switching to "All chains" keeps page interactive (no crash on
    re-filter)
Smoke suite: 19/19 pass.
2026-05-01 17:29:34 -04:00
Vijay Janapa Reddi
231374586e fix(staffml): override transitive postcss to ^8.5.12 (GHSA, XSS via stringify)
Direct postcss already ^8.5.12, but next@16.2.4 was bringing in a
nested postcss@8.4.31 that tripped GHSA-...-postcss CSS-stringify XSS.
Top-level override forces all postcss instances to ^8.5.12 (resolves
8.5.13); nested next/postcss copy is no longer present in lockfile.

Closes Dependabot #45.
2026-05-01 17:23:35 -04:00
Vijay Janapa Reddi
202397f594 Merge origin/dev into yaml-audit
Pull in the dev work that landed since yaml-audit was last synced:
  - --legacy-json renamed to --local-json (2b381bb949) — script/doc
    updates needed below in this branch
  - CI workflow refactor (validate-dev / validate-vault now reusable)
  - all-contributors automation, gitignore tightening, codespell list
  - PR #1622 navbar URL rewrite for dev preview
  - PR #1619 clone-size refactor, #1618 milestone3 xor fix, #1617
    perceptron seed, #1616 tito status M3
  - Chapter 9 PDF layout refinement
  - assorted staffml/practice fixes (pickRandom deps, GitHub star gate)

This merges the canonical dev state into yaml-audit so subsequent
work continues on top of the freshest base. Conflicts in
practice/page.tsx + corpus.ts + ARCHITECTURE.md resolved to keep both
sides' additive changes (Phase 2 tier work + dev's later refactors).
2026-05-01 17:11:31 -04:00
Vijay Janapa Reddi
836d481b54 Merge branch 'yaml-audit' into dev (Phase 1 + 2 + 3 pilot + 4.8 docs)
Brings the chain corpus growth + tier-aware UI work into dev:

  - Phase 1: chains 373 → 879 (second-pass coverage build, primary +
    secondary tier; bucket coverage 33% → 91%)
  - Phase 2: tier surfacing through schema → TypeScript → UI (primary
    chains default; secondary reachable via ?chain= URL with "alt path"
    badge); 17/17 playwright
  - Phase 3 pilot: 5 gap-driven generations, 4 promoted as drafts
    (status=draft pending human review). edge-2535 left as .yaml.draft
    (failed originality gate).
  - Phase 4.8: ARCHITECTURE.md §3.6 + README "Chain build pipeline"
    section documenting v1.1 sidecar + hierarchy + tier model.

State at merge:
  - vault check --strict: 10,705 loaded (4 new drafts), 0 invariant failures
  - vault build --legacy-json: 9438 published, chainCount=879
    (drafts excluded by status filter — releaseHash unchanged from Phase 1)
  - playwright chain-and-vault-smoke: 17/17 (last yaml-audit run)

Phase 3.e (chain rebuild absorbing the new questions) gated on the
human review of the 4 drafts. Runbook in CHAIN_ROADMAP.md.
2026-05-01 13:39:33 -04:00
Vijay Janapa Reddi
9680e8e9fd feat(vault+staffml): Phase 2 — tier surfacing, schema → TS → UI
Carries the primary/secondary chain tier (from Phase 1) through the
build pipeline into the practice + explore surfaces, so primary chains
are the unmarked default and secondary chains are an opt-in alternative
path the user can deep-link into via ?chain=<id>.

Backend (2.1):
  - legacy_export.py emits chain_tiers per question alongside chain_ids
    and chain_positions; missing chain-tier defaults to "primary".
  - vault build re-run: 2953 chained questions, all carry chain_tiers
    (releaseHash unchanged — new field is additive, doesn't perturb the
    manifest hash inputs).
  - Existing legacy_export tests were stale (asserted on the v1.0 YAML
    chains: field path; v1.1 made chains.json the sidecar source).
    Rewrote them to write chains.json fixtures into tmp_path and added
    chain_tiers assertions, plus a focused
    test_chain_tiers_emitted_per_membership case.

TypeScript (2.2):
  - Question.chain_tiers? (Record<string, "primary"|"secondary">)
  - ChainTier export, ChainInfo.tier required.
  - getChainForQuestion / getAllChainsForQuestion populate tier;
    getAllChains... sorts primary first.
  - New getPrimaryChainForQuestion(qid) helper for default surfaces.

UI (2.3):
  - practice page reads ?chain=<id> URL param; defaults to
    getPrimaryChainForQuestion when unset.
  - ChainBadge gains an inline "alt path" pill when tier=secondary
    (always visible — no click needed).
  - ChainStrip mirrors that pill in the progress row for users who
    expand the strip.
  - Explore page prefers the first non-secondary chain when picking
    activeChainId for the related-questions panel.
  - Deferred to a follow-up commit (intentional, scoped via Progress Log):
    explore-page "Primary only / All" filter; daily/mock routing.

Tests (2.4):
  - test7_tier_aware_chain_routing in chain-and-vault-smoke.mjs:
    secondary reachable via ?chain=, alt-path badge visible on
    secondary, primary regression, alt-path badge ABSENT on primary.
  - Full smoke suite: 17/17 pass (was 13/13).

Validation:
  - vault check --strict: 10,701 loaded, 0 failures
  - vault build --legacy-json: 9438 published, chainCount=879
  - pytest interviews/vault-cli/tests: 74/74
  - npx tsc --noEmit: 0 errors
  - playwright chain-and-vault-smoke: 17/17

Phase 2 complete. Next: Phase 3 (gap-driven authoring; 407-gap backlog).
2026-04-30 20:22:54 -04:00
Vijay Janapa Reddi
5b8bab2657 feat(vault+staffml): Phase 2 — tier surfacing, schema → TS → UI
Carries the primary/secondary chain tier (from Phase 1) through the
build pipeline into the practice + explore surfaces, so primary chains
are the unmarked default and secondary chains are an opt-in alternative
path the user can deep-link into via ?chain=<id>.

Backend (2.1):
  - legacy_export.py emits chain_tiers per question alongside chain_ids
    and chain_positions; missing chain-tier defaults to "primary".
  - vault build re-run: 2953 chained questions, all carry chain_tiers
    (releaseHash unchanged — new field is additive, doesn't perturb the
    manifest hash inputs).
  - Existing legacy_export tests were stale (asserted on the v1.0 YAML
    chains: field path; v1.1 made chains.json the sidecar source).
    Rewrote them to write chains.json fixtures into tmp_path and added
    chain_tiers assertions, plus a focused
    test_chain_tiers_emitted_per_membership case.

TypeScript (2.2):
  - Question.chain_tiers? (Record<string, "primary"|"secondary">)
  - ChainTier export, ChainInfo.tier required.
  - getChainForQuestion / getAllChainsForQuestion populate tier;
    getAllChains... sorts primary first.
  - New getPrimaryChainForQuestion(qid) helper for default surfaces.

UI (2.3):
  - practice page reads ?chain=<id> URL param; defaults to
    getPrimaryChainForQuestion when unset.
  - ChainBadge gains an inline "alt path" pill when tier=secondary
    (always visible — no click needed).
  - ChainStrip mirrors that pill in the progress row for users who
    expand the strip.
  - Explore page prefers the first non-secondary chain when picking
    activeChainId for the related-questions panel.
  - Deferred to a follow-up commit (intentional, scoped via Progress Log):
    explore-page "Primary only / All" filter; daily/mock routing.

Tests (2.4):
  - test7_tier_aware_chain_routing in chain-and-vault-smoke.mjs:
    secondary reachable via ?chain=, alt-path badge visible on
    secondary, primary regression, alt-path badge ABSENT on primary.
  - Full smoke suite: 17/17 pass (was 13/13).

Validation:
  - vault check --strict: 10,701 loaded, 0 failures
  - vault build --legacy-json: 9438 published, chainCount=879
  - pytest interviews/vault-cli/tests: 74/74
  - npx tsc --noEmit: 0 errors
  - playwright chain-and-vault-smoke: 17/17

Phase 2 complete. Next: Phase 3 (gap-driven authoring; 407-gap backlog).
2026-04-30 20:22:54 -04:00
Vijay Janapa Reddi
83fe0f7193 feat(vault): Phase 1 — second-pass chain coverage build (373 → 879)
Diagnoses uncovered (track, topic) buckets and runs a relaxed Gemini
sweep targeting them. New chains tier="secondary"; pre-existing chains
backfilled tier="primary".

Tools (Phases 1.1, 1.2/1.3, 1.5):
  - diagnose_chain_coverage.py: surface buckets with no chains
    (committed earlier on yaml-audit)
  - build_chains_with_gemini.py: --mode lenient adds Δ ∈ {0,1,2,3}
    (committed earlier on yaml-audit)
  - merge_chain_passes.py: merges primary + secondary, enforces the
    multi-membership cap (max 2 chains/qid; non-L1/L2 capped at 1)

Sweep (Phase 1.4):
  - 17 Gemini-3.1-pro-preview calls, ~22 min wall time, 211 buckets
  - 506 chains accepted (above the 200-400 estimate), 269 new gaps
  - validator caught a few cross-bucket and Δ=4 hallucinations inline
  - Δ distribution: Δ=1 69.1%, Δ=2 21.1%, Δ=3 4.6%, Δ=0 5.2%
    (10.9% of chains contain at least one Δ=0 — within target band)
  - random spot-check of 5 Δ=0 chains: all share scenario threads
    (DMA, CMSIS-NN, on-device routing, PB-scale pipelines)

Coverage gains (chains/topic before → after):
  - cloud   2.95 → 4.37   (242 + 116 secondary)
  - edge    0.64 → 2.59   ( 49 + 148 secondary)
  - mobile  0.74 → 2.56   ( 46 + 113 secondary)
  - tinyml  0.80 → 2.64   ( 36 +  83 secondary)
  - global  0.00 → 0.96   (  0 +  46 secondary)
  Buckets with ≥1 chain: 102 / 313 (33%) → 285 / 313 (91%).

Validation:
  - apply_proposed_chains.py --dry-run: validation clean (879 chains)
  - vault check --strict: 10,701 loaded, 0 invariant failures
  - vault build --legacy-json: chainCount 373 → 879, release_hash
    rolled to 04ee8a23…
  - playwright chain-and-vault-smoke.mjs: 13/13 pass

Phase 1 complete. Next: Phase 2 (tier surfacing in staffml UI).
2026-04-30 20:12:27 -04:00
Vijay Janapa Reddi
9e6f87bbd4 feat(vault): Phase 1 — second-pass chain coverage build (373 → 879)
Diagnoses uncovered (track, topic) buckets and runs a relaxed Gemini
sweep targeting them. New chains tier="secondary"; pre-existing chains
backfilled tier="primary".

Tools (Phases 1.1, 1.2/1.3, 1.5):
  - diagnose_chain_coverage.py: surface buckets with no chains
    (committed earlier on yaml-audit)
  - build_chains_with_gemini.py: --mode lenient adds Δ ∈ {0,1,2,3}
    (committed earlier on yaml-audit)
  - merge_chain_passes.py: merges primary + secondary, enforces the
    multi-membership cap (max 2 chains/qid; non-L1/L2 capped at 1)

Sweep (Phase 1.4):
  - 17 Gemini-3.1-pro-preview calls, ~22 min wall time, 211 buckets
  - 506 chains accepted (above the 200-400 estimate), 269 new gaps
  - validator caught a few cross-bucket and Δ=4 hallucinations inline
  - Δ distribution: Δ=1 69.1%, Δ=2 21.1%, Δ=3 4.6%, Δ=0 5.2%
    (10.9% of chains contain at least one Δ=0 — within target band)
  - random spot-check of 5 Δ=0 chains: all share scenario threads
    (DMA, CMSIS-NN, on-device routing, PB-scale pipelines)

Coverage gains (chains/topic before → after):
  - cloud   2.95 → 4.37   (242 + 116 secondary)
  - edge    0.64 → 2.59   ( 49 + 148 secondary)
  - mobile  0.74 → 2.56   ( 46 + 113 secondary)
  - tinyml  0.80 → 2.64   ( 36 +  83 secondary)
  - global  0.00 → 0.96   (  0 +  46 secondary)
  Buckets with ≥1 chain: 102 / 313 (33%) → 285 / 313 (91%).

Validation:
  - apply_proposed_chains.py --dry-run: validation clean (879 chains)
  - vault check --strict: 10,701 loaded, 0 invariant failures
  - vault build --legacy-json: chainCount 373 → 879, release_hash
    rolled to 04ee8a23…
  - playwright chain-and-vault-smoke.mjs: 13/13 pass

Phase 1 complete. Next: Phase 2 (tier surfacing in staffml UI).
2026-04-30 20:12:27 -04:00
Vijay Janapa Reddi
63b2aa5c5a Merge remote-tracking branch 'origin/dev' into dev 2026-04-30 19:10:08 -04:00
Vijay Janapa Reddi
0acd0cfedf Merge remote-tracking branch 'origin/dev' into dev 2026-04-30 19:10:08 -04:00
Rocky
291716d6a3 fix(staffml): add pickRandom to keyboard effect and pickNext dep arrays (#1602)
The keyboard shortcut useEffect captured a stale pickRandom closure
because pickRandom was not in its dependency array. When a user changed
a filter, pool updated and pickRandom got a new identity via useCallback,
but the keyboard handler kept firing the old closure. Pressing N after
a filter change would pick from the pre-filter pool.

Same stale-closure bug existed in pickNext, which calls pickRandom but
only listed [reviewMode, pool] as deps.

Fix: move pickRandom before the keyboard effect (satisfies the
before-use declaration order) and add it to both dep arrays.
2026-04-30 18:48:39 -04:00
Rocky
42e7de96fa fix(staffml): add pickRandom to keyboard effect and pickNext dep arrays (#1602)
The keyboard shortcut useEffect captured a stale pickRandom closure
because pickRandom was not in its dependency array. When a user changed
a filter, pool updated and pickRandom got a new identity via useCallback,
but the keyboard handler kept firing the old closure. Pressing N after
a filter change would pick from the pre-filter pool.

Same stale-closure bug existed in pickNext, which calls pickRandom but
only listed [reviewMode, pool] as deps.

Fix: move pickRandom before the keyboard effect (satisfies the
before-use declaration order) and add it to both dep arrays.
2026-04-30 18:48:39 -04:00
Rocky
3bf15a8856 fix(staffml): use summary fallback when Worker fetch fails in gauntlet (#1601)
If getQuestionFullDetail returns undefined for even one question, the
previous code's `hydrated.length === selected.length` guard discarded
the entire hydrated batch, silently leaving the whole gauntlet with
empty scenario/details fields regardless of which questions succeeded.

Fall back to the original summary per-question instead so partial
Worker outages degrade gracefully rather than wiping all hydrated data.
2026-04-30 18:48:36 -04:00
Rocky
2166d04a20 fix(staffml): use summary fallback when Worker fetch fails in gauntlet (#1601)
If getQuestionFullDetail returns undefined for even one question, the
previous code's `hydrated.length === selected.length` guard discarded
the entire hydrated batch, silently leaving the whole gauntlet with
empty scenario/details fields regardless of which questions succeeded.

Fall back to the original summary per-question instead so partial
Worker outages degrade gracefully rather than wiping all hydrated data.
2026-04-30 18:48:36 -04:00
Vijay Janapa Reddi
b289a5eb75 Merge branch 'yaml-audit' into dev
Brings the vault chain rebuild + sidecar architecture work into dev:

  - Hierarchical question layout (interviews/vault/questions/<track>/<area>/<id>.yaml)
    completed in earlier dev merge; this branch adds the sidecar split
  - chains.json is now the authoritative chain registry; YAML chains: field
    stripped from all 10,701 question files
  - 373 chains rebuilt via Gemini 3.1 Pro Preview with strict progression
    rules (Δ ∈ {1,2}, single-track, single-topic, multi-membership cap=2)
  - 138 gaps surfaced into gaps.proposed.json for Phase 3 authoring
  - Tooling: build_chains_with_gemini.py, apply_proposed_chains.py,
    summarize_proposed_chains.py, diagnose_chain_coverage.py
  - CHAIN_ROADMAP.md captures the resumable Phase 1-4 plan

State at merge:
  - vault check --strict: 10,701 loaded, 0 invariant failures
  - vault build --legacy-json: clean, releaseId=dev, 9438 published, 373 chains
  - playwright UI suite (last run on yaml-audit): 13/13 pass

Phase 1.1 (diagnose_chain_coverage.py) shipped on yaml-audit; Phase
1.2-1.6 (lenient sweep, tier merge) still pending. See CHAIN_ROADMAP.md
Progress Log for the resumable cursor.
2026-04-30 18:39:05 -04:00
Vijay Janapa Reddi
f527c230f3 Merge branch 'yaml-audit' into dev
Brings the vault chain rebuild + sidecar architecture work into dev:

  - Hierarchical question layout (interviews/vault/questions/<track>/<area>/<id>.yaml)
    completed in earlier dev merge; this branch adds the sidecar split
  - chains.json is now the authoritative chain registry; YAML chains: field
    stripped from all 10,701 question files
  - 373 chains rebuilt via Gemini 3.1 Pro Preview with strict progression
    rules (Δ ∈ {1,2}, single-track, single-topic, multi-membership cap=2)
  - 138 gaps surfaced into gaps.proposed.json for Phase 3 authoring
  - Tooling: build_chains_with_gemini.py, apply_proposed_chains.py,
    summarize_proposed_chains.py, diagnose_chain_coverage.py
  - CHAIN_ROADMAP.md captures the resumable Phase 1-4 plan

State at merge:
  - vault check --strict: 10,701 loaded, 0 invariant failures
  - vault build --legacy-json: clean, releaseId=dev, 9438 published, 373 chains
  - playwright UI suite (last run on yaml-audit): 13/13 pass

Phase 1.1 (diagnose_chain_coverage.py) shipped on yaml-audit; Phase
1.2-1.6 (lenient sweep, tier merge) still pending. See CHAIN_ROADMAP.md
Progress Log for the resumable cursor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 18:39:05 -04:00
Vijay Janapa Reddi
b2b3893152 fix(staffml/ecosystem-bar): portal dropdown menus to escape parent clip/stacking
The EcosystemBar dropdowns rendered behind the StaffML Nav bar despite
sitting in a higher-z-index sticky parent. Root cause is the bar's
`overflow-x: clip` + `position: sticky` combo: WebKit composites such
parents into a layer that does not extend past their box, so children
painted below the box (the open dropdown) end up beneath the next
sticky sibling — Nav, whose `backdrop-blur-md` makes it its own
stacking context. Z-index alone cannot fix this.

Render the dropdown via `createPortal` to `document.body` with
`position: fixed` and a coordinate computed from the trigger's
`getBoundingClientRect()`, recomputed on resize and capture-phase
scroll. The menu is now immune to any ancestor's overflow, transform,
filter, or stacking-context shenanigans — present or future. Outside-
click handler accepts clicks inside the portalled dropdown via a
`data-ecosystem-dropdown` attribute; Escape closes; mobile inline
expansion is unchanged.
2026-04-30 17:44:13 -04:00
Vijay Janapa Reddi
52c5ce9404 fix(staffml/ecosystem-bar): portal dropdown menus to escape parent clip/stacking
The EcosystemBar dropdowns rendered behind the StaffML Nav bar despite
sitting in a higher-z-index sticky parent. Root cause is the bar's
`overflow-x: clip` + `position: sticky` combo: WebKit composites such
parents into a layer that does not extend past their box, so children
painted below the box (the open dropdown) end up beneath the next
sticky sibling — Nav, whose `backdrop-blur-md` makes it its own
stacking context. Z-index alone cannot fix this.

Render the dropdown via `createPortal` to `document.body` with
`position: fixed` and a coordinate computed from the trigger's
`getBoundingClientRect()`, recomputed on resize and capture-phase
scroll. The menu is now immune to any ancestor's overflow, transform,
filter, or stacking-context shenanigans — present or future. Outside-
click handler accepts clicks inside the portalled dropdown via a
`data-ecosystem-dropdown` attribute; Escape closes; mobile inline
expansion is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 17:44:13 -04:00
Vijay Janapa Reddi
1ac7d4c564 feat(vault): rebuild chains.json via Gemini 3.1 Pro Preview — 373 curated chains
Replaced the 726 author-curated chains with 373 LLM-curated chains
generated bucket-by-bucket within (track, topic). Gemini was prompted
with the strict-progression + multi-chain constraints we agreed on:

  - Δ ∈ {1, 2} between consecutive members (prefer +1)
  - Up to 2-chain membership only for L1/L2 anchors
  - Single-topic, 2-6 members, no Δ=0 same-level pairs
  - Validated structurally on apply — vault check --strict passes

Sweep stats:
  - 44 calls to gemini-3.1-pro-preview (well under 250/day cap)
  - 313 (track, topic) buckets processed in ~80 minutes
  - 373 chains accepted (51% of legacy count, much higher per-chain
    quality after strict filter)
  - Level-Δ distribution: 949 strict +1 (93%), 73 +2 (7%) — 0 +0/+3+
  - Chain sizes: 26 size-2, 141 size-3, 128 size-4, 60 size-5, 18 size-6
  - 1,395 questions in chains (15% of corpus, vs ~20% before)
  - 54 of ~87 topics have at least 1 chain
  - 138 corpus gaps identified (gaps.proposed.json) — missing-rung
    questions that would complete chains; feeds future authoring pass

Why fewer chains than before is fine:
  - Old chains had a long tail with cos<0.65 (worse than random
    same-bucket pairs). LLM curation rejects those.
  - We trade quantity for pedagogical coherence.
  - The 138 gaps capture what was implicit in old chains via
    questions-that-shouldnt-have-been-paired; we make it explicit.

Files:
  - chains.json — applied (was backed up to chains.json.bak by
    apply_proposed_chains.py)
  - chains.proposed.json — kept for review/audit
  - gaps.proposed.json — authoring backlog
  - vault-manifest.json + corpus-summary.json — regenerated
  - corpus.json — gitignored (CI regenerates)

Validation: vault check --strict 0 failures, vault build clean,
playwright UI suite 13/13 pass.
2026-04-30 15:15:45 -04:00
Vijay Janapa Reddi
48d68062f3 Merge refactor/vault-cli-local-json-flag into dev 2026-04-30 09:31:03 -04:00
Vijay Janapa Reddi
318165b5c8 Merge refactor/vault-cli-local-json-flag into dev 2026-04-30 09:31:03 -04:00
Vijay Janapa Reddi
9fdbfb9a4c refactor(vault-cli): rename --legacy-json to --local-json
The flag is the StaffML frontend's local-dev fallback (read corpus.json
from disk via NEXT_PUBLIC_VAULT_FALLBACK=static), not a deprecated path.
"Legacy" implied "soon to be removed"; "local-json" describes its actual
role and reads correctly in scripts and docs.

- vault-cli: rename CLI flag, parameter, result key, and help text.
- CI workflows + pre-commit config: invoke the new flag name.
- All scripts that print the command (suggest_exemplars,
  pre_commit_corpus_guard, promote_validated, rename_legacy_ids,
  export_to_staffml, the paper analyze_corpus/generate_*) updated.
- Comments and docs (ARCHITECTURE, CHANGELOG, REVIEWS, TESTING,
  MASSIVE_BUILD_RUNBOOK, DEPRECATED, AUTHORING, plus frontend
  comments and .env.example / .gitignore) updated.

The "legacy_json" sentinel string in corpus_stats.json._meta.source
is intentionally NOT renamed — it is a stable artifact format read
by downstream paper-generation tooling.
2026-04-30 09:30:28 -04:00
Vijay Janapa Reddi
2b381bb949 refactor(vault-cli): rename --legacy-json to --local-json
The flag is the StaffML frontend's local-dev fallback (read corpus.json
from disk via NEXT_PUBLIC_VAULT_FALLBACK=static), not a deprecated path.
"Legacy" implied "soon to be removed"; "local-json" describes its actual
role and reads correctly in scripts and docs.

- vault-cli: rename CLI flag, parameter, result key, and help text.
- CI workflows + pre-commit config: invoke the new flag name.
- All scripts that print the command (suggest_exemplars,
  pre_commit_corpus_guard, promote_validated, rename_legacy_ids,
  export_to_staffml, the paper analyze_corpus/generate_*) updated.
- Comments and docs (ARCHITECTURE, CHANGELOG, REVIEWS, TESTING,
  MASSIVE_BUILD_RUNBOOK, DEPRECATED, AUTHORING, plus frontend
  comments and .env.example / .gitignore) updated.

The "legacy_json" sentinel string in corpus_stats.json._meta.source
is intentionally NOT renamed — it is a stable artifact format read
by downstream paper-generation tooling.
2026-04-30 09:30:28 -04:00
Vijay Janapa Reddi
d2e7c2331b style(staffml/practice): align Track filter button size with Competency/Zone
Track buttons used text-sm + rounded-md + lg:py-2; Competency and Zone
items use text-xs + rounded + py-1.5. Bringing Track in line so the
sidebar filter sections share one visual hierarchy.
2026-04-30 09:30:03 -04:00
Vijay Janapa Reddi
d5f9c2eb97 style(staffml/practice): align Track filter button size with Competency/Zone
Track buttons used text-sm + rounded-md + lg:py-2; Competency and Zone
items use text-xs + rounded + py-1.5. Bringing Track in line so the
sidebar filter sections share one visual hierarchy.
2026-04-30 09:30:03 -04:00
Vijay Janapa Reddi
5eec8692b3 feat(staffml): make GitHub star the only ask, gated on revealed answers
Replace the daily-FREE_LIMIT modal with a single mission-aligned ask
shown once after 5 lifetime reveals. The gate now retires forever on
star, honor-confirm, or dismiss — no daily cap, no username verify.

- Live stargazer count fetched from the GitHub API (24h cache).
- Copy borrows site/about: "Our only ask. Every star tells universities,
  publishers, and funders that AI engineering education matters."
- Wires the same gate into the gauntlet revealAnswer path so Mock
  Interview no longer bypasses the ask.
- Adds a Playwright smoke covering practice + gauntlet + dismiss
  persistence across reloads.
2026-04-30 09:29:46 -04:00
Vijay Janapa Reddi
d656104e54 feat(staffml): make GitHub star the only ask, gated on revealed answers
Replace the daily-FREE_LIMIT modal with a single mission-aligned ask
shown once after 5 lifetime reveals. The gate now retires forever on
star, honor-confirm, or dismiss — no daily cap, no username verify.

- Live stargazer count fetched from the GitHub API (24h cache).
- Copy borrows site/about: "Our only ask. Every star tells universities,
  publishers, and funders that AI engineering education matters."
- Wires the same gate into the gauntlet revealAnswer path so Mock
  Interview no longer bypasses the ask.
- Adds a Playwright smoke covering practice + gauntlet + dismiss
  persistence across reloads.
2026-04-30 09:29:46 -04:00
Vijay Janapa Reddi
681e404633 feat(chains): add gap detection + multi-chain UI helpers
build_chains_with_gemini.py: prompt now asks Gemini to also surface
missing-rung gaps — e.g., 'this bucket has L1 + L3 questions on the same
scenario thread but no L2 to bridge them.' Gaps are captured to
interviews/vault/gaps.proposed.json as a separate authoring backlog.
This is a free signal: it costs no extra calls, identifies pedagogical
holes the corpus doesn't yet fill, and feeds a future generation pass
(with independent validation before any new question is committed).

corpus.ts: getChainForQuestion now accepts an optional preferredChainId
so multi-chain questions can disambiguate via URL (?chain=...). Adds
getAllChainsForQuestion() returning every chain a qid belongs to.
Default behavior unchanged when only one chain exists.
2026-04-30 09:02:35 -04:00
Vijay Janapa Reddi
efeedb8cc5 feat(vault): chains as sidecar metadata — chains.json is authoritative
v1.0 -> v1.1: question YAMLs no longer carry a chains: field. The
canonical chain registry is interviews/vault/chains.json. The build
joins YAML + sidecar to produce per-question chain_ids/chain_positions
in the runtime corpus.json.

Why this matters: chain operations (add/remove/reshape) now touch ONE
file (chains.json) instead of rewriting the chains: field across
hundreds of question YAMLs. Lets us regenerate chains in bulk (e.g.,
the upcoming Gemini chain-builder pass) without polluting blame on
1800+ unrelated YAMLs.

Migration applied:
  - stripped chains: field from 1929 question YAMLs (regex pass)
  - reconciled 1 chains.json/YAML mismatch (cloud-chain-467
    membership)
  - updated 117 stale level fields in chains.json metadata to match
    live YAML levels
  - sorted 47 chains by YAML-side Bloom level so position = array index
    is monotonic
  - loader.load_all() now joins sidecar chain data onto each
    LoadedQuestion at parse time (existing q.chains readers still work)
  - validator builds chain_members from chains.json registry, not from
    q.chains list
  - legacy_export reads sidecar to populate corpus.json chain_ids

vault check --strict: 10,701 loaded, 0 invariant failures
vault build: 9,438 published, 726 chains
1,825 questions in corpus.json carry chain_ids (unchanged from before)
2026-04-30 08:46:14 -04:00
Vijay Janapa Reddi
514414d6d2 Merge yaml-audit into dev — hierarchical layout + chain audit infrastructure
Brings in the full yaml-audit work:
  - 10,701 YAMLs migrated to <track>/<area>/<id>.yaml hierarchy
  - path-vs-body invariants enforced in vault check --strict
  - chain integrity repaired (orphans stripped, positions renumbered)
  - chains.json pruned (164 stale entries + 520 dead member refs)
  - vault chains audit + suggest commands
  - playwright smoke + chain integration test suite
  - staffml UI fix: render q.question instead of empty q.scenario in
    list-context previews (the '...' bug)
  - 3 misplaced cloud-* yamls reconciled

vault check --strict: 10,701 loaded, 0 invariant failures
vault build release_hash: stable through all transformations
2026-04-29 20:48:51 -04:00
Vijay Janapa Reddi
78f4cd5e8c fix(staffml/version-toast): only fire on real upgrade, not phantom rollback
Plain string \!== triggered the toast whenever local RELEASE_ID differed
from worker's release_id — including when local was *newer* (dev preview
ahead of prod worker). That produced 'New version available v0.1.0 (you
have v0.1.2-dev)' which reads backwards.

Now uses semver-aware compare: only fire when remote is strictly newer
in the numeric prefix. Suffix differences at the same numeric version
are ignored.
2026-04-29 20:48:39 -04:00
Vijay Janapa Reddi
e85416931b test(staffml): playwright smoke + chain integration suite
13 checks covering:
  - landing page + vault area rendering
  - topic drilldown question card preview text (regression for the '...' bug)
  - practice page loads + renders chain members
  - chain indicator surfaces on chain-member questions
  - hierarchical layout doesn't break runtime: practice loads
    cloud-0000, edge-0001, mobile-0000, tinyml-0000

All 13 pass against current build. Run via:
  cd interviews/staffml && npm run dev
  node tests/chain-and-vault-smoke.mjs
2026-04-29 19:06:15 -04:00