learn-cli 0.4.2__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- learn_cli-0.5.1/.github/workflows/deploy-site.yml +122 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.gitignore +2 -1
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.markdownlint-cli2.yaml +9 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/CHANGELOG.md +27 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/CLAUDE.md +7 -2
- {learn_cli-0.4.2 → learn_cli-0.5.1}/PKG-INFO +2 -1
- learn_cli-0.5.1/docs/hosting.md +76 -0
- learn_cli-0.5.1/docs/specs/subject-plugin-contract.md +262 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/__init__.py +16 -0
- learn_cli-0.5.1/learn/cli/_commands/auth.py +279 -0
- learn_cli-0.5.1/learn/cli/_commands/mcp.py +118 -0
- learn_cli-0.5.1/learn/cli/_commands/next_cmd.py +88 -0
- learn_cli-0.5.1/learn/cli/_commands/progress.py +192 -0
- learn_cli-0.5.1/learn/cli/_commands/record.py +121 -0
- learn_cli-0.5.1/learn/cli/_commands/site.py +142 -0
- learn_cli-0.5.1/learn/cli/_commands/subject.py +112 -0
- learn_cli-0.5.1/learn/cli/_commands/subjects.py +51 -0
- learn_cli-0.5.1/learn/contract/__init__.py +99 -0
- learn_cli-0.5.1/learn/contract/_validate.py +327 -0
- learn_cli-0.5.1/learn/contract/schemas/advice.json +34 -0
- learn_cli-0.5.1/learn/contract/schemas/doctor.json +37 -0
- learn_cli-0.5.1/learn/contract/schemas/error.json +20 -0
- learn_cli-0.5.1/learn/contract/schemas/lesson.json +80 -0
- learn_cli-0.5.1/learn/contract/schemas/overview.json +52 -0
- learn_cli-0.5.1/learn/contract/schemas/practice.json +75 -0
- learn_cli-0.5.1/learn/contract/schemas/progress.json +85 -0
- learn_cli-0.5.1/learn/contract/schemas/record.json +74 -0
- learn_cli-0.5.1/learn/contract/schemas/story.json +131 -0
- learn_cli-0.5.1/learn/contract/schemas/story_list.json +33 -0
- learn_cli-0.5.1/learn/contract/schemas/story_read.json +40 -0
- learn_cli-0.5.1/learn/explain/catalog.py +437 -0
- learn_cli-0.5.1/learn/front/__init__.py +175 -0
- learn_cli-0.5.1/learn/front/_docs.py +167 -0
- learn_cli-0.5.1/learn/front/_driver.py +129 -0
- learn_cli-0.5.1/learn/front/_export.py +171 -0
- learn_cli-0.5.1/learn/motivation/__init__.py +117 -0
- learn_cli-0.5.1/learn/motivation/_constants.py +90 -0
- learn_cli-0.5.1/learn/motivation/_decay.py +47 -0
- learn_cli-0.5.1/learn/motivation/_models.py +267 -0
- learn_cli-0.5.1/learn/motivation/_review.py +47 -0
- learn_cli-0.5.1/learn/motivation/_scoring.py +72 -0
- learn_cli-0.5.1/learn/motivation/_state.py +113 -0
- learn_cli-0.5.1/learn/motivation/_streaks.py +103 -0
- learn_cli-0.5.1/learn/motivation/_whatnext.py +180 -0
- learn_cli-0.5.1/learn/profile/__init__.py +101 -0
- learn_cli-0.5.1/learn/profile/_api.py +135 -0
- learn_cli-0.5.1/learn/profile/_auth.py +93 -0
- learn_cli-0.5.1/learn/profile/_ledger.py +68 -0
- learn_cli-0.5.1/learn/profile/_paths.py +66 -0
- learn_cli-0.5.1/learn/profile/_sync.py +81 -0
- learn_cli-0.5.1/learn/profile/_syncstate.py +48 -0
- learn_cli-0.5.1/learn/subjects/__init__.py +200 -0
- learn_cli-0.5.1/learn/subjects/conformance.py +278 -0
- learn_cli-0.5.1/learn/subjects/driver.py +119 -0
- learn_cli-0.5.1/learn/subjects/registry.json +29 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/pyproject.toml +5 -2
- learn_cli-0.5.1/site-astro/.gitignore +25 -0
- learn_cli-0.5.1/site-astro/.nvmrc +1 -0
- learn_cli-0.5.1/site-astro/README.md +335 -0
- learn_cli-0.5.1/site-astro/astro.config.mjs +20 -0
- learn_cli-0.5.1/site-astro/package-lock.json +4135 -0
- learn_cli-0.5.1/site-astro/package.json +23 -0
- learn_cli-0.5.1/site-astro/public/apple-touch-icon.png +0 -0
- learn_cli-0.5.1/site-astro/public/favicon.ico +0 -0
- learn_cli-0.5.1/site-astro/public/favicon.svg +16 -0
- learn_cli-0.5.1/site-astro/scripts/check-export-pages.mjs +154 -0
- learn_cli-0.5.1/site-astro/scripts/check-static-auth.mjs +360 -0
- learn_cli-0.5.1/site-astro/src/components/Footer.astro +137 -0
- learn_cli-0.5.1/site-astro/src/components/Header.astro +251 -0
- learn_cli-0.5.1/site-astro/src/components/LearnerPanelOverview.astro +152 -0
- learn_cli-0.5.1/site-astro/src/components/LearnerPanelSubject.astro +180 -0
- learn_cli-0.5.1/site-astro/src/components/Mark.astro +27 -0
- learn_cli-0.5.1/site-astro/src/components/PageHero.astro +62 -0
- learn_cli-0.5.1/site-astro/src/content-export/docs/portal.md +39 -0
- learn_cli-0.5.1/site-astro/src/content-export/docs/subject-plugin-contract.md +46 -0
- learn_cli-0.5.1/site-astro/src/content-export/docs/subjects.md +27 -0
- learn_cli-0.5.1/site-astro/src/content-export/meta.json +9 -0
- learn_cli-0.5.1/site-astro/src/content-export/stories-culture-guide.json +311 -0
- learn_cli-0.5.1/site-astro/src/content-export/stories-french.json +1086 -0
- learn_cli-0.5.1/site-astro/src/content-export/stories-spanish.json +1094 -0
- learn_cli-0.5.1/site-astro/src/content-export/subjects.json +95 -0
- learn_cli-0.5.1/site-astro/src/data/site.ts +30 -0
- learn_cli-0.5.1/site-astro/src/layouts/Layout.astro +104 -0
- learn_cli-0.5.1/site-astro/src/lib/api.ts +20 -0
- learn_cli-0.5.1/site-astro/src/lib/content.ts +99 -0
- learn_cli-0.5.1/site-astro/src/pages/[subject]/index.astro +250 -0
- learn_cli-0.5.1/site-astro/src/pages/[subject]/stories/[id]/index.astro +312 -0
- learn_cli-0.5.1/site-astro/src/pages/index.astro +175 -0
- learn_cli-0.5.1/site-astro/src/scripts/learner.js +278 -0
- learn_cli-0.5.1/site-astro/src/styles/global.css +398 -0
- learn_cli-0.5.1/site-astro/tsconfig.json +5 -0
- learn_cli-0.5.1/tests/conftest.py +182 -0
- learn_cli-0.5.1/tests/e2e/__init__.py +0 -0
- learn_cli-0.5.1/tests/e2e/conftest.py +84 -0
- learn_cli-0.5.1/tests/e2e/test_cli_golden.py +85 -0
- learn_cli-0.5.1/tests/e2e/test_mcp_harness.py +80 -0
- learn_cli-0.5.1/tests/fixtures/motivation/completed_track_current.json +59 -0
- learn_cli-0.5.1/tests/fixtures/motivation/completed_track_decayed.json +137 -0
- learn_cli-0.5.1/tests/fixtures/motivation/cross_subject.json +87 -0
- learn_cli-0.5.1/tests/fixtures/motivation/single_subject_progression.json +98 -0
- learn_cli-0.5.1/tests/fixtures/payloads/advice.json +20 -0
- learn_cli-0.5.1/tests/fixtures/payloads/doctor.json +30 -0
- learn_cli-0.5.1/tests/fixtures/payloads/error.json +5 -0
- learn_cli-0.5.1/tests/fixtures/payloads/lesson.json +42 -0
- learn_cli-0.5.1/tests/fixtures/payloads/overview.json +28 -0
- learn_cli-0.5.1/tests/fixtures/payloads/practice.json +40 -0
- learn_cli-0.5.1/tests/fixtures/payloads/progress.json +42 -0
- learn_cli-0.5.1/tests/fixtures/payloads/record.json +28 -0
- learn_cli-0.5.1/tests/fixtures/payloads/story.json +41 -0
- learn_cli-0.5.1/tests/fixtures/payloads/story_list.json +25 -0
- learn_cli-0.5.1/tests/fixtures/payloads/story_read.json +51 -0
- learn_cli-0.5.1/tests/fixtures/stories/culture-guide-scenario-overloaded-agent.json +76 -0
- learn_cli-0.5.1/tests/fixtures/stories/french-beginner-le-marche.json +86 -0
- learn_cli-0.5.1/tests/fixtures/stories/spanish-beginner-la-panaderia.json +86 -0
- learn_cli-0.5.1/tests/fixtures/subjects/conformant_subject.py +363 -0
- learn_cli-0.5.1/tests/fixtures/subjects/drifted_subject.py +130 -0
- learn_cli-0.5.1/tests/test_auth_cli.py +256 -0
- learn_cli-0.5.1/tests/test_cli_faces.py +163 -0
- learn_cli-0.5.1/tests/test_contract_schemas.py +146 -0
- learn_cli-0.5.1/tests/test_contract_validator.py +160 -0
- learn_cli-0.5.1/tests/test_front_app.py +177 -0
- learn_cli-0.5.1/tests/test_front_driver.py +121 -0
- learn_cli-0.5.1/tests/test_motivation_decay_review.py +122 -0
- learn_cli-0.5.1/tests/test_motivation_extras.py +110 -0
- learn_cli-0.5.1/tests/test_motivation_golden.py +101 -0
- learn_cli-0.5.1/tests/test_motivation_purity.py +144 -0
- learn_cli-0.5.1/tests/test_motivation_scoring.py +108 -0
- learn_cli-0.5.1/tests/test_motivation_streaks.py +110 -0
- learn_cli-0.5.1/tests/test_motivation_whatnext.py +240 -0
- learn_cli-0.5.1/tests/test_profile_store.py +164 -0
- learn_cli-0.5.1/tests/test_progress_next_record.py +290 -0
- learn_cli-0.5.1/tests/test_site_export.py +231 -0
- learn_cli-0.5.1/tests/test_story_fixtures.py +133 -0
- learn_cli-0.5.1/tests/test_subject_doctor.py +167 -0
- learn_cli-0.5.1/tests/test_subject_driver.py +132 -0
- learn_cli-0.5.1/tests/test_subject_registry.py +151 -0
- learn_cli-0.5.1/tests/test_surfaces_agree.py +67 -0
- learn_cli-0.5.1/tools/launch-gate/.gitignore +4 -0
- learn_cli-0.5.1/tools/launch-gate/README.md +167 -0
- learn_cli-0.5.1/tools/launch-gate/api-server.mjs +190 -0
- learn_cli-0.5.1/tools/launch-gate/launch_bar.py +177 -0
- learn_cli-0.5.1/tools/launch-gate/package.json +13 -0
- learn_cli-0.5.1/tools/launch-gate/report.py +117 -0
- learn_cli-0.5.1/tools/launch-gate/run.sh +106 -0
- learn_cli-0.5.1/tools/launch-gate/walk.mjs +394 -0
- learn_cli-0.5.1/uv.lock +1118 -0
- learn_cli-0.5.1/workers/learn-api/.gitignore +10 -0
- learn_cli-0.5.1/workers/learn-api/README.md +206 -0
- learn_cli-0.5.1/workers/learn-api/package.json +20 -0
- learn_cli-0.5.1/workers/learn-api/schema.sql +44 -0
- learn_cli-0.5.1/workers/learn-api/src/auth.js +50 -0
- learn_cli-0.5.1/workers/learn-api/src/db.js +77 -0
- learn_cli-0.5.1/workers/learn-api/src/github.js +119 -0
- learn_cli-0.5.1/workers/learn-api/src/index.js +336 -0
- learn_cli-0.5.1/workers/learn-api/src/progress.js +82 -0
- learn_cli-0.5.1/workers/learn-api/src/session.js +65 -0
- learn_cli-0.5.1/workers/learn-api/src/util.js +120 -0
- learn_cli-0.5.1/workers/learn-api/src/validate.js +112 -0
- learn_cli-0.5.1/workers/learn-api/test/helpers.js +167 -0
- learn_cli-0.5.1/workers/learn-api/test/session.test.js +45 -0
- learn_cli-0.5.1/workers/learn-api/test/validate.test.js +73 -0
- learn_cli-0.5.1/workers/learn-api/test/worker.test.js +475 -0
- learn_cli-0.5.1/workers/learn-api/wrangler.toml +66 -0
- learn_cli-0.4.2/learn/explain/catalog.py +0 -129
- learn_cli-0.4.2/uv.lock +0 -478
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/agent-config/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/agent-config/data/backend-fingerprints.yaml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/agent-config/scripts/show.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/ask-colleague/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/ask-colleague/prompts/explore.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/ask-colleague/prompts/review.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/ask-colleague/prompts/write.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/ask-colleague/scripts/ask-colleague.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/assign-to-workforce/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/assign-to-workforce/scripts/assign-to-workforce.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/scripts/_resolve-nick.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/scripts/portability-lint.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/scripts/pr-reply.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/scripts/pr-status.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/cicd/scripts/workflow.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/fetch-issues.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/mesh-message.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/post-comment.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/post-issue.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/templates/skill-new-brief.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/communicate/scripts/templates/skill-update-brief.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/doc-test-alignment/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/doc-test-alignment/scripts/check.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/pypi-maintainer/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/pypi-maintainer/scripts/switch-source.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/recall/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/recall/scripts/recall.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/remember/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/remember/scripts/remember.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/run-tests/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/run-tests/scripts/test.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/sonarclaude/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/sonarclaude/scripts/sonar.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/spec-to-plan/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/spec-to-plan/scripts/spec-to-plan.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/think/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/think/scripts/think.sh +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/version-bump/SKILL.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills/version-bump/scripts/bump.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.claude/skills.local.yaml.example +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.devague/current +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.devague/current_plan +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.devague/frames/agentculture-org-learn-is-live-one-learning-hub-in.json +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.devague/plans/agentculture-org-learn-is-live-one-learning-hub-in.json +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.flake8 +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.github/workflows/publish.yml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/.github/workflows/tests.yml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/AGENTS.colleague.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/LICENSE +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/README.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/culture.yaml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/docs/plans/.markdownlint-cli2.yaml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/docs/plans/2026-07-10-agentculture-org-learn-is-live-one-learning-hub-in.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/docs/skill-sources.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/docs/specs/.markdownlint-cli2.yaml +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/docs/specs/2026-07-10-agentculture-org-learn-is-live-one-learning-hub-in.md +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/__init__.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/__main__.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/__init__.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/cli.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/doctor.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/explain.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/learn.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/overview.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_commands/whoami.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_errors.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/cli/_output.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/learn/explain/__init__.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/sonar-project.properties +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/tests/__init__.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/tests/test_cli.py +0 -0
- {learn_cli-0.4.2 → learn_cli-0.5.1}/tests/test_cli_introspection.py +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
name: Deploy site
|
|
2
|
+
|
|
3
|
+
# learn-cli's web face: site-astro/ builds the /learn/ landing page, one
|
|
4
|
+
# sub-page per subject, and a story reader per story, then deploys to
|
|
5
|
+
# Cloudflare Pages. Modeled on org/site-astro's deploy.yml (same steps, same
|
|
6
|
+
# gating pattern) but targets a NEW, learn-cli-owned Pages project
|
|
7
|
+
# ("agentculture-learn") rather than org's "agentculture-org" — how (or
|
|
8
|
+
# whether) agentculture.org/learn/* gets routed to it is a separate,
|
|
9
|
+
# still-open integration decision; see site-astro/README.md.
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [main]
|
|
14
|
+
paths:
|
|
15
|
+
- 'site-astro/**'
|
|
16
|
+
- '.github/workflows/deploy-site.yml'
|
|
17
|
+
pull_request:
|
|
18
|
+
paths:
|
|
19
|
+
- 'site-astro/**'
|
|
20
|
+
- '.github/workflows/deploy-site.yml'
|
|
21
|
+
workflow_dispatch: {}
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
deploy:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
concurrency:
|
|
29
|
+
group: pages-deploy-${{ github.ref }}
|
|
30
|
+
cancel-in-progress: true
|
|
31
|
+
# Mapped here so later steps can gate with `if: env.CLOUDFLARE_API_TOKEN
|
|
32
|
+
# != ''` — secrets.* isn't usable directly in an `if:` conditional.
|
|
33
|
+
env:
|
|
34
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
35
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
38
|
+
|
|
39
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
40
|
+
with:
|
|
41
|
+
node-version-file: site-astro/.nvmrc
|
|
42
|
+
cache: npm
|
|
43
|
+
cache-dependency-path: site-astro/package-lock.json
|
|
44
|
+
|
|
45
|
+
- name: Install dependencies
|
|
46
|
+
working-directory: site-astro
|
|
47
|
+
run: npm ci
|
|
48
|
+
|
|
49
|
+
- name: Build site
|
|
50
|
+
working-directory: site-astro
|
|
51
|
+
run: npm run build
|
|
52
|
+
|
|
53
|
+
# Fails the job if the content-export fixtures and the built pages
|
|
54
|
+
# have drifted apart — a subject/module/story with no page, or a page
|
|
55
|
+
# with no export backing. See scripts/check-export-pages.mjs.
|
|
56
|
+
- name: Check export/page consistency
|
|
57
|
+
working-directory: site-astro
|
|
58
|
+
run: npm run check
|
|
59
|
+
|
|
60
|
+
# astro.config.mjs sets base: '/learn', so Astro's own bundled asset
|
|
61
|
+
# links (fonts, per-page CSS) come out of `astro build` as absolute
|
|
62
|
+
# `/learn/_astro/...` hrefs — but the physical dist/ tree stays flat
|
|
63
|
+
# (dist/_astro/..., no dist/learn/ nesting; only Astro's own dev/
|
|
64
|
+
# preview server understands `base` and rewrites around that). A dumb
|
|
65
|
+
# static host — Cloudflare Pages included — serves dist/ as literal
|
|
66
|
+
# paths, so those asset requests would 404 unless the deployed tree's
|
|
67
|
+
# physical layout matches the emitted hrefs. Wrapping dist/ under a
|
|
68
|
+
# `learn/` folder before upload fixes that for both the Pages preview
|
|
69
|
+
# domain (which then serves the site at .../learn/, not its bare
|
|
70
|
+
# root — intentional, so preview and production share one URL shape)
|
|
71
|
+
# and the eventual agentculture.org/learn mount. See site-astro/README.md
|
|
72
|
+
# ("Base path and relative links").
|
|
73
|
+
- name: Prepare deploy directory
|
|
74
|
+
run: |
|
|
75
|
+
mkdir -p site-astro/dist-deploy/learn
|
|
76
|
+
cp -r site-astro/dist/. site-astro/dist-deploy/learn/
|
|
77
|
+
# Bare-root visits (e.g. the raw *.pages.dev URL) land on /learn/
|
|
78
|
+
# instead of 404ing.
|
|
79
|
+
printf '/ /learn/ 302\n' > site-astro/dist-deploy/_redirects
|
|
80
|
+
|
|
81
|
+
# No cloudflare/wrangler-action — invoking wrangler directly via npx
|
|
82
|
+
# keeps the deploy dependency-light and auditable, matching org's
|
|
83
|
+
# deploy.yml (and this repo's zero-runtime-dependency temperament).
|
|
84
|
+
# site-astro/package.json is not touched: wrangler is fetched on
|
|
85
|
+
# demand, not installed as a devDep.
|
|
86
|
+
#
|
|
87
|
+
# --branch selects production vs. preview: the Cloudflare Pages project
|
|
88
|
+
# (agentculture-learn) has production branch "main", so pushing to main
|
|
89
|
+
# (or a manual workflow_dispatch off main) deploys to production, while
|
|
90
|
+
# a pull_request run passes the PR's head branch and gets a preview
|
|
91
|
+
# deployment alias instead.
|
|
92
|
+
- name: Deploy to Cloudflare Pages
|
|
93
|
+
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
|
|
94
|
+
run: >-
|
|
95
|
+
npx wrangler@4 pages deploy site-astro/dist-deploy
|
|
96
|
+
--project-name agentculture-learn
|
|
97
|
+
--branch "${{ github.head_ref || github.ref_name }}"
|
|
98
|
+
|
|
99
|
+
- name: Skip notice (Cloudflare secrets not configured)
|
|
100
|
+
if: env.CLOUDFLARE_API_TOKEN == '' || env.CLOUDFLARE_ACCOUNT_ID == ''
|
|
101
|
+
run: |
|
|
102
|
+
echo "::notice::CLOUDFLARE_API_TOKEN and/or CLOUDFLARE_ACCOUNT_ID are not configured on this repo yet — skipping the Cloudflare Pages deploy. The build and export/page check above still ran, so this validates the site builds cleanly."
|
|
103
|
+
{
|
|
104
|
+
echo '### Deploy skipped'
|
|
105
|
+
echo ''
|
|
106
|
+
echo 'The Cloudflare secrets (`CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`) are not both set on this repo yet, so the Cloudflare Pages deploy step was skipped. The build and export/page consistency check above still ran and validated cleanly.'
|
|
107
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
108
|
+
|
|
109
|
+
- name: Deployment summary
|
|
110
|
+
if: env.CLOUDFLARE_API_TOKEN != '' && env.CLOUDFLARE_ACCOUNT_ID != ''
|
|
111
|
+
run: |
|
|
112
|
+
BRANCH="${{ github.head_ref || github.ref_name }}"
|
|
113
|
+
{
|
|
114
|
+
echo '### Deployed to Cloudflare Pages'
|
|
115
|
+
echo ''
|
|
116
|
+
if [ "$BRANCH" = "main" ]; then
|
|
117
|
+
echo '- Production: https://agentculture-learn.pages.dev/learn/'
|
|
118
|
+
echo '- Production (custom domain, once the agentculture.org/learn route is wired up): https://agentculture.org/learn/'
|
|
119
|
+
else
|
|
120
|
+
echo "- Preview: https://${BRANCH}.agentculture-learn.pages.dev/learn/"
|
|
121
|
+
fi
|
|
122
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -16,6 +16,13 @@ config:
|
|
|
16
16
|
|
|
17
17
|
ignores:
|
|
18
18
|
- "node_modules/**"
|
|
19
|
+
# site-astro/ (and any future npm-managed subproject) has its own nested
|
|
20
|
+
# node_modules/ — a bare "node_modules/**" only matches the root one, so
|
|
21
|
+
# this recursive form is needed too (learn-cli#<t13>: the site-astro build).
|
|
22
|
+
- "**/node_modules/**"
|
|
23
|
+
# The uv virtualenv — third-party packages (starlette, uvicorn, httpx, …) ship
|
|
24
|
+
# their own LICENSE.md / README.md; never lint installed dependencies.
|
|
25
|
+
- ".venv/**"
|
|
19
26
|
- ".local/**"
|
|
20
27
|
- ".afi/**"
|
|
21
28
|
- ".teken/**"
|
|
@@ -23,3 +30,5 @@ ignores:
|
|
|
23
30
|
- ".claude/skills/**"
|
|
24
31
|
# devague working state (generated review artifacts, gitignored).
|
|
25
32
|
- ".devague/**"
|
|
33
|
+
# Generated by `learn site export` — registry content, not authored prose.
|
|
34
|
+
- "site-astro/src/content-export/**"
|
|
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
|
|
6
6
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.1] - 2026-07-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- API worker: the /learn zone-mount proxy strips Cookie/Authorization before forwarding to the Pages origin (review finding: session leak)
|
|
13
|
+
- API worker: withCors appends to an existing Vary header instead of overwriting it
|
|
14
|
+
- Profile ledger: created 0600 like auth.json (learner data is private)
|
|
15
|
+
- Profile ledger: a malformed ledger line is skipped instead of crashing progress/next/sync
|
|
16
|
+
|
|
17
|
+
## [0.5.0] - 2026-07-11
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- The learn platform: subject registry fronting french-cli, spanish-cli, and culture-guide as external runtimes (`learn subjects`, `learn subject doctor <name>` conformance gate)
|
|
22
|
+
- Subject-plugin contract v1.0: 11 versioned JSON schemas + stdlib validator as package data (learn/contract), directive pattern, story schema shared by all subjects
|
|
23
|
+
- Motivation layer (learn/motivation): deterministic scores, streaks, time-decayed mastery, review queues, and never-ending what-next over recorded history
|
|
24
|
+
- Learner profile (learn/profile + auth/progress/next/record verbs): GitHub device-flow sign-in, offline-first local ledger with best-effort sync
|
|
25
|
+
- Three faces via agentfront>=0.20: learn mcp serve (stdio MCP), learn site serve (agent-readable web), learn site export (pinned content export), surfaces-agree CI test
|
|
26
|
+
- Web face (site-astro/): 29 static pages in org's design system (zero new tokens), signed-out/in split with client hydration and a zero-API static proof, Cloudflare Pages deploy workflow
|
|
27
|
+
- API worker (workers/learn-api): GitHub OAuth, KV/D1 learner ledger, tutoring broker, and the agentculture.org/learn/* zone mount (API + static proxy in one worker)
|
|
28
|
+
- E2E launch gate (tools/launch-gate): 66 machine-checked assertions across web/CLI/MCP audiences with live-mode rerun
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Runtime dependencies: agentfront[mcp]>=0.20 (was zero-dependency scaffold)
|
|
33
|
+
- CLAUDE.md and lint config updated for the new surfaces
|
|
34
|
+
|
|
8
35
|
## [0.4.2] - 2026-07-11
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -46,8 +46,13 @@ consistent → cross-subject learner state → hosting + model-access wiring.
|
|
|
46
46
|
|
|
47
47
|
## Commands
|
|
48
48
|
|
|
49
|
-
Python ≥3.12, managed with **uv**. The runtime package
|
|
50
|
-
|
|
49
|
+
Python ≥3.12, managed with **uv**. The runtime package depends on
|
|
50
|
+
**`agentfront[mcp]>=0.20`** (`dependencies = ["agentfront[mcp]>=0.20"]`) — the
|
|
51
|
+
one library the three faces are derived from: the `mcp` extra powers
|
|
52
|
+
`learn mcp serve`, while the CLI/HTTP/export faces work without it. Everything
|
|
53
|
+
else (test + lint tooling) lives in the `dev` group. (The hand-rolled argparse
|
|
54
|
+
CLI under `learn/cli/` stays; agentfront backs the *parallel* App registry in
|
|
55
|
+
`learn/front/` that the MCP server, HTTP site, and `learn site export` read.)
|
|
51
56
|
|
|
52
57
|
```bash
|
|
53
58
|
uv sync # install deps + dev tools into .venv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learn-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: A CLI, MCP server, and web front for learning — a hosted site where humans and agents learn a subject step by step. Fronts the french-cli and spanish-cli language tutors, and generalizes to other learnable domains such as leading teams of agents. The learn command is the local operator surface and how agents drive it.
|
|
5
5
|
Project-URL: Homepage, https://github.com/agentculture/learn-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/agentculture/learn-cli/issues
|
|
@@ -13,6 +13,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Topic :: Software Development
|
|
15
15
|
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: agentfront[mcp]>=0.20
|
|
16
17
|
Description-Content-Type: text/markdown
|
|
17
18
|
|
|
18
19
|
# learn-cli
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Hosting and cost-when-busy note
|
|
2
|
+
|
|
3
|
+
This records the hosting choice for `agentculture.org/learn` and its
|
|
4
|
+
cost-when-busy profile, per the brief's honesty condition h8 (the hosting
|
|
5
|
+
choice must be recorded with a cost-when-busy note before build) and spec
|
|
6
|
+
decisions c21/c22.
|
|
7
|
+
|
|
8
|
+
## Shape of the deployment
|
|
9
|
+
|
|
10
|
+
Two surfaces, deliberately split by cost:
|
|
11
|
+
|
|
12
|
+
1. **Static shell + signed-out tour — Cloudflare Pages.** The subject catalog,
|
|
13
|
+
module sub-pages, sample stories, and the whole visitor tour are pre-built
|
|
14
|
+
static content (the `org` house style, Astro `output: 'static'`). Serving
|
|
15
|
+
them is free and infinitely cacheable at the edge.
|
|
16
|
+
2. **Dynamic layer — the learn API Worker** (`workers/learn-api/`). Auth
|
|
17
|
+
sessions, the learner ledger (KV + D1), and the tutoring broker. Only
|
|
18
|
+
signed-in learners reach it.
|
|
19
|
+
|
|
20
|
+
The load-bearing invariant: **signed-out traffic is served entirely from the
|
|
21
|
+
static pages and can never reach the broker.** Sign-in is the resource gate
|
|
22
|
+
(spec c21). `POST /api/tutor` is the only route that spends model tokens, and it
|
|
23
|
+
sits behind auth middleware that runs before the route body — so a visitor who
|
|
24
|
+
never signs in costs nothing beyond static bandwidth (free) and cannot trigger a
|
|
25
|
+
model call. This is enforced in code and proven by test
|
|
26
|
+
(`workers/learn-api/test/worker.test.js`).
|
|
27
|
+
|
|
28
|
+
## Cost-when-busy
|
|
29
|
+
|
|
30
|
+
Rough order-of-magnitude figures (Cloudflare pricing as of 2026; confirm
|
|
31
|
+
against the current plan before launch — these move):
|
|
32
|
+
|
|
33
|
+
| Component | When idle | When busy | Notes |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| Cloudflare Pages (static shell + tour) | free | free | Signed-out visitors, however many, cost ~0. Static assets cache at the edge. |
|
|
36
|
+
| Workers (the learn API) | ~$5/mo base (paid plan) | + per-request beyond the included allotment (~10M requests/mo included; ~$0.30 per additional million) | Only signed-in requests hit the Worker. CPU-time billing is negligible for this thin JSON layer. |
|
|
37
|
+
| KV (`SESSIONS`) | pennies | usage-priced reads/writes + storage | Only device-flow codes + revocation tombstones — tiny. |
|
|
38
|
+
| D1 (`learn-ledger`) | free tier covers early usage | usage-priced (rows read/written + storage) | Append-only ledger; one small row per recorded result. Reads on progress. |
|
|
39
|
+
| Inference (tutoring broker) | $0 | **per-use, and the dominant cost when busy** | Live tutoring tokens billed per-call through the served `cloudai-cli` / `ec2bedrock-cli` endpoint. Signed-in interactive lessons only. |
|
|
40
|
+
|
|
41
|
+
### The dominant term when busy is inference, and only signed-in learners incur it
|
|
42
|
+
|
|
43
|
+
Everything except inference is either free (static, Pages) or cheap and
|
|
44
|
+
usage-metered (Workers/KV/D1 for signed-in JSON traffic). The one materially
|
|
45
|
+
variable cost is **live model tokens**, and by construction it is gated behind
|
|
46
|
+
sign-in:
|
|
47
|
+
|
|
48
|
+
- A traffic spike of signed-out visitors (a launch, a link on the front page)
|
|
49
|
+
scales on free static bandwidth — **zero marginal model cost**.
|
|
50
|
+
- Only a signed-in learner running an interactive lesson brokers a model call,
|
|
51
|
+
and each such call is billed per-use by the served inference endpoint
|
|
52
|
+
(`cloudai-cli` / `ec2bedrock-cli`), never a bespoke provider SDK.
|
|
53
|
+
- Pre-generated content (stories, starter lessons) is batch-produced, human
|
|
54
|
+
reviewed, and committed to each subject repo (spec decision on model access),
|
|
55
|
+
so serving it is static and free — live calls are reserved for genuinely
|
|
56
|
+
interactive, personalized tutoring.
|
|
57
|
+
|
|
58
|
+
### Containing the busy-case bill
|
|
59
|
+
|
|
60
|
+
Levers available if signed-in inference volume grows:
|
|
61
|
+
|
|
62
|
+
- Rate-limit `POST /api/tutor` per learner (add a KV counter — the binding is
|
|
63
|
+
already present).
|
|
64
|
+
- Point `INFERENCE_URL` at a cheaper served model or an autoscaled
|
|
65
|
+
`ec2bedrock-cli` endpoint that scales to zero when idle.
|
|
66
|
+
- Cache/pre-generate more content so fewer interactions need a live call.
|
|
67
|
+
- Keep the token TTL short (already 1h) so abandoned sessions can't be replayed
|
|
68
|
+
to spend tokens.
|
|
69
|
+
|
|
70
|
+
## Why this split
|
|
71
|
+
|
|
72
|
+
It honors the brief's cost discipline: the expensive resource (model tokens) is
|
|
73
|
+
spent only for signed-in, interactive learning; the free resource (static edge
|
|
74
|
+
content) carries every anonymous visitor. The dynamic layer stays thin and
|
|
75
|
+
Cloudflare-native (Worker + KV + D1) rather than a always-on server, so idle
|
|
76
|
+
cost is near the Workers base plan and busy cost tracks actual signed-in usage.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# The subject-plugin contract (v1.0)
|
|
2
|
+
|
|
3
|
+
**Status:** authoritative for contract version `1.0` · **Owner:** learn-cli ·
|
|
4
|
+
**Machine-readable half:** `learn/contract/schemas/*.json` (package data, importable as `learn.contract`)
|
|
5
|
+
|
|
6
|
+
This is the central design of the learn uplift: the interface a subject CLI must satisfy to be
|
|
7
|
+
hosted by learn-cli. It descends from the converged spec
|
|
8
|
+
([`docs/specs/2026-07-10-agentculture-org-learn-is-live-one-learning-hub-in.md`](2026-07-10-agentculture-org-learn-is-live-one-learning-hub-in.md),
|
|
9
|
+
claims c9, c15/c17, c18, c27/c28, c29) and generalizes culture-guide's proven `teach` engine.
|
|
10
|
+
Everything downstream composes on it: the registry + conformance gate (t3), the french/spanish
|
|
11
|
+
implementations (t4/t5), the culture-guide adapter (t6), story content (t7/t8), and the
|
|
12
|
+
motivation layer (t9).
|
|
13
|
+
|
|
14
|
+
## 1. The model: portal, subject, driver
|
|
15
|
+
|
|
16
|
+
Three roles, three kinds of intelligence:
|
|
17
|
+
|
|
18
|
+
- **The subject CLI** (french, spanish, culture-guide, …) is an **LLM-free engine**. It owns
|
|
19
|
+
committed content (stories, lessons, exercises) and per-learner mastery state, resolves *what*
|
|
20
|
+
to teach next within itself, and emits **structured teaching directives**. It never converses,
|
|
21
|
+
never grades free text, never calls a model, and **never computes scores**.
|
|
22
|
+
- **The driver** (an agent or human harness — Claude Code, a colleague backend, the learn API's
|
|
23
|
+
tutoring broker) does the conversational tutoring the directive describes: presents, explains,
|
|
24
|
+
quizzes, grades `pass|partial|fail` against the answer or rubric, and **writes raw results
|
|
25
|
+
back** via the subject's `record` verb.
|
|
26
|
+
- **learn-cli** (the portal) drives subjects **as external runtimes over subprocess `--json`** —
|
|
27
|
+
it never imports subject code — and owns the uniform **motivation layer**: numeric scores per
|
|
28
|
+
exercise, per-subject and cross-subject streaks, time-decayed review queues, and adaptive
|
|
29
|
+
what-next, all computed **deterministically** from the recorded-result history. No LLM in the
|
|
30
|
+
scoring path; the same history yields the same numbers on web, CLI, and MCP.
|
|
31
|
+
|
|
32
|
+
This is the **directive pattern** (culture-guide's `teach` shape, generalized): the subject emits
|
|
33
|
+
a directive; the driver tutors; the driver records; the subject updates mastery; learn-cli
|
|
34
|
+
ledgers the raw result and derives motivation.
|
|
35
|
+
|
|
36
|
+
### State division (normative)
|
|
37
|
+
|
|
38
|
+
| Concern | Owner |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| Content: stories, lessons, exercises (committed files) | subject repo |
|
|
41
|
+
| Per-learner mastery ladder + item history, per subject | subject CLI (XDG-path state file) |
|
|
42
|
+
| Within-subject next-step recommendation | subject CLI (deterministic) |
|
|
43
|
+
| Conversational tutoring, grading of answers | driver |
|
|
44
|
+
| Raw-result ledger across subjects | learn-cli |
|
|
45
|
+
| Numeric scores, streaks, review queues, cross-subject what-next | learn-cli (pure functions of the ledger) |
|
|
46
|
+
| Cross-subject learner profile / identity | learn-cli |
|
|
47
|
+
|
|
48
|
+
Two hard prohibitions fall out of this:
|
|
49
|
+
|
|
50
|
+
1. **Subjects never emit scores.** The `record` ack's `recorded` object structurally forbids
|
|
51
|
+
`score`/`grade`/`points` fields (a `not` clause in `record.json`). Subjects report raw
|
|
52
|
+
observations only: `result`, optional `correct`/`total` counts, `duration_seconds`.
|
|
53
|
+
2. **learn-cli never contains subject content or progression logic.** Deleting a subject's
|
|
54
|
+
registry entry removes it from every face.
|
|
55
|
+
|
|
56
|
+
## 2. Conventions every verb follows
|
|
57
|
+
|
|
58
|
+
- **Invocation:** `<subject-command> <verb> [args] --json [--learner <id>]`. The subject command
|
|
59
|
+
is a console script on PATH (e.g. `french`), declared in the registry entry.
|
|
60
|
+
- **`--json` everywhere.** learn-cli only consumes `--json` output. Text mode exists for humans
|
|
61
|
+
and is uncontracted (rubric rules still apply inside the subject repo).
|
|
62
|
+
- **`--learner <id>`** on every learner-scoped verb. When omitted, the subject resolves a default
|
|
63
|
+
(its own env var, then the OS user) and **must echo the resolved id** in the payload's
|
|
64
|
+
`learner` field. The learner may be a human or an agent.
|
|
65
|
+
- **Streams:** results → stdout, errors/diagnostics → stderr, never mixed.
|
|
66
|
+
- **Exit codes:** `0` success · `1` user error · `2` environment error · `3+` reserved.
|
|
67
|
+
- **Errors:** on any failure, stderr carries the `error.json` shape
|
|
68
|
+
`{code, message, remediation}` in `--json` mode (`error: …` + `hint: …` in text mode), and the
|
|
69
|
+
process exits with `code`. No traceback ever reaches stderr.
|
|
70
|
+
- **Versioning:** every payload carries `schema_version` (see §5).
|
|
71
|
+
- **Open payloads:** schemas validate required structure but allow extra fields, so subjects can
|
|
72
|
+
extend (e.g. culture-guide's `unclear_questions_for_ori`) and minor contract versions can add
|
|
73
|
+
optional fields without breaking older validators. Consumers must ignore unknown fields.
|
|
74
|
+
|
|
75
|
+
## 3. The eight verbs
|
|
76
|
+
|
|
77
|
+
Schema files live in `learn/contract/schemas/`; golden example payloads (validated in CI) in
|
|
78
|
+
`tests/fixtures/payloads/`. "Learner-scoped" means the verb reads or writes per-learner state.
|
|
79
|
+
|
|
80
|
+
| # | Verb | Invocation | Payload schema | `kind` | Learner-scoped | Writes state |
|
|
81
|
+
| - | --- | --- | --- | --- | --- | --- |
|
|
82
|
+
| 1 | overview | `overview --json` | `overview.json` | `subject_overview` | no | no |
|
|
83
|
+
| 2 | progress | `progress --json` | `progress.json` | `progress` | yes | no |
|
|
84
|
+
| 3 | advice | `advice --json` | `advice.json` | `advice` | yes | no |
|
|
85
|
+
| 4 | story | `story list --json` / `story read <id> --json` | `story_list.json` / `story_read.json` | `story_list` / `story_read` | list: no · read: yes | read: may set current position |
|
|
86
|
+
| 5 | lesson | `lesson start [<target>]` / `lesson next` / `lesson repeat [<id>] [--harder]`, all `--json` | `lesson.json` | `lesson_directive` | yes | yes (current lesson; first exposure lifts items to `introduced`) |
|
|
87
|
+
| 6 | practice | `practice [<scope>] --json` | `practice.json` | `practice_directive` | yes | no |
|
|
88
|
+
| 7 | record | `record --item <id> --result pass\|partial\|fail […] --json` | `record.json` | `record_ack` | yes | yes (history + mastery) |
|
|
89
|
+
| 8 | doctor | `doctor --json` | `doctor.json` | `subject_doctor` | no | no |
|
|
90
|
+
|
|
91
|
+
### 3.1 `overview` — the subject describes itself
|
|
92
|
+
|
|
93
|
+
Identity, description, ordered **modules** (the web face renders one sub-page per module), and
|
|
94
|
+
content counts. Learner-independent and deterministic, so the static site can be built from it
|
|
95
|
+
with zero model or state access.
|
|
96
|
+
|
|
97
|
+
### 3.2 `progress` — where the learner stands
|
|
98
|
+
|
|
99
|
+
Per-item mastery map on the shared ladder (§4), counters
|
|
100
|
+
(`items_total`/`items_touched`/`items_mastered`), completed and weak item lists, timestamps, and
|
|
101
|
+
the subject's own `next` recommendation `{done, text, command, item_id?, module_id?}`. `done:
|
|
102
|
+
true` never dead-ends the learner: it is the signal for learn-cli to switch to maintenance +
|
|
103
|
+
depth mode (review decayed items, `lesson repeat --harder`, fresh stories).
|
|
104
|
+
|
|
105
|
+
### 3.3 `advice` — deterministic study advice
|
|
106
|
+
|
|
107
|
+
What to shore up and why, each entry with a runnable `command`. A pure function of stored state —
|
|
108
|
+
the "tutor's opinion" a driver can relay verbatim. May be empty for a brand-new learner.
|
|
109
|
+
|
|
110
|
+
### 3.4 `story` — the shared content surface
|
|
111
|
+
|
|
112
|
+
`story list` returns level-tagged summaries (id, title, level, exercise count) — enough for the
|
|
113
|
+
public catalog. `story read <id>` returns the **full story object exactly as committed**
|
|
114
|
+
(`story.json`, §6) wrapped in a directive: present paragraph-at-a-time, use the glossary on
|
|
115
|
+
demand, run the comprehension exercises, record each result. Unknown story id → exit 1 with the
|
|
116
|
+
error shape.
|
|
117
|
+
|
|
118
|
+
### 3.5 `lesson` — start / next / repeat
|
|
119
|
+
|
|
120
|
+
The subject resolves *what* to teach — `start` (optionally targeted at a module or item), `next`
|
|
121
|
+
(continue from mastery state), or `repeat [<id>] [--harder]` (re-issue a completed lesson;
|
|
122
|
+
`--harder` increments the lesson's integer `difficulty` rung — the repeatable-lessons half of
|
|
123
|
+
never-ending progression). The payload carries the lesson's items (each with teachable `points`)
|
|
124
|
+
plus the directive (optionally with a `persona`). Item ids in the lesson are exactly what
|
|
125
|
+
`record --item` expects back.
|
|
126
|
+
|
|
127
|
+
### 3.6 `practice` — exercises to run
|
|
128
|
+
|
|
129
|
+
A batch of exercises scoped to an item, a module, or `review` (no argument: the subject picks its
|
|
130
|
+
weakest touched items). Exercise types: `multiple_choice`, `true_false`, `cloze`, `short_answer`,
|
|
131
|
+
`translation`, `open`, `discussion`. Checkable types carry `answer`; open types carry `rubric`
|
|
132
|
+
(what passes, what is partial).
|
|
133
|
+
|
|
134
|
+
### 3.7 `record` — the write-back (the motivation layer's input)
|
|
135
|
+
|
|
136
|
+
The driver reports one graded outcome:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
french record --learner ori --item numbers-money --activity practice \
|
|
140
|
+
--exercise nm-p1 --result pass --correct 1 --total 1 --duration-seconds 38 --json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The subject appends the raw result to its history, updates the item's mastery (inferred from
|
|
144
|
+
`result` unless `--mastery` is given explicitly; inference never regresses a level), and acks
|
|
145
|
+
with:
|
|
146
|
+
|
|
147
|
+
- `recorded` — the normalized raw-result object `{item_id, activity, exercise_id?, story_id?,
|
|
148
|
+
lesson_id?, result, correct?, total?, duration_seconds?, notes?, at}`. **This object is the
|
|
149
|
+
contract's scoring input:** learn-cli appends it verbatim to its cross-subject ledger, and the
|
|
150
|
+
motivation layer (t9) computes scores/streaks/review-queues from the ledger alone. It
|
|
151
|
+
structurally rejects `score`/`grade`/`points`.
|
|
152
|
+
- `mastery` — the item's post-record level.
|
|
153
|
+
- `next` — the refreshed within-subject recommendation.
|
|
154
|
+
|
|
155
|
+
Result inference default (culture-guide's proven mapping): `fail → introduced`,
|
|
156
|
+
`partial → practiced`, `pass → mastered`, never regressing.
|
|
157
|
+
|
|
158
|
+
### 3.8 `doctor` — self-check + contract pin
|
|
159
|
+
|
|
160
|
+
Health checks in the established doctor shape (`{id, passed, severity, message, remediation}`)
|
|
161
|
+
plus **`contract_version`** — the contract version this subject pins. `learn subject doctor`
|
|
162
|
+
(t3) reads the pin first, then validates the other seven verbs' payloads against that version's
|
|
163
|
+
schemas. Exit 0 when healthy, 2 when not. Recommended checks: content files validate against
|
|
164
|
+
`story.json`, learner-state dir writable, pinned contract version supported.
|
|
165
|
+
|
|
166
|
+
## 4. Shared vocabularies
|
|
167
|
+
|
|
168
|
+
Exported as constants from `learn.contract` and embedded in the schemas (tests assert they
|
|
169
|
+
agree):
|
|
170
|
+
|
|
171
|
+
- **Mastery ladder** (ordered): `unknown → introduced → practiced → mastered`.
|
|
172
|
+
- **Raw results:** `pass | partial | fail`.
|
|
173
|
+
- **Story/lesson levels:** `beginner | intermediate | advanced` (finer grading goes in
|
|
174
|
+
`level_detail`, e.g. `"A1"`, `"B2"`, `"scenario"`).
|
|
175
|
+
- **Ids:** subjects match `^[a-z][a-z0-9-]*$`; items/stories/lessons/exercises/checks match
|
|
176
|
+
`^[a-z0-9][a-z0-9._-]*$`. Item ids are the join key across `lesson`/`practice`/`story`
|
|
177
|
+
exercises, `record`, and `progress.mastery`.
|
|
178
|
+
|
|
179
|
+
## 5. Versioning (`schema_version`)
|
|
180
|
+
|
|
181
|
+
- The contract version is a `major.minor` string; this document and the shipped schemas are
|
|
182
|
+
**`1.0`** (`learn.contract.CONTRACT_VERSION`).
|
|
183
|
+
- **Every payload** (and every story content file) carries `schema_version`; v1 schemas pin the
|
|
184
|
+
pattern `^1\.[0-9]+$`, so a v2 payload fails v1 validation loudly.
|
|
185
|
+
- **Minor bump** (`1.0 → 1.1`): additive only — new optional fields, new enum members at the
|
|
186
|
+
tail. Older consumers keep validating (open payloads, §2).
|
|
187
|
+
- **Major bump** (`2.0`): anything breaking — removed/renamed fields, changed semantics, new
|
|
188
|
+
required fields. Requires new schema files and a coordinated subject migration.
|
|
189
|
+
- Subjects **pin** the version they satisfy in `doctor.contract_version`; each subject repo runs
|
|
190
|
+
the conformance gate in its own CI, so drift fails the subject's build, not learn-cli's
|
|
191
|
+
runtime.
|
|
192
|
+
- The `error.json` stderr shape predates the contract (it is the template chassis's `CliError`)
|
|
193
|
+
and carries no `schema_version`.
|
|
194
|
+
|
|
195
|
+
## 6. The story schema (`story.json`)
|
|
196
|
+
|
|
197
|
+
One schema for all subjects (spec c29): language subjects ship **graded-reader ladders**;
|
|
198
|
+
culture-guide ships **narrative scenarios** (an agent-team incident the learner reasons through)
|
|
199
|
+
in the same shape. Stories are committed content files in each subject repo, validated against
|
|
200
|
+
this schema in the subject's CI, and embedded verbatim in `story read` payloads.
|
|
201
|
+
|
|
202
|
+
| Field | Req | Meaning |
|
|
203
|
+
| --- | --- | --- |
|
|
204
|
+
| `schema_version` | ✓ | contract version family (`^1\.[0-9]+$`) |
|
|
205
|
+
| `kind` | ✓ | const `story` |
|
|
206
|
+
| `id` | ✓ | stable slug, unique within the subject |
|
|
207
|
+
| `subject` | ✓ | owning subject id |
|
|
208
|
+
| `title` | ✓ | display title |
|
|
209
|
+
| `level` | ✓ | `beginner \| intermediate \| advanced` |
|
|
210
|
+
| `level_detail` | – | finer grading: CEFR tag or `scenario` |
|
|
211
|
+
| `language` | – | BCP-47 tag of the body (`fr`, `es`, `en`) |
|
|
212
|
+
| `summary` | – | one-line teaser, safe for the signed-out catalog |
|
|
213
|
+
| `body` | ✓ | the story text (markdown, blank-line paragraphs) |
|
|
214
|
+
| `glossary` | ✓ | glossary/annotations: `[{term, definition, note?}]` (may be empty) |
|
|
215
|
+
| `exercises` | ✓ | ≥1 comprehension exercises (same exercise shape as `practice`) |
|
|
216
|
+
| `audio` | – | **reserved slot** for the spoken-practice follow-up: `null` or `{url?, format?, voice?, duration_seconds?}`; producers omit/null until the audio pipeline exists |
|
|
217
|
+
| `source` | – | provenance for the batch pipeline: `{generator?, model?, reviewed_by?, generated_at?}` |
|
|
218
|
+
|
|
219
|
+
Three reference fixtures validate against it in this repo's test suite
|
|
220
|
+
(`tests/test_story_fixtures.py`), one per launch subject:
|
|
221
|
+
|
|
222
|
+
- `tests/fixtures/stories/french-beginner-le-marche.json` — French graded story (A1).
|
|
223
|
+
- `tests/fixtures/stories/spanish-beginner-la-panaderia.json` — Spanish graded story (A1).
|
|
224
|
+
- `tests/fixtures/stories/culture-guide-scenario-overloaded-agent.json` — culture-guide
|
|
225
|
+
narrative scenario.
|
|
226
|
+
|
|
227
|
+
## 7. How a new subject registers (registration, not a fork)
|
|
228
|
+
|
|
229
|
+
Adding subject #4 requires **zero new platform code** in learn-cli:
|
|
230
|
+
|
|
231
|
+
1. **Build a conformant CLI** — implement the eight verbs over the subject's own content and
|
|
232
|
+
state (the french/spanish implementation is the reference; culture-guide's adapter shows the
|
|
233
|
+
wrap-an-existing-engine path: `teach status → progress`, `teach lesson → lesson`,
|
|
234
|
+
`teach quiz → practice`, `teach record → record`).
|
|
235
|
+
2. **Add a registry entry** in learn-cli's data-driven subject registry (t3) — no code, just
|
|
236
|
+
data: subject `id`, `display_name`, the executable `command` (console script on PATH), and
|
|
237
|
+
the pinned `contract_version`.
|
|
238
|
+
3. **Prove conformance** — `learn subject doctor <subject>` must pass: it spawns the subject's
|
|
239
|
+
verbs as subprocesses, validates each `--json` payload against the pinned version's schemas,
|
|
240
|
+
and checks the exit-code and stream contracts. Wire the same gate into the subject repo's CI.
|
|
241
|
+
4. **Ship content** — committed story/lesson files that validate against `story.json` (launch
|
|
242
|
+
bar: ≥10 stories across ≥3 levels for a language, ≥3 scenarios for a non-language subject).
|
|
243
|
+
|
|
244
|
+
Deleting the registry entry cleanly removes the subject from all three faces.
|
|
245
|
+
|
|
246
|
+
## 8. Using the machine-readable contract
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from learn import contract
|
|
250
|
+
|
|
251
|
+
contract.CONTRACT_VERSION # "1.0"
|
|
252
|
+
contract.SCHEMA_NAMES # the 11 shipped schemas
|
|
253
|
+
schema = contract.load_schema("story")
|
|
254
|
+
errors = contract.validate(payload, "story") # [] means valid
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Schemas are package data (`learn/contract/schemas/*.json`) loaded via `importlib.resources`, so
|
|
258
|
+
the installed wheel carries them — t3's conformance gate and any subject repo's CI can validate
|
|
259
|
+
without this repo checked out. Validation is stdlib-only (`learn/contract/_validate.py`);
|
|
260
|
+
runtime dependencies stay empty. The validator supports exactly the keyword subset the schemas
|
|
261
|
+
use, and a guard test (`test_schema_uses_only_supported_keywords`) keeps schemas inside that
|
|
262
|
+
subset. Cross-file references are limited to sibling schemas (`story_read.json` → `story.json`).
|
|
@@ -62,11 +62,19 @@ def _argv_has_json(argv: list[str] | None) -> bool:
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def _build_parser() -> argparse.ArgumentParser:
|
|
65
|
+
from learn.cli._commands import auth as _auth_group
|
|
65
66
|
from learn.cli._commands import cli as _cli_group
|
|
66
67
|
from learn.cli._commands import doctor as _doctor_cmd
|
|
67
68
|
from learn.cli._commands import explain as _explain_cmd
|
|
68
69
|
from learn.cli._commands import learn as _learn_cmd
|
|
70
|
+
from learn.cli._commands import mcp as _mcp_group
|
|
71
|
+
from learn.cli._commands import next_cmd as _next_cmd
|
|
69
72
|
from learn.cli._commands import overview as _overview_cmd
|
|
73
|
+
from learn.cli._commands import progress as _progress_cmd
|
|
74
|
+
from learn.cli._commands import record as _record_cmd
|
|
75
|
+
from learn.cli._commands import site as _site_group
|
|
76
|
+
from learn.cli._commands import subject as _subject_group
|
|
77
|
+
from learn.cli._commands import subjects as _subjects_cmd
|
|
70
78
|
from learn.cli._commands import whoami as _whoami_cmd
|
|
71
79
|
|
|
72
80
|
parser = _CliArgumentParser(
|
|
@@ -88,6 +96,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
88
96
|
_overview_cmd.register(sub)
|
|
89
97
|
_doctor_cmd.register(sub)
|
|
90
98
|
_cli_group.register(sub)
|
|
99
|
+
_subjects_cmd.register(sub)
|
|
100
|
+
_subject_group.register(sub)
|
|
101
|
+
_mcp_group.register(sub)
|
|
102
|
+
_site_group.register(sub)
|
|
103
|
+
_auth_group.register(sub)
|
|
104
|
+
_progress_cmd.register(sub)
|
|
105
|
+
_next_cmd.register(sub)
|
|
106
|
+
_record_cmd.register(sub)
|
|
91
107
|
# Register your own noun groups here:
|
|
92
108
|
# from learn.cli._commands import my_noun as _my_noun_group
|
|
93
109
|
# _my_noun_group.register(sub)
|