salak 0.1.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 (283) hide show
  1. salak-0.1.0/.cursorrules +69 -0
  2. salak-0.1.0/.gitattributes +16 -0
  3. salak-0.1.0/.github/copilot-instructions.md +84 -0
  4. salak-0.1.0/.github/workflows/ci.yml +96 -0
  5. salak-0.1.0/.github/workflows/clean-machine.yml +138 -0
  6. salak-0.1.0/.github/workflows/publish-testpypi.yml +71 -0
  7. salak-0.1.0/.github/workflows/release.yml +96 -0
  8. salak-0.1.0/.gitignore +36 -0
  9. salak-0.1.0/AGENTS.md +84 -0
  10. salak-0.1.0/CLAUDE.md +561 -0
  11. salak-0.1.0/LICENSE +21 -0
  12. salak-0.1.0/PKG-INFO +234 -0
  13. salak-0.1.0/README.md +214 -0
  14. salak-0.1.0/context-pack.md +279 -0
  15. salak-0.1.0/docs/RELEASING.md +152 -0
  16. salak-0.1.0/docs/SALAK-AUDIT-2026-07-29.md +84 -0
  17. salak-0.1.0/docs/SALAK-SDD-v1.0.md +411 -0
  18. salak-0.1.0/docs/SALAK-SDD-v1_1.md +620 -0
  19. salak-0.1.0/docs/SALAK-SDD-v1_2.md +660 -0
  20. salak-0.1.0/docs/SALAK-SDD-v1_3.md +850 -0
  21. salak-0.1.0/docs/SALAK-SDD-v1_4.md +1431 -0
  22. salak-0.1.0/docs/cli-reference.md +382 -0
  23. salak-0.1.0/docs/salak-wbs-v1_1.md +2183 -0
  24. salak-0.1.0/docs/salak-wbs-v1_2.md +2364 -0
  25. salak-0.1.0/docs/salak-wbs-v1_3.md +2399 -0
  26. salak-0.1.0/docs/salak-wbs-v1_4.md +2417 -0
  27. salak-0.1.0/docs/salak-wbs-v1_5.md +2551 -0
  28. salak-0.1.0/docs/schema-guide.md +420 -0
  29. salak-0.1.0/docs/troubleshooting.md +365 -0
  30. salak-0.1.0/package-lock.json +75 -0
  31. salak-0.1.0/package.json +13 -0
  32. salak-0.1.0/project-memory/00-master-plan.md +158 -0
  33. salak-0.1.0/project-memory/01-decision-register.md +446 -0
  34. salak-0.1.0/project-memory/02-gap-analysis.md +362 -0
  35. salak-0.1.0/project-memory/03-progress.md +7139 -0
  36. salak-0.1.0/project-memory/consumer-proxy-2026-08-01.md +86 -0
  37. salak-0.1.0/project-memory/precision-audit-2026-08-13.md +536 -0
  38. salak-0.1.0/project-memory/recall-baseline-2026-08-13.md +128 -0
  39. salak-0.1.0/project-memory/spike-0/README.md +98 -0
  40. salak-0.1.0/project-memory/spike-0/findings-python.md +282 -0
  41. salak-0.1.0/project-memory/spike-0/findings-size.md +263 -0
  42. salak-0.1.0/project-memory/spike-0/findings-typescript.md +274 -0
  43. salak-0.1.0/project-memory/spike-0/package-lock.json +73 -0
  44. salak-0.1.0/project-memory/spike-0/spike-0-findings.md +361 -0
  45. salak-0.1.0/project-memory/spike-0/versions.txt +70 -0
  46. salak-0.1.0/project-memory/v02-exit-gate-2026-08-13.md +121 -0
  47. salak-0.1.0/project-memory/v03-speedup-2026-08-22.md +58 -0
  48. salak-0.1.0/pyproject.toml +133 -0
  49. salak-0.1.0/src/salak/__init__.py +27 -0
  50. salak-0.1.0/src/salak/adapters/__init__.py +10 -0
  51. salak-0.1.0/src/salak/adapters/parse_recursion.py +45 -0
  52. salak-0.1.0/src/salak/adapters/python/__init__.py +382 -0
  53. salak-0.1.0/src/salak/adapters/python/imports.py +326 -0
  54. salak-0.1.0/src/salak/adapters/python/modules.py +649 -0
  55. salak-0.1.0/src/salak/adapters/python/sites.py +456 -0
  56. salak-0.1.0/src/salak/adapters/python/structure.py +528 -0
  57. salak-0.1.0/src/salak/adapters/typescript/__init__.py +384 -0
  58. salak-0.1.0/src/salak/adapters/typescript/imports.py +363 -0
  59. salak-0.1.0/src/salak/adapters/typescript/modules.py +603 -0
  60. salak-0.1.0/src/salak/adapters/typescript/sites.py +357 -0
  61. salak-0.1.0/src/salak/adapters/typescript/structure.py +416 -0
  62. salak-0.1.0/src/salak/adapters/typescript/tsconfig.py +438 -0
  63. salak-0.1.0/src/salak/check/__init__.py +27 -0
  64. salak-0.1.0/src/salak/check/exit_codes.py +32 -0
  65. salak-0.1.0/src/salak/check/staleness.py +94 -0
  66. salak-0.1.0/src/salak/cli/__init__.py +9 -0
  67. salak-0.1.0/src/salak/cli/check.py +74 -0
  68. salak-0.1.0/src/salak/cli/diff.py +44 -0
  69. salak-0.1.0/src/salak/cli/doctor.py +134 -0
  70. salak-0.1.0/src/salak/cli/exit_codes.py +46 -0
  71. salak-0.1.0/src/salak/cli/main.py +185 -0
  72. salak-0.1.0/src/salak/cli/scan.py +688 -0
  73. salak-0.1.0/src/salak/cli/validate.py +174 -0
  74. salak-0.1.0/src/salak/cli/version.py +43 -0
  75. salak-0.1.0/src/salak/core/__init__.py +6 -0
  76. salak-0.1.0/src/salak/core/adapter.py +190 -0
  77. salak-0.1.0/src/salak/core/discovery.py +444 -0
  78. salak-0.1.0/src/salak/core/emit.py +338 -0
  79. salak-0.1.0/src/salak/core/ignore.py +408 -0
  80. salak-0.1.0/src/salak/core/merge.py +217 -0
  81. salak-0.1.0/src/salak/core/models.py +651 -0
  82. salak-0.1.0/src/salak/core/node_id.py +172 -0
  83. salak-0.1.0/src/salak/core/paths.py +202 -0
  84. salak-0.1.0/src/salak/core/prior.py +529 -0
  85. salak-0.1.0/src/salak/core/registry.py +255 -0
  86. salak-0.1.0/src/salak/core/stats.py +219 -0
  87. salak-0.1.0/src/salak/core/syntax_error.py +116 -0
  88. salak-0.1.0/src/salak/core/write.py +162 -0
  89. salak-0.1.0/src/salak/diff/__init__.py +72 -0
  90. salak-0.1.0/src/salak/diff/compute.py +164 -0
  91. salak-0.1.0/src/salak/diff/exit_codes.py +41 -0
  92. salak-0.1.0/src/salak/diff/loading.py +150 -0
  93. salak-0.1.0/src/salak/diff/render.py +176 -0
  94. salak-0.1.0/src/salak/incremental/__init__.py +157 -0
  95. salak-0.1.0/src/salak/incremental/candidate_closure.py +78 -0
  96. salak-0.1.0/src/salak/incremental/carry_over.py +111 -0
  97. salak-0.1.0/src/salak/incremental/closure.py +208 -0
  98. salak-0.1.0/src/salak/incremental/compatibility.py +178 -0
  99. salak-0.1.0/src/salak/incremental/deletion.py +153 -0
  100. salak-0.1.0/src/salak/incremental/dirty.py +207 -0
  101. salak-0.1.0/src/salak/incremental/partial_pass1.py +84 -0
  102. salak-0.1.0/src/salak/incremental/partial_pass2.py +158 -0
  103. salak-0.1.0/src/salak/incremental/pipeline.py +263 -0
  104. salak-0.1.0/src/salak/incremental/recompute.py +109 -0
  105. salak-0.1.0/src/salak/incremental/reverse.py +179 -0
  106. salak-0.1.0/src/salak/incremental/safety_valve.py +127 -0
  107. salak-0.1.0/src/salak/incremental/symbol_delta.py +174 -0
  108. salak-0.1.0/src/salak/lsp/__init__.py +13 -0
  109. salak-0.1.0/src/salak/lsp/client.py +605 -0
  110. salak-0.1.0/src/salak/lsp/detect.py +357 -0
  111. salak-0.1.0/src/salak/lsp/readiness.py +316 -0
  112. salak-0.1.0/src/salak/precision/__init__.py +47 -0
  113. salak-0.1.0/src/salak/precision/__main__.py +71 -0
  114. salak-0.1.0/src/salak/precision/render.py +252 -0
  115. salak-0.1.0/src/salak/precision/sample.py +306 -0
  116. salak-0.1.0/src/salak/py.typed +0 -0
  117. salak-0.1.0/src/salak/resolution/__init__.py +75 -0
  118. salak-0.1.0/src/salak/resolution/edges.py +410 -0
  119. salak-0.1.0/src/salak/resolution/engine.py +267 -0
  120. salak-0.1.0/src/salak/resolution/models.py +137 -0
  121. salak-0.1.0/src/salak/resolution/provenance.py +292 -0
  122. salak-0.1.0/src/salak/resolution/unresolved.py +140 -0
  123. salak-0.1.0/src/salak/schema/__init__.py +120 -0
  124. salak-0.1.0/src/salak/schema/repo-graph-1.0.schema.json +621 -0
  125. salak-0.1.0/tests/acceptance_matrix.py +144 -0
  126. salak-0.1.0/tests/fixtures/README.md +67 -0
  127. salak-0.1.0/tests/fixtures/acceptance-matrix/src/barrel/index.ts +1 -0
  128. salak-0.1.0/tests/fixtures/acceptance-matrix/src/caller.ts +6 -0
  129. salak-0.1.0/tests/fixtures/acceptance-matrix/src/consumer.ts +5 -0
  130. salak-0.1.0/tests/fixtures/acceptance-matrix/src/depends_on_removable.ts +5 -0
  131. salak-0.1.0/tests/fixtures/acceptance-matrix/src/impl.ts +9 -0
  132. salak-0.1.0/tests/fixtures/acceptance-matrix/src/mystery.ts +5 -0
  133. salak-0.1.0/tests/fixtures/acceptance-matrix/src/removable.ts +3 -0
  134. salak-0.1.0/tests/fixtures/acceptance-matrix/src/standalone.ts +3 -0
  135. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_00.ts +3 -0
  136. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_01.ts +3 -0
  137. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_02.ts +3 -0
  138. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_03.ts +3 -0
  139. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_04.ts +3 -0
  140. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_05.ts +3 -0
  141. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_06.ts +3 -0
  142. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_07.ts +3 -0
  143. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_08.ts +3 -0
  144. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_09.ts +3 -0
  145. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_10.ts +3 -0
  146. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_11.ts +3 -0
  147. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_12.ts +3 -0
  148. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_13.ts +3 -0
  149. salak-0.1.0/tests/fixtures/acceptance-matrix/src/unrelated_14.ts +3 -0
  150. salak-0.1.0/tests/fixtures/broken/EXPECTED.md +127 -0
  151. salak-0.1.0/tests/fixtures/broken/src/dispatch.ts +6 -0
  152. salak-0.1.0/tests/fixtures/broken/src/half_merged.py +10 -0
  153. salak-0.1.0/tests/fixtures/broken/src/inventory.ts +7 -0
  154. salak-0.1.0/tests/fixtures/broken/src/ledger.py +15 -0
  155. salak-0.1.0/tests/fixtures/broken/src/money.py +5 -0
  156. salak-0.1.0/tests/fixtures/broken/src/reporting.py +7 -0
  157. salak-0.1.0/tests/fixtures/broken/src/truncated.ts +7 -0
  158. salak-0.1.0/tests/fixtures/broken/src/units.ts +5 -0
  159. salak-0.1.0/tests/fixtures/graphs/corrupt-1.0.json +108 -0
  160. salak-0.1.0/tests/fixtures/graphs/no-schema-version.json +109 -0
  161. salak-0.1.0/tests/fixtures/graphs/not-an-object.json +1 -0
  162. salak-0.1.0/tests/fixtures/graphs/unknown-version.json +110 -0
  163. salak-0.1.0/tests/fixtures/graphs/valid-1.0.json +110 -0
  164. salak-0.1.0/tests/fixtures/py-basic/EXPECTED.md +146 -0
  165. salak-0.1.0/tests/fixtures/py-basic/app/__init__.py +1 -0
  166. salak-0.1.0/tests/fixtures/py-basic/app/models.py +15 -0
  167. salak-0.1.0/tests/fixtures/py-basic/app/service.py +13 -0
  168. salak-0.1.0/tests/fixtures/py-basic/pyproject.toml +8 -0
  169. salak-0.1.0/tests/fixtures/py-sites/EXPECTED.md +87 -0
  170. salak-0.1.0/tests/fixtures/py-sites/src/__init__.py +6 -0
  171. salak-0.1.0/tests/fixtures/py-sites/src/kinds.py +28 -0
  172. salak-0.1.0/tests/fixtures/py-sites/src/service.py +28 -0
  173. salak-0.1.0/tests/fixtures/ts-basic/EXPECTED.md +115 -0
  174. salak-0.1.0/tests/fixtures/ts-basic/package.json +10 -0
  175. salak-0.1.0/tests/fixtures/ts-basic/src/billing.ts +16 -0
  176. salak-0.1.0/tests/fixtures/ts-basic/src/types.ts +8 -0
  177. salak-0.1.0/tests/fixtures/ts-basic/tsconfig.json +10 -0
  178. salak-0.1.0/tests/fixtures/ts-resolution/EXPECTED.md +172 -0
  179. salak-0.1.0/tests/fixtures/ts-resolution/src/aliases.ts +10 -0
  180. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/chained/deep/impl.ts +1 -0
  181. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/chained/deep/index.ts +1 -0
  182. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/chained/index.ts +1 -0
  183. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/competing/index.ts +2 -0
  184. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/competing/one.ts +1 -0
  185. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/competing/two.ts +1 -0
  186. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/namespaced/impl.ts +1 -0
  187. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/namespaced/index.ts +1 -0
  188. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/renamed/impl.ts +1 -0
  189. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/renamed/index.ts +1 -0
  190. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/star/impl.ts +1 -0
  191. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels/star/index.ts +1 -0
  192. salak-0.1.0/tests/fixtures/ts-resolution/src/barrels.ts +10 -0
  193. salak-0.1.0/tests/fixtures/ts-resolution/src/dual-a/thing.ts +1 -0
  194. salak-0.1.0/tests/fixtures/ts-resolution/src/dual-b/thing.ts +1 -0
  195. salak-0.1.0/tests/fixtures/ts-resolution/src/feature/impl.ts +1 -0
  196. salak-0.1.0/tests/fixtures/ts-resolution/src/feature/index.ts +1 -0
  197. salak-0.1.0/tests/fixtures/ts-resolution/src/money.ts +3 -0
  198. salak-0.1.0/tests/fixtures/ts-resolution/src/ui/button.ts +1 -0
  199. salak-0.1.0/tests/fixtures/ts-resolution/src/utils/format.ts +1 -0
  200. salak-0.1.0/tests/fixtures/ts-resolution/tsconfig.json +12 -0
  201. salak-0.1.0/tests/fixtures/ts-sites/EXPECTED.md +104 -0
  202. salak-0.1.0/tests/fixtures/ts-sites/src/kinds.ts +14 -0
  203. salak-0.1.0/tests/fixtures/ts-sites/src/service.ts +29 -0
  204. salak-0.1.0/tests/test_adapter_protocol.py +123 -0
  205. salak-0.1.0/tests/test_adapter_registry.py +314 -0
  206. salak-0.1.0/tests/test_alias_barrel_v02.py +343 -0
  207. salak-0.1.0/tests/test_candidate_closure.py +262 -0
  208. salak-0.1.0/tests/test_carry_over.py +252 -0
  209. salak-0.1.0/tests/test_check_staleness.py +128 -0
  210. salak-0.1.0/tests/test_cli_check.py +104 -0
  211. salak-0.1.0/tests/test_cli_diff.py +111 -0
  212. salak-0.1.0/tests/test_cli_doctor.py +142 -0
  213. salak-0.1.0/tests/test_cli_help.py +40 -0
  214. salak-0.1.0/tests/test_cli_scan.py +419 -0
  215. salak-0.1.0/tests/test_cli_validate.py +254 -0
  216. salak-0.1.0/tests/test_cli_version.py +58 -0
  217. salak-0.1.0/tests/test_closure.py +319 -0
  218. salak-0.1.0/tests/test_compatibility_guard.py +307 -0
  219. salak-0.1.0/tests/test_context_pack.py +114 -0
  220. salak-0.1.0/tests/test_core_imports_no_adapter.py +163 -0
  221. salak-0.1.0/tests/test_degradation.py +318 -0
  222. salak-0.1.0/tests/test_degraded_mode.py +255 -0
  223. salak-0.1.0/tests/test_deletion.py +243 -0
  224. salak-0.1.0/tests/test_determinism.py +602 -0
  225. salak-0.1.0/tests/test_determinism_pass2.py +350 -0
  226. salak-0.1.0/tests/test_diff_compute.py +209 -0
  227. salak-0.1.0/tests/test_diff_ground_truth.py +228 -0
  228. salak-0.1.0/tests/test_diff_loading.py +210 -0
  229. salak-0.1.0/tests/test_diff_render.py +184 -0
  230. salak-0.1.0/tests/test_dirty_set.py +368 -0
  231. salak-0.1.0/tests/test_discovery.py +405 -0
  232. salak-0.1.0/tests/test_emit.py +493 -0
  233. salak-0.1.0/tests/test_fixtures.py +175 -0
  234. salak-0.1.0/tests/test_git_friendliness.py +469 -0
  235. salak-0.1.0/tests/test_ignore_rules.py +511 -0
  236. salak-0.1.0/tests/test_inheritance.py +388 -0
  237. salak-0.1.0/tests/test_instantiates.py +394 -0
  238. salak-0.1.0/tests/test_integration_reverification.py +293 -0
  239. salak-0.1.0/tests/test_lsp_client.py +345 -0
  240. salak-0.1.0/tests/test_merge.py +354 -0
  241. salak-0.1.0/tests/test_node_id.py +272 -0
  242. salak-0.1.0/tests/test_overwrite_guard.py +734 -0
  243. salak-0.1.0/tests/test_packaging_metadata.py +69 -0
  244. salak-0.1.0/tests/test_partial_pass1.py +210 -0
  245. salak-0.1.0/tests/test_partial_pass2.py +297 -0
  246. salak-0.1.0/tests/test_path_containment.py +441 -0
  247. salak-0.1.0/tests/test_precision_sample.py +422 -0
  248. salak-0.1.0/tests/test_provenance.py +413 -0
  249. salak-0.1.0/tests/test_python_adapter.py +314 -0
  250. salak-0.1.0/tests/test_python_external.py +354 -0
  251. salak-0.1.0/tests/test_python_imports.py +619 -0
  252. salak-0.1.0/tests/test_python_sites.py +458 -0
  253. salak-0.1.0/tests/test_python_structure.py +472 -0
  254. salak-0.1.0/tests/test_readiness.py +445 -0
  255. salak-0.1.0/tests/test_recompute.py +200 -0
  256. salak-0.1.0/tests/test_references.py +494 -0
  257. salak-0.1.0/tests/test_resilience.py +373 -0
  258. salak-0.1.0/tests/test_resolution.py +650 -0
  259. salak-0.1.0/tests/test_resolution_fixture.py +290 -0
  260. salak-0.1.0/tests/test_resource_bounds.py +339 -0
  261. salak-0.1.0/tests/test_reverse_index.py +329 -0
  262. salak-0.1.0/tests/test_safety_valve.py +130 -0
  263. salak-0.1.0/tests/test_schema_bundling.py +209 -0
  264. salak-0.1.0/tests/test_schema_envelope.py +283 -0
  265. salak-0.1.0/tests/test_schema_records.py +443 -0
  266. salak-0.1.0/tests/test_server_detection.py +388 -0
  267. salak-0.1.0/tests/test_stats.py +262 -0
  268. salak-0.1.0/tests/test_subprocess_hardening.py +468 -0
  269. salak-0.1.0/tests/test_symbol_delta.py +350 -0
  270. salak-0.1.0/tests/test_syntax_error.py +173 -0
  271. salak-0.1.0/tests/test_typescript_adapter.py +243 -0
  272. salak-0.1.0/tests/test_typescript_external.py +399 -0
  273. salak-0.1.0/tests/test_typescript_imports.py +583 -0
  274. salak-0.1.0/tests/test_typescript_sites.py +438 -0
  275. salak-0.1.0/tests/test_typescript_structure.py +390 -0
  276. salak-0.1.0/tests/test_typescript_tsconfig.py +344 -0
  277. salak-0.1.0/tests/test_unresolved.py +443 -0
  278. salak-0.1.0/tests/test_untrusted_resolver_output.py +155 -0
  279. salak-0.1.0/tests/test_v03_acceptance_matrix.py +206 -0
  280. salak-0.1.0/tests/test_v03_incremental_equals_full.py +273 -0
  281. salak-0.1.0/tests/test_wheel_installation.py +99 -0
  282. salak-0.1.0/tests/test_write_path_safety.py +233 -0
  283. salak-0.1.0/uv.lock +563 -0
