framework-py 0.1.0.dev1__tar.gz → 0.1.0.dev2__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 (339) hide show
  1. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/PKG-INFO +1 -1
  2. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/pyproject.toml +2 -2
  3. framework_py-0.1.0.dev2/scripts/build_docs.py +377 -0
  4. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/scripts/sync_assets.py +2 -0
  5. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/__main__.py +6 -1
  6. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_assets/hooks/pre-commit +14 -6
  7. framework_py-0.1.0.dev2/src/framework/_assets/schema/docs-reference.v1.json +137 -0
  8. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_paths.py +10 -0
  9. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/ai.py +4 -1
  10. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/bootstrap.py +8 -2
  11. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/cache.py +20 -2
  12. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/catalog.py +72 -20
  13. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/cloud.py +69 -8
  14. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/config_home.py +19 -2
  15. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/connections.py +44 -1
  16. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/console.py +6 -1
  17. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/create.py +59 -14
  18. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/data.py +43 -23
  19. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/global_config.py +65 -5
  20. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/hooks.py +47 -2
  21. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/notebooks.py +94 -4
  22. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/outputs.py +16 -1
  23. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/packages.py +19 -1
  24. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/project.py +7 -0
  25. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/quarto.py +44 -4
  26. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/scaffold.py +17 -2
  27. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/scratch.py +1 -1
  28. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/security.py +13 -11
  29. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/settings.py +56 -8
  30. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/spec/check.py +10 -1
  31. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/spec/resolve.py +7 -1
  32. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/storage.py +94 -3
  33. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_bootstrap.py +28 -0
  34. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_catalog.py +53 -0
  35. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_cloud.py +23 -0
  36. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_create.py +33 -1
  37. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_data.py +66 -0
  38. framework_py-0.1.0.dev2/tests/test_docs_site.py +82 -0
  39. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_hooks.py +2 -1
  40. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_package.py +2 -2
  41. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_scaffold.py +45 -0
  42. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_security.py +60 -15
  43. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_settings.py +36 -0
  44. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/.gitignore +0 -0
  45. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/LICENSE +0 -0
  46. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/README.md +0 -0
  47. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/parity.toml +0 -0
  48. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/scripts/__init__.py +0 -0
  49. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/scripts/check_parity.py +0 -0
  50. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/scripts/make_golden_trees.py +0 -0
  51. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/scripts/make_r_fixtures.R +0 -0
  52. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/__init__.py +0 -0
  53. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_assets/init.sql +0 -0
  54. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_assets/schema/layer.v1.json +0 -0
  55. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_assets/schema/layers.v1.json +0 -0
  56. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_assets/schema/spec.v1.json +0 -0
  57. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_log.py +0 -0
  58. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_secretservice.py +0 -0
  59. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_time.py +0 -0
  60. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_wincred.py +0 -0
  61. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/_yaml.py +0 -0
  62. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/db.py +0 -0
  63. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/dotenv.py +0 -0
  64. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/errors.py +0 -0
  65. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/integrity.py +0 -0
  66. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/py.typed +0 -0
  67. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/spec/__init__.py +0 -0
  68. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/spec/merge.py +0 -0
  69. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/defaults.yml +0 -0
  70. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/gitignore/course +0 -0
  71. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/gitignore/presentation +0 -0
  72. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/gitignore/project +0 -0
  73. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/gitignore/sensitive +0 -0
  74. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/skills/framework-data/SKILL.md +0 -0
  75. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/skills/framework-outputs/SKILL.md +0 -0
  76. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/skills/framework-packages/SKILL.md +0 -0
  77. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/skills/framework-sensitive-data/SKILL.md +0 -0
  78. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/skills/framework-workflow/SKILL.md +0 -0
  79. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/stubs/notebook-default.ipynb +0 -0
  80. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/stubs/notebook-default.qmd +0 -0
  81. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/stubs/notebook-minimal.qmd +0 -0
  82. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/stubs/notebook-revealjs.qmd +0 -0
  83. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/src/framework/templates/stubs/script-default.py +0 -0
  84. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/__init__.py +0 -0
  85. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/conftest.py +0 -0
  86. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.claude/skills/framework-data/SKILL.md +0 -0
  87. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.claude/skills/framework-outputs/SKILL.md +0 -0
  88. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.claude/skills/framework-packages/SKILL.md +0 -0
  89. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.claude/skills/framework-workflow/SKILL.md +0 -0
  90. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.env +0 -0
  91. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.framework/spec.json +0 -0
  92. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/.gitignore +0 -0
  93. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/AGENTS.md +0 -0
  94. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/CLAUDE.md +0 -0
  95. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/_quarto.yml +0 -0
  96. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/docs/_quarto.yml +0 -0
  97. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/fixture-study.Rproj +0 -0
  98. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/fixture-study.code-workspace +0 -0
  99. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/framework.db +0 -0
  100. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/intermediate/r_clean.csv +0 -0
  101. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/private/ids.csv +0 -0
  102. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/raw/survey.csv +0 -0
  103. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/raw/visits.tsv +0 -0
  104. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/reference/test.dta +0 -0
  105. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/reference/test.sav +0 -0
  106. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/inputs/reference/test.xpt +0 -0
  107. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/notebooks/_quarto.yml +0 -0
  108. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/outputs/cache/r_value.rds +0 -0
  109. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/outputs/tables/r_summary.csv +0 -0
  110. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/scaffold.R +0 -0
  111. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/ai.yml +0 -0
  112. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/author.yml +0 -0
  113. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/connections.yml +0 -0
  114. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/data.yml +0 -0
  115. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/git.yml +0 -0
  116. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/packages.yml +0 -0
  117. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/quarto.yml +0 -0
  118. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings/scaffold.yml +0 -0
  119. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project/settings.yml +0 -0
  120. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/fixtures/r_project.expected.json +0 -0
  121. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/bare/expected/.framework/spec.json +0 -0
  122. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/bare/expected/.gitignore +0 -0
  123. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/bare/expected/pyproject.toml +0 -0
  124. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/bare/expected/settings.yml +0 -0
  125. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/bare/layers.json +0 -0
  126. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.claude/skills/framework-data/SKILL.md +0 -0
  127. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  128. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  129. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  130. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.env +0 -0
  131. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.framework/spec.json +0 -0
  132. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/.gitignore +0 -0
  133. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/AGENTS.md +0 -0
  134. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/CLAUDE.md +0 -0
  135. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/_quarto.yml +0 -0
  136. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/assignments/_quarto.yml +0 -0
  137. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/course_docs/_quarto.yml +0 -0
  138. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/golden-course.code-workspace +0 -0
  139. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/modules/_quarto.yml +0 -0
  140. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/pyproject.toml +0 -0
  141. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/scaffold.py +0 -0
  142. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/settings.yml +0 -0
  143. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/expected/slides/_quarto.yml +0 -0
  144. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/course/layers.json +0 -0
  145. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/framework-data/SKILL.md +0 -0
  146. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  147. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  148. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  149. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/study-protocol/SKILL.md +0 -0
  150. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.claude/skills/study-protocol/references/consort.md +0 -0
  151. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.env +0 -0
  152. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.framework/spec.json +0 -0
  153. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/.gitignore +0 -0
  154. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/AGENTS.md +0 -0
  155. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/CLAUDE.md +0 -0
  156. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/README.md +0 -0
  157. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/_quarto.yml +0 -0
  158. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/docs/_quarto.yml +0 -0
  159. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/golden-files-and-skills.code-workspace +0 -0
  160. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/notebooks/_quarto.yml +0 -0
  161. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/notes.md +0 -0
  162. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/pyproject.toml +0 -0
  163. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/scaffold.py +0 -0
  164. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/ai.yml +0 -0
  165. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/author.yml +0 -0
  166. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/connections.yml +0 -0
  167. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/data.yml +0 -0
  168. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/git.yml +0 -0
  169. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/quarto.yml +0 -0
  170. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings/scaffold.yml +0 -0
  171. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/expected/settings.yml +0 -0
  172. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/files-and-skills/layers.json +0 -0
  173. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.agents/skills/framework-data/SKILL.md +0 -0
  174. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.agents/skills/framework-outputs/SKILL.md +0 -0
  175. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.agents/skills/framework-packages/SKILL.md +0 -0
  176. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.agents/skills/framework-workflow/SKILL.md +0 -0
  177. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.env +0 -0
  178. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.framework/spec.json +0 -0
  179. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/.gitignore +0 -0
  180. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/AGENTS.md +0 -0
  181. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/_quarto.yml +0 -0
  182. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/docs/_quarto.yml +0 -0
  183. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/golden-no-assistants.code-workspace +0 -0
  184. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/notebooks/_quarto.yml +0 -0
  185. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/pyproject.toml +0 -0
  186. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/scaffold.py +0 -0
  187. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/ai.yml +0 -0
  188. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/author.yml +0 -0
  189. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/connections.yml +0 -0
  190. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/data.yml +0 -0
  191. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/git.yml +0 -0
  192. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/quarto.yml +0 -0
  193. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings/scaffold.yml +0 -0
  194. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/expected/settings.yml +0 -0
  195. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/no-assistants/layers.json +0 -0
  196. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.claude/skills/framework-data/SKILL.md +0 -0
  197. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  198. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  199. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  200. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.env +0 -0
  201. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.framework/spec.json +0 -0
  202. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/.gitignore +0 -0
  203. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/AGENTS.md +0 -0
  204. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/CLAUDE.md +0 -0
  205. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/_quarto.yml +0 -0
  206. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/golden-presentation.code-workspace +0 -0
  207. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/presentation.qmd +0 -0
  208. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/pyproject.toml +0 -0
  209. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/scaffold.py +0 -0
  210. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/expected/settings.yml +0 -0
  211. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/presentation/layers.json +0 -0
  212. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.claude/skills/framework-data/SKILL.md +0 -0
  213. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  214. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  215. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  216. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.env +0 -0
  217. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.framework/spec.json +0 -0
  218. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/.gitignore +0 -0
  219. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/AGENTS.md +0 -0
  220. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/CLAUDE.md +0 -0
  221. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/_quarto.yml +0 -0
  222. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/docs/_quarto.yml +0 -0
  223. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/golden-project.code-workspace +0 -0
  224. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/notebooks/_quarto.yml +0 -0
  225. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/pyproject.toml +0 -0
  226. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/scaffold.py +0 -0
  227. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/ai.yml +0 -0
  228. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/author.yml +0 -0
  229. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/connections.yml +0 -0
  230. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/data.yml +0 -0
  231. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/git.yml +0 -0
  232. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/quarto.yml +0 -0
  233. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings/scaffold.yml +0 -0
  234. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/expected/settings.yml +0 -0
  235. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project/layers.json +0 -0
  236. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/settings.json +0 -0
  237. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/skills/framework-data/SKILL.md +0 -0
  238. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  239. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  240. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/skills/framework-sensitive-data/SKILL.md +0 -0
  241. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  242. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.env +0 -0
  243. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.framework/spec.json +0 -0
  244. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/.gitignore +0 -0
  245. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/AGENTS.md +0 -0
  246. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/CLAUDE.md +0 -0
  247. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/_quarto.yml +0 -0
  248. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/docs/_quarto.yml +0 -0
  249. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/golden-project-sensitive.code-workspace +0 -0
  250. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/notebooks/_quarto.yml +0 -0
  251. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/pyproject.toml +0 -0
  252. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/scaffold.py +0 -0
  253. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/ai.yml +0 -0
  254. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/author.yml +0 -0
  255. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/connections.yml +0 -0
  256. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/data.yml +0 -0
  257. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/git.yml +0 -0
  258. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/quarto.yml +0 -0
  259. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings/scaffold.yml +0 -0
  260. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/expected/settings.yml +0 -0
  261. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/project-sensitive/layers.json +0 -0
  262. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.agents/skills/framework-data/SKILL.md +0 -0
  263. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.agents/skills/framework-outputs/SKILL.md +0 -0
  264. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.agents/skills/framework-packages/SKILL.md +0 -0
  265. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.agents/skills/framework-workflow/SKILL.md +0 -0
  266. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.env +0 -0
  267. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.framework/spec.json +0 -0
  268. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.github/copilot-instructions.md +0 -0
  269. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/.gitignore +0 -0
  270. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/AGENTS.md +0 -0
  271. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/_quarto.yml +0 -0
  272. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/docs/_quarto.yml +0 -0
  273. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/golden-removals-and-assistants.code-workspace +0 -0
  274. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/notebooks/_quarto.yml +0 -0
  275. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/pyproject.toml +0 -0
  276. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/scaffold.py +0 -0
  277. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/ai.yml +0 -0
  278. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/author.yml +0 -0
  279. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/connections.yml +0 -0
  280. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/data.yml +0 -0
  281. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/git.yml +0 -0
  282. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/quarto.yml +0 -0
  283. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings/scaffold.yml +0 -0
  284. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/expected/settings.yml +0 -0
  285. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/removals-and-assistants/layers.json +0 -0
  286. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/settings.json +0 -0
  287. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/skills/framework-data/SKILL.md +0 -0
  288. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/skills/framework-outputs/SKILL.md +0 -0
  289. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/skills/framework-packages/SKILL.md +0 -0
  290. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/skills/framework-sensitive-data/SKILL.md +0 -0
  291. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.claude/skills/framework-workflow/SKILL.md +0 -0
  292. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.env +0 -0
  293. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.framework/spec.json +0 -0
  294. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/.gitignore +0 -0
  295. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/AGENTS.md +0 -0
  296. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/CLAUDE.md +0 -0
  297. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/_quarto.yml +0 -0
  298. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/docs/_quarto.yml +0 -0
  299. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/golden-sensitive-facet.code-workspace +0 -0
  300. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/notebooks/_quarto.yml +0 -0
  301. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/pyproject.toml +0 -0
  302. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/scaffold.py +0 -0
  303. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/ai.yml +0 -0
  304. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/author.yml +0 -0
  305. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/connections.yml +0 -0
  306. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/data.yml +0 -0
  307. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/git.yml +0 -0
  308. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/quarto.yml +0 -0
  309. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings/scaffold.yml +0 -0
  310. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/expected/settings.yml +0 -0
  311. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/golden/sensitive-facet/layers.json +0 -0
  312. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/compose.yml +0 -0
  313. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/mariadb/init.sql +0 -0
  314. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/mysql/init.sql +0 -0
  315. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/postgres/init.sql +0 -0
  316. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/sqlserver/init.sql +0 -0
  317. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/test_cloud_staging.py +0 -0
  318. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/integration/test_servers.py +0 -0
  319. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/NAMESPACE +0 -0
  320. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/README.md +0 -0
  321. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/merge/atomic-entries.json +0 -0
  322. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/merge/conflicts-and-labels.json +0 -0
  323. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/merge/empty-sections.json +0 -0
  324. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/merge/gitignore-and-assistants.json +0 -0
  325. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/shared/conformance/merge/maps-and-null-removal.json +0 -0
  326. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_cache.py +0 -0
  327. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_config_home.py +0 -0
  328. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_conformance.py +0 -0
  329. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_connections.py +0 -0
  330. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_db.py +0 -0
  331. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_dotenv.py +0 -0
  332. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_global_config.py +0 -0
  333. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_notebooks.py +0 -0
  334. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_outputs.py +0 -0
  335. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_packages.py +0 -0
  336. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_r_compat.py +0 -0
  337. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_r_live.py +0 -0
  338. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_secretservice.py +0 -0
  339. {framework_py-0.1.0.dev1 → framework_py-0.1.0.dev2}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: framework-py
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
4
4
  Summary: Structured data science projects: a data catalog, integrity tracking, caching, outputs, and AI guardrails
