aitlc 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 (109) hide show
  1. aitlc-0.1.0/.gitignore +8 -0
  2. aitlc-0.1.0/LICENSE +21 -0
  3. aitlc-0.1.0/PKG-INFO +176 -0
  4. aitlc-0.1.0/README.md +135 -0
  5. aitlc-0.1.0/USER-GUIDE.md +464 -0
  6. aitlc-0.1.0/aitlc.toml.example +79 -0
  7. aitlc-0.1.0/pyproject.toml +140 -0
  8. aitlc-0.1.0/src/aitlc/__init__.py +3 -0
  9. aitlc-0.1.0/src/aitlc/adapters/__init__.py +0 -0
  10. aitlc-0.1.0/src/aitlc/adapters/jira/__init__.py +0 -0
  11. aitlc-0.1.0/src/aitlc/adapters/jira/tasks.py +64 -0
  12. aitlc-0.1.0/src/aitlc/adapters/lambdatest/__init__.py +0 -0
  13. aitlc-0.1.0/src/aitlc/adapters/lambdatest/queue.py +112 -0
  14. aitlc-0.1.0/src/aitlc/adapters/lambdatest/tunnel.py +109 -0
  15. aitlc-0.1.0/src/aitlc/adapters/s3/__init__.py +0 -0
  16. aitlc-0.1.0/src/aitlc/adapters/s3/evidence.py +118 -0
  17. aitlc-0.1.0/src/aitlc/adapters/teams/__init__.py +0 -0
  18. aitlc-0.1.0/src/aitlc/adapters/teams/webhook.py +168 -0
  19. aitlc-0.1.0/src/aitlc/adapters/xray/__init__.py +0 -0
  20. aitlc-0.1.0/src/aitlc/adapters/xray/client.py +412 -0
  21. aitlc-0.1.0/src/aitlc/adapters/xray/gherkin_normalize.py +94 -0
  22. aitlc-0.1.0/src/aitlc/cli.py +70 -0
  23. aitlc-0.1.0/src/aitlc/commands/__init__.py +0 -0
  24. aitlc-0.1.0/src/aitlc/commands/cdp_cmd.py +179 -0
  25. aitlc-0.1.0/src/aitlc/commands/classify_cmd.py +58 -0
  26. aitlc-0.1.0/src/aitlc/commands/doctor.py +157 -0
  27. aitlc-0.1.0/src/aitlc/commands/history_cmd.py +88 -0
  28. aitlc-0.1.0/src/aitlc/commands/init_cmd.py +89 -0
  29. aitlc-0.1.0/src/aitlc/commands/jira_cmd.py +63 -0
  30. aitlc-0.1.0/src/aitlc/commands/notify_cmd.py +81 -0
  31. aitlc-0.1.0/src/aitlc/commands/parallel_cmd.py +365 -0
  32. aitlc-0.1.0/src/aitlc/commands/passthrough_cmd.py +161 -0
  33. aitlc-0.1.0/src/aitlc/commands/propose_fix_cmd.py +109 -0
  34. aitlc-0.1.0/src/aitlc/commands/record_cmd.py +101 -0
  35. aitlc-0.1.0/src/aitlc/commands/report_cmd.py +136 -0
  36. aitlc-0.1.0/src/aitlc/commands/run.py +402 -0
  37. aitlc-0.1.0/src/aitlc/commands/s3_cmd.py +211 -0
  38. aitlc-0.1.0/src/aitlc/commands/start_cmd.py +196 -0
  39. aitlc-0.1.0/src/aitlc/commands/steps_cmd.py +157 -0
  40. aitlc-0.1.0/src/aitlc/commands/trace_cmd.py +156 -0
  41. aitlc-0.1.0/src/aitlc/commands/tunnel_cmd.py +77 -0
  42. aitlc-0.1.0/src/aitlc/commands/users_cmd.py +91 -0
  43. aitlc-0.1.0/src/aitlc/commands/xray_cmd.py +316 -0
  44. aitlc-0.1.0/src/aitlc/config.py +229 -0
  45. aitlc-0.1.0/src/aitlc/core/__init__.py +0 -0
  46. aitlc-0.1.0/src/aitlc/core/behave_runner.py +274 -0
  47. aitlc-0.1.0/src/aitlc/core/blast_radius.py +55 -0
  48. aitlc-0.1.0/src/aitlc/core/cdp_attach.py +247 -0
  49. aitlc-0.1.0/src/aitlc/core/chrome_cdp.py +393 -0
  50. aitlc-0.1.0/src/aitlc/core/codegen.py +73 -0
  51. aitlc-0.1.0/src/aitlc/core/dotenv.py +33 -0
  52. aitlc-0.1.0/src/aitlc/core/feature_select.py +135 -0
  53. aitlc-0.1.0/src/aitlc/core/focus.py +87 -0
  54. aitlc-0.1.0/src/aitlc/core/history.py +169 -0
  55. aitlc-0.1.0/src/aitlc/core/init_config.py +430 -0
  56. aitlc-0.1.0/src/aitlc/core/live_status.py +69 -0
  57. aitlc-0.1.0/src/aitlc/core/locator_scan.py +64 -0
  58. aitlc-0.1.0/src/aitlc/core/locks.py +120 -0
  59. aitlc-0.1.0/src/aitlc/core/patterns.py +111 -0
  60. aitlc-0.1.0/src/aitlc/core/playbook.py +60 -0
  61. aitlc-0.1.0/src/aitlc/core/redact.py +53 -0
  62. aitlc-0.1.0/src/aitlc/core/report_summary.py +230 -0
  63. aitlc-0.1.0/src/aitlc/core/script_runner.py +96 -0
  64. aitlc-0.1.0/src/aitlc/core/step_console.py +683 -0
  65. aitlc-0.1.0/src/aitlc/core/terminal_replay.py +150 -0
  66. aitlc-0.1.0/src/aitlc/core/toon.py +67 -0
  67. aitlc-0.1.0/src/aitlc/core/trace_evidence.py +67 -0
  68. aitlc-0.1.0/src/aitlc/core/unused_steps.py +307 -0
  69. aitlc-0.1.0/src/aitlc/runtime/__init__.py +13 -0
  70. aitlc-0.1.0/src/aitlc/runtime/attach.py +176 -0
  71. aitlc-0.1.0/src/aitlc/runtime/runner.py +159 -0
  72. aitlc-0.1.0/tests/test_behave_runner.py +231 -0
  73. aitlc-0.1.0/tests/test_blast_radius.py +77 -0
  74. aitlc-0.1.0/tests/test_chrome_cdp_and_focus.py +226 -0
  75. aitlc-0.1.0/tests/test_cmd_env_loading.py +78 -0
  76. aitlc-0.1.0/tests/test_codegen.py +34 -0
  77. aitlc-0.1.0/tests/test_config.py +91 -0
  78. aitlc-0.1.0/tests/test_dotenv.py +36 -0
  79. aitlc-0.1.0/tests/test_feature_select.py +118 -0
  80. aitlc-0.1.0/tests/test_gherkin_normalize.py +77 -0
  81. aitlc-0.1.0/tests/test_history.py +83 -0
  82. aitlc-0.1.0/tests/test_init_config.py +161 -0
  83. aitlc-0.1.0/tests/test_jira_tasks.py +69 -0
  84. aitlc-0.1.0/tests/test_live_status.py +78 -0
  85. aitlc-0.1.0/tests/test_locator_scan.py +52 -0
  86. aitlc-0.1.0/tests/test_locks.py +57 -0
  87. aitlc-0.1.0/tests/test_notify_cmd.py +75 -0
  88. aitlc-0.1.0/tests/test_packaging.py +159 -0
  89. aitlc-0.1.0/tests/test_parallel_browser_pool.py +114 -0
  90. aitlc-0.1.0/tests/test_patterns.py +104 -0
  91. aitlc-0.1.0/tests/test_propose_fix_cmd.py +96 -0
  92. aitlc-0.1.0/tests/test_queue.py +68 -0
  93. aitlc-0.1.0/tests/test_redact.py +45 -0
  94. aitlc-0.1.0/tests/test_report_summary.py +165 -0
  95. aitlc-0.1.0/tests/test_run_remote_platform_env.py +74 -0
  96. aitlc-0.1.0/tests/test_run_retry.py +145 -0
  97. aitlc-0.1.0/tests/test_runtime_attach.py +247 -0
  98. aitlc-0.1.0/tests/test_s3_cmd.py +130 -0
  99. aitlc-0.1.0/tests/test_s3_evidence.py +66 -0
  100. aitlc-0.1.0/tests/test_script_runner.py +111 -0
  101. aitlc-0.1.0/tests/test_start_cmd.py +82 -0
  102. aitlc-0.1.0/tests/test_step_console.py +145 -0
  103. aitlc-0.1.0/tests/test_teams_webhook.py +100 -0
  104. aitlc-0.1.0/tests/test_terminal_replay.py +61 -0
  105. aitlc-0.1.0/tests/test_toon.py +37 -0
  106. aitlc-0.1.0/tests/test_trace_evidence.py +58 -0
  107. aitlc-0.1.0/tests/test_tunnel.py +53 -0
  108. aitlc-0.1.0/tests/test_xray_client.py +208 -0
  109. aitlc-0.1.0/user-guide.html +615 -0
