phrase-gym 0.2.0__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.
Files changed (125) hide show
  1. phrase_gym-0.2.0/.coveragerc +17 -0
  2. phrase_gym-0.2.0/.github/workflows/ci.yml +117 -0
  3. phrase_gym-0.2.0/.github/workflows/docs.yml +43 -0
  4. phrase_gym-0.2.0/.github/workflows/pip_publish.yml +63 -0
  5. phrase_gym-0.2.0/.github/workflows/static.yml +24 -0
  6. phrase_gym-0.2.0/.gitignore +40 -0
  7. phrase_gym-0.2.0/.nvmrc +1 -0
  8. phrase_gym-0.2.0/.pre-commit-config.yaml +60 -0
  9. phrase_gym-0.2.0/.python-version +1 -0
  10. phrase_gym-0.2.0/AGENTS.md +102 -0
  11. phrase_gym-0.2.0/CLAUDE.md +1 -0
  12. phrase_gym-0.2.0/LICENSE +20 -0
  13. phrase_gym-0.2.0/PKG-INFO +139 -0
  14. phrase_gym-0.2.0/README.md +97 -0
  15. phrase_gym-0.2.0/activate.sh +14 -0
  16. phrase_gym-0.2.0/docs/common/reference.md +8 -0
  17. phrase_gym-0.2.0/docs/development.md +75 -0
  18. phrase_gym-0.2.0/docs/importing-phrases.md +104 -0
  19. phrase_gym-0.2.0/docs/mkdocs.yml +33 -0
  20. phrase_gym-0.2.0/docs/src/en/index.md +14 -0
  21. phrase_gym-0.2.0/docs/src/en/installation.md +32 -0
  22. phrase_gym-0.2.0/examples/meeting-phrases.json +44 -0
  23. phrase_gym-0.2.0/frontend/.prettierignore +6 -0
  24. phrase_gym-0.2.0/frontend/.prettierrc.json +6 -0
  25. phrase_gym-0.2.0/frontend/index.html +2 -0
  26. phrase_gym-0.2.0/frontend/openapi.json +1335 -0
  27. phrase_gym-0.2.0/frontend/package-lock.json +2332 -0
  28. phrase_gym-0.2.0/frontend/package.json +34 -0
  29. phrase_gym-0.2.0/frontend/playwright.config.ts +25 -0
  30. phrase_gym-0.2.0/frontend/src/App.vue +165 -0
  31. phrase_gym-0.2.0/frontend/src/api.generated.ts +923 -0
  32. phrase_gym-0.2.0/frontend/src/api.ts +27 -0
  33. phrase_gym-0.2.0/frontend/src/audioSession.ts +33 -0
  34. phrase_gym-0.2.0/frontend/src/captureOwners.ts +35 -0
  35. phrase_gym-0.2.0/frontend/src/components/AddMaterial.vue +166 -0
  36. phrase_gym-0.2.0/frontend/src/components/SavedHistory.vue +28 -0
  37. phrase_gym-0.2.0/frontend/src/components/StudyCard.vue +118 -0
  38. phrase_gym-0.2.0/frontend/src/composables/useCapture.ts +214 -0
  39. phrase_gym-0.2.0/frontend/src/composables/usePlayback.ts +118 -0
  40. phrase_gym-0.2.0/frontend/src/composables/useStudy.ts +49 -0
  41. phrase_gym-0.2.0/frontend/src/discardedLearnerData.ts +29 -0
  42. phrase_gym-0.2.0/frontend/src/main.ts +7 -0
  43. phrase_gym-0.2.0/frontend/src/practice.css +71 -0
  44. phrase_gym-0.2.0/frontend/src/practiceJournal.ts +128 -0
  45. phrase_gym-0.2.0/frontend/src/practiceTypes.ts +13 -0
  46. phrase_gym-0.2.0/frontend/src/study.css +97 -0
  47. phrase_gym-0.2.0/frontend/src/studyTypes.ts +6 -0
  48. phrase_gym-0.2.0/frontend/src/style.css +299 -0
  49. phrase_gym-0.2.0/frontend/src/takeJournalRecovery.ts +32 -0
  50. phrase_gym-0.2.0/frontend/src/takeTransfers.ts +82 -0
  51. phrase_gym-0.2.0/frontend/tests/audio-session.test.ts +59 -0
  52. phrase_gym-0.2.0/frontend/tests/browser/fixtures.ts +61 -0
  53. phrase_gym-0.2.0/frontend/tests/browser/offline-import.spec.ts +41 -0
  54. phrase_gym-0.2.0/frontend/tests/browser/silent-playback.ts +18 -0
  55. phrase_gym-0.2.0/frontend/tests/browser/study.spec.ts +774 -0
  56. phrase_gym-0.2.0/frontend/tests/journal-reset.test.ts +58 -0
  57. phrase_gym-0.2.0/frontend/tests/silent-contexts.test.ts +36 -0
  58. phrase_gym-0.2.0/frontend/tests/study.test.ts +51 -0
  59. phrase_gym-0.2.0/frontend/tests/take-transfers.test.ts +77 -0
  60. phrase_gym-0.2.0/frontend/tsconfig.json +15 -0
  61. phrase_gym-0.2.0/frontend/vite.config.ts +8 -0
  62. phrase_gym-0.2.0/frontend/vitest.config.ts +2 -0
  63. phrase_gym-0.2.0/invoke.yml +5 -0
  64. phrase_gym-0.2.0/pyproject.toml +63 -0
  65. phrase_gym-0.2.0/pytest.ini +4 -0
  66. phrase_gym-0.2.0/scripts/__init__.py +0 -0
  67. phrase_gym-0.2.0/scripts/generate_api.py +13 -0
  68. phrase_gym-0.2.0/scripts/release.py +90 -0
  69. phrase_gym-0.2.0/scripts/verup.sh +4 -0
  70. phrase_gym-0.2.0/scripts/verup_action.sh +4 -0
  71. phrase_gym-0.2.0/specs/START-HERE.md +17 -0
  72. phrase_gym-0.2.0/specs/architecture.md +63 -0
  73. phrase_gym-0.2.0/specs/decision-core-trainer.md +46 -0
  74. phrase_gym-0.2.0/specs/decision-external-preparation-agent.md +35 -0
  75. phrase_gym-0.2.0/specs/decision-online-only.md +9 -0
  76. phrase_gym-0.2.0/specs/decision-personal-preferences.md +21 -0
  77. phrase_gym-0.2.0/specs/decision-recording-and-comparison.md +31 -0
  78. phrase_gym-0.2.0/specs/decision-targeted-drilling.md +47 -0
  79. phrase_gym-0.2.0/specs/decision-transfer-and-engagement.md +39 -0
  80. phrase_gym-0.2.0/specs/open-problems.md +12 -0
  81. phrase_gym-0.2.0/specs/plan/implementation-plan.md +107 -0
  82. phrase_gym-0.2.0/specs/plan/orchestration-handout.md +38 -0
  83. phrase_gym-0.2.0/specs/research/README.md +37 -0
  84. phrase_gym-0.2.0/specs/research/automatic-use.md +33 -0
  85. phrase_gym-0.2.0/specs/research/motivation.md +18 -0
  86. phrase_gym-0.2.0/specs/research/phrase-prompts.md +63 -0
  87. phrase_gym-0.2.0/specs/research/spacing-and-grading.md +23 -0
  88. phrase_gym-0.2.0/specs/research/words-in-context.md +47 -0
  89. phrase_gym-0.2.0/src/phrase_gym/__about__.py +1 -0
  90. phrase_gym-0.2.0/src/phrase_gym/__init__.py +5 -0
  91. phrase_gym-0.2.0/src/phrase_gym/coach/__init__.py +0 -0
  92. phrase_gym-0.2.0/src/phrase_gym/coach/bounded_process.py +81 -0
  93. phrase_gym-0.2.0/src/phrase_gym/coach/main.py +66 -0
  94. phrase_gym-0.2.0/src/phrase_gym/coach/speech.py +174 -0
  95. phrase_gym-0.2.0/src/phrase_gym/deploy/__init__.py +0 -0
  96. phrase_gym-0.2.0/src/phrase_gym/server/__init__.py +0 -0
  97. phrase_gym-0.2.0/src/phrase_gym/server/api/__init__.py +0 -0
  98. phrase_gym-0.2.0/src/phrase_gym/server/api/coach.py +34 -0
  99. phrase_gym-0.2.0/src/phrase_gym/server/api/imports.py +41 -0
  100. phrase_gym-0.2.0/src/phrase_gym/server/api/recordings.py +92 -0
  101. phrase_gym-0.2.0/src/phrase_gym/server/api/study.py +38 -0
  102. phrase_gym-0.2.0/src/phrase_gym/server/app.py +43 -0
  103. phrase_gym-0.2.0/src/phrase_gym/server/boundary.py +94 -0
  104. phrase_gym-0.2.0/src/phrase_gym/server/contracts.py +147 -0
  105. phrase_gym-0.2.0/src/phrase_gym/server/importer.py +171 -0
  106. phrase_gym-0.2.0/src/phrase_gym/server/main.py +51 -0
  107. phrase_gym-0.2.0/src/phrase_gym/server/publication.py +61 -0
  108. phrase_gym-0.2.0/src/phrase_gym/server/reference_audio.py +48 -0
  109. phrase_gym-0.2.0/src/phrase_gym/server/static/assets/index-BtTv3vAH.js +19 -0
  110. phrase_gym-0.2.0/src/phrase_gym/server/static/assets/index-CKTwL26h.css +1 -0
  111. phrase_gym-0.2.0/src/phrase_gym/server/static/index.html +4 -0
  112. phrase_gym-0.2.0/src/phrase_gym/server/storage.py +101 -0
  113. phrase_gym-0.2.0/src/phrase_gym/server/study.py +187 -0
  114. phrase_gym-0.2.0/tasks.py +163 -0
  115. phrase_gym-0.2.0/tests/browser_server.py +29 -0
  116. phrase_gym-0.2.0/tests/conftest.py +41 -0
  117. phrase_gym-0.2.0/tests/test_boundary.py +35 -0
  118. phrase_gym-0.2.0/tests/test_cli.py +44 -0
  119. phrase_gym-0.2.0/tests/test_imports.py +203 -0
  120. phrase_gym-0.2.0/tests/test_release.py +133 -0
  121. phrase_gym-0.2.0/tests/test_speech.py +161 -0
  122. phrase_gym-0.2.0/tests/test_tailscale.py +62 -0
  123. phrase_gym-0.2.0/tests/test_tasks.py +98 -0
  124. phrase_gym-0.2.0/tests/test_workflow.py +124 -0
  125. phrase_gym-0.2.0/uv.lock +887 -0