5
5
  Project-URL: Homepage, https://framework.pub
6
6
  Project-URL: Source, https://github.com/frameworktools/framework-py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "framework-py"
3
- version = "0.1.0.dev1"
3
+ version = "0.1.0.dev2"
4
4
  description = "Structured data science projects: a data catalog, integrity tracking, caching, outputs, and AI guardrails"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -85,7 +85,7 @@ strict = true
85
85
  files = ["src", "scripts"]
86
86
 
87
87
  [[tool.mypy.overrides]]
88
- module = ["pyarrow", "pyarrow.*", "keyring", "keyring.*", "secretstorage", "matplotlib", "matplotlib.*", "openpyxl", "pyreadstat", "boto3", "botocore", "botocore.*", "pyodbc", "pymysql", "psycopg", "psycopg.*", "duckdb"]
88
+ module = ["pyarrow", "pyarrow.*", "keyring", "keyring.*", "secretstorage", "jsonschema", "matplotlib", "matplotlib.*", "openpyxl", "pyreadstat", "boto3", "botocore", "botocore.*", "pyodbc", "pymysql", "psycopg", "psycopg.*", "duckdb"]
89
89
  ignore_missing_imports = true
90
90
 
91
91
  [tool.pytest.ini_options]
@@ -0,0 +1,377 @@
1
+ """Build the docs site archive for py.framework.pub.
2
+
3
+ Writes ``reference.json`` from the docstrings of everything in
4
+ ``framework.__all__``, checks it against ``docs-reference.v1.json``, and
5
+ packs ``docs/site`` with it into ``docs-site.tar.gz``. The format is R's
6
+ ``inst/schema/docs-site.md``.
7
+
8
+ python scripts/build_docs.py # check only
9
+ python scripts/build_docs.py --reference out.json # write reference.json
10
+ python scripts/build_docs.py --archive dist-docs/docs-site.tar.gz
11
+
12
+ Docstrings are Google style. The first line is the summary, the paragraphs
13
+ before the first section are the description, and ``Args:`` (or
14
+ ``Attributes:`` for classes), ``Returns:``, and ``Raises:`` become
15
+ parameters, returns, and a section.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import dataclasses
22
+ import inspect
23
+ import io
24
+ import json
25
+ import re
26
+ import sys
27
+ import tarfile
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ import jsonschema
32
+ import yaml
33
+
34
+ import framework
35
+
36
+ ROOT = Path(__file__).resolve().parents[1]
37
+ SITE = ROOT / "docs" / "site"
38
+ SCHEMA = ROOT / "src" / "framework" / "_assets" / "schema" / "docs-reference.v1.json"
39
+
40
+ _SECTION = re.compile(r"^(Args|Arguments|Attributes|Returns|Yields|Raises|Examples?|Notes?):\s*$")
41
+ _ITEM = re.compile(r"^(\*{0,2}[A-Za-z_][A-Za-z0-9_]*)(?:\s*\(([^)]*)\))?:\s*(.*)$")
42
+
43
+
44
+ class DocsError(Exception):
45
+ pass
46
+
47
+
48
+ # Docstrings -------------------------------------------------------------------
49
+
50
+
51
+ def _markdown(text: str) -> str:
52
+ """reStructuredText inline code to Markdown, linking exported names."""
53
+ exported = set(framework.__all__)
54
+
55
+ def code(match: re.Match[str]) -> str:
56
+ inner = match.group(1)
57
+ name = inner.removesuffix("()")
58
+ if name in exported:
59
+ return f"[`{inner}`](ref:{name})"
60
+ return f"`{inner}`"
61
+
62
+ return re.sub(r"``(.+?)``", code, text).strip()
63
+
64
+
65
+ def _dedent_block(lines: list[str]) -> list[str]:
66
+ indents = [len(line) - len(line.lstrip()) for line in lines if line.strip()]
67
+ cut = min(indents, default=0)
68
+ return [line[cut:] for line in lines]
69
+
70
+
71
+ def parse_docstring(doc: str) -> dict[str, Any]:
72
+ """Split a Google-style docstring into summary, description, and sections."""
73
+ lines = doc.strip().splitlines()
74
+ summary = lines[0].strip() if lines else ""
75
+ body: list[str] = []
76
+ sections: dict[str, list[str]] = {}
77
+ current: list[str] = body
78
+ for line in lines[1:]:
79
+ match = _SECTION.match(line)
80
+ if match:
81
+ key = {"Arguments": "Args", "Example": "Examples", "Notes": "Note"}.get(
82
+ match.group(1), match.group(1)
83
+ )
84
+ current = sections.setdefault(key, [])
85
+ continue
86
+ current.append(line)
87
+ paragraphs = re.split(r"\n\s*\n", "\n".join(body).strip())
88
+ paragraphs = [p for p in paragraphs if p.strip()]
89
+ return {
90
+ "summary": summary,
91
+ "description": "\n\n".join(paragraphs[:2]),
92
+ "details": "\n\n".join(paragraphs[2:]),
93
+ "sections": {key: _dedent_block(value) for key, value in sections.items()},
94
+ }
95
+
96
+
97
+ def parse_items(lines: list[str]) -> list[tuple[str, str]]:
98
+ """``name: text`` items, with indented continuation lines folded in."""
99
+ items: list[tuple[str, list[str]]] = []
100
+ for line in lines:
101
+ if not line.strip():
102
+ if items:
103
+ items[-1][1].append("")
104
+ continue
105
+ match = _ITEM.match(line) if not line.startswith(" ") else None
106
+ if match:
107
+ items.append((match.group(1), [match.group(3)]))
108
+ elif items:
109
+ items[-1][1].append(line.strip())
110
+ return [
111
+ (name, re.sub(r"(?<!\n)\n(?!\n)", " ", "\n".join(text).strip())) for name, text in items
112
+ ]
113
+
114
+
115
+ # Entries ----------------------------------------------------------------------
116
+
117
+
118
+ def _annotation(value: Any) -> str | None:
119
+ if value is inspect.Parameter.empty:
120
+ return None
121
+ return value if isinstance(value, str) else str(inspect.formatannotation(value))
122
+
123
+
124
+ def _prefix(param: inspect.Parameter) -> str:
125
+ if param.kind is inspect.Parameter.VAR_POSITIONAL:
126
+ return "*"
127
+ if param.kind is inspect.Parameter.VAR_KEYWORD:
128
+ return "**"
129
+ return ""
130
+
131
+
132
+ def _parameter(param: inspect.Parameter) -> str:
133
+ """A parameter as written in the source, shortening long defaults."""
134
+ text = _prefix(param) + param.name
135
+ annotation = _annotation(param.annotation)
136
+ if annotation:
137
+ text += f": {annotation}"
138
+ if param.default is not param.empty:
139
+ default = repr(param.default)
140
+ if len(default) > 30:
141
+ default = f"{default[0]}...{default[-1]}" if default[0] in "([{" else "..."
142
+ text += f" = {default}" if annotation else f"={default}"
143
+ return text
144
+
145
+
146
+ def _usage(name: str, signature: inspect.Signature) -> str:
147
+ params: list[str] = []
148
+ for param in signature.parameters.values():
149
+ if param.name == "self":
150
+ continue
151
+ if param.kind == param.KEYWORD_ONLY and not any(p.startswith("*") for p in params):
152
+ params.append("*")
153
+ params.append(_parameter(param))
154
+ returns = _annotation(signature.return_annotation)
155
+ tail = f" -> {returns}" if returns else ""
156
+ one_line = f"{name}({', '.join(params)}){tail}"
157
+ if len(one_line) <= 88:
158
+ return one_line
159
+ inner = "".join(f" {p},\n" for p in params)
160
+ return f"{name}(\n{inner}){tail}"
161
+
162
+
163
+ def _signature(obj: Any) -> inspect.Signature | None:
164
+ try:
165
+ return inspect.signature(obj)
166
+ except (TypeError, ValueError):
167
+ return None
168
+
169
+
170
+ def build_entry(name: str, obj: Any, category: str, problems: list[str]) -> dict[str, Any]:
171
+ doc = inspect.getdoc(obj)
172
+ if not doc:
173
+ raise DocsError(f"{name} has no docstring")
174
+ parsed = parse_docstring(doc)
175
+ sections = parsed["sections"]
176
+ is_class = inspect.isclass(obj)
177
+ entry: dict[str, Any] = {
178
+ "id": name,
179
+ "name": name,
180
+ "kind": "class" if is_class else "function",
181
+ "category": category,
182
+ "summary": _markdown(parsed["summary"]),
183
+ }
184
+ for key in ("description", "details"):
185
+ if parsed[key]:
186
+ entry[key] = _markdown(parsed[key])
187
+
188
+ documented = dict(parse_items(sections.get("Attributes" if is_class else "Args", [])))
189
+ parameters: list[dict[str, Any]] = []
190
+ if is_class and dataclasses.is_dataclass(obj):
191
+ for field in dataclasses.fields(obj):
192
+ if field.name.startswith("_"):
193
+ continue
194
+ if field.name not in documented:
195
+ problems.append(f"{name}: attribute '{field.name}' is not documented")
196
+ item: dict[str, Any] = {
197
+ "name": field.name,
198
+ "description": _markdown(documented.get(field.name, "")),
199
+ }
200
+ if _annotation(field.type):
201
+ item["type"] = _annotation(field.type)
202
+ parameters.append(item)
203
+ elif not is_class:
204
+ signature = _signature(obj)
205
+ if signature is None:
206
+ raise DocsError(f"{name}: no signature")
207
+ entry["usage"] = _usage(name, signature)
208
+ for param in signature.parameters.values():
209
+ key = _prefix(param)
210
+ label = f"{key}{param.name}"
211
+ text = documented.get(label) or documented.get(param.name)
212
+ if text is None:
213
+ problems.append(f"{name}: parameter '{param.name}' is not documented")
214
+ item = {"name": label, "description": _markdown(text or "")}
215
+ if _annotation(param.annotation):
216
+ item["type"] = _annotation(param.annotation)
217
+ if param.default is not param.empty:
218
+ item["default"] = repr(param.default)
219
+ elif key == "":
220
+ item["required"] = True
221
+ parameters.append(item)
222
+ if parameters:
223
+ entry["parameters"] = parameters
224
+
225
+ for key in ("Returns", "Yields"):
226
+ if key in sections:
227
+ entry["returns"] = _markdown("\n".join(sections[key]))
228
+ if "Note" in sections:
229
+ entry["note"] = _markdown("\n".join(sections["Note"]))
230
+ if "Raises" in sections:
231
+ raises = "\n".join(
232
+ f"- `{error}`: {_markdown(text)}" for error, text in parse_items(sections["Raises"])
233
+ )
234
+ entry["sections"] = [{"title": "Raises", "content": raises}]
235
+ if "Examples" in sections:
236
+ code = "\n".join(sections["Examples"]).strip()
237
+ code = "\n".join(re.sub(r"^(>>> |\.\.\. )", "", line) for line in code.splitlines())
238
+ entry["examples"] = [{"code": code}]
239
+ return entry
240
+
241
+
242
+ def build_reference(categories: list[dict[str, Any]], problems: list[str]) -> dict[str, Any]:
243
+ exported = list(framework.__all__)
244
+ placed: dict[str, str] = {}
245
+ for category in categories:
246
+ for name in category["names"]:
247
+ if name in placed:
248
+ raise DocsError(f"{name} is in both {placed[name]} and {category['id']}")
249
+ placed[name] = category["id"]
250
+ for name in category.get("common", []):
251
+ if name not in category["names"]:
252
+ raise DocsError(f"{category['id']}: common name {name} is not in its names")
253
+ documented = [name for name in exported if not name.startswith("__")]
254
+ missing = sorted(set(documented) - set(placed))
255
+ unknown = sorted(set(placed) - set(documented))
256
+ if missing or unknown:
257
+ raise DocsError(
258
+ f"categories.yml: missing {missing or 'none'}; not exported {unknown or 'none'}"
259
+ )
260
+ entries = [
261
+ build_entry(name, getattr(framework, name), placed[name], problems)
262
+ for category in categories
263
+ for name in category["names"]
264
+ ]
265
+ return {
266
+ "schema": "docs-reference.v1",
267
+ "product": "py",
268
+ "version": framework.__version__,
269
+ "categories": [
270
+ {
271
+ key: value
272
+ for key, value in {
273
+ "id": category["id"],
274
+ "title": category["title"],
275
+ "description": category.get("description"),
276
+ "position": position,
277
+ "common": category.get("common"),
278
+ }.items()
279
+ if value is not None
280
+ }
281
+ for position, category in enumerate(categories, start=1)
282
+ ],
283
+ "entries": entries,
284
+ }
285
+
286
+
287
+ # Site -------------------------------------------------------------------------
288
+
289
+
290
+ def _prose(markdown: str) -> str:
291
+ """Markdown without fenced code blocks, whose lines are not headings or links."""
292
+ return re.sub(r"^(```|~~~).*?^\1[ \t]*$", "", markdown, flags=re.M | re.S)
293
+
294
+
295
+ def check_site(reference: dict[str, Any]) -> list[str]:
296
+ """Check site.yml, the guides' front matter, and every guide:/ref: link."""
297
+ problems: list[str] = []
298
+ site = yaml.safe_load((SITE / "site.yml").read_text(encoding="utf-8"))
299
+ sections = {section["id"] for section in site["sections"]}
300
+ guides: dict[str, Path] = {}
301
+ for path in sorted((SITE / "guides").glob("*.md")):
302
+ text = path.read_text(encoding="utf-8")
303
+ match = re.match(r"\A---\n(.*?)\n---\n", text, re.S)
304
+ if not match:
305
+ problems.append(f"{path.name}: no front matter")
306
+ continue
307
+ meta = yaml.safe_load(match.group(1)) or {}
308
+ for key in ("id", "title", "description", "section", "position"):
309
+ if key not in meta:
310
+ problems.append(f"{path.name}: front matter is missing {key}")
311
+ if meta.get("section") not in sections:
312
+ problems.append(f"{path.name}: unknown section {meta.get('section')}")
313
+ if meta.get("id") != path.stem:
314
+ problems.append(f"{path.name}: id {meta.get('id')} does not match the file name")
315
+ if re.search(r"^# ", _prose(text[match.end() :]), re.M):
316
+ problems.append(f"{path.name}: guides have no h1")
317
+ guides[path.stem] = path
318
+ ids = {entry["id"] for entry in reference["entries"]}
319
+ texts = [(path.name, _prose(path.read_text(encoding="utf-8"))) for path in guides.values()]
320
+ texts.append(("reference.json", json.dumps(reference)))
321
+ for source, text in texts:
322
+ for kind, target in re.findall(r"\((guide|ref):([^)#\s]+)", text):
323
+ if kind == "guide" and target not in guides:
324
+ problems.append(f"{source}: no guide {target}")
325
+ if kind == "ref" and target not in ids:
326
+ problems.append(f"{source}: no reference entry {target}")
327
+ for image in re.findall(r"\]\((images/[^)\s]+)\)", text):
328
+ if not (SITE / image).is_file():
329
+ problems.append(f"{source}: missing {image}")
330
+ return problems
331
+
332
+
333
+ def write_archive(reference: dict[str, Any], target: Path) -> None:
334
+ target.parent.mkdir(parents=True, exist_ok=True)
335
+ with tarfile.open(target, "w:gz") as archive:
336
+ for path in sorted(SITE.rglob("*")):
337
+ if path.is_file() and path.name != "categories.yml":
338
+ archive.add(path, arcname=f"docs-site/{path.relative_to(SITE).as_posix()}")
339
+ data = (json.dumps(reference, indent=2, ensure_ascii=False) + "\n").encode("utf-8")
340
+ info = tarfile.TarInfo("docs-site/reference.json")
341
+ info.size = len(data)
342
+ archive.addfile(info, io.BytesIO(data))
343
+
344
+
345
+ def main(argv: list[str] | None = None) -> int:
346
+ parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
347
+ parser.add_argument("--reference", type=Path, help="write reference.json here")
348
+ parser.add_argument("--archive", type=Path, help="write docs-site.tar.gz here")
349
+ parser.add_argument("--schema", type=Path, default=SCHEMA, help="docs-reference.v1.json")
350
+ args = parser.parse_args(argv)
351
+ problems: list[str] = []
352
+ try:
353
+ categories = yaml.safe_load((SITE / "categories.yml").read_text(encoding="utf-8"))
354
+ reference = build_reference(categories, problems)
355
+ except DocsError as error:
356
+ print(f"error: {error}", file=sys.stderr)
357
+ return 1
358
+ schema = json.loads(args.schema.read_text(encoding="utf-8"))
359
+ errors = sorted(
360
+ jsonschema.Draft202012Validator(schema).iter_errors(reference), key=lambda e: e.path
361
+ )
362
+ problems += [f"reference.json: {list(e.path)}: {e.message}" for e in errors]
363
+ problems += check_site(reference)
364
+ if problems:
365
+ print("\n".join(f"error: {problem}" for problem in problems), file=sys.stderr)
366
+ return 1
367
+ if args.reference:
368
+ args.reference.write_text(json.dumps(reference, indent=2, ensure_ascii=False) + "\n")
369
+ if args.archive:
370
+ write_archive(reference, args.archive)
371
+ guides = len(list((SITE / "guides").glob("*.md")))
372
+ print(f"{len(reference['entries'])} reference entries and {guides} guides are valid")
373
+ return 0
374
+
375
+
376
+ if __name__ == "__main__":
377
+ sys.exit(main())
@@ -28,6 +28,8 @@ WORKTREE = "worktree"
28
28
  # Runtime files go in the package; test-only files go in tests/shared.
