code2okf 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 (120) hide show
  1. code2okf-0.1.0/.coderabbit.yaml +168 -0
  2. code2okf-0.1.0/.cspell.json +103 -0
  3. code2okf-0.1.0/.editorconfig +13 -0
  4. code2okf-0.1.0/.github/workflows/ci.yml +184 -0
  5. code2okf-0.1.0/.github/workflows/release.yml +209 -0
  6. code2okf-0.1.0/.gitignore +25 -0
  7. code2okf-0.1.0/.markdownlint-cli2.yaml +9 -0
  8. code2okf-0.1.0/.mcp.json +23 -0
  9. code2okf-0.1.0/.python-version +1 -0
  10. code2okf-0.1.0/.shellcheckrc +34 -0
  11. code2okf-0.1.0/.vscode/extensions.json +5 -0
  12. code2okf-0.1.0/.vscode/mcp.json +16 -0
  13. code2okf-0.1.0/.vscode/settings.json +3 -0
  14. code2okf-0.1.0/.yamllint +10 -0
  15. code2okf-0.1.0/AGENTS.md +177 -0
  16. code2okf-0.1.0/CHANGELOG.md +95 -0
  17. code2okf-0.1.0/CLAUDE.md +1 -0
  18. code2okf-0.1.0/CONTRIBUTING.md +216 -0
  19. code2okf-0.1.0/LICENSE +21 -0
  20. code2okf-0.1.0/LICENSE-OKF-SPEC.txt +203 -0
  21. code2okf-0.1.0/Makefile +166 -0
  22. code2okf-0.1.0/NOTICE-OKF-SPEC.md +37 -0
  23. code2okf-0.1.0/PKG-INFO +391 -0
  24. code2okf-0.1.0/README.md +368 -0
  25. code2okf-0.1.0/SPEC.md +1006 -0
  26. code2okf-0.1.0/VERSION +1 -0
  27. code2okf-0.1.0/kits/code2okf/README.md +128 -0
  28. code2okf-0.1.0/kits/code2okf/files/home/.local/lib/code2okf/mount-state.sh +48 -0
  29. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/AGENTS.md +185 -0
  30. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/models.json +84 -0
  31. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/settings.json +7 -0
  32. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/compile-okf/SKILL.md +142 -0
  33. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/compile-okf/scripts/check-okf.sh +155 -0
  34. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/compile-okf/scripts/frontmatter-guard.py +289 -0
  35. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/curate-okf/SKILL.md +68 -0
  36. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/inspect-md/SKILL.md +52 -0
  37. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/inspect-okf/SKILL.md +47 -0
  38. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/merkle-okf/SKILL.md +59 -0
  39. code2okf-0.1.0/kits/code2okf/files/home/.pi/agent/skills/size-okf/SKILL.md +52 -0
  40. code2okf-0.1.0/kits/code2okf/spec.yaml +312 -0
  41. code2okf-0.1.0/md/.gitkeep +0 -0
  42. code2okf-0.1.0/md/GoogleStyleGuide-abridged.md +2505 -0
  43. code2okf-0.1.0/pdf2md/README.md +71 -0
  44. code2okf-0.1.0/pdf2md/pyproject.toml +11 -0
  45. code2okf-0.1.0/pdf2md/uv.lock +2148 -0
  46. code2okf-0.1.0/pyproject.toml +118 -0
  47. code2okf-0.1.0/scripts/check-release-tag.sh +52 -0
  48. code2okf-0.1.0/scripts/inspectmd/README.md +42 -0
  49. code2okf-0.1.0/scripts/inspectmd/pyproject.toml +40 -0
  50. code2okf-0.1.0/scripts/inspectmd/src/inspectmd/__init__.py +8 -0
  51. code2okf-0.1.0/scripts/inspectmd/src/inspectmd/__main__.py +5 -0
  52. code2okf-0.1.0/scripts/inspectmd/src/inspectmd/cli.py +159 -0
  53. code2okf-0.1.0/scripts/inspectmd/src/inspectmd/parse.py +212 -0
  54. code2okf-0.1.0/scripts/inspectmd/tests/test_cli.py +108 -0
  55. code2okf-0.1.0/scripts/inspectmd/tests/test_parse.py +142 -0
  56. code2okf-0.1.0/scripts/inspectmd/uv.lock +79 -0
  57. code2okf-0.1.0/scripts/inspectokf/README.md +46 -0
  58. code2okf-0.1.0/scripts/inspectokf/pyproject.toml +40 -0
  59. code2okf-0.1.0/scripts/inspectokf/src/inspectokf/__init__.py +8 -0
  60. code2okf-0.1.0/scripts/inspectokf/src/inspectokf/__main__.py +5 -0
  61. code2okf-0.1.0/scripts/inspectokf/src/inspectokf/cli.py +104 -0
  62. code2okf-0.1.0/scripts/inspectokf/tests/test_cli.py +149 -0
  63. code2okf-0.1.0/scripts/inspectokf/uv.lock +79 -0
  64. code2okf-0.1.0/scripts/merkleokf/README.md +102 -0
  65. code2okf-0.1.0/scripts/merkleokf/pyproject.toml +40 -0
  66. code2okf-0.1.0/scripts/merkleokf/src/merkleokf/__init__.py +8 -0
  67. code2okf-0.1.0/scripts/merkleokf/src/merkleokf/__main__.py +5 -0
  68. code2okf-0.1.0/scripts/merkleokf/src/merkleokf/cli.py +121 -0
  69. code2okf-0.1.0/scripts/merkleokf/src/merkleokf/merkle.py +145 -0
  70. code2okf-0.1.0/scripts/merkleokf/tests/test_cli.py +168 -0
  71. code2okf-0.1.0/scripts/merkleokf/tests/test_merkle.py +185 -0
  72. code2okf-0.1.0/scripts/merkleokf/uv.lock +79 -0
  73. code2okf-0.1.0/scripts/release-notes.sh +59 -0
  74. code2okf-0.1.0/scripts/sizeokf/README.md +94 -0
  75. code2okf-0.1.0/scripts/sizeokf/pyproject.toml +40 -0
  76. code2okf-0.1.0/scripts/sizeokf/src/sizeokf/__init__.py +8 -0
  77. code2okf-0.1.0/scripts/sizeokf/src/sizeokf/__main__.py +5 -0
  78. code2okf-0.1.0/scripts/sizeokf/src/sizeokf/cli.py +93 -0
  79. code2okf-0.1.0/scripts/sizeokf/src/sizeokf/sizes.py +155 -0
  80. code2okf-0.1.0/scripts/sizeokf/tests/test_cli.py +134 -0
  81. code2okf-0.1.0/scripts/sizeokf/tests/test_sizes.py +203 -0
  82. code2okf-0.1.0/scripts/sizeokf/uv.lock +79 -0
  83. code2okf-0.1.0/scripts/sync-descriptions.py +169 -0
  84. code2okf-0.1.0/scripts/validate-spec.sh +23 -0
  85. code2okf-0.1.0/src/code2okf/__init__.py +8 -0
  86. code2okf-0.1.0/src/code2okf/cli.py +234 -0
  87. code2okf-0.1.0/src/code2okf/compile.py +267 -0
  88. code2okf-0.1.0/src/code2okf/events.py +86 -0
  89. code2okf-0.1.0/src/code2okf/resources.py +74 -0
  90. code2okf-0.1.0/src/code2okf/sandbox.py +266 -0
  91. code2okf-0.1.0/src/code2okf/workbench.py +572 -0
  92. code2okf-0.1.0/tests/conftest.py +207 -0
  93. code2okf-0.1.0/tests/test-mount-state.sh +216 -0
  94. code2okf-0.1.0/tests/test-sandbox-guest.sh +362 -0
  95. code2okf-0.1.0/tests/test-sandbox.sh +41 -0
  96. code2okf-0.1.0/tests/test_cli.py +334 -0
  97. code2okf-0.1.0/tests/test_compile.py +483 -0
  98. code2okf-0.1.0/tests/test_events.py +92 -0
  99. code2okf-0.1.0/tests/test_kit.py +37 -0
  100. code2okf-0.1.0/tests/test_package.py +86 -0
  101. code2okf-0.1.0/tests/test_resources.py +57 -0
  102. code2okf-0.1.0/tests/test_sandbox.py +263 -0
  103. code2okf-0.1.0/tests/test_workbench.py +701 -0
  104. code2okf-0.1.0/uv.lock +78 -0
  105. code2okf-0.1.0/web2md/README.md +94 -0
  106. code2okf-0.1.0/web2md/pyproject.toml +44 -0
  107. code2okf-0.1.0/web2md/src/web2md.py +768 -0
  108. code2okf-0.1.0/web2md/tests/conftest.py +98 -0
  109. code2okf-0.1.0/web2md/tests/test_assemble.py +103 -0
  110. code2okf-0.1.0/web2md/tests/test_cleaning.py +182 -0
  111. code2okf-0.1.0/web2md/tests/test_cli.py +69 -0
  112. code2okf-0.1.0/web2md/tests/test_constants.py +98 -0
  113. code2okf-0.1.0/web2md/tests/test_converter.py +123 -0
  114. code2okf-0.1.0/web2md/tests/test_discovery.py +63 -0
  115. code2okf-0.1.0/web2md/tests/test_fetch.py +198 -0
  116. code2okf-0.1.0/web2md/tests/test_links.py +180 -0
  117. code2okf-0.1.0/web2md/tests/test_run.py +147 -0
  118. code2okf-0.1.0/web2md/tests/test_slugs.py +70 -0
  119. code2okf-0.1.0/web2md/tests/test_validate.py +153 -0
  120. code2okf-0.1.0/web2md/uv.lock +292 -0