@@ -0,0 +1,69 @@
1
+ Salak — deterministic code knowledge graph generator. Python 3.11+, PyPI `salak`.
2
+ Parses a repo, emits one provenance-tagged JSON file (`repo-graph.json`).
3
+ It is a BUILD ARTIFACT, not a database. Built to unblock Jeruk AI/MANGGA, not to
4
+ become a product.
5
+
6
+ READ THESE TWO FILES BEFORE DOING ANYTHING:
7
+ 1. CLAUDE.md — working rules, the binding documents by name,
8
+ and what to do next
9
+ 2. project-memory/03-progress.md — authoritative state, newest snapshot at the top
10
+
11
+ THIS FILE DELIBERATELY CARRIES NO PROJECT STATE.
12
+ No task numbers, no phase status, no document version numbers, no "next task".
13
+ It used to carry all four, and it went roughly thirty tasks stale without anyone
14
+ noticing — it still named a superseded spec and a task finished weeks earlier.
15
+ Five documents claimed the same facts and one of them was being maintained. So
16
+ the duplication is gone rather than merely corrected, and a test enforces it
17
+ (tests/test_context_pack.py). If you are tempted to paste status here, put it in
18
+ 03-progress.md and let this file keep pointing at it.
19
+
20
+ DURABLE RULES — these do not change from task to task:
21
+
22
+ - Precision over recall. A wrong edge is worse than a missing edge.
23
+ - Provenance downgrades, never upgrades. `extracted` means the relation is
24
+ literally in source AND the target was confirmed — guess any part of the target
25
+ and it is not `extracted`.
26
+ - Determinism is a test, not an aspiration. Two runs over an unchanged tree are
27
+ byte-identical except `generated_at`. Never let `set` or `dict` iteration order
28
+ reach the output.
29
+ - Core must never import an adapter. Registration goes through the
30
+ `salak.adapters` entry-point group, and a test enforces the direction.
31
+ - Every path in the artifact is repo-relative with POSIX separators. This repo is
32
+ developed on Windows; assume nothing.
33
+ - Degraded is not failed. No language server means structure plus import edges,
34
+ not an error. `health_check()` never raises; `resolve()` may return empty.
35
+ - Diagnostics never abort a run. A broken file yields a diagnostic and the scan
36
+ completes.
37
+ - Exit codes are a public contract: 0 clean · 1 fatal · 2 completed with
38
+ diagnostics · 3 overwrite guard refused.
39
+ - The bundled JSON Schema beats the prose. If the two disagree, the schema is
40
+ right and the prose gets fixed.
41
+ - No placeholders. No "implement later", no stub returning a fake value. If a task
42
+ is too large to finish, say so and split it before starting.
43
+ - Any binding decision the spec does not already make: STOP and ask the owner,
44
+ then record the approved answer in project-memory/01-decision-register.md.
45
+ - One task at a time, in the plan's order — not numeric order; the two disagree,
46
+ and following the numbers has already caused a task to be skipped. Check
47
+ dependencies before starting.
48
+ - Gates are hard. A `rework` verdict invalidates the downstream task list rather
49
+ than delaying it. Never pull later-phase work forward.
50
+ - Run the real tools. Never describe output you did not see.
51
+ - Update project-memory/03-progress.md in the same turn as the work it records.
52
+
53
+ HARD BOUNDARIES — decisions, not preferences. Do not build, scaffold, or
54
+ "prepare for": a UI or graph viewer · document/PDF/image ingestion · any LLM
55
+ call · clustering or community detection · cross-repo graphs · any language
56
+ beyond TypeScript and Python · dev-kickoff integration · `salak query` · watch
57
+ mode · metrics or complexity scoring. Touching one of these is an anti-pattern,
58
+ not initiative. Name it out loud instead.
59
+
60
+ VERIFY THE REPO:
61
+ uv sync && uv run ruff check . && uv run mypy src tests && uv run pytest
62
+
63
+ CONVENTIONS: `src/salak/` layout · ruff + mypy strict · CI on 3.11 and 3.12 ·
64
+ snake_case functions, PascalCase classes, SCREAMING_SNAKE_CASE diagnostic codes ·
65
+ full type hints · `LanguageAdapter` is a `typing.Protocol` · Conventional Commits
66
+ with the phase as scope, e.g. `feat(v0.1): emit files section`.
67
+
68
+ Talk to the owner in Bahasa Indonesia. Write every file, comment, commit message
69
+ and document in English.
@@ -0,0 +1,16 @@
1
+ # LF everywhere, on every platform.
2
+ #
3
+ # This repository is developed on Windows but its output is a build artifact whose
4
+ # whole point is byte-identical, file-scoped diffs (SDD §3.6, acceptance criterion 6).
5
+ # Mixed line endings make every diff noisy and would eventually be mistaken for a
6
+ # determinism defect. Normalised repo-wide at TASK-016 after 12 tracked files had
7
+ # drifted to CRLF.
8
+ * text=auto eol=lf
9
+
10
+ # Keep the artifact's own rules explicit — it is JSON, emitted one record per line.
11
+ *.json text eol=lf
12
+ repo-graph.json text eol=lf
13
+
14
+ *.png binary
15
+ *.jpg binary
16
+ *.ico binary
@@ -0,0 +1,84 @@
1
+ # Copilot instructions — Salak
2
+
3
+ Salak — deterministic code knowledge graph generator. Python 3.11+, published on
4
+ PyPI as `salak`. It parses a repository and emits one provenance-tagged JSON file
5
+ (`repo-graph.json`) so an assistant can answer *"what depends on this module?"*
6
+ from a recorded fact instead of a guess. The graph is a **build artifact, not a
7
+ database**. It exists to unblock Jeruk AI and MANGGA; it is not meant to become a
8
+ product.
9
+
10
+ ## Read these two files before doing anything
11
+
12
+ | File | What it holds |
13
+ |---|---|
14
+ | `CLAUDE.md` | The working rules, the binding documents **by name**, and what to do next |
15
+ | `project-memory/03-progress.md` | Authoritative state — newest snapshot at the top |
16
+
17
+ ## This file deliberately carries no project state
18
+
19
+ No task numbers, no phase status, no document version numbers, no "next task".
20
+
21
+ It used to carry all four, and it went roughly thirty tasks stale without anyone
22
+ noticing — still naming a superseded spec and a task finished weeks earlier. The
23
+ cause was structural rather than careless: five documents claimed the same facts
24
+ and only one of them was being maintained. So the duplication is gone rather than
25
+ merely corrected, and a test enforces it (`tests/test_context_pack.py`).
26
+
27
+ If you are tempted to paste status here, put it in `03-progress.md` and let this
28
+ file keep pointing at it.
29
+
30
+ ## Durable rules — these do not change from task to task
31
+
32
+ - **Precision over recall.** A wrong edge is worse than a missing edge.
33
+ - **Provenance downgrades, never upgrades.** `extracted` means the relation is
34
+ literally in source **and** the target was confirmed. Guess any part of the
35
+ target and it is not `extracted`.
36
+ - **Determinism is a test.** Two runs over an unchanged tree are byte-identical
37
+ except `generated_at`. Never let a `set` or `dict` iteration order reach the
38
+ output.
39
+ - **Core must never import an adapter.** Registration goes through the
40
+ `salak.adapters` entry-point group, and a test enforces the direction.
41
+ - **Every path in the artifact is repo-relative with POSIX separators.** This
42
+ repository is developed on Windows; assume nothing.
43
+ - **Degraded is not failed.** No language server means structure plus import
44
+ edges, not an error. `health_check()` never raises.
45
+ - **Diagnostics never abort a run.** A broken file yields a diagnostic and the
46
+ scan completes.
47
+ - **Exit codes are a public contract:** `0` clean · `1` fatal · `2` completed with
48
+ diagnostics · `3` overwrite guard refused.
49
+ - **The bundled JSON Schema beats the prose.** If they disagree, the schema is
50
+ right and the prose gets fixed.
51
+ - **No placeholders.** No "implement later", no stub returning a fake value. If a
52
+ task is too large to finish, say so and split it before starting.
53
+ - **Any binding decision the spec does not make: STOP and ask the owner**, then
54
+ record the approved answer in `project-memory/01-decision-register.md`.
55
+ - **One task at a time, in the plan's order** — not numeric order; the two
56
+ disagree, and following the numbers has already caused a task to be skipped.
57
+ - **Gates are hard.** A `rework` verdict invalidates the downstream task list
58
+ rather than delaying it. Never pull later-phase work forward.
59
+ - **Run the real tools.** Never describe output you did not see.
60
+
61
+ ## Hard boundaries — decisions, not preferences
62
+
63
+ Do not build, scaffold, or "prepare for": a UI or graph viewer · document, PDF or
64
+ image ingestion · **any LLM call** · clustering or community detection ·
65
+ cross-repo graphs · any language beyond TypeScript and Python · dev-kickoff
66
+ integration · **`salak query`** · watch mode · metrics or complexity scoring.
67
+
68
+ Touching one of these is an anti-pattern, not initiative. Name it out loud instead.
69
+
70
+ ## Verify the repo
71
+
72
+ ```bash
73
+ uv sync && uv run ruff check . && uv run mypy src tests && uv run pytest
74
+ ```
75
+
76
+ ## Conventions
77
+
78
+ `src/salak/` layout · ruff + mypy strict · CI on 3.11 and 3.12 · `snake_case`
79
+ functions · `PascalCase` classes · `SCREAMING_SNAKE_CASE` diagnostic codes · full
80
+ type hints · `LanguageAdapter` is a `typing.Protocol` · Conventional Commits with
81
+ the phase as scope, e.g. `feat(v0.1): emit files section`.
82
+
83
+ Talk to the owner in **Bahasa Indonesia**. Write every file, comment, commit
84
+ message and document in **English**.
@@ -0,0 +1,96 @@
1
+ # TASK-009 — lint, type-check and tests on Python 3.11 and 3.12.
2
+ #
3
+ # Three things here are deliberate rather than boilerplate:
4
+ #
5
+ # * **The steps are ordered and none carries `continue-on-error`.** A failing `ruff`
6
+ # or `mypy` fails the job and stops it before the tests run, which is DoD item 3.
7
+ # Nothing here may be softened into a warning.
8
+ # * **mypy is given the matrix version explicitly.** `pyproject.toml` pins
9
+ # `python_version = "3.11"`, so without `--python-version` the 3.12 leg would run the
10
+ # *same* type-check as the 3.11 leg and the matrix would only be varying the pytest
11
+ # runtime. The CLI flag overrides the config, so both legs genuinely differ.
12
+ # * **`pytest` exit code 5 is accepted, loudly.** See the test step.
13
+
14
+ name: CI
15
+
16
+ on:
17
+ push:
18
+ branches: [main]
19
+ pull_request:
20
+ branches: [main]
21
+
22
+ # Read-only by default. The security track (P5, TASK-099…102) treats an
23
+ # over-permissioned workflow token as a finding, and this is the cheap moment to
24
+ # get it right.
25
+ permissions:
26
+ contents: read
27
+
28
+ concurrency:
29
+ group: ci-${{ github.ref }}
30
+ cancel-in-progress: true
31
+
32
+ jobs:
33
+ check:
34
+ name: py${{ matrix.python-version }}
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ # Both legs always report. Cancelling 3.12 because 3.11 failed hides
38
+ # version-specific breakage, which is the only thing a matrix exists to find.
39
+ fail-fast: false
40
+ matrix:
41
+ python-version: ["3.11", "3.12"]
42
+
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+
46
+ - name: Install uv
47
+ uses: astral-sh/setup-uv@v5
48
+ with:
49
+ enable-cache: true
50
+
51
+ - name: Sync dependencies
52
+ # `--frozen` refuses to update uv.lock. A pyproject.toml edited without
53
+ # re-locking then fails here rather than silently resolving to something no
54
+ # developer has ever run.
55
+ run: uv sync --frozen --python ${{ matrix.python-version }}
56
+
57
+ - name: Install Node
58
+ # Phase 2 is entirely about language servers, so a CI that cannot run them cannot
59
+ # verify the phase at all. `npm ci` installs the exact pins in package.json —
60
+ # pyright, typescript-language-server, and typescript 5.9.3 as a CEILING (D15).
61
+ # The postinstall hook fails the job if `typescript/lib/tsserver.js` is missing,
62
+ # which is the whole of that ceiling's compatibility test.
63
+ uses: actions/setup-node@v4
64
+ with:
65
+ node-version: "24"
66
+
67
+ - name: Install language servers (npm ci)
68
+ run: npm ci --no-audit --no-fund
69
+
70
+ - name: Lint (ruff)
71
+ run: uv run ruff check .
72
+
73
+ - name: Type-check (mypy, strict, src + tests)
74
+ run: uv run mypy --python-version ${{ matrix.python-version }}
75
+
76
+ - name: Test (pytest)
77
+ shell: bash
78
+ # `pytest` exits **5** when it collects no tests, and an exit-5 CI step is red.
79
+ # TASK-009's Definition of Done is literally "CI green on an empty test suite",
80
+ # so the condition is handled explicitly instead of being left to depend on the
81
+ # suite happening to be non-empty (gap analysis G1).
82
+ #
83
+ # Exit 5 means specifically "no tests collected" — a collection *error* exits 2
84
+ # or 3 and still fails the job — so this cannot mask a broken test module. It
85
+ # can mask a mis-set `testpaths`, which is why it annotates the run instead of
86
+ # passing in silence.
87
+ run: |
88
+ set +e
89
+ uv run pytest
90
+ code=$?
91
+ set -e
92
+ if [ "$code" -eq 5 ]; then
93
+ echo "::warning title=Empty test suite::pytest collected no tests (exit 5). Treated as a pass per TASK-009 DoD, but a non-empty suite is expected from TASK-010 onward."
94
+ exit 0
95
+ fi
96
+ exit "$code"
@@ -0,0 +1,138 @@
1
+ # TASK-095 — clean-machine installation acceptance test. SDD §6 v1.0 done criterion.
2
+ #
3
+ # §2.1 requires two Node-based language servers that PyPI cannot install, so
4
+ # "installable on a clean machine" is satisfiable in a state where those servers are
5
+ # absent entirely — this task exists to force that gap into the open rather than let
6
+ # "clean machine" mean whatever the person running the check happens to have installed.
7
+ #
8
+ # **D54 (project-memory/01-decision-register.md): "clean machine" is defined as three
9
+ # states, all three required to pass, none more authoritative than another:**
10
+ # 1. No Node.js at all
11
+ # 2. Node.js present, no language server installed
12
+ # 3. Node.js present, both pinned language servers installed (`npm ci`, same shape
13
+ # `ci.yml` already uses)
14
+ #
15
+ # **What "capability difference" means today, proven rather than asserted**: after
16
+ # K15/D43, no module under `salak.resolution` imports `salak.lsp`, so a language
17
+ # server's presence changes nothing about what a scan emits. The final step proves
18
+ # this concretely — all three states scan the identical fixture and their graphs must
19
+ # be byte-identical except `generated_at`, checked with `salak diff` itself.
20
+ #
21
+ # **One job, not three-plus-a-comparison.** The first version of this workflow used
22
+ # four jobs (one per state, one to compare) and passed graphs between them via
23
+ # `actions/upload-artifact`/`download-artifact`. Real run, 2026-08-26: every state's
24
+ # own `doctor`/`scan` step passed, but every `upload-artifact` step failed —
25
+ # "Artifact storage quota has been hit" — an account-level GitHub Actions storage
26
+ # limit, not a bug in the check itself. Node's presence and installed servers are
27
+ # runtime, not install-time, facts, so all three states can be exercised sequentially
28
+ # against **one** build and **one** install in **one** container's filesystem, with the
29
+ # three graphs compared as local files — no artifact transfer needed at all.
30
+
31
+ name: Clean-machine matrix
32
+
33
+ on:
34
+ push:
35
+ branches: [main]
36
+ pull_request:
37
+ branches: [main]
38
+
39
+ permissions:
40
+ contents: read
41
+
42
+ concurrency:
43
+ group: clean-machine-${{ github.ref }}
44
+ cancel-in-progress: true
45
+
46
+ jobs:
47
+ clean-machine:
48
+ name: Three states, one container, no artifacts
49
+ runs-on: ubuntu-latest
50
+ # Debian-based, so Node can be installed with apt partway through — and, until
51
+ # that step runs, genuinely has no Node anywhere on the image (checked, not
52
+ # trusted, in "State 1" below).
53
+ container: python:3.11-slim
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+
57
+ - name: Install uv
58
+ run: pip install --no-cache-dir uv
59
+
60
+ - name: Sync and build once — every state below installs the same wheel
61
+ run: |
62
+ uv sync --frozen --python 3.11
63
+ uv build
64
+
65
+ - name: Install salak from the built wheel into a fresh venv
66
+ run: |
67
+ uv venv /tmp/clean-venv --python 3.11
68
+ uv pip install --python /tmp/clean-venv/bin/python dist/*.whl
69
+
70
+ - name: Make a tiny fixture, scanned identically in every state below
71
+ run: |
72
+ mkdir -p /tmp/fixture
73
+ printf 'export const x = 1;\n' > /tmp/fixture/a.ts
74
+
75
+ # ----------------------------------------------------------------- State 1
76
+
77
+ - name: "State 1: confirm no Node, then doctor + scan"
78
+ run: |
79
+ if command -v node >/dev/null 2>&1; then
80
+ echo "::error::this image already has Node on PATH; State 1 is not testing what it claims to"
81
+ exit 1
82
+ fi
83
+ /tmp/clean-venv/bin/salak doctor | tee doctor-1.txt
84
+ grep -q "Node.js: NOT FOUND" doctor-1.txt
85
+ grep -q "python: ok" doctor-1.txt
86
+ grep -q "typescript: ok" doctor-1.txt
87
+ /tmp/clean-venv/bin/salak scan /tmp/fixture --out "$PWD/graph-1-no-node.json" --no-lsp
88
+
89
+ # ----------------------------------------------------------------- State 2
90
+
91
+ - name: Install Node (no language servers yet)
92
+ run: |
93
+ apt-get update -qq
94
+ apt-get install -y --no-install-recommends nodejs npm >/dev/null
95
+
96
+ - name: "State 2: Node present, no servers — doctor + scan"
97
+ run: |
98
+ command -v node >/dev/null 2>&1 || { echo "::error::Node install did not put node on PATH"; exit 1; }
99
+ /tmp/clean-venv/bin/salak doctor | tee doctor-2.txt
100
+ grep -q "Node.js: found" doctor-2.txt
101
+ grep -q "pyright: not detected" doctor-2.txt
102
+ grep -q "typescript-language-server: not detected" doctor-2.txt
103
+ grep -q "python: ok" doctor-2.txt
104
+ grep -q "typescript: ok" doctor-2.txt
105
+ /tmp/clean-venv/bin/salak scan /tmp/fixture --out "$PWD/graph-2-node-no-servers.json" --no-lsp
106
+
107
+ # ----------------------------------------------------------------- State 3
108
+
109
+ - name: Install language servers (npm ci)
110
+ run: npm ci --no-audit --no-fund
111
+
112
+ - name: "State 3: Node present, both servers — doctor + scan"
113
+ run: |
114
+ /tmp/clean-venv/bin/salak doctor | tee doctor-3.txt
115
+ grep -q "Node.js: found" doctor-3.txt
116
+ grep -q "pyright " doctor-3.txt
117
+ grep -q "typescript-language-server:" doctor-3.txt
118
+ grep -q "python: ok" doctor-3.txt
119
+ grep -q "typescript: ok" doctor-3.txt
120
+ /tmp/clean-venv/bin/salak scan /tmp/fixture --out "$PWD/graph-3-node-with-servers.json" --no-lsp
121
+
122
+ # -------------------------------------------------------- Prove parity
123
+
124
+ - name: All three states must produce an identical graph
125
+ # The tool verifying its own DoD claim: `salak diff` already ignores
126
+ # `generated_at` (its whole reason to exist), so "empty" here means
127
+ # genuinely identical, not merely eyeballed-similar.
128
+ run: |
129
+ set -e
130
+ for pair in \
131
+ "graph-1-no-node.json graph-2-node-no-servers.json" \
132
+ "graph-1-no-node.json graph-3-node-with-servers.json" \
133
+ "graph-2-node-no-servers.json graph-3-node-with-servers.json"; do
134
+ echo "::group::diff $pair"
135
+ /tmp/clean-venv/bin/salak diff $pair
136
+ echo "::endgroup::"
137
+ done
138
+ echo "All three clean-machine states produced an identical graph."
@@ -0,0 +1,71 @@
1
+ # TASK-086 — TestPyPI publish rehearsal. SDD §5, WBS §4.1.
2
+ #
3
+ # Manually triggered (`workflow_dispatch`), not tag-triggered — a TestPyPI rehearsal is
4
+ # something the owner runs deliberately before cutting a real release, as many times as
5
+ # needed, without it consuming a real version tag. Give it whatever version string you
6
+ # want to test with; TestPyPI's index is separate from PyPI's, so nothing here can
7
+ # collide with a real published version.
8
+ #
9
+ # Requires the same one-time PyPI Trusted Publishing setup as `release.yml`, done on
10
+ # **test.pypi.org** specifically (a separate account/project registration from the real
11
+ # pypi.org one) — see `docs/RELEASING.md`.
12
+
13
+ name: Publish to TestPyPI
14
+
15
+ on:
16
+ workflow_dispatch:
17
+ inputs:
18
+ version:
19
+ description: "Version string to build and publish (e.g. 0.1.0.dev1) — TestPyPI only, does not need to match any git tag"
20
+ required: true
21
+ type: string
22
+
23
+ jobs:
24
+ publish-testpypi:
25
+ name: Build and publish to TestPyPI
26
+ runs-on: ubuntu-latest
27
+ environment: testpypi
28
+ # Job-level `permissions:` REPLACES the workflow-level block for this job, it does
29
+ # not merge with it — a real bug found running this workflow for the first time
30
+ # (2026-08-26): `contents: read` lived only at the workflow level, so the job's
31
+ # own `permissions: {id-token: write}` silently dropped it, and actions/checkout
32
+ # got a token with no read access to the repo at all ("repository ... not found").
33
+ # Both permissions this job actually needs are listed here, explicitly, together.
34
+ permissions:
35
+ contents: read
36
+ id-token: write
37
+
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+
41
+ - name: Rewrite version for this rehearsal
42
+ # Not committed back, same as release.yml — this build never needs to match
43
+ # anything the repository's own history records.
44
+ run: |
45
+ version="${{ inputs.version }}"
46
+ sed -i "s/^version = \".*\"/version = \"${version}\"/" pyproject.toml
47
+ sed -i "s/^__version__ = \".*\"/__version__ = \"${version}\"/" src/salak/__init__.py
48
+ grep -q "version = \"${version}\"" pyproject.toml
49
+ grep -q "__version__ = \"${version}\"" src/salak/__init__.py
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v5
53
+ with:
54
+ enable-cache: true
55
+
56
+ - name: Sync dependencies
57
+ run: uv sync --python "3.11"
58
+
59
+ - name: Verify (lint, type-check, packaging + wheel-install tests)
60
+ run: |
61
+ uv run ruff check .
62
+ uv run mypy src tests
63
+ uv run pytest tests/test_cli_version.py tests/test_packaging_metadata.py tests/test_wheel_installation.py -q
64
+
65
+ - name: Build
66
+ run: uv build
67
+
68
+ - name: Publish to TestPyPI
69
+ uses: pypa/gh-action-pypi-publish@release/v1
70
+ with:
71
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,96 @@
1
+ # TASK-087 — release automation from a git tag. SDD §5, WBS Sec4.1.
2
+ #
3
+ # Triggered by pushing a tag matching `v*` (e.g. `v1.0.0`). Three things here are
4
+ # deliberate rather than boilerplate:
5
+ #
6
+ # * **The version is derived from the tag, not typed twice.** `pyproject.toml` and
7
+ # `src/salak/__init__.py` both carry a version string
8
+ # (`tests/test_cli_version.py::test_package_version_matches_pyprojects_declared_version`
9
+ # already guards the two staying equal to *each other*); this job additionally makes
10
+ # *the tag itself* the source of truth by rewriting both to match it before building,
11
+ # so "version string matches the release tag" (TASK-087 DoD item 3) holds by
12
+ # construction rather than by someone remembering to bump two files before tagging.
13
+ # D53 records why this is a workflow-time rewrite rather than adopting a VCS-derived
14
+ # dynamic-versioning build backend (`hatch-vcs`) — see `project-memory/01-decision-register.md`.
15
+ # * **Full verification runs against the *retagged* tree**, not the tree as committed —
16
+ # a version string is part of what ships, and a bug the bump script itself introduces
17
+ # (a malformed version, a partial rewrite) must fail the job before anything is
18
+ # published, the same way a broken feature would.
19
+ # * **Publishing uses PyPI Trusted Publishing (OIDC)**, not a stored API token. No
20
+ # long-lived secret to leak or rotate; scoped by PyPI to exactly this repository and
21
+ # workflow file. Requires a one-time setup on pypi.org (see `docs/RELEASING.md`)
22
+ # before the first real run — until that's done, this job's publish step fails with a
23
+ # clear PyPI-side error, not a silent no-op.
24
+
25
+ name: Release
26
+
27
+ on:
28
+ push:
29
+ tags:
30
+ - "v*"
31
+
32
+ jobs:
33
+ release:
34
+ name: Build, verify and publish
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ # Job-level `permissions:` REPLACES the workflow-level block for this job, it does
38
+ # not merge with it — a real bug found running publish-testpypi.yml's identical
39
+ # shape for the first time (2026-08-26): `contents: read` lived only at the
40
+ # workflow level, so the job's own `permissions: {id-token: write}` (needed for
41
+ # Trusted Publishing's OIDC exchange) silently dropped it, and actions/checkout
42
+ # got a token with no read access to the repo at all ("repository ... not found").
43
+ # Both permissions this job actually needs are listed here, explicitly, together.
44
+ permissions:
45
+ contents: read
46
+ id-token: write
47
+
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Derive version from the tag
52
+ id: version
53
+ run: |
54
+ tag="${GITHUB_REF_NAME}"
55
+ version="${tag#v}"
56
+ if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+((a|b|rc|\.dev)[0-9]+)?$ ]]; then
57
+ echo "::error::tag '$tag' does not look like vMAJOR.MINOR.PATCH, optionally suffixed a[N]/b[N]/rc[N]/.dev[N] (PEP 440)"
58
+ exit 1
59
+ fi
60
+ echo "version=$version" >> "$GITHUB_OUTPUT"
61
+
62
+ - name: Rewrite version to match the tag
63
+ # Not committed back — this mutates the ephemeral checkout only, so the
64
+ # build artifact carries the tag's version without the repository ever
65
+ # holding a second, potentially-stale copy of it outside a release.
66
+ # Plain `sed`, deliberately — a shell one-liner is easier to verify at a
67
+ # glance than a heredoc'd Python string embedded inside a shell step.
68
+ run: |
69
+ version="${{ steps.version.outputs.version }}"
70
+ sed -i "s/^version = \".*\"/version = \"${version}\"/" pyproject.toml
71
+ sed -i "s/^__version__ = \".*\"/__version__ = \"${version}\"/" src/salak/__init__.py
72
+ grep -q "version = \"${version}\"" pyproject.toml
73
+ grep -q "__version__ = \"${version}\"" src/salak/__init__.py
74
+
75
+ - name: Install uv
76
+ uses: astral-sh/setup-uv@v5
77
+ with:
78
+ enable-cache: true
79
+
80
+ - name: Sync dependencies
81
+ run: uv sync --python "3.11"
82
+
83
+ - name: Verify the retagged tree (lint, type-check, package-metadata + version tests)
84
+ # Full pytest is CI's job (already green on the commit this tag points at);
85
+ # here it's the version-sync and packaging tests specifically that matter,
86
+ # since those are what the rewrite step above could break.
87
+ run: |
88
+ uv run ruff check .
89
+ uv run mypy src tests
90
+ uv run pytest tests/test_cli_version.py tests/test_packaging_metadata.py tests/test_wheel_installation.py -q
91
+
92
+ - name: Build
93
+ run: uv build
94
+
95
+ - name: Publish to PyPI
96
+ uses: pypa/gh-action-pypi-publish@release/v1
salak-0.1.0/.gitignore ADDED
@@ -0,0 +1,36 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ dist/
7
+ build/
8
+
9
+ # Tooling caches
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .pytest_cache/
13
+ .coverage
14
+
15
+ # Editors / OS
16
+ .idea/
17
+ .vscode/
18
+ .DS_Store
19
+
20
+ # Spike 0 raw evidence — local only. Finding G3.
21
+ # The 40 log files enumerate the internal structure of jeruk-ai and mangga-pa: route
22
+ # names, admin pages, auth flows, module layout, and the absolute paths of the machine
23
+ # they were measured on. No secret *values* (verified), but this package is destined
24
+ # for PyPI with a public GitHub homepage, and publishing another project's file
25
+ # inventory is cheap to prevent and impossible to undo. The findings that cite them
26
+ # stay tracked; the logs stay on disk, uncommitted, and `README.md` in that directory
27
+ # says so.
28
+ project-memory/spike-0/logs/
29
+
30
+ # NOTE: project-memory/repo-graph.json is NOT ignored.
31
+ # It is the artifact this tool exists to produce, and SDD §9 Q2 decided it is
32
+ # committed to git for v0.1 (1.08 MB measured, five times under the threshold).
33
+
34
+ # Language servers for the Phase 2 tests. Pinned in package.json, never committed:
35
+ # they are ~100 MB of platform-specific JavaScript, and `npm ci` reproduces them exactly.
36
+ node_modules/