29
29
  ASSETS: dict[str, str] = {
30
30
  "inst/schema/*.json": "src/framework/_assets/schema",
31
+ # The docs site format (docs/site, scripts/build_docs.py).
32
+ "inst/schema/docs-site.md": "docs",
31
33
  "inst/templates/init.sql": "src/framework/_assets",
32
34
  "inst/hooks/pre-commit": "src/framework/_assets/hooks",
33
35
  "inst/conformance/README.md": "tests/shared/conformance",
@@ -37,7 +37,12 @@ def _parser() -> argparse.ArgumentParser:
37
37
  create.add_argument(
38
38
  "--interactive",
39
39
  action="store_true",
40
- help="prompt for missing answers, initialize git, and register the project",
40
+ help=(
41
+ "with --name, ask for the type when --type is missing (the project is "
42
+ "registered and gets a git repository either way, unless your defaults turn "
43
+ "git off); with --from-layers, initialize git and register the project "
44
+ "instead of running headless"
45
+ ),
41
46
  )
42
47
 
43
48
  bootstrap = commands.add_parser("bootstrap", help="prepare a cloned project")
@@ -12,23 +12,31 @@ set -u
12
12
 
13
13
  cd "$(git rev-parse --show-toplevel)" || exit 1
14
14
 
15
+ # Each runner returns 127 when its runtime or the framework package is
16
+ # missing, so the next runtime gets a turn and the message below can say why
17
+
15
18
  run_r() {
16
19
  command -v Rscript >/dev/null 2>&1 || return 127
20
+ # framework 1.x from CRAN has no shared hook entry point
21
+ probe='ok <- requireNamespace("framework", quietly = TRUE) && exists(".hook_run", envir = asNamespace("framework"))'
22
+ Rscript -e "$probe; quit(status = if (ok) 0 else 127)" >/dev/null 2>&1 || return 127
17
23
  Rscript -e 'quit(status = framework:::.hook_run("pre-commit"))'
18
24
  }
19
25
 
20
26
  run_python() {
21
27
  if [ -x .venv/bin/python ]; then
22
- .venv/bin/python -m framework hook pre-commit
28
+ set -- .venv/bin/python
23
29
  elif [ -x .venv/Scripts/python.exe ]; then
24
- .venv/Scripts/python.exe -m framework hook pre-commit
30
+ set -- .venv/Scripts/python.exe
25
31
  elif [ -f uv.lock ] && command -v uv >/dev/null 2>&1; then
26
- uv run --frozen python -m framework hook pre-commit
32
+ set -- uv run --frozen python
27
33
  elif command -v python3 >/dev/null 2>&1; then
28
- python3 -m framework hook pre-commit
34
+ set -- python3
29
35
  else
30
36
  return 127
31
37
  fi
38
+ "$@" -c 'import framework' >/dev/null 2>&1 || return 127
39
+ "$@" -m framework hook pre-commit
32
40
  }
33
41
 
34
42
  has_rproj() {
@@ -52,8 +60,8 @@ if [ "$status" -eq 127 ] && { [ -f pyproject.toml ] || [ -f uv.lock ] || [ -d .v
52
60
  fi
53
61
 
54
62
  if [ "$status" -eq 127 ]; then
55
- echo "Framework hook: no R or Python runtime with framework installed was found." >&2
56
- echo "Install one, or run 'git commit --no-verify' if you are sure this commit is safe." >&2
63
+ echo "Framework hook: found neither R with framework 2.0 or later nor Python with framework-py." >&2
64
+ echo "Install one of them, or run 'git commit --no-verify' if you are sure this commit is safe." >&2
57
65
  exit 1
58
66
  fi
59
67
  exit "$status"
@@ -0,0 +1,137 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://framework.pub/schema/docs-reference.v1.json",
4
+ "title": "Framework docs reference",
5
+ "description": "The generated function or command reference for one product's docs site (docs/site/reference.json). Text fields hold CommonMark with GFM tables.",
6
+ "type": "object",
7
+ "required": ["schema", "product", "version", "categories", "entries"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "schema": { "const": "docs-reference.v1" },
11
+ "product": { "enum": ["r", "py", "cli"] },
12
+ "version": { "type": "string", "minLength": 1, "description": "The release this reference was built from, such as 2.0.0 or 0.1.0.dev1." },
13
+ "categories": {
14
+ "type": "array",
15
+ "items": { "$ref": "#/$defs/category" }
16
+ },
17
+ "entries": {
18
+ "type": "array",
19
+ "items": { "$ref": "#/$defs/entry" }
20
+ }
21
+ },
22
+ "$defs": {
23
+ "id": {
24
+ "type": "string",
25
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]*$",
26
+ "description": "URL-safe and unique within the product: /reference/{id}."
27
+ },
28
+ "markdown": { "type": "string" },
29
+ "category": {
30
+ "type": "object",
31
+ "required": ["id", "title", "position"],
32
+ "additionalProperties": false,
33
+ "properties": {
34
+ "id": { "$ref": "#/$defs/id" },
35
+ "title": { "type": "string", "minLength": 1 },
36
+ "description": { "type": "string", "description": "One line, shown under the title in the Reference dropdown." },
37
+ "position": { "type": "integer" },
38
+ "common": {
39
+ "type": "array",
40
+ "items": { "type": "string" },
41
+ "description": "Entry names listed first, under a Common heading."
42
+ }
43
+ }
44
+ },
45
+ "titled": {
46
+ "type": "object",
47
+ "required": ["title", "content"],
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "title": { "type": "string", "minLength": 1 },
51
+ "content": { "$ref": "#/$defs/markdown" }
52
+ }
53
+ },
54
+ "entry": {
55
+ "type": "object",
56
+ "required": ["id", "name", "kind", "category", "summary"],
57
+ "additionalProperties": false,
58
+ "properties": {
59
+ "id": { "$ref": "#/$defs/id" },
60
+ "name": { "type": "string", "minLength": 1, "description": "As users write it: data_read, AuditResult, or \"project bootstrap\"." },
61
+ "kind": { "enum": ["function", "class", "command"] },
62
+ "category": { "type": "string", "description": "A category id." },
63
+ "title": { "type": "string", "description": "A short heading. Defaults to name." },
64
+ "summary": { "type": "string", "minLength": 1, "description": "One line, used in lists and search results." },
65
+ "usage": { "type": "string", "description": "The signature or command line, shown as code." },
66
+ "description": { "$ref": "#/$defs/markdown", "description": "A paragraph or two (roxygen @description, a docstring's opening paragraphs)." },
67
+ "details": { "$ref": "#/$defs/markdown", "description": "The long body (roxygen @details)." },
68
+ "parameters": {
69
+ "type": "array",
70
+ "description": "Arguments, flags, or a class's fields, in signature order.",
71
+ "items": {
72
+ "type": "object",
73
+ "required": ["name", "description"],
74
+ "additionalProperties": false,
75
+ "properties": {
76
+ "name": { "type": "string", "minLength": 1, "description": "x, *, --path, or -p, --path." },
77
+ "type": { "type": "string" },
78
+ "default": { "type": "string", "description": "As code, such as NULL, None, or \".\"." },
79
+ "required": { "type": "boolean" },
80
+ "env": { "type": "string", "description": "An environment variable that sets it (commands)." },
81
+ "description": { "$ref": "#/$defs/markdown" }
82
+ }
83
+ }
84
+ },
85
+ "returns": { "$ref": "#/$defs/markdown" },
86
+ "note": { "$ref": "#/$defs/markdown" },
87
+ "sections": {
88
+ "type": "array",
89
+ "items": {
90
+ "type": "object",
91
+ "required": ["title", "content"],
92
+ "additionalProperties": false,
93
+ "properties": {
94
+ "title": { "type": "string", "minLength": 1 },
95
+ "content": { "$ref": "#/$defs/markdown" },
96
+ "subsections": { "type": "array", "items": { "$ref": "#/$defs/titled" } }
97
+ }
98
+ }
99
+ },
100
+ "examples": {
101
+ "type": "array",
102
+ "items": {
103
+ "type": "object",
104
+ "required": ["code"],
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "title": { "type": "string" },
108
+ "code": { "type": "string", "minLength": 1 },
109
+ "runnable": { "type": "boolean", "default": true, "description": "False for R's \\dontrun and for examples that need a server or credentials." }
110
+ }
111
+ }
112
+ },
113
+ "see_also": {
114
+ "type": "array",
115
+ "items": {
116
+ "oneOf": [
117
+ { "type": "string", "minLength": 1, "description": "An entry name in this product, or a link target such as py:ref:data_read." },
118
+ {
119
+ "type": "object",
120
+ "required": ["title", "url"],
121
+ "additionalProperties": false,
122
+ "properties": {
123
+ "title": { "type": "string", "minLength": 1 },
124
+ "url": { "type": "string", "pattern": "^https://" }
125
+ }
126
+ }
127
+ ]
128
+ }
129
+ },
130
+ "aliases": { "type": "array", "items": { "type": "string" } },
131
+ "lifecycle": { "enum": ["stable", "deprecated", "experimental"], "default": "stable" },
132
+ "replaced_by": { "type": "string", "description": "The entry name to use instead, for deprecated entries." },
133
+ "since": { "type": "string", "description": "The version that added it." }
134
+ }
135
+ }
136
+ }
137
+ }
@@ -2,11 +2,21 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
8
9
  from framework.project import project_root