@@ -0,0 +1,168 @@
1
+ # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
2
+ #
3
+ # CodeRabbit configuration for code2okf.
4
+ # Anything not listed here uses CodeRabbit's own default.
5
+ language: en-US
6
+ tone_instructions: "Be concise and focus on critical issues only. Do not comment on trivial matters, nitpicks, or stylistic preferences. Only flag issues that could cause bugs, security vulnerabilities, or significant maintainability problems."
7
+ early_access: false
8
+ enable_free_tier: true
9
+ reviews:
10
+ profile: quiet
11
+ request_changes_workflow: false
12
+ high_level_summary: true
13
+ high_level_summary_in_walkthrough: false
14
+ review_status: true
15
+ commit_status: true
16
+ fail_commit_status: false
17
+ collapse_walkthrough: true
18
+ changed_files_summary: true
19
+ sequence_diagrams: false
20
+ estimate_code_review_effort: true
21
+ assess_linked_issues: true
22
+ related_issues: true
23
+ related_prs: true
24
+ suggested_labels: true
25
+ auto_apply_labels: false
26
+ suggested_reviewers: true
27
+ auto_assign_reviewers: false
28
+ in_progress_fortune: false
29
+ poem: false
30
+ abort_on_close: true
31
+ # Generated, gitignored, or manually maintained content that is not reviewed:
32
+ # the wiki's input and output, the lockfile, the optional PDF step, and
33
+ # SPEC.md, which is the upstream OKF revision this repo builds against
34
+ # rather than code of ours to review.
35
+ path_filters:
36
+ - "!SPEC.md"
37
+ - "!uv.lock"
38
+ - "!md/**"
39
+ - "!okf/**/*.md"
40
+ - "!pdf2md/**"
41
+ - "!.cursor/mcp.json"
42
+ - "!.cursor/plans/**"
43
+ - "!.claude/plans/**"
44
+ path_instructions:
45
+ - path: "**/*.sh"
46
+ instructions: |
47
+ Apply robust Bash practices: strict error handling, quoted expansions,
48
+ arrays over constructed command strings, and no silently masked
49
+ failures. Resolve paths independently of the caller's working
50
+ directory, validate prerequisites early and exit non-zero with a clear
51
+ message on stderr, clean up temporary resources with traps, keep
52
+ secrets out of arguments and logs, and require ShellCheck-clean code.
53
+ - path: "**/spec.yaml"
54
+ instructions: |
55
+ For Docker Sandbox Kit specs, treat schema conformance as load-bearing:
56
+ flag renamed or restructured keys that a schema check would reject, and
57
+ say so rather than asserting the spec is valid. Keep capabilities
58
+ least-privilege and network access default-deny with a stated reason
59
+ for every allowed host, keep credentials proxy-managed and real secrets
60
+ out of the file, pin installed versions, make setup commands
61
+ idempotent, and bound retries and timeouts.
62
+ - path: ".github/workflows/**"
63
+ instructions: |
64
+ Apply GitHub Actions best practices: pin third-party actions and
65
+ downloaded binaries to a version, keep workflow permissions
66
+ least-privilege, avoid running untrusted input with elevated
67
+ privileges, fail loudly on download and script errors, and never echo
68
+ secrets into logs. Keep duplicated trigger configuration in sync.
69
+ - path: "**/*.md"
70
+ instructions: |
71
+ Review documentation against the rest of the diff rather than for prose
72
+ style: flag commands, paths, options, and examples that this change has
73
+ made wrong or stale, and instructions that contradict each other. Do
74
+ not raise wording, grammar, or line-length nitpicks.
75
+ - path: "**/*.py"
76
+ instructions: |
77
+ Review Python code for:
78
+ - PEP 8, type hints, docstrings, error handling, and performance. Flag mutable default arguments.
79
+ - Blocking calls inside async functions (use asyncio equivalents)
80
+ - API keys or secrets accidentally logged or exposed in error messages
81
+ - Missing context managers for network/file resources
82
+ - Unbounded retries or missing timeouts on external calls
83
+ - User-controlled strings passed unsanitized to shell commands or LLM prompts
84
+ - path: "**/tests/**/*.py"
85
+ instructions: |
86
+ Review test code for:
87
+ - Check pytest best practices, fixture usage, test isolation, and edge case coverage.
88
+ - Async test functions must have @pytest.mark.asyncio decorator
89
+ - Tests creating files/directories must have cleanup fixtures
90
+ - Prefer tmp_path over manual temp directories, and monkeypatch over
91
+ direct mutation of module state, so tests stay isolated.
92
+ - Tests must not perform real network I/O; HTTP belongs behind
93
+ httpx.MockTransport.
94
+ auto_review:
95
+ enabled: true
96
+ auto_incremental_review: true
97
+ ignore_title_keywords:
98
+ - "DO NOT REVIEW"
99
+ - "WIP"
100
+ drafts: false
101
+ # The web2md scraper (web2md/src/) and its pytest suite (web2md/tests/) are the
102
+ # only first-party code here.
103
+ finishing_touches:
104
+ docstrings:
105
+ enabled: true
106
+ unit_tests:
107
+ enabled: true
108
+ pre_merge_checks:
109
+ docstrings:
110
+ mode: "off"
111
+ title:
112
+ mode: warning
113
+ requirements: "Start with an imperative verb, name the area touched, and keep it under 72 characters."
114
+ description:
115
+ mode: warning
116
+ issue_assessment:
117
+ mode: warning
118
+ # Only deliberate overrides are listed. Every other tool is left at its
119
+ # default so it starts working on its own if this repo grows that language.
120
+ tools:
121
+ shellcheck:
122
+ enabled: true
123
+ checkmake:
124
+ enabled: true
125
+ yamllint:
126
+ enabled: true
127
+ actionlint:
128
+ enabled: true
129
+ markdownlint:
130
+ enabled: true
131
+ gitleaks:
132
+ enabled: true
133
+ checkov:
134
+ enabled: true
135
+ semgrep:
136
+ enabled: true
137
+ osvScanner:
138
+ enabled: true
139
+ # Grammar and style findings are exactly what tone_instructions above tells
140
+ # the reviewer not to raise, so leave this off rather than filtering it.
141
+ languagetool:
142
+ enabled: false
143
+ github-checks:
144
+ enabled: true
145
+ timeout_ms: 900000
146
+ ruff:
147
+ enabled: true
148
+ config_file: "web2md/pyproject.toml"
149
+ chat:
150
+ art: false
151
+ auto_reply: true
152
+ knowledge_base:
153
+ opt_out: false
154
+ web_search:
155
+ enabled: true
156
+ code_guidelines:
157
+ enabled: true
158
+ learnings:
159
+ scope: auto
160
+ issues:
161
+ scope: auto
162
+ pull_requests:
163
+ scope: auto
164
+ issue_enrichment:
165
+ auto_enrich:
166
+ enabled: true
167
+ planning:
168
+ enabled: true
@@ -0,0 +1,103 @@
1
+ {
2
+ "version": "0.2",
3
+ "language": "en,en-GB",
4
+ "ignorePaths": [
5
+ "md/**",
6
+ ".claude/**",
7
+ ".cursor/**",
8
+ "SPEC.md",
9
+ "CLAUDE.md",
10
+ "uv.lock",
11
+ "web2md/tests/**",
12
+ "scripts/inspectmd/tests/**",
13
+ "scripts/inspectokf/tests/**",
14
+ "scripts/sizeokf/tests/**",
15
+ "scripts/merkleokf/tests/**",
16
+ "tests/**"
17
+ ],
18
+ "words": [
19
+ "okf",
20
+ "frontmatter",
21
+ "deepseek",
22
+ "litellm",
23
+ "gemini",
24
+ "thisismydesign",
25
+ "MarkerMarkdown",
26
+ "OpenRouter",
27
+ "Ollama",
28
+ "sbx",
29
+ "sbxenv",
30
+ "npmjs",
31
+ "maxsockets",
32
+ "earendil",
33
+ "passwordless",
34
+ "sandboxed",
35
+ "shellcheck",
36
+ "nullglob",
37
+ "nolog",
38
+ "pytest",
39
+ "pyproject",
40
+ "pipefail",
41
+ "psutil",
42
+ "fdfind",
43
+ "venv",
44
+ "pycache",
45
+ "httpx",
46
+ "pythonpath",
47
+ "testpaths",
48
+ "minversion",
49
+ "addopts",
50
+ "coderabbit",
51
+ "pycodestyle",
52
+ "pyflakes",
53
+ "pydocstyle",
54
+ "docstring",
55
+ "docstrings",
56
+ "conftest",
57
+ "monkeypatch",
58
+ "argparse",
59
+ "workbench",
60
+ "qwen",
61
+ "resumability",
62
+ "resumable",
63
+ "deepinfra",
64
+ "xhigh",
65
+ "gemma",
66
+ "idempotently",
67
+ "misordered",
68
+ "scannable",
69
+ "writable",
70
+ "inspectmd",
71
+ "uvx",
72
+ "hatchling",
73
+ "sdist",
74
+ "pypi",
75
+ "stdlib",
76
+ "context7",
77
+ "upstash",
78
+ "inspectokf",
79
+ "sizeokf",
80
+ "merkleokf",
81
+ "Merkle",
82
+ "worldbuilding",
83
+ "retcon",
84
+ "retconned",
85
+ "retcons",
86
+ "uncited",
87
+ "wikilinks",
88
+ "outlinks",
89
+ "dataview",
90
+ "throughline",
91
+ "plannable",
92
+ "leyline",
93
+ "Dragonsteel",
94
+ "Holocron",
95
+ "Dramatica",
96
+ "Egri",
97
+ "usermod",
98
+ "newgrp",
99
+ "okfctl",
100
+ "okflintrc",
101
+ "cwest"
102
+ ]
103
+ }
@@ -0,0 +1,13 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+
9
+ [*.md]
10
+ trim_trailing_whitespace = false
11
+
12
+ [Makefile]
13
+ indent_style = tab
@@ -0,0 +1,184 @@
1
+ ---
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+
9
+ # The jobs only read the checked-out tree.
10
+ permissions:
11
+ contents: read
12
+
13
+ # Cancel superseded runs
14
+ concurrency:
15
+ group: ci-${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ # Host-side shell tests for state mount selection and relocation. The Linux
20
+ # runner must exercise the real bind-mount and idempotency cases.
21
+ test-shell:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 5
24
+ steps:
25
+ - name: Checkout
26
+ uses: actions/checkout@v4
27
+ with:
28
+ persist-credentials: false
29
+
30
+ - name: Test state mounts
31
+ run: make test-shell CODE2OKF_REQUIRE_BIND=1
32
+
33
+ # Lint Markdown, JSON, YAML, shell, spelling, and Python. Reuses `make lint`
34
+ # so the lint definition lives in one place (the Makefile). markdownlint runs
35
+ # via npx; yamllint and ruff run ephemerally at a pinned version via
36
+ # `uv tool run`, belonging to no project, so this job installs neither
37
+ # project's dependencies. cspell runs via npx. shellcheck and jq are both
38
+ # pre-installed on ubuntu-latest. The JSON check is syntax only (`jq empty`)
39
+ # — no schema validation.
40
+ lint:
41
+ runs-on: ubuntu-latest
42
+ timeout-minutes: 10
43
+ steps:
44
+ - name: Checkout
45
+ uses: actions/checkout@v4
46
+ with:
47
+ # No git operations after checkout, so don't leave the job token in
48
+ # .git/config where a later third-party step could read it.
49
+ persist-credentials: false
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v9.0.0
53
+ with:
54
+ enable-cache: true
55
+
56
+ - name: Lint (markdownlint, jq, yamllint, shellcheck, cspell, ruff)
57
+ run: make lint MARKDOWNLINT='npx --yes markdownlint-cli2'
58
+
59
+ # Unit-test the web2md scraper. Reuses `make test-web2md` so the definition
60
+ # stays in the Makefile. web2md is its own project, so its lockfile holds
61
+ # only the scraper's own deps plus pytest — marker-pdf / torch cannot reach
62
+ # it and there is nothing to exclude. The suite is offline
63
+ # (httpx.MockTransport), so this job needs no network beyond the install.
64
+ test-web2md:
65
+ runs-on: ubuntu-latest
66
+ timeout-minutes: 10
67
+ steps:
68
+ - name: Checkout
69
+ uses: actions/checkout@v4
70
+ with:
71
+ # No git operations after checkout, so don't leave the job token in
72
+ # .git/config where a later third-party step could read it.
73
+ persist-credentials: false
74
+
75
+ - name: Install uv
76
+ uses: astral-sh/setup-uv@v9.0.0
77
+ with:
78
+ enable-cache: true
79
+
80
+ - name: Test (pytest)
81
+ run: make test-web2md
82
+
83
+ # Unit-test the four host CLIs (inspectmd, inspectokf, sizeokf, merkleokf).
84
+ # Reuses `make test-clis`. Each CLI is its own project and lockfile;
85
+ # stdlib-only subjects under test (inspectokf mocks tree). Offline: no
86
+ # network beyond the install.
87
+ test-clis:
88
+ runs-on: ubuntu-latest
89
+ timeout-minutes: 10
90
+ steps:
91
+ - name: Checkout
92
+ uses: actions/checkout@v4
93
+ with:
94
+ # No git operations after checkout, so don't leave the job token in
95
+ # .git/config where a later third-party step could read it.
96
+ persist-credentials: false
97
+
98
+ - name: Install uv
99
+ uses: astral-sh/setup-uv@v9.0.0
100
+ with:
101
+ enable-cache: true
102
+
103
+ - name: Test (pytest)
104
+ run: make test-clis
105
+
106
+ # Unit-test the code2okf driver itself. Reuses `make test-code2okf`. Offline:
107
+ # every sbx call goes through the one seam in code2okf.sandbox, which the
108
+ # suite replaces with a fake rather than shelling out to a real sandbox.
109
+ test-code2okf:
110
+ runs-on: ubuntu-latest
111
+ timeout-minutes: 10
112
+ steps:
113
+ - name: Checkout
114
+ uses: actions/checkout@v4
115
+ with:
116
+ # No git operations after checkout, so don't leave the job token in
117
+ # .git/config where a later third-party step could read it.
118
+ persist-credentials: false
119
+
120
+ - name: Install uv
121
+ uses: astral-sh/setup-uv@v9.0.0
122
+ with:
123
+ enable-cache: true
124
+
125
+ - name: Test (pytest)
126
+ run: make test-code2okf
127
+
128
+ # Build the distribution and prove it works away from the checkout. Reuses
129
+ # `make dist` so the definition stays in the Makefile. `uv build` builds the
130
+ # wheel from the sdist, so an sdist missing kits/, SPEC.md or
131
+ # scripts/<cli>/ breaks here rather than after a release; the smoke run then
132
+ # exercises the *bundled* assets, which nothing run from the repository can
133
+ # do -- resources.py finds the real tree and every lookup succeeds either
134
+ # way. Offline apart from the installs: --dry-run creates no sandbox.
135
+ build-package:
136
+ runs-on: ubuntu-latest
137
+ timeout-minutes: 10
138
+ steps:
139
+ - name: Checkout
140
+ uses: actions/checkout@v4
141
+ with:
142
+ # No git operations after checkout, so don't leave the job token in
143
+ # .git/config where a later third-party step could read it.
144
+ persist-credentials: false
145
+
146
+ - name: Install uv
147
+ uses: astral-sh/setup-uv@v9.0.0
148
+ with:
149
+ enable-cache: true
150
+
151
+ - name: Build and smoke-test the distribution
152
+ run: make dist
153
+
154
+ # Validate kits/code2okf/spec.yaml against the current Sandbox Kit schema.
155
+ # Static check — no Docker or login; only the CLI download needs the network.
156
+ validate-kit:
157
+ runs-on: ubuntu-latest
158
+ timeout-minutes: 10
159
+ steps:
160
+ - name: Checkout
161
+ uses: actions/checkout@v4
162
+ with:
163
+ # No git operations after checkout, so don't leave the job token in
164
+ # .git/config where a later third-party step could read it.
165
+ persist-credentials: false
166
+
167
+ # Install the latest sbx CLI. "latest" is intentional: the schema is
168
+ # bundled in the binary, so this surfaces schema drift as soon as it ships.
169
+ # Install steps mirror docker/sbx-kits-contrib .github/workflows/tck.yml.
170
+ - name: Install sbx CLI
171
+ run: |
172
+ set -euo pipefail
173
+ curl -fsSL \
174
+ "https://github.com/docker/sbx-releases/releases/latest/download/DockerSandboxes-linux.tar.gz" \
175
+ -o /tmp/DockerSandboxes-linux.tar.gz
176
+ tar xzf /tmp/DockerSandboxes-linux.tar.gz -C /tmp
177
+ sudo PREFIX="${HOME}/.docker/sbx" /tmp/docker-sbx/install.sh
178
+ echo "${HOME}/.docker/sbx/bin" >> "$GITHUB_PATH"
179
+
180
+ - name: Verify sbx
181
+ run: sbx version
182
+
183
+ - name: Validate Pi kit spec
184
+ run: make validate
@@ -0,0 +1,209 @@
1
+ ---
2
+ name: Release
3
+
4
+ # A tag push is the release: pushing vX.Y.Z verifies the repository agrees with
5
+ # itself, builds the distribution once, publishes it to PyPI, and then creates a
6
+ # GitHub Release whose notes are the matching section of CHANGELOG.md and whose
7
+ # assets are those same artifacts.
8
+ #
9
+ # The job order is deliberate and must stay a chain, not a fan-out. Two jobs
10
+ # that both merely `needs: [verify]` would run concurrently, and the one
11
+ # attaching assets could reach a Release that does not exist yet. Publishing
12
+ # first also means a tag on the Releases page always corresponds to a version
13
+ # that is actually installable.
14
+ #
15
+ # One build feeds both publishers, which is what makes a re-run safe: PyPI
16
+ # treats an upload of a byte-identical file as idempotent, so re-running after
17
+ # a partial failure re-uploads the same bytes rather than colliding with them.
18
+ #
19
+ # All the version and notes logic lives in scripts/, so `make lint`
20
+ # shellchecks the helpers and they can be rehearsed locally.
21
+
22
+ on:
23
+ push:
24
+ # Digit-only, so vX.Y.Z-rc1 and other shapes cannot trigger a release.
25
+ # Matches the pattern scripts/check-release-tag.sh enforces.
26
+ tags: ["v[0-9]+.[0-9]+.[0-9]+"]
27
+
28
+ # Narrowed further per job: github-release writes to the repository,
29
+ # publish-pypi mints an OIDC token, and verify and build need neither.
30
+ permissions:
31
+ contents: read
32
+
33
+ # Never cancel a release in flight.
34
+ concurrency:
35
+ group: release-${{ github.ref }}
36
+ cancel-in-progress: false
37
+
38
+ jobs:
39
+ # Gate the release on the repo agreeing with itself before anything is
40
+ # announced. `make lint` is the substance here: it already fails when VERSION
41
+ # and CHANGELOG.md's latest release heading disagree, so a tag whose version
42
+ # is inconsistent cannot create a Release.
43
+ verify:
44
+ runs-on: ubuntu-latest
45
+ timeout-minutes: 10
46
+ outputs:
47
+ version: ${{ steps.version.outputs.version }}
48
+ steps:
49
+ - name: Checkout
50
+ uses: actions/checkout@v4
51
+ with:
52
+ # No git operations after checkout, so don't leave the job token in
53
+ # .git/config where a later third-party step could read it.
54
+ persist-credentials: false
55
+
56
+ # The tag is authoritative and must agree with VERSION. check-release-tag.sh
57
+ # writes version= on fd 3, which is why it is redirected rather than
58
+ # piped: its human output must not reach $GITHUB_OUTPUT.
59
+ - name: Resolve version
60
+ id: version
61
+ run: ./scripts/check-release-tag.sh "${GITHUB_REF_NAME}" 3>>"$GITHUB_OUTPUT"
62
+
63
+ # Fail an empty changelog section here, before a Release is created.
64
+ # `make lint` cannot catch this: it checks only that the latest
65
+ # `## [X.Y.Z]` heading exists and agrees with VERSION, and a heading with
66
+ # no body passes that. The github-release job runs the same script again
67
+ # for the notes themselves, so there is one definition of "non-empty".
68
+ - name: Check the release notes are not empty
69
+ env:
70
+ RELEASE_VERSION: ${{ steps.version.outputs.version }}
71
+ run: ./scripts/release-notes.sh "${RELEASE_VERSION}" >/dev/null
72
+
73
+ - name: Install uv
74
+ uses: astral-sh/setup-uv@v9.0.0
75
+ with:
76
+ enable-cache: true
77
+
78
+ - name: Lint (includes the version-agreement check)
79
+ run: make lint MARKDOWNLINT='npx --yes markdownlint-cli2'
80
+
81
+ # Build the wheel and the sdist once, and hand the same bytes to both
82
+ # publishers below. `make dist` is the same target CI runs on every pull
83
+ # request: it builds both artifacts, rebuilds the wheel from the sdist in a
84
+ # scratch directory, and smoke-tests the result from outside the checkout, so
85
+ # a release cannot ship an sdist that cannot rebuild itself.
86
+ build:
87
+ needs: [verify]
88
+ runs-on: ubuntu-latest
89
+ timeout-minutes: 10
90
+ steps:
91
+ - name: Checkout
92
+ uses: actions/checkout@v4
93
+ with:
94
+ # No git operations after checkout, so don't leave the job token in
95
+ # .git/config where a later third-party step could read it.
96
+ persist-credentials: false
97
+
98
+ - name: Install uv
99
+ uses: astral-sh/setup-uv@v9.0.0
100
+ with:
101
+ enable-cache: true
102
+
103
+ - name: Build and smoke-test the distribution
104
+ run: make dist
105
+
106
+ # `overwrite` defaults to false, which fails the upload outright when an
107
+ # artifact of this name already exists in the run. Re-running the whole
108
+ # workflow after a partial failure would then die here, before reaching
109
+ # PyPI — so overwrite rather than collide, matching the re-run safety the
110
+ # rest of this workflow is built for.
111
+ - name: Upload the distribution
112
+ uses: actions/upload-artifact@v7
113
+ with:
114
+ name: dist
115
+ path: dist/
116
+ if-no-files-found: error
117
+ overwrite: true
118
+
119
+ # Publish to PyPI by trusted publishing: no token exists to leak or rotate,
120
+ # and the `pypi` environment is what PyPI's publisher configuration is keyed
121
+ # to. A separate job from the build on purpose — the OIDC credential is never
122
+ # present in the job that produced the artifacts.
123
+ publish-pypi:
124
+ needs: [build]
125
+ runs-on: ubuntu-latest
126
+ timeout-minutes: 10
127
+ environment:
128
+ name: pypi
129
+ url: https://pypi.org/p/code2okf
130
+ permissions:
131
+ # Mint the OIDC token PyPI exchanges for an upload token. Nothing else.
132
+ id-token: write
133
+ steps:
134
+ - name: Install uv
135
+ uses: astral-sh/setup-uv@v9.0.0
136
+ with:
137
+ enable-cache: false
138
+
139
+ - name: Download the distribution
140
+ uses: actions/download-artifact@v8
141
+ with:
142
+ name: dist
143
+ path: dist/
144
+
145
+ # `always` rather than uv's default `automatic`: with no credentials
146
+ # configured the default silently falls back and the upload fails later
147
+ # as an opaque 403, while `always` fails here, saying the OIDC exchange
148
+ # is what went wrong.
149
+ # The artifacts are named rather than left to uv's default `dist/*`:
150
+ # `uv build` also drops a .gitignore in there, and naming the two files
151
+ # keeps anything else that appears out of the upload.
152
+ - name: Publish to PyPI
153
+ run: uv publish --trusted-publishing always dist/*.tar.gz dist/*.whl
154
+
155
+ # Create the GitHub Release. A tag and a Release are different objects and
156
+ # pushing a tag never creates one, so without this the Releases page stays
157
+ # empty however many versions ship. The notes are the changelog's own section,
158
+ # so there is no second description of a release to drift.
159
+ github-release:
160
+ # verify for the version output, publish-pypi for the ordering: the assets
161
+ # attached here are the ones already on PyPI.
162
+ needs: [verify, publish-pypi]
163
+ runs-on: ubuntu-latest
164
+ timeout-minutes: 10
165
+ permissions:
166
+ # The only job in this workflow that writes to the repository.
167
+ contents: write
168
+ steps:
169
+ - name: Checkout
170
+ uses: actions/checkout@v4
171
+ with:
172
+ # Needed for CHANGELOG.md and the script. gh authenticates with
173
+ # GH_TOKEN below, not with the checkout credential.
174
+ persist-credentials: false
175
+
176
+ - name: Download the distribution
177
+ uses: actions/download-artifact@v8
178
+ with:
179
+ name: dist
180
+ path: dist/
181
+
182
+ - name: Create the GitHub Release
183
+ env:
184
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
185
+ TAG: ${{ github.ref_name }}
186
+ RELEASE_VERSION: ${{ needs.verify.outputs.version }}
187
+ run: |
188
+ set -euo pipefail
189
+ # Re-running a release is normal, so converge rather than fail: an
190
+ # existing Release keeps its notes -- they may have been edited by
191
+ # hand -- and only its assets are refreshed, which is what a re-run
192
+ # after a half-finished upload needs.
193
+ if gh release view "${TAG}" >/dev/null 2>&1; then
194
+ echo "NOTICE: release ${TAG} already exists — refreshing its assets only"
195
+ gh release upload "${TAG}" dist/* --clobber
196
+ exit 0
197
+ fi
198
+ # Read the notes first and only then create, rather than piping one
199
+ # into the other. In a pipeline both sides start together, so a
200
+ # failing extractor would hand gh an empty stdin and the Release
201
+ # would be created empty before pipefail reported the failure — and
202
+ # the existing-release check above would then skip it forever. The
203
+ # verify job makes that unreachable in practice; this makes it
204
+ # impossible.
205
+ notes="$(./scripts/release-notes.sh "${RELEASE_VERSION}")"
206
+ # --verify-tag refuses to invent a tag that does not exist, turning a
207
+ # typo into a failure instead of a stray tag.
208
+ printf '%s\n' "${notes}" |
209
+ gh release create "${TAG}" --title "${TAG}" --verify-tag --notes-file - dist/*