aitlc-0.1.0/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ # Local dev artifacts — never part of the tracked tool.
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ dist_check/
aitlc-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omanand Swami
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
aitlc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: aitlc
3
+ Version: 0.1.0
4
+ Summary: Structured, JSON/TOON-first CLI for Behave+Playwright debugging and Xray verification workflows.
5
+ Project-URL: Homepage, https://github.com/omanandswami2005/aitlc
6
+ Project-URL: Source, https://github.com/omanandswami2005/aitlc
7
+ Project-URL: Issues, https://github.com/omanandswami2005/aitlc/issues
8
+ Project-URL: Documentation, https://github.com/omanandswami2005/aitlc/blob/main/USER-GUIDE.md
9
+ Author-email: Omanand Swami <2005omanandswami@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: bdd,behave,cli,cucumber,playwright,testing,xray
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Topic :: Software Development :: Testing :: Acceptance
25
+ Requires-Python: >=3.10
26
+ Requires-Dist: boto3>=1.34
27
+ Requires-Dist: jira>=3.8
28
+ Requires-Dist: playwright>=1.45
29
+ Requires-Dist: pyte>=0.8
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: requests>=2.31
32
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
33
+ Requires-Dist: typer>=0.12
34
+ Provides-Extra: dev
35
+ Requires-Dist: behave>=1.2; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0; extra == 'dev'
37
+ Provides-Extra: publish
38
+ Requires-Dist: build>=1.2; extra == 'publish'
39
+ Requires-Dist: twine>=5.0; extra == 'publish'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # aitlc
43
+
44
+ A CLI for debugging Behave + Playwright test suites and keeping them in sync
45
+ with Xray. Structured JSON output first, so a person and an agent can read the
46
+ same result.
47
+
48
+ **It never asks you to edit the suite it debugs.** No hook blocks, no
49
+ `environment.py` changes, nothing to keep in sync — instrumentation attaches
50
+ through behave's own runner API, with a fallback for versions that lack it.
51
+
52
+ ```bash
53
+ uv tool install aitlc # or: pipx install aitlc
54
+ cd /path/to/your/project
55
+ aitlc init # detects your layout, writes aitlc.toml
56
+ aitlc run PROJ-1234
57
+ ```
58
+
59
+ The command is always `aitlc`. It is published under two names — `aitlc` and
60
+ `dax-aitlc` — which install the same tool; use whichever your org prefers.
61
+
62
+ ## What it does
63
+
64
+ **Run and target tests.** Bare test IDs resolve recursively, so you never need
65
+ a full path, and never need to tag other features to narrow a run. One Examples
66
+ row of a Scenario Outline can be run on its own.
67
+
68
+ ```bash
69
+ aitlc run PROJ-1234 # structured JSON result
70
+ aitlc run PROJ-1234 --debug # halt on failure, browser stays open
71
+ aitlc parallel run -j 4 # concurrent, without editing tags
72
+ aitlc parallel focus PROJ-1234 # pin a selection, then just `aitlc parallel run`
73
+ ```
74
+
75
+ **Debug live.** Keep one browser across many iterations instead of paying setup
76
+ and login on every change. `aitlc steps run --range 14-19` resumes a scenario
77
+ partway through in an already-open browser — replacing the habit of commenting
78
+ out the steps that already passed.
79
+
80
+ ```bash
81
+ aitlc cdp launch # detached; survives the shell that started it
82
+ aitlc cdp launch --new # isolated: own port + own profile
83
+ aitlc steps run PROJ-1234 --range 14-19 --cdp-url http://127.0.0.1:9333
84
+ ```
85
+
86
+ **Read a page as text, not pixels.** The accessibility tree answers "is X on
87
+ screen" as assertable text, and carries nesting, control state and field values
88
+ a screenshot cannot express. Measured on one real page: 55 KB screenshot →
89
+ 1,961 characters for the full tree → 20 characters for a targeted query.
90
+
91
+ ```bash
92
+ aitlc cdp inspect --cdp-url http://127.0.0.1:9333 --a11y --a11y-query "Save"
93
+ ```
94
+
95
+ **Find dead step definitions.** behave has no equivalent of Cucumber's
96
+ unused-step report. Matching goes through behave's own registry, so the answer
97
+ agrees with what the runner would dispatch, and steps invoked via
98
+ `context.execute_steps(...)` count as used.
99
+
100
+ ```bash
101
+ aitlc steps unused
102
+ ```
103
+
104
+ **Track real flakiness.** Signature matching only ever covers flakes somebody
105
+ already described. `aitlc history` records every run outcome, so a new flake is
106
+ visible the second time it happens. A test that has only ever failed is
107
+ reported as broken rather than flaky — retrying it spends time to reach the
108
+ same answer.
109
+
110
+ **Sync with Xray.** Read, compare and write a Test's Gherkin; pull every
111
+ feature from a Test Execution or Plan; find where a step is really used.
112
+
113
+ **Escape hatches.** `aitlc behave` and `aitlc pw` run those tools directly with
114
+ your project's `.env` and interpreter already set up, so adopting aitlc never
115
+ means losing a flag it does not wrap. `--print-command` shows the exact
116
+ invocation without running it.
117
+
118
+ ## Documentation
119
+
120
+ | File | For |
121
+ |---|---|
122
+ | `USER-GUIDE.md` | Full reference — written to be read directly by an agent |
123
+ | `user-guide.html` | The same guide as a browsable page |
124
+ | `aitlc.toml.example` | Annotated configuration template |
125
+
126
+ ## Requirements
127
+
128
+ Python 3.10+. The suite under test keeps its own environment; aitlc runs
129
+ outside it and shells in, so the two never need to share dependencies.
130
+
131
+ ## Design notes
132
+
133
+ Three rules the code holds to, each learned from a bug that cost real time:
134
+
135
+ - **Never report success for something that did not happen.** Missing setup, an
136
+ incomplete feature corpus, or an instrumentation fallback are each reported
137
+ explicitly, because silence surfaces later as an unrelated-looking failure.
138
+ - **Ask the tool, do not assume its version.** behave's custom-runner option
139
+ changed both its name and its argument format across releases, so aitlc
140
+ probes `behave --help` rather than parsing a version string.
141
+ - **Nothing project-specific in the code.** Layout, hook names and credential
142
+ variable names all come from `aitlc.toml`, and `aitlc init` detects them.
143
+
144
+ ## Development
145
+
146
+ ```bash
147
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
148
+ .venv/bin/pytest tests/ -q
149
+ ```
150
+
151
+ ## Releasing
152
+
153
+ ```bash
154
+ .venv/bin/pip install -e ".[publish]"
155
+ python scripts/build_dual_name.py # both names, into dist/
156
+ python -m twine check dist/*
157
+ python -m twine upload --repository testpypi dist/*
158
+ python -m twine upload dist/*
159
+ ```
160
+
161
+ `build_dual_name.py` rewrites the distribution name, builds, and restores
162
+ `pyproject.toml` in a `finally` — a failed build never leaves the repo holding
163
+ a name nobody chose.
164
+
165
+ Install from TestPyPI into a scratch environment and run `aitlc init` against a
166
+ real project before the last line. That is the step that catches a broken
167
+ config template or a missing dependency; nothing local will.
168
+
169
+ Two things about the final upload are irreversible: a version number can never
170
+ be reused, and deleting a project does not release its name. Check the
171
+ `[project.urls]` values first — they are baked into a version's metadata and
172
+ cannot be edited afterwards.
173
+
174
+ ## License
175
+
176
+ MIT — see `LICENSE`.
aitlc-0.1.0/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # aitlc
2
+
3
+ A CLI for debugging Behave + Playwright test suites and keeping them in sync
4
+ with Xray. Structured JSON output first, so a person and an agent can read the
5
+ same result.
6
+
7
+ **It never asks you to edit the suite it debugs.** No hook blocks, no
8
+ `environment.py` changes, nothing to keep in sync — instrumentation attaches
9
+ through behave's own runner API, with a fallback for versions that lack it.
10
+
11
+ ```bash
12
+ uv tool install aitlc # or: pipx install aitlc
13
+ cd /path/to/your/project
14
+ aitlc init # detects your layout, writes aitlc.toml
15
+ aitlc run PROJ-1234
16
+ ```
17
+
18
+ The command is always `aitlc`. It is published under two names — `aitlc` and
19
+ `dax-aitlc` — which install the same tool; use whichever your org prefers.
20
+
21
+ ## What it does
22
+
23
+ **Run and target tests.** Bare test IDs resolve recursively, so you never need
24
+ a full path, and never need to tag other features to narrow a run. One Examples
25
+ row of a Scenario Outline can be run on its own.
26
+
27
+ ```bash
28
+ aitlc run PROJ-1234 # structured JSON result
29
+ aitlc run PROJ-1234 --debug # halt on failure, browser stays open
30
+ aitlc parallel run -j 4 # concurrent, without editing tags
31
+ aitlc parallel focus PROJ-1234 # pin a selection, then just `aitlc parallel run`
32
+ ```
33
+
34
+ **Debug live.** Keep one browser across many iterations instead of paying setup
35
+ and login on every change. `aitlc steps run --range 14-19` resumes a scenario
36
+ partway through in an already-open browser — replacing the habit of commenting
37
+ out the steps that already passed.
38
+
39
+ ```bash
40
+ aitlc cdp launch # detached; survives the shell that started it
41
+ aitlc cdp launch --new # isolated: own port + own profile
42
+ aitlc steps run PROJ-1234 --range 14-19 --cdp-url http://127.0.0.1:9333
43
+ ```
44
+
45
+ **Read a page as text, not pixels.** The accessibility tree answers "is X on
46
+ screen" as assertable text, and carries nesting, control state and field values
47
+ a screenshot cannot express. Measured on one real page: 55 KB screenshot →
48
+ 1,961 characters for the full tree → 20 characters for a targeted query.
49
+
50
+ ```bash
51
+ aitlc cdp inspect --cdp-url http://127.0.0.1:9333 --a11y --a11y-query "Save"
52
+ ```
53
+
54
+ **Find dead step definitions.** behave has no equivalent of Cucumber's
55
+ unused-step report. Matching goes through behave's own registry, so the answer
56
+ agrees with what the runner would dispatch, and steps invoked via
57
+ `context.execute_steps(...)` count as used.
58
+
59
+ ```bash
60
+ aitlc steps unused
61
+ ```
62
+
63
+ **Track real flakiness.** Signature matching only ever covers flakes somebody
64
+ already described. `aitlc history` records every run outcome, so a new flake is
65
+ visible the second time it happens. A test that has only ever failed is
66
+ reported as broken rather than flaky — retrying it spends time to reach the
67
+ same answer.
68
+
69
+ **Sync with Xray.** Read, compare and write a Test's Gherkin; pull every
70
+ feature from a Test Execution or Plan; find where a step is really used.
71
+
72
+ **Escape hatches.** `aitlc behave` and `aitlc pw` run those tools directly with
73
+ your project's `.env` and interpreter already set up, so adopting aitlc never
74
+ means losing a flag it does not wrap. `--print-command` shows the exact
75
+ invocation without running it.
76
+
77
+ ## Documentation
78
+
79
+ | File | For |
80
+ |---|---|
81
+ | `USER-GUIDE.md` | Full reference — written to be read directly by an agent |
82
+ | `user-guide.html` | The same guide as a browsable page |
83
+ | `aitlc.toml.example` | Annotated configuration template |
84
+
85
+ ## Requirements
86
+
87
+ Python 3.10+. The suite under test keeps its own environment; aitlc runs
88
+ outside it and shells in, so the two never need to share dependencies.
89
+
90
+ ## Design notes
91
+
92
+ Three rules the code holds to, each learned from a bug that cost real time:
93
+
94
+ - **Never report success for something that did not happen.** Missing setup, an
95
+ incomplete feature corpus, or an instrumentation fallback are each reported
96
+ explicitly, because silence surfaces later as an unrelated-looking failure.
97
+ - **Ask the tool, do not assume its version.** behave's custom-runner option
98
+ changed both its name and its argument format across releases, so aitlc
99
+ probes `behave --help` rather than parsing a version string.
100
+ - **Nothing project-specific in the code.** Layout, hook names and credential
101
+ variable names all come from `aitlc.toml`, and `aitlc init` detects them.
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
107
+ .venv/bin/pytest tests/ -q
108
+ ```
109
+
110
+ ## Releasing
111
+
112
+ ```bash
113
+ .venv/bin/pip install -e ".[publish]"
114
+ python scripts/build_dual_name.py # both names, into dist/
115
+ python -m twine check dist/*
116
+ python -m twine upload --repository testpypi dist/*
117
+ python -m twine upload dist/*
118
+ ```
119
+
120
+ `build_dual_name.py` rewrites the distribution name, builds, and restores
121
+ `pyproject.toml` in a `finally` — a failed build never leaves the repo holding
122
+ a name nobody chose.
123
+
124
+ Install from TestPyPI into a scratch environment and run `aitlc init` against a
125
+ real project before the last line. That is the step that catches a broken
126
+ config template or a missing dependency; nothing local will.
127
+
128
+ Two things about the final upload are irreversible: a version number can never
129
+ be reused, and deleting a project does not release its name. Check the
130
+ `[project.urls]` values first — they are baked into a version's metadata and
131
+ cannot be edited afterwards.
132
+
133
+ ## License
134
+
135
+ MIT — see `LICENSE`.