9
10
 
11
+ # A folder named like private, confidential, or sensitive (outputs_private and
12
+ # private_raw count too), as the git security audit and R's audit match them.
13
+ PRIVATE_SEGMENT = re.compile(r"(^|/)[^/]*(private|confidential|sensitive)[^/]*(/|$)", re.IGNORECASE)
14
+
15
+
16
+ def has_private_segment(path: str | Path) -> bool:
17
+ """Whether a folder path, with ``/`` separators, has a private segment."""
18
+ return bool(PRIVATE_SEGMENT.search(Path(path).as_posix()))
19
+
10
20
 
11
21
  def relative_to_root(path: Path, root: Path) -> str:
12
22
  """``path`` relative to the project root when it is inside it, else absolute."""
@@ -179,13 +179,16 @@ def ai_skills_update(project_path: str | Path = ".") -> list[str]:
179
179
  come from ``.framework/spec.json``, or from the settings for projects
180
180
  without one.
181
181
 
182
+ Args:
183
+ project_path: The project's root directory. Default: the current directory.
184
+
182
185
  Returns:
183
186
  The skill folders written.
184
187
  """
185
188
  from framework._log import inform, show_messages
186
189
 
187
190
  show_messages()
188
- root = Path(project_path)
191
+ root = Path(project_path).expanduser()
189
192
  spec = _installed_spec(root)
190
193
  folders, _ = assistant_targets(spec)
191
194
  updated = []