tickwork 0.3.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.
- tickwork-0.3.0/LICENSE +21 -0
- tickwork-0.3.0/MANIFEST.in +4 -0
- tickwork-0.3.0/PKG-INFO +9 -0
- tickwork-0.3.0/README.md +235 -0
- tickwork-0.3.0/examples/items.jsonl +3 -0
- tickwork-0.3.0/examples/logins.yaml +7 -0
- tickwork-0.3.0/examples/pr-loop.states.yaml +81 -0
- tickwork-0.3.0/pyproject.toml +34 -0
- tickwork-0.3.0/setup.cfg +4 -0
- tickwork-0.3.0/tickwork/__init__.py +11 -0
- tickwork-0.3.0/tickwork/__main__.py +8 -0
- tickwork-0.3.0/tickwork/cli.py +288 -0
- tickwork-0.3.0/tickwork/effect_log.py +319 -0
- tickwork-0.3.0/tickwork/pool.py +214 -0
- tickwork-0.3.0/tickwork/providers/__init__.py +10 -0
- tickwork-0.3.0/tickwork/providers/openai_compat.py +82 -0
- tickwork-0.3.0/tickwork/replay.py +134 -0
- tickwork-0.3.0/tickwork/workflow_states.py +226 -0
- tickwork-0.3.0/tickwork.egg-info/PKG-INFO +9 -0
- tickwork-0.3.0/tickwork.egg-info/SOURCES.txt +22 -0
- tickwork-0.3.0/tickwork.egg-info/dependency_links.txt +1 -0
- tickwork-0.3.0/tickwork.egg-info/entry_points.txt +2 -0
- tickwork-0.3.0/tickwork.egg-info/requires.txt +1 -0
- tickwork-0.3.0/tickwork.egg-info/top_level.txt +1 -0
tickwork-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) The tickwork Authors
|
|
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.
|
tickwork-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tickwork
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A generic, instance-neutral workflow engine: states, transitions, sweeps and call replay over a queue of work items
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: PyYAML>=6.0
|
|
9
|
+
Dynamic: license-file
|
tickwork-0.3.0/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# tickwork
|
|
2
|
+
|
|
3
|
+
A generic, instance-neutral workflow engine: it loads a declarative
|
|
4
|
+
"states-as-data" file, validates it, and evaluates which state each work item
|
|
5
|
+
in a queue of issues and pull requests is in — including a per-item anomaly
|
|
6
|
+
report when an item matches zero or several states. It knows nothing about
|
|
7
|
+
any specific deployment or consumer: callers supply the states file, the set
|
|
8
|
+
of allowed owner role names, and the role-to-login mapping.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install tickwork
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Python 3.11+ and PyYAML.
|
|
17
|
+
|
|
18
|
+
## States as data
|
|
19
|
+
|
|
20
|
+
A states file maps each state to a `when` predicate, a board bucket, and an
|
|
21
|
+
`owner` (a role name, or `human`). Generic role names only — the file, like
|
|
22
|
+
the package, names no instance:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
states:
|
|
26
|
+
queued:
|
|
27
|
+
when:
|
|
28
|
+
issue.assignee: none
|
|
29
|
+
board: queued
|
|
30
|
+
owner: dev
|
|
31
|
+
in_review:
|
|
32
|
+
when:
|
|
33
|
+
pr.requested_reviewers: reviewer
|
|
34
|
+
board: blocked
|
|
35
|
+
owner: reviewer
|
|
36
|
+
waiting_on_human:
|
|
37
|
+
when:
|
|
38
|
+
any:
|
|
39
|
+
- pr.state: merged
|
|
40
|
+
- issue.state: merged
|
|
41
|
+
board: waiting_on_human
|
|
42
|
+
owner: human
|
|
43
|
+
done:
|
|
44
|
+
when:
|
|
45
|
+
issue.state: closed
|
|
46
|
+
board: done
|
|
47
|
+
terminal: true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Load, validate, evaluate, and report anomalies:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import tickwork.workflow_states as workflow_states
|
|
54
|
+
|
|
55
|
+
states = workflow_states.load("states.yaml", roles={"dev", "reviewer", "planner"})
|
|
56
|
+
|
|
57
|
+
item = {...} # repo, issue (number/state/assignees/open_dependencies), pr
|
|
58
|
+
logins = {"dev": "d", "reviewer": "r"}
|
|
59
|
+
mapped = workflow_states.evaluate(states, item, logins=logins)
|
|
60
|
+
|
|
61
|
+
items = [item]
|
|
62
|
+
anomalies = workflow_states.check(states, items, logins=logins) # zero- or 2+-matched items
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Effect log
|
|
66
|
+
|
|
67
|
+
`tickwork.effect_log` is an append-only JSONL log of intended and applied
|
|
68
|
+
writes ("effects"): a caller plans one pending row per effect *before*
|
|
69
|
+
applying any of them, then each row is marked applied / failed / superseded
|
|
70
|
+
with timing. On the next run the caller folds the log and resumes rows that
|
|
71
|
+
are still unfinished, instead of re-deciding a job it can no longer finish.
|
|
72
|
+
The module names no deployment: the log directory is a constructor parameter
|
|
73
|
+
and every call names its role explicitly — there is no default role.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from tickwork.effect_log import EffectLog
|
|
77
|
+
|
|
78
|
+
log = EffectLog("/var/lib/myapp/effects") # one <role>.jsonl per role
|
|
79
|
+
|
|
80
|
+
rows = log.plan("repo#7", "run-42", [("comment", {"body": "..."})], head_sha="abc", role="bot")
|
|
81
|
+
log.apply(rows[0], lambda: post_comment(rows[0]["payload"])) # marks applied/failed with timing
|
|
82
|
+
log.mark(rows[0], "superseded") # void a row without a write
|
|
83
|
+
|
|
84
|
+
resume = log.unfinished("bot") # {item: [pending rows in seq order]}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`tickwork.effect_log.summarize(rows)` reads the same log back and returns a
|
|
88
|
+
JSON-able dict: per `(role, effect)` counts of `applied` / `failed` /
|
|
89
|
+
`superseded` / `pending` after folding (the latest status row per
|
|
90
|
+
`(item, tick_id, seq)` wins), nearest-rank `p50`/`p90` of `ms` over folded
|
|
91
|
+
`applied` rows (`None` where no measured write exists), a `finished_later`
|
|
92
|
+
count of fold keys whose `failed` status was followed, later in log order, by
|
|
93
|
+
an `applied` one (a resume finished it), and the still-`pending` rows. The
|
|
94
|
+
companion command renders that object read-only, never creating or writing
|
|
95
|
+
the log directory, and exits 0 on a missing or empty one:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
tickwork effects /var/lib/myapp/effects # every <role>.jsonl
|
|
99
|
+
tickwork effects /var/lib/myapp/effects --role bot # one role
|
|
100
|
+
tickwork effects /var/lib/myapp/effects --json # one JSON object
|
|
101
|
+
tickwork effects /var/lib/myapp/effects --since 2026-07-01
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Checking a queue
|
|
105
|
+
|
|
106
|
+
A tickwork install carries two commands:
|
|
107
|
+
|
|
108
|
+
- `tickwork check STATES --items ITEMS --logins LOGINS` — report items
|
|
109
|
+
matching zero or 2+ states
|
|
110
|
+
- `tickwork replay RECORDS... --model M --provider openai-compat
|
|
111
|
+
--base-url URL --key-env VAR` — re-send and score the recorded LLM calls
|
|
112
|
+
in *RECORDS* (final transcript records, one JSONL object per line, each
|
|
113
|
+
carrying `final: true` and the full `messages` array) against a model and
|
|
114
|
+
print one JSON metrics line per (record, model, repeat). The metrics line
|
|
115
|
+
carries the record identity (`repo`, `issue`, `role`, `stage`), the
|
|
116
|
+
per-call facts (`ms`, token usage, `finish_reason`, `error`)
|
|
117
|
+
plus the built-in scorer's `response_len` and `exact_match` fields: the
|
|
118
|
+
reply's length and whether it matches the recorded response exactly.
|
|
119
|
+
Exit code `2` means a file could not be read, an unknown `--provider` was
|
|
120
|
+
given or `--repeats` was below 1; a run with zero scored and zero skipped
|
|
121
|
+
records exits `1`.
|
|
122
|
+
|
|
123
|
+
`STATES` is a states file in the format shown above, `ITEMS` is a JSONL file
|
|
124
|
+
with one item per line, and `LOGINS` is a YAML mapping of role name to login.
|
|
125
|
+
The package ships three example files under `examples/`:
|
|
126
|
+
|
|
127
|
+
- `examples/pr-loop.states.yaml` — a states file (see "States as data").
|
|
128
|
+
- `examples/items.jsonl` — three sample work items.
|
|
129
|
+
- `examples/logins.yaml` — the role-to-login map the predicates resolve
|
|
130
|
+
through.
|
|
131
|
+
|
|
132
|
+
Every item line in `ITEMS` is a JSON object with a `repo` name, an `issue`
|
|
133
|
+
map (`number`, `state`, `assignees`, `open_dependencies`) and either a null
|
|
134
|
+
or a `pr` map (`number`, `state`, `assignees`, `requested_reviewers`,
|
|
135
|
+
`reviewed_on_head`, `approved_on_head`). The example items are contrived so
|
|
136
|
+
that one is clean, one matches no state, and one matches two at once, so a
|
|
137
|
+
run over them reports two anomalies and exits `1`. After an install, the
|
|
138
|
+
three files live under `<sys.prefix>/share/tickwork/examples/`; from a
|
|
139
|
+
source checkout, the same files sit at `release/tickwork/examples/`, and
|
|
140
|
+
every path below works with either location:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
tickwork check examples/pr-loop.states.yaml \
|
|
144
|
+
--items examples/items.jsonl --logins examples/logins.yaml
|
|
145
|
+
# pip-installed: point the arguments at the installed share directories, e.g.
|
|
146
|
+
# tickwork check "$(python -c 'import sys; print(sys.prefix)')/share/tickwork/examples/pr-loop.states.yaml" ...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The check prints one line per item that matches zero or more than one state
|
|
150
|
+
(an anomaly: an item no state owns, or two states contest), then a summary
|
|
151
|
+
line. Exit code `1` means at least one anomaly was found, `0` means the
|
|
152
|
+
input was clean, and `2` means a file could not be read, parsed or validated.
|
|
153
|
+
|
|
154
|
+
## Replaying recorded calls
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
tickwork replay issue-42/20260915-reviewer-review-final.jsonl \
|
|
158
|
+
--model my-model --provider openai-compat \
|
|
159
|
+
--base-url https://example.internal/v1 --key-env MY_API_KEY
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The provider adapter is stdlib-only (`urllib`) and speaks any
|
|
163
|
+
OpenAI-compatible `/chat/completions` endpoint: the base URL and the name of
|
|
164
|
+
the API-key environment variable are flags, not baked-in endpoints. The key
|
|
165
|
+
is read from the environment and used only in the request's `Authorization`
|
|
166
|
+
header — it is never logged or printed. A provider failure (bad URL, missing
|
|
167
|
+
or unset key, refused connection) does not abort the run or change the exit
|
|
168
|
+
code: it rides in the metrics line's `error` field, so an errored call is
|
|
169
|
+
still one printed line with `response: null`. `--provider` names the adapter
|
|
170
|
+
(`openai-compat`; that default is used when the flag is omitted), and
|
|
171
|
+
`--repeats N` (a positive integer) sends each recorded call N times.
|
|
172
|
+
|
|
173
|
+
For library use the replay core is provider- and scorer-agnostic — callers
|
|
174
|
+
supply both:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
import tickwork.replay as replay
|
|
178
|
+
|
|
179
|
+
records = replay.read_records(["run-final.jsonl"])
|
|
180
|
+
skips: dict[str, int] = {}
|
|
181
|
+
for metrics in replay.replay(
|
|
182
|
+
records,
|
|
183
|
+
provider=my_provider, # .generate(model, messages) -> reply
|
|
184
|
+
models=["model-a", "model-b"],
|
|
185
|
+
scorers={("*", "*"): my_scorer}, # (record, reply) -> extra metrics keys
|
|
186
|
+
repeats=2,
|
|
187
|
+
skips=skips, # counted skip reasons
|
|
188
|
+
):
|
|
189
|
+
print(metrics)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`replay.deep_merge(base, overlay)` is the recursive variant-overlay merge
|
|
193
|
+
(nested mappings merge key by key; other values are replaced) — use it to
|
|
194
|
+
compose a config variant before replaying under it.
|
|
195
|
+
|
|
196
|
+
## Worker pools
|
|
197
|
+
|
|
198
|
+
`tickwork.pool` provides the concurrency primitives a worker pool needs,
|
|
199
|
+
built on non-blocking `fcntl.flock`: no waiting, no daemon, and no explicit
|
|
200
|
+
cleanup — the kernel releases the lock when the holding process exits, so a
|
|
201
|
+
crashed worker never leaves a stale lock behind. Four functions:
|
|
202
|
+
|
|
203
|
+
- `try_claim(role, state_dir, kind, repo, number)` — claim exclusive
|
|
204
|
+
ownership of one item (e.g. `kind="issue"` or `"pr"`) so two concurrent
|
|
205
|
+
ticks of the same role never work the same item. Returns a context
|
|
206
|
+
manager holding the claim, or `None` when another process already holds
|
|
207
|
+
it.
|
|
208
|
+
- `acquire_worker_slot(role, state_dir, pool_size)` — claim the
|
|
209
|
+
lowest-numbered free slot in `1..pool_size` for per-tick disambiguation
|
|
210
|
+
(branch naming, local state). Returns a context manager yielding the slot
|
|
211
|
+
number, or `None` when every slot is taken — skip the tick rather than
|
|
212
|
+
wait.
|
|
213
|
+
- `lock_worker_slot(role, state_dir, slot)` — claim a *specific* slot's
|
|
214
|
+
lease (no free-slot scan), for when the slot is pinned from outside the
|
|
215
|
+
process.
|
|
216
|
+
- `flock_nonblock(path)` — open *path* and take a non-blocking exclusive
|
|
217
|
+
`flock`; returns the file descriptor, or `None` when another holder has
|
|
218
|
+
the lock. Public so a caller can hold an extra lock file next to the
|
|
219
|
+
generic ones.
|
|
220
|
+
|
|
221
|
+
Lock files live under `<state_dir>/claims/<role>/...` (one per claimed
|
|
222
|
+
item) and `<state_dir>/slots/<role>/<slot>.lock` (one per worker slot). The
|
|
223
|
+
state directory is a call parameter and the module reads no environment
|
|
224
|
+
variables, so it names nothing of the instance it runs for. The module is
|
|
225
|
+
POSIX-only: it fails at import on platforms without `fcntl`.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import tickwork.pool as pool
|
|
229
|
+
|
|
230
|
+
claim = pool.try_claim("worker", "/var/lib/myapp/state", "issue", "owner/name", 42)
|
|
231
|
+
if claim is None:
|
|
232
|
+
return # another tick already owns issue 42 — skip this one
|
|
233
|
+
with claim:
|
|
234
|
+
... # compose the work; the claim is released on exit or crash
|
|
235
|
+
```
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{"repo": "example/widget", "issue": {"number": 101, "state": "open", "assignees": ["dee"], "open_dependencies": []}, "pr": null}
|
|
2
|
+
{"repo": "example/widget", "issue": {"number": 102, "state": "open", "assignees": ["dee"], "open_dependencies": []}, "pr": {"number": 202, "state": "open", "assignees": [], "requested_reviewers": [], "reviewed_on_head": false, "approved_on_head": false}}
|
|
3
|
+
{"repo": "example/widget", "issue": {"number": 103, "state": "open", "assignees": ["dee"], "open_dependencies": []}, "pr": {"number": 203, "state": "open", "assignees": [], "requested_reviewers": ["ria"], "reviewed_on_head": false, "approved_on_head": true}}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# The PR loop's states as data: the exactly-one-owner state machine the
|
|
2
|
+
# tickwork workflow engine drives, one entry per state.
|
|
3
|
+
# Load via tickwork.workflow_states.load(); nothing in the loop evaluates
|
|
4
|
+
# these predicates yet (the engine ships them before the loop consumes them).
|
|
5
|
+
#
|
|
6
|
+
# Each state: a predicate over artifacts → (owner, board bucket).
|
|
7
|
+
# `override: true` states are cross-cutting (closed / held by the human /
|
|
8
|
+
# unassigned intake / blocked) and win in listed order. All other predicates
|
|
9
|
+
# must stay disjoint:
|
|
10
|
+
# an item matching zero or 2+ of them is an anomaly, reported by
|
|
11
|
+
# tickwork.workflow_states.check(): nothing in the loop evaluates these
|
|
12
|
+
# predicates yet (the engine ships them before the loop consumes them).
|
|
13
|
+
#
|
|
14
|
+
# Roles are role names (dev, reviewer, planner, verifier) and `human`;
|
|
15
|
+
# the caller maps roles to logins. Never a persona login here.
|
|
16
|
+
|
|
17
|
+
workflow: pr-loop
|
|
18
|
+
version: 1
|
|
19
|
+
states:
|
|
20
|
+
done:
|
|
21
|
+
override: true
|
|
22
|
+
when: { issue.state: closed }
|
|
23
|
+
board: done
|
|
24
|
+
terminal: true
|
|
25
|
+
|
|
26
|
+
waiting_on_human:
|
|
27
|
+
override: true
|
|
28
|
+
# a merged PR leaves this state even while still assigned to the human,
|
|
29
|
+
# so a human merge hands off to `verifying` like an automated one
|
|
30
|
+
when: { any: [ { issue.assignee: human }, { pr.assignee: human, pr.state: open } ] }
|
|
31
|
+
board: waiting_on_human
|
|
32
|
+
owner: human
|
|
33
|
+
|
|
34
|
+
intake:
|
|
35
|
+
# the board buckets unassigned open issues here even when they have open
|
|
36
|
+
# dependencies, so this must win over `blocked`
|
|
37
|
+
override: true
|
|
38
|
+
when: { issue.state: open, issue.assignee: none, pr: none }
|
|
39
|
+
board: intake
|
|
40
|
+
owner: human # visibility only; a human or the planner picks it up
|
|
41
|
+
|
|
42
|
+
blocked:
|
|
43
|
+
override: true
|
|
44
|
+
when: { issue.dependencies: open, not: { issue.assignee: human } }
|
|
45
|
+
board: blocked
|
|
46
|
+
owner: human # unblocking is a manual step; entry assigns the human
|
|
47
|
+
|
|
48
|
+
triage:
|
|
49
|
+
when: { issue.assignee: planner, pr: none }
|
|
50
|
+
board: queued
|
|
51
|
+
owner: planner
|
|
52
|
+
|
|
53
|
+
ready_for_dev:
|
|
54
|
+
when: { issue.assignee: dev, pr: none }
|
|
55
|
+
board: queued
|
|
56
|
+
owner: dev
|
|
57
|
+
|
|
58
|
+
in_review:
|
|
59
|
+
when: { pr.state: open, pr.requested_reviewers: reviewer, pr.reviewed_on_head: false }
|
|
60
|
+
board: in_progress
|
|
61
|
+
owner: reviewer
|
|
62
|
+
|
|
63
|
+
revising:
|
|
64
|
+
when: { pr.state: open, pr.assignee: dev }
|
|
65
|
+
board: in_progress
|
|
66
|
+
owner: dev
|
|
67
|
+
|
|
68
|
+
merging:
|
|
69
|
+
when: { pr.state: open, pr.requested_reviewers: reviewer, pr.approved_on_head: true }
|
|
70
|
+
board: in_progress
|
|
71
|
+
owner: reviewer
|
|
72
|
+
|
|
73
|
+
decomposing:
|
|
74
|
+
when: { pr.state: open, pr.assignee: planner }
|
|
75
|
+
board: in_progress
|
|
76
|
+
owner: planner
|
|
77
|
+
|
|
78
|
+
verifying:
|
|
79
|
+
when: { issue.state: open, pr.state: merged } # whoever merged
|
|
80
|
+
board: in_progress
|
|
81
|
+
owner: verifier
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Build metadata for the standalone tickwork distribution: the release
|
|
2
|
+
# pipeline stages this beside a copy of the tickwork/ package. The tree must
|
|
3
|
+
# stay instance-neutral (the boundary lint scans .toml files here with the
|
|
4
|
+
# same forbidden names as the package itself).
|
|
5
|
+
[build-system]
|
|
6
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "tickwork"
|
|
11
|
+
version = "0.3.0"
|
|
12
|
+
description = "A generic, instance-neutral workflow engine: states, transitions, sweeps and call replay over a queue of work items"
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"PyYAML>=6.0",
|
|
16
|
+
]
|
|
17
|
+
license = { text = "MIT" }
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
tickwork = "tickwork.cli:main"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
include = ["tickwork*"]
|
|
24
|
+
|
|
25
|
+
# The README documents the example files as installed side data: they are
|
|
26
|
+
# not part of the tickwork package, so data-files is the only way they reach
|
|
27
|
+
# the wheel/sdist. The install destination stays instance-neutral
|
|
28
|
+
# (share/tickwork/examples under sys.prefix).
|
|
29
|
+
[tool.setuptools.data-files]
|
|
30
|
+
"share/tickwork/examples" = [
|
|
31
|
+
"examples/pr-loop.states.yaml",
|
|
32
|
+
"examples/items.jsonl",
|
|
33
|
+
"examples/logins.yaml",
|
|
34
|
+
]
|
tickwork-0.3.0/setup.cfg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""The public tickwork engine: a generic, instance-neutral workflow engine.
|
|
2
|
+
|
|
3
|
+
It drives states, transitions and sweeps over a queue of work items. It is
|
|
4
|
+
developed in-tree but deliberately names nothing of the instance it runs
|
|
5
|
+
for: no persona, host, repository or person names appear here, and the
|
|
6
|
+
package imports only the standard library, PyYAML and other ``tickwork``
|
|
7
|
+
modules — everything
|
|
8
|
+
instance-specific is supplied by the caller.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__all__: list[str] = []
|