@@ -0,0 +1,17 @@
1
+ [run]
2
+ relative_files = true
3
+ omit =
4
+ */tests/*
5
+ */*_check.py
6
+ */__init__.py
7
+ */conftest.py
8
+ */.tox/*
9
+ */.venv/*
10
+ */.eggs/*
11
+ */docs/*
12
+ */build/*
13
+ */dist/*
14
+ */.git/*
15
+ */.github/*
16
+ */.vscode/*
17
+ */.pytest_cache/*
@@ -0,0 +1,117 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_call:
8
+
9
+ permissions:
10
+ contents: write
11
+ pull-requests: write
12
+
13
+ jobs:
14
+ server:
15
+ name: Server (Linux)
16
+ runs-on: ubuntu-24.04
17
+ concurrency:
18
+ group: github-pages
19
+ cancel-in-progress: false
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v6
23
+ with:
24
+ version: 0.11.7
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: '3.13'
28
+ - run: uv sync --frozen
29
+ - run: >-
30
+ uv run pytest --junitxml=pytest.xml --alluredir=allure-results
31
+ --cov=phrase_gym --cov-report=term-missing --cov-report=xml --cov-report=html
32
+ - name: Upload test and coverage results
33
+ if: always()
34
+ uses: actions/upload-artifact@v4
35
+ with:
36
+ name: server-test-results
37
+ path: |
38
+ pytest.xml
39
+ coverage.xml
40
+ htmlcov/
41
+ allure-results/
42
+ - name: Load Allure history
43
+ if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'
44
+ uses: actions/checkout@v4
45
+ continue-on-error: true
46
+ with:
47
+ ref: gh-pages
48
+ path: gh-pages-dir
49
+ - name: Generate Allure report
50
+ if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'
51
+ uses: andgineer/allure-report@v3.6
52
+ id: allure-report
53
+ with:
54
+ allure-results: allure-results
55
+ website: gh-pages-dir
56
+ reports-site-path: builds/tests
57
+ - name: Publish Allure report
58
+ if: always() && steps.allure-report.outcome == 'success'
59
+ uses: peaceiris/actions-gh-pages@v4
60
+ with:
61
+ github_token: ${{ secrets.GITHUB_TOKEN }}
62
+ publish_branch: gh-pages
63
+ publish_dir: ${{ steps.allure-report.outputs.reports-site }}
64
+ destination_dir: ${{ steps.allure-report.outputs.reports-site-path }}
65
+ - name: Coverage comment
66
+ if: always() && (github.event_name == 'pull_request' || github.ref == 'refs/heads/main')
67
+ id: coverage-comment
68
+ uses: py-cov-action/python-coverage-comment-action@v3
69
+ with:
70
+ GITHUB_TOKEN: ${{ github.token }}
71
+ MINIMUM_GREEN: 85
72
+ MINIMUM_ORANGE: 70
73
+ - name: Store coverage comment
74
+ if: steps.coverage-comment.outputs.COMMENT_FILE_WRITTEN == 'true'
75
+ uses: actions/upload-artifact@v4
76
+ with:
77
+ name: python-coverage-comment-action
78
+ path: python-coverage-comment-action.txt
79
+ - name: Coveralls
80
+ if: always() && github.event_name != 'workflow_call'
81
+ continue-on-error: true
82
+ uses: coverallsapp/github-action@v2
83
+ - name: Codecov
84
+ if: always() && github.event_name != 'workflow_call'
85
+ continue-on-error: true
86
+ uses: codecov/codecov-action@v4.0.1
87
+ with:
88
+ fail_ci_if_error: true
89
+ token: ${{ secrets.CODECOV_TOKEN }}
90
+ checks:
91
+ name: Full checks (macOS)
92
+ runs-on: macos-15
93
+ steps:
94
+ - uses: actions/checkout@v4
95
+ - uses: astral-sh/setup-uv@v6
96
+ with:
97
+ version: 0.11.7
98
+ - uses: actions/setup-python@v5
99
+ with:
100
+ python-version: '3.13'
101
+ - uses: actions/setup-node@v4
102
+ with:
103
+ node-version-file: .nvmrc
104
+ cache: npm
105
+ cache-dependency-path: frontend/package-lock.json
106
+ - run: uv sync --frozen
107
+ - run: npm --prefix frontend ci
108
+ - run: npx playwright install --with-deps chromium webkit
109
+ working-directory: frontend
110
+ - run: uv run inv pre
111
+ - run: uv run inv test
112
+ - uses: actions/upload-artifact@v4
113
+ if: failure()
114
+ with:
115
+ name: browser-test-results
116
+ path: frontend/test-results/
117
+ if-no-files-found: ignore
@@ -0,0 +1,43 @@
1
+ name: docs
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ permissions:
7
+ contents: write
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-24.04
11
+ concurrency:
12
+ group: github-pages
13
+ cancel-in-progress: false
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ with:
18
+ version: 0.11.7
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: '3.13'
22
+ - run: uv sync --frozen --all-extras
23
+ - run: uv run inv build-docs
24
+ - name: Load existing reports
25
+ if: github.event_name == 'push'
26
+ uses: actions/checkout@v4
27
+ continue-on-error: true
28
+ with:
29
+ ref: gh-pages
30
+ path: gh-pages
31
+ - name: Preserve test reports
32
+ if: github.event_name == 'push'
33
+ run: |
34
+ if [ -d gh-pages/builds ]; then
35
+ cp -r gh-pages/builds build/docs/site/builds
36
+ fi
37
+ - name: Publish documentation
38
+ if: github.event_name == 'push'
39
+ uses: peaceiris/actions-gh-pages@v4
40
+ with:
41
+ github_token: ${{ secrets.GITHUB_TOKEN }}
42
+ publish_branch: gh-pages
43
+ publish_dir: build/docs/site
@@ -0,0 +1,63 @@
1
+ name: Upload Python Package
2
+ on:
3
+ push:
4
+ tags: ['v*.*.*']
5
+ permissions:
6
+ contents: write
7
+ pull-requests: write
8
+ jobs:
9
+ check-tag:
10
+ runs-on: ubuntu-24.04
11
+ outputs:
12
+ version: ${{ steps.version.outputs.version }}
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: '3.13'
18
+ - name: Validate release tag and package version
19
+ id: version
20
+ env:
21
+ RELEASE_TAG: ${{ github.ref_name }}
22
+ run: |
23
+ python - <<'PY'
24
+ import os
25
+ from scripts.release import validate_tag
26
+ version = validate_tag(os.environ['RELEASE_TAG'])
27
+ with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
28
+ output.write(f'version={version}\n')
29
+ PY
30
+ checks:
31
+ needs: check-tag
32
+ uses: ./.github/workflows/ci.yml
33
+ secrets: inherit
34
+ publish:
35
+ needs: [check-tag, checks]
36
+ runs-on: ubuntu-24.04
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v6
40
+ with:
41
+ version: 0.11.7
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: '3.13'
45
+ - uses: actions/setup-node@v4
46
+ with:
47
+ node-version-file: .nvmrc
48
+ cache: npm
49
+ cache-dependency-path: frontend/package-lock.json
50
+ - run: uv run inv build
51
+ - name: Publish to PyPI
52
+ uses: pypa/gh-action-pypi-publish@release/v1
53
+ with:
54
+ user: ${{ secrets.PYPI_USERNAME }}
55
+ password: ${{ secrets.PYPI_PASSWORD }}
56
+ packages-dir: dist
57
+ - name: Create release
58
+ uses: softprops/action-gh-release@v2
59
+ with:
60
+ name: Release ${{ needs.check-tag.outputs.version }}
61
+ tag_name: ${{ github.ref_name }}
62
+ target_commitish: ${{ github.sha }}
63
+ body: https://pypi.org/project/phrase-gym/${{ needs.check-tag.outputs.version }}/
@@ -0,0 +1,24 @@
1
+ name: static source code checks
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ static:
8
+ runs-on: ubuntu-24.04
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: astral-sh/setup-uv@v6
12
+ with:
13
+ version: 0.11.7
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: '3.13'
17
+ - uses: actions/setup-node@v4
18
+ with:
19
+ node-version-file: .nvmrc
20
+ cache: npm
21
+ cache-dependency-path: frontend/package-lock.json
22
+ - run: uv sync --frozen --all-extras
23
+ - run: npm --prefix frontend ci
24
+ - run: uv run inv pre
@@ -0,0 +1,40 @@
1
+ .DS_Store
2
+ .env
3
+ .idea/
4
+
5
+ # Local tool environments and caches
6
+ .venv/
7
+ **/__pycache__
8
+ **/.pytest_cache
9
+ .ruff_cache/
10
+ .coverage
11
+ build/
12
+ dist/
13
+ site/
14
+ **/*.egg-info
15
+ pytest.xml
16
+ pytest-coverage.txt
17
+ .dmypy.json
18
+ *.py-e
19
+ **/.setup-scripts/
20
+ allure-results/
21
+ htmlcov/
22
+ coverage.xml
23
+
24
+ # Client dependencies
25
+ **/node_modules/
26
+
27
+ # Production client build and local application data
28
+ frontend/dist/
29
+ frontend/test-results/
30
+ frontend/playwright-report/
31
+ .data/
32
+ *.sqlite3
33
+ *.sqlite3-wal
34
+ *.sqlite3-shm
35
+
36
+ # Rust build output
37
+ /target/
38
+
39
+ # Private local Coach attempts and diagnostics
40
+ .phrase-gym-coach/
@@ -0,0 +1 @@
1
+ 22
@@ -0,0 +1,60 @@
1
+ exclude: ^(src/phrase_gym/server/static/|frontend/src/api.generated.ts|frontend/openapi.json|build/|dist/)
2
+ repos:
3
+ - repo: https://github.com/pre-commit/pre-commit-hooks
4
+ rev: v5.0.0
5
+ hooks:
6
+ - id: check-yaml
7
+ - id: end-of-file-fixer
8
+ - id: trailing-whitespace
9
+ - repo: https://github.com/astral-sh/ruff-pre-commit
10
+ rev: v0.14.2
11
+ hooks:
12
+ - id: ruff-check
13
+ exclude: ^tests/
14
+ args:
15
+ - --fix
16
+ - --line-length=100
17
+ - --target-version=py313
18
+ - --select=PL,A001,ARG,B,BLE,C,COM,E,F,G,I,ISC,N,PIE,Q,RET,S,SIM,SLF,TID,TRY,UP,W
19
+ - --ignore=B019,S101,G004,TRY003,TRY300,TRY301,TRY400
20
+ # Development commands resolve trusted tools from the operator's PATH.
21
+ - --per-file-ignores=scripts/**:S603,scripts/**:S607
22
+ - id: ruff-check
23
+ name: ruff check tests
24
+ files: ^tests/
25
+ args: [--fix, --line-length=100, --target-version=py313, '--select=E,F,I,UP,B,SIM']
26
+ - id: ruff-format
27
+ args: [--line-length=100, --target-version=py313]
28
+ - repo: https://github.com/facebook/pyrefly-pre-commit
29
+ rev: 0.50.0
30
+ hooks:
31
+ - id: pyrefly-check
32
+ language: system
33
+ entry: uv run pyrefly check
34
+ pass_filenames: false
35
+ - repo: local
36
+ hooks:
37
+ - id: uv-lock
38
+ name: Dependency lock
39
+ entry: uv lock --check
40
+ language: system
41
+ pass_filenames: false
42
+ always_run: true
43
+ - id: module-sizes
44
+ name: Production module sizes
45
+ entry: uv run inv check-sizes
46
+ language: system
47
+ pass_filenames: false
48
+ always_run: true
49
+ - id: client-format
50
+ name: Client formatting
51
+ entry: npm --prefix frontend run format:check
52
+ language: system
53
+ pass_filenames: false
54
+ always_run: true
55
+ - id: api-contract
56
+ name: Generated API contract
57
+ entry: uv run inv check-api
58
+ language: system
59
+ pass_filenames: false
60
+ always_run: true
@@ -0,0 +1 @@
1
+ 3.13.13
@@ -0,0 +1,102 @@
1
+ # Phrase Gym — agent instructions
2
+
3
+ Read [specs/START-HERE.md](specs/START-HERE.md) and [remaining work](specs/plan/implementation-plan.md). Specs retain current decisions and hard-to-infer design constraints; code and tests describe implemented behavior. The application is a small local Python service with the retained Vue client in `frontend/`. Earlier Python and Rust code in Git are prototypes, not parity requirements.
4
+
5
+ For a root-session request to implement the next stage, follow the [orchestration handout](specs/plan/orchestration-handout.md) before making changes or delegating. Delegated agents follow their assigned scope and the regular rules below.
6
+
7
+ `CLAUDE.md` links to this file; keep one instruction source.
8
+
9
+ ## Architecture and file structure
10
+
11
+ - Keep architecture under active review in every batch. Each module and service has one coherent responsibility. Split unrelated workflows before adding more methods; do not grow a catch-all service or application component.
12
+ - Aim for no more than 250 lines of handwritten production code per file; review and split before 300. Generated contracts and migrations may be longer. A shorter file does not justify circular dependencies, meaningless wrappers or fragmented invariants.
13
+ - Dependencies point inward: transport and UI adapters use contracts/domain services; domain services never import HTTP routers, application startup or UI modules. Python owns the generated API contract. Construct services at the application boundary. Keep Phrase Gym lightweight and Coach separate under the [application boundary](specs/architecture.md#application-boundary); do not recreate the prototype's general distributed worker or native Coach GUI.
14
+ - Keep validation, publication transactions and library reads separate. One workflow owns each transaction; helpers receive that transaction instead of opening hidden writes. Keep all package publication atomic.
15
+ - Vue views render state. Route orchestration, preferences and audio effects belong in focused composables/adapters; the practice reducer stays pure. Generated API types are the client contract.
16
+ - Preserve the original Vue shell, practice card, microphone control and responsive styling as the Python backend grows. Adapt or hide controls for unavailable workflows; do not replace the interface with a new design.
17
+ - When moving a symbol, update all importers and tests. No re-export compatibility shims. Shared helpers are preferable to duplicated logic.
18
+
19
+ ## Commands and environment
20
+
21
+ | Task | Command |
22
+ |---|---|
23
+ | Python lint, format, types, module size, client format and contract drift | `uv run inv pre` |
24
+ | Python tests, client typecheck/unit/build and both browser engines | `uv run inv test` |
25
+ | Python iteration | `uv run pytest` |
26
+ | Client iteration | `npm --prefix frontend test` |
27
+ | Browser iteration | `npm --prefix frontend run test:browser` |
28
+ | Refresh OpenAPI and generated client types | `uv run python scripts/generate_api.py && npm --prefix frontend run api-types` |
29
+ | Build the retained client | `npm --prefix frontend run build` |
30
+ | Run the local application on 127.0.0.1:8000 | `uv run phrase-gym run` |
31
+ | Build the frontend and run the local application | `uv run inv run --build` |
32
+ | Install the pre-commit hooks (once per clone) | `uv run pre-commit install` |
33
+ | Scaffold tasks and release commands | `uv run inv --list` |
34
+ | Build documentation | `uv run inv build-docs` |
35
+ | Build the client and Python distributions | `uv run inv build` |
36
+
37
+ Use `uv sync --frozen` and `npm --prefix frontend ci`; install browser binaries from `frontend/` with `npx playwright install chromium webkit`. Python 3.13 and application dependencies are pinned in `uv.lock`. Use Node 22.12+ on an LTS release (`.nvmrc`). The pre-commit framework runs the pre gate and needs uv, Python, Node and the client dependencies; `inv pre` runs the same hooks manually. Dependency changes update their committed lockfiles. Piper uses its separately pinned private environment described in [the import guide](docs/importing-phrases.md#piper-voice). Missing dependencies or browsers are environment failures to fix, not reasons to skip suites.
38
+
39
+ Preserve the repository scaffold: pre-commit, Invoke tasks, version/tag helpers, package metadata, environment/build scripts, documentation publishing, test/coverage reports and PyPI release workflows. Extend these for the application instead of replacing or deleting them. Only application/documentation boilerplate should be replaced with real implementation and documentation. Follow [development and release operations](docs/development.md).
40
+
41
+ Pre-commit owns the static checks and all Ruff settings. Use Pyrefly for Python type checking; do not add a duplicate mypy gate or a wrapper that reruns the same tools. Invoke owns repository development, build, test and release tasks; the packaged CLI owns user runtime and deployment commands, which Invoke may wrap. Keep root-level executable scripts limited to the retained environment activation script.
42
+
43
+ Run the full browser/audio gate on macOS and retain the separate Linux Python server job in CI, following the [platform boundary](specs/architecture.md#application-boundary). Do not skip native recorder tests to accommodate Linux WebKit's missing API.
44
+
45
+ ## Done gate
46
+
47
+ Choose the gate from the complete change, including staged and unstaged files.
48
+
49
+ **CI results are always part of verification:** inspect GitHub Actions for the current remote commit before making changes. After the local gate passes, commit and push under the standing authorization below, then wait for the workflows on the exact pushed commit to finish and inspect every result before delivery. Inspect failed jobs and their logs, fix failures, and repeat the applicable checks and push until CI passes. Local checks do not establish that CI passed. Report the commit, run link and outcome separately from local checks. Missing, pending, cancelled or inaccessible runs are unverified, never passing; report any actual block explicitly.
50
+
51
+ **Documentation-only changes:** when the change contains only prose in specs, plans, README, docs or agent instructions, do not run application builds or Python/client/browser suites; the installed pre-commit hooks still run. Run `git diff --check` and `git diff --cached --check`, inspect the full diff, verify changed local links/anchors and check consistency with related decisions and plans. Report these documentation checks without claiming application tests passed. Documentation describing future behavior does not require implementing or testing that behavior.
52
+
53
+ **Implementation or mixed changes:** changes to production code, tests, runtime prompts, generated contracts, migrations, dependencies, executable scripts, build configuration, hooks or CI require the full gate, even when accompanied by specs. Before describing such a milestone as done, ready, fixed, landed or green, run these in order and inspect their complete results:
54
+
55
+ 1. `uv run inv pre`: pre-commit hygiene, Python formatting/lint/types, module-size, client formatting and generated-contract drift checks all pass.
56
+ 2. `uv run inv test`: Python, client and both real-backend browser suites pass; frontend checks must run rather than skip.
57
+
58
+ Use focused checks during iteration. Run the full ordered gate once per coherent implementation deliverable before commit or delivery, rather than after every internal edit. Rerun relevant checks for review fixes and the full gate after substantive changes; a narrow subset cannot replace it. Use the documentation-only gate for prose-only changes. Never bypass commit hooks with `--no-verify`.
59
+
60
+ Fix findings even when they appear pre-existing. Deferral requires evidence that the same failure exists on the committed baseline and the user's agreement in the current turn. Never silently report a failing tree as passing. If execution or approval infrastructure blocks a check, describe that block explicitly and do not claim the gate passed.
61
+
62
+ ## Tests
63
+
64
+ - New modules/functions ship meaningful behavioral coverage in the same commit. Keep tests with the responsibility they verify; do not grow one catch-all test file.
65
+ - Behavior that only exists in the browser needs a real-browser test against the real server and built client. Assert provisional/error states deterministically; do not race an artificial delay.
66
+ - For implementation changes, iterate with subsets if useful, then verify the full suite. Documentation-only changes use the done gate above.
67
+ - Never launch system dialogs or native popup paths automatically. If setup, login, permission or a native check needs attended interaction, ask the user directly and report the unresolved acceptance instead of opening it.
68
+ - Before a full browser suite in a restricted environment, run one existing test per browser engine with `--max-failures=1` to verify startup. Stop immediately if browser launch is denied or crashes; do not let the suite repeatedly launch failing browsers and flood the desktop with crash dialogs. Report the environment block and leave the full gate incomplete until it is resolved. Do not rerun browser checks after the user asks to stop them.
69
+ - Automated tests must emit no sound on the host. Mute every browser context, including manually created contexts, while preserving native decoding, playback clocks and events. Synthetic capture feeds a media stream, never the speaker destination. Live listening and physical microphone checks require attended learner interaction and are separate from automated tests.
70
+ - Tests make no real provider, TTS, source mutation or external delivery calls. Fake those boundaries; never read the operator's `.env` or private recordings into fixtures or logs.
71
+ - Never leave a failing test. Diagnose clock, ordering, race and environment causes; do not skip, xfail, delete or weaken the assertion to hide a failure.
72
+ - Software checks are not language-content, speech-quality or actual-device acceptance. Keep Safari/headset, iPhone and content gaps explicit.
73
+ - Prototype fixtures in Git are not replacement compatibility requirements ([architecture](specs/architecture.md#releases-and-data-compatibility)). Preserve meaningful coverage, and replace obsolete expectations only for an intended spec change. The Python application uses library integrations with focused tests rather than carrying forward numerical-port equivalence.
74
+
75
+ ## Code conventions
76
+
77
+ - Python passes Ruff and Pyrefly; Vue passes Prettier and TypeScript checking. The executable pre gate enforces the handwritten production module-size boundary and generated-contract drift.
78
+ - English only in comments, docstrings, plans and repository documentation. Follow the project's English communication rule. Learning data, language names and voice IDs retain their original language/script as quoted, searchable literals.
79
+ - All imports at module top level, none inside functions and none to break cycles; fix the dependency structure.
80
+ - No re-export shims when moving symbols. Import shared helpers directly from their owner.
81
+ - Comments/docstrings explain a non-obvious why in one or two lines: a hidden constraint, library workaround or invariant. Do not restate the code, argue architectural decisions there, or cite phase/plan names; decisions belong in `specs/`.
82
+
83
+ ## Specs and plans
84
+
85
+ - `specs/` contains current decisions, their rationale and substantial design constraints that are difficult to infer from code. Code, tests and generated contracts document implemented behavior; usage and operational commands belong in README or `docs/`.
86
+ - Keep one authoritative home per decision. Do not retain historical references, superseded sketches, review logs, implementation inventories or completed plans in specs. Git preserves earlier versions.
87
+ - The implementation plan contains only remaining work. Delete completed entries and plans; retain any lasting decision in its focused spec. Correct contradictory text in the same commit and keep acceptance gaps concise.
88
+
89
+ ## Model integration
90
+
91
+ - When local AI preparation is used, Codex uses the normal user home and Phrase Gym repository as its working directory, as requested by the learner. Never copy login tokens. Existing private attempt files stay ignored under `.phrase-gym-coach/`; retain subscription-only login, bounded execution and an allowlisted child environment. Deny repository `.env` reads and keep application stores/recordings outside the permitted model workspace. Normal-home instructions/skills may load; do not claim customization isolation. The first MVP imports externally prepared content and needs no internal LLM adapter.
92
+
93
+ Follow the [standing local English demo authorization](specs/plan/implementation-plan.md#standing-authorization-for-the-local-english-demo): routine demo runs and continuations require no separate budget, temporary authentication workspace or login approval. Existing runtime bounds govern prototype runs; they do not require reproducing its distributed infrastructure. Preserve learner data and recordings during replacement.
94
+
95
+ When changing prompts, provider payloads or interpretation of real model answers, mocks alone cannot establish model behavior. Plan representative fixtures and real-provider measurements before execution; inspect availability and every concrete result. Record semantic review separately from automated conformance. A fresh reviewer is required for those model-result measurements, not for ordinary refactors. Do not create learner effectiveness experiments or unrequested general review loops.
96
+
97
+ ## Git, secrets and deployment
98
+
99
+ - Commit authorized work directly to `main` and push it to `origin/main` after the applicable local gate passes, then verify CI as required by the done gate. This is standing authorization; do not ask for separate push approval unless the user explicitly requests local-only work. Do not create branches or PRs by default, and never force-push.
100
+ - Publishing releases and deployment still require the user's explicit request for that action.
101
+ - Keep `.env`, provider keys, learner data and recordings ignored, private and server-side. Never bundle or print secrets.
102
+ - Source repositories, vaults, echo-words, legacy trainers and Anki stay read-only unless separately authorized. Real deployment must be reproducible from committed code and must verify its actual health/access boundary; no one-off server edits.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2026 Andrey Sorokin (andrey@sorokin.engineer)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the “Software”), to deal in
5
+ the Software without restriction, including without limitation the rights to use,
6
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
7
+ Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: phrase-gym
3
+ Version: 0.2.0
4
+ Summary: A small local trainer for bringing useful phrases into speech.
5
+ Project-URL: Homepage, https://andgineer.github.io/phrase-gym/
6
+ Project-URL: Documentation, https://andgineer.github.io/phrase-gym/
7
+ Project-URL: Repository, https://github.com/andgineer/phrase-gym
8
+ Author-email: Andrey Sorokin <andrey@sorokin.engineer>
9
+ License: Copyright (c) 2026 Andrey Sorokin (andrey@sorokin.engineer)
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
12
+ this software and associated documentation files (the “Software”), to deal in
13
+ the Software without restriction, including without limitation the rights to use,
14
+ copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
15
+ Software, and to permit persons to whom the Software is furnished to do so,
16
+ subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
22
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
23
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
25
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28
+ OTHER DEALINGS IN THE SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: language-learning,phrases,spoken-retrieval
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Requires-Python: <3.14,>=3.13
35
+ Requires-Dist: fastapi==0.119.1
36
+ Requires-Dist: platformdirs==4.5.0
37
+ Requires-Dist: pydantic==2.12.3
38
+ Requires-Dist: uvicorn==0.38.0
39
+ Provides-Extra: coach
40
+ Provides-Extra: deploy
41
+ Description-Content-Type: text/markdown
42
+
43
+ # Phrase Gym
44
+
45
+ [![CI](https://github.com/andgineer/phrase-gym/workflows/CI/badge.svg)](https://github.com/andgineer/phrase-gym/actions)
46
+ [![Coverage](https://raw.githubusercontent.com/andgineer/phrase-gym/python-coverage-comment-action-data/badge.svg)](https://htmlpreview.github.io/?https://github.com/andgineer/phrase-gym/blob/python-coverage-comment-action-data/htmlcov/index.html)
47
+
48
+ Phrase Gym is a small local Python/Vue application for studying useful English phrases and
49
+ keeping private learner recordings. This checkpoint supports package import, ungraded first
50
+ study, reference playback, optional recording, comparison, and saved replay. Later recall and
51
+ scheduling remain the next stage.
52
+
53
+ ## Install and launch
54
+
55
+ The server runs on macOS and Linux. Use a macOS browser for the MVP media player and recording;
56
+ Linux browser audio support is planned after the MVP.
57
+
58
+ For a source checkout, use Python 3.13, [uv](https://docs.astral.sh/uv/), Node 22.12+ and a
59
+ configured English Piper voice for Coach synthesis. Install the pinned environments and build the client once:
60
+
61
+ ```bash
62
+ uv sync --frozen
63
+ npm --prefix frontend ci
64
+ npm --prefix frontend run build
65
+ ```
66
+
67
+ Start Phrase Gym without any Piper installation:
68
+
69
+ ```bash
70
+ uv run phrase-gym run
71
+ ```
72
+
73
+ For private iPhone access on your existing tailnet, use `uv run inv run --build --tailscale`. This points Tailscale Serve at the new server
74
+ and allows its exact HTTPS machine hostname. Open the HTTPS URL printed by Tailscale on the
75
+ iPhone with Tailscale connected.
76
+
77
+ The built wheel contains the Vue client. A lean installed package runs `phrase-gym run`.
78
+ Coach runs separately: set `PHRASE_GYM_PIPER_CONFIG` to the private voice file in
79
+ [the import guide](docs/importing-phrases.md#piper-voice), then run
80
+ `uv run phrase-gym coach` in another terminal. The private Piper environment belongs to Coach;
81
+ the server needs only core dependencies. Service registration and deployment commands remain
82
+ unavailable.
83
+
84
+ Open <http://127.0.0.1:8000> on the Mac. The app listens only on loopback and stores its database,
85
+ references, and recordings in the operating system's Phrase Gym application-data directory.
86
+ For a separate data directory, add `--data-dir /absolute/path`.
87
+
88
+ ## Add and study material
89
+
90
+ Open **Add material**, paste a package or choose its UTF-8 JSON file, and select **Import**.
91
+ The app saves the request before acknowledging it. A text-only package stays pending while Coach
92
+ is offline; Coach prepares reference audio and returns it through the API. An external agent can
93
+ submit the same package with complete WAV references in one API request, without Coach. The server
94
+ validates and publishes all cards and references together. A preparation error remains visible
95
+ for explicit retry. Exact package
96
+ retries return the existing result; a changed package ID or repeated source/item identity is
97
+ rejected without overwriting learned material.
98
+
99
+ Study shows the full phrase and meaning/use help. Reference playback and recording are optional.
100
+ Every stopped take is retained separately and saved automatically; pending uploads remain in the
101
+ browser for retry. **Next** records ungraded study completion. The finish screen deliberately does
102
+ not invent a recall grade or schedule before the later-recall stage exists.
103
+
104
+ See [the package schema, example, and external-agent instruction](docs/importing-phrases.md).
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ . ./activate.sh
110
+ uv run pre-commit install
111
+ inv --list
112
+ uv run inv pre
113
+ uv run inv test
114
+ ```
115
+
116
+ `inv test` runs Python tests, client typechecking/unit/build checks, and silent real-backend
117
+ browser tests in Chromium and WebKit on macOS. CI runs the full gate on macOS and separately
118
+ runs all Python server tests on Linux. For server-only iteration, use `uv run pytest`.
119
+ Install browser binaries from `frontend/` with
120
+ `npx playwright install chromium webkit`. If migrating from the temporary custom hook, run
121
+ `git config --unset core.hooksPath` before `pre-commit install`.
122
+
123
+ The scaffold's `inv pre`, `inv reqs`, `inv docs-en`, `inv build-docs`, `inv version`, and
124
+ `inv ver-bug` / `inv ver-feature` / `inv ver-release` commands are available.
125
+ `uv run inv build` builds the client and Python distributions. A version command checks and commits the
126
+ version update, then pushes its release tag; the tag workflow publishes to PyPI only after CI passes.
127
+ See [development and release operations](docs/development.md) for setup and dry-run commands.
128
+
129
+ Use `uv run inv run --build` to rebuild the frontend and start the server, or `uv run inv run`
130
+ to start it with the existing client build.
131
+
132
+ [Documentation](https://andgineer.github.io/phrase-gym/),
133
+ [Allure reports](https://andgineer.github.io/phrase-gym/builds/tests/),
134
+ [Codecov](https://app.codecov.io/gh/andgineer/phrase-gym), and
135
+ [Coveralls](https://coveralls.io/github/andgineer/phrase-gym) retain the scaffold's reporting links.
136
+
137
+ Created using [cookiecutter-python-package](https://github.com/andgineer/cookiecutter-python-package).
138
+
139
+ Current decisions and remaining work start at [specs/START-HERE.md](specs/START-HERE.md).