starpulse 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 (59) hide show
  1. starpulse-0.1.0/LICENSE +21 -0
  2. starpulse-0.1.0/PKG-INFO +321 -0
  3. starpulse-0.1.0/README.md +304 -0
  4. starpulse-0.1.0/pyproject.toml +57 -0
  5. starpulse-0.1.0/setup.cfg +4 -0
  6. starpulse-0.1.0/starpulse/__init__.py +1 -0
  7. starpulse-0.1.0/starpulse/__main__.py +24 -0
  8. starpulse-0.1.0/starpulse/adapter_kit.py +327 -0
  9. starpulse-0.1.0/starpulse/agent_cli.py +855 -0
  10. starpulse-0.1.0/starpulse/analytics.py +105 -0
  11. starpulse-0.1.0/starpulse/board.py +153 -0
  12. starpulse-0.1.0/starpulse/board_feed.py +501 -0
  13. starpulse-0.1.0/starpulse/claude_code.py +122 -0
  14. starpulse-0.1.0/starpulse/codex.py +132 -0
  15. starpulse-0.1.0/starpulse/config.py +182 -0
  16. starpulse-0.1.0/starpulse/contracts.py +249 -0
  17. starpulse-0.1.0/starpulse/dagu.py +419 -0
  18. starpulse-0.1.0/starpulse/demo.py +464 -0
  19. starpulse-0.1.0/starpulse/doctor.py +98 -0
  20. starpulse-0.1.0/starpulse/emit.py +69 -0
  21. starpulse-0.1.0/starpulse/event_log.py +298 -0
  22. starpulse-0.1.0/starpulse/events.py +46 -0
  23. starpulse-0.1.0/starpulse/harness.py +15 -0
  24. starpulse-0.1.0/starpulse/harnesses.py +100 -0
  25. starpulse-0.1.0/starpulse/history.py +309 -0
  26. starpulse-0.1.0/starpulse/machine.schema.json +128 -0
  27. starpulse-0.1.0/starpulse/machine_definition.py +247 -0
  28. starpulse-0.1.0/starpulse/machine_tasks.py +127 -0
  29. starpulse-0.1.0/starpulse/machines/harness.yaml +23 -0
  30. starpulse-0.1.0/starpulse/mermaid_import.py +144 -0
  31. starpulse-0.1.0/starpulse/native.py +178 -0
  32. starpulse-0.1.0/starpulse/otlp.py +201 -0
  33. starpulse-0.1.0/starpulse/pull_requests.py +143 -0
  34. starpulse-0.1.0/starpulse/push_runs.py +140 -0
  35. starpulse-0.1.0/starpulse/run_events.py +39 -0
  36. starpulse-0.1.0/starpulse/schemas/board.schema.json +169 -0
  37. starpulse-0.1.0/starpulse/schemas/machine-events.schema.json +66 -0
  38. starpulse-0.1.0/starpulse/schemas/pools.schema.json +37 -0
  39. starpulse-0.1.0/starpulse/schemas/runs.schema.json +234 -0
  40. starpulse-0.1.0/starpulse/server.py +783 -0
  41. starpulse-0.1.0/starpulse/session_start.py +40 -0
  42. starpulse-0.1.0/starpulse/settings.py +71 -0
  43. starpulse-0.1.0/starpulse/skill_install.py +70 -0
  44. starpulse-0.1.0/starpulse/skills/authoring-starpulse-machines/SKILL.md +67 -0
  45. starpulse-0.1.0/starpulse/skills/operating-starpulse-board/SKILL.md +45 -0
  46. starpulse-0.1.0/starpulse/skills/setting-up-starpulse/SKILL.md +37 -0
  47. starpulse-0.1.0/starpulse/skills/writing-starpulse-adapters/SKILL.md +65 -0
  48. starpulse-0.1.0/starpulse/snapshot.py +92 -0
  49. starpulse-0.1.0/starpulse/static/assets/index-CmFGyNSW.js +33 -0
  50. starpulse-0.1.0/starpulse/static/assets/index-DghGjk0a.css +1 -0
  51. starpulse-0.1.0/starpulse/static/index.html +16 -0
  52. starpulse-0.1.0/starpulse/tables.py +49 -0
  53. starpulse-0.1.0/starpulse/upstream_backlog.py +340 -0
  54. starpulse-0.1.0/starpulse.egg-info/PKG-INFO +321 -0
  55. starpulse-0.1.0/starpulse.egg-info/SOURCES.txt +57 -0
  56. starpulse-0.1.0/starpulse.egg-info/dependency_links.txt +1 -0
  57. starpulse-0.1.0/starpulse.egg-info/entry_points.txt +2 -0
  58. starpulse-0.1.0/starpulse.egg-info/requires.txt +8 -0
  59. starpulse-0.1.0/starpulse.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adin Ballew
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.
@@ -0,0 +1,321 @@
1
+ Metadata-Version: 2.4
2
+ Name: starpulse
3
+ Version: 0.1.0
4
+ Summary: StarPulse visualizes the workflows runs adapters read, lifecycle machines and the agents moving through them
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.14
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: sqlalchemy>=2.1.1
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Dist: python-statemachine==3.2.1
12
+ Requires-Dist: pyyaml>=6.0.3
13
+ Requires-Dist: jsonschema>=4.25.1
14
+ Provides-Extra: postgres
15
+ Requires-Dist: psycopg[binary]>=3.2; extra == "postgres"
16
+ Dynamic: license-file
17
+
18
+ # StarPulse
19
+
20
+ See the flow of work: your scheduler's workflows, the lifecycle your tasks move through, and the agents and
21
+ people moving them, live on one page.
22
+
23
+ **[Live demo](https://trantor-org.github.io/starpulse-demo/main/flow-view.html)**: a demo board with synthetic
24
+ tasks, running in your browser with no server.
25
+
26
+ ## What it draws
27
+
28
+ - **Tasks on their lifecycle.** Each board status is a state on a machine, and a task travels the transitions
29
+ as it moves. The default board is StarPulse's own: Markdown task files under `.starpulse/board/`, created empty
30
+ the first time you serve. A [Backlog.md](https://github.com/MrLesk/Backlog.md) project is one `[board]` setting
31
+ away; a Kanban view shows the same tasks as columns, and moves made there are written back to the board when its
32
+ adapter has a writer.
33
+ - **Workflow runs.** Each workflow is drawn as its step graph with its latest run. [Dagu](https://github.com/dagu-org/dagu) is
34
+ read directly; any other scheduler reports its runs with `starpulse emit`.
35
+ - **Agents at work.** A Claude Code session's OpenTelemetry log export moves the session through its own
36
+ machine, so you see which agent is prompting, running a tool or waiting.
37
+ - **History.** Every move is kept, so hovering a task traces the path it took.
38
+
39
+ ## Quickstart
40
+
41
+ You need [uv](https://docs.astral.sh/uv/).
42
+
43
+ ```sh
44
+ cd your-project
45
+ uvx starpulse serve
46
+ ```
47
+
48
+ Open <http://localhost:8766>. The first serve creates `.starpulse/board/` in the working directory (beside the config file when `--config`
49
+ names one): a `config.yml`
50
+ holding the lanes (To Do, In Progress, Done) and an empty `tasks/` directory, one Markdown file per task. The
51
+ Kanban view starts with no tasks: **New task**, at the top right beside the task count, opens a form for its title, description, priority, labels,
52
+ milestone, assignee, dependencies and acceptance criteria and adds it to the first column, and
53
+ **Connect a tracker** opens the `[board]` setting for each public adapter. If the directory has a Backlog.md project (`backlog/config.yml`), serve prints one
54
+ line naming it and the `[board] type = "upstream_backlog"` setting that shows it instead (see Configure); the page's
55
+ snapshot carries the same line as `hint`, and Connect a tracker shows it. StarPulse keeps its event log and history in
56
+ `starpulse-history.sqlite` beside its config file, or in the working directory without one. The page has no sign-in and listens on every interface, so run it on a machine or network you
57
+ trust.
58
+
59
+ `starpulse serve --port 8800 --hours 24` changes the port and how far back a task's latest move counts toward
60
+ where it is drawn (6 hours by default). The Admin page's Server card overrides that window for everyone
61
+ viewing, and `starpulse-settings.json` beside the config keeps the override across restarts.
62
+
63
+ ### Show your Claude Code sessions
64
+
65
+ Run the receiver beside the server, then start Claude Code with its log export pointed at it:
66
+
67
+ ```sh
68
+ uvx --from starpulse python -m starpulse.claude_code
69
+
70
+ CLAUDE_CODE_ENABLE_TELEMETRY=1 OTEL_LOGS_EXPORTER=otlp OTEL_EXPORTER_OTLP_PROTOCOL=http/json \
71
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318 OTEL_LOG_TOOL_DETAILS=1 \
72
+ OTEL_RESOURCE_ATTRIBUTES="vcs.ref.head.name=$(git branch --show-current)" claude
73
+ ```
74
+
75
+ The receiver decodes each export but publishes only which session moved, on which event and when; prompt,
76
+ reply and tool text is neither stored nor forwarded.
77
+
78
+ ### Report runs from any scheduler
79
+
80
+ ```sh
81
+ starpulse emit start --workflow nightly --run 2026-10-03 --status running
82
+ starpulse emit end --workflow nightly --run 2026-10-03 --status succeeded --step load --depends fetch
83
+ ```
84
+
85
+ A workflow no adapter lists is drawn from the step graph its `emit` calls add up to, and that graph is kept in
86
+ the history store across restarts.
87
+
88
+ ### Work the board from an agent
89
+
90
+ ```sh
91
+ starpulse board --milestone launch --label api # tasks per column, with dependencies, pull requests and moves
92
+ starpulse task show PROJ-45 # one task: lane, what it waits on, pull requests, moves
93
+ starpulse task moves PROJ-45 # every column it may move to, allowed or refused to the agent
94
+ starpulse task move PROJ-45 review # move it as the agent; a refusal is the verdict, exit 1
95
+ starpulse task trace PROJ-45 --flow in-progress # where the task has been: Board lanes, or one machine's events
96
+ starpulse machine show in-progress # a machine's states, transitions and the tasks now in each
97
+ starpulse runs list # each workflow as <instance>/<workflow> with its latest status
98
+ starpulse runs start prod/nightly # start a run-safe workflow through Run now; its run id
99
+ starpulse watch --task PROJ-45 # one JSON line per change, until you stop it: wait without polling
100
+ starpulse analytics health --hours 72 # dwell and WIP per state, throughput and stuck tasks, with gap warnings
101
+ starpulse snapshot # everything the page draws, as one document
102
+ starpulse doctor # does this install work: each check passes or fails, with why
103
+ starpulse skills install --claude --codex # copy the bundled skills into .claude/skills and .agents/skills
104
+ starpulse help --agent # every verb with its arguments, output keys and exit codes
105
+ ```
106
+
107
+ Four verbs work on files, and only `demo` without `--mockup` reads the server; each writes one JSON document the same way:
108
+
109
+ ```sh
110
+ starpulse machine validate .starpulse/machines/*.yaml # schema and compile errors, each with its file and line
111
+ starpulse machine import mermaid flow.mmd # draft a machine file from a stateDiagram-v2 diagram
112
+ starpulse config check --config starpulse.toml # unknown keys by name, and the effective config with defaults
113
+ starpulse demo --out demo.html # the self-contained demo page (reads the server's snapshot)
114
+ ```
115
+
116
+ These verbs read the running server (`starpulse serve`) over HTTP: `--server URL`, else `STARPULSE_URL`, else
117
+ `http://localhost:8766`. Each writes one JSON document to stdout and nothing to stderr, except `watch`, which writes
118
+ one JSON line per change; an error is `{"error": "...", "code": "..."}`. The exit code is 0 for success, 1 for a refused or invalid request, a refused move or a failed `doctor` check, 2 for a usage
119
+ error, 3 when the server is unreachable (the error names the address tried) or has no board writer and 4 for something not found. A verb
120
+ reads the server on every call and keeps nothing, and `help --agent` is generated from the command parser, so it
121
+ lists exactly the verbs there are.
122
+
123
+ | Verb | Arguments | Document |
124
+ |---|---|---|
125
+ | `snapshot` | | the server's snapshot: `graphs`, `flows`, `dags`, `pools`, `pulls`, `claims`, `settled`, ... |
126
+ | `board` | `--state`, `--milestone`, `--label`, `--assignee` | `columns`: each `{state, name, tasks}`; a task is `{id, title, lane, assignee, milestone, labels, dependencies, waiting_on, prs, moves}` |
127
+ | `task show` | `TASK` | a task as above, with its `description`; a completed or archived task has only its `id` and where it settled as `lane` |
128
+ | `task moves` | `TASK` | `task`, `lane` and `moves`: each column the task may move to as `{allowed, reason, skill}`, as the agent meets it |
129
+ | `task move` | `TASK`, `TO`, `--session` | `ok`, `task`, `to`, `reason`, `skill` and `advice`: the move made, or the refusal and the skill that satisfies it (exit 1); exit 3 when the board has no writer |
130
+ | `task trace` | `TASK`, `--flow` | `task`, `flow` (null without `--flow`), `path` and `steps`; the Board's `{at, from, to}` lane changes, or with `--flow` that machine's `{at, event, state}`, oldest first; an unknown flow exits 4, a task never seen has an empty `path` |
131
+ | `machine list` | | `machines`: each `{name, states, tasks}` with its state ids and its live task count |
132
+ | `machine show` | `NAME` | `name`, `states` (each `{id, name, initial, final, count, tasks}`) and `transitions`; an unknown machine exits 4 and names those drawn |
133
+ | `machine validate` | `PATH...` | `ok` and `machines`: each `{path, ok, errors}`, an error `{file, line, message}` (`line` is null when the compiler cannot place it); exit 1 when any file is refused. A guard or action name is taken as the adapter's to register |
134
+ | `machine import mermaid` | `SOURCE`, `--out` | `written`: the new file, by default `.starpulse/machines/<name>.yaml`; an existing file is refused (exit 1), a missing source is exit 4 |
135
+ | `runs list` | | `runs`: each workflow as `{workflow, status, raw, run_id, started_at, finished_at}` with `workflow` `<instance>/<workflow>`, and `error`, the runs adapters' error or null |
136
+ | `runs start` | `WORKFLOW` | `workflow` and `run_id`: starts `<instance>/<workflow>` through the server's Run now path, so only a workflow in the instance's `run_safe` starts and only from the loopback or private network (exit 1 when refused); an instance with no start exits 3, a workflow outside `run_safe` exits 4 |
137
+ | `watch` | `--machine`, `--task` | one line per change after the connect snapshot, `{event, data}` with `event` `task`, `move`, `pulls`, `claim` or `dags` and `data` the server's delta; `--machine` keeps that machine's changes (`board` takes `task`, `pulls` and `claim`), `--task` that task's, and either drops `dags`; an unknown machine exits 4, the server ending the stream exits 3, and an interrupt exits 0 |
138
+ | `analytics health` | `--hours`, `--stuck-hours` | `now`, `window_s`, `stuck_after_s`, `states`, `throughput`, `stuck` and `warnings`, below |
139
+ | `config check` | `--config` | `ok`, `file`, `unknown_keys`, `errors` and `config`, the effective config with every default filled in (null when it does not load); exit 1 when it does not load |
140
+ | `demo` | `--out`, `--mockup`, `--server` | `written`: the HTML file; it reads the server's snapshot, or a design mockup directory with `--mockup` |
141
+ | `doctor` | `--config` | `ok` and `checks`: each `{check, status, reason}`, `status` `pass` or `fail`; exit 1 when any fails |
142
+ | `skills list` | `--user` | `scope` and `skills`: each `{name, description, claude, codex}`, each harness `absent`, `installed`, `outdated` or `modified` |
143
+ | `skills install` | `--claude`, `--codex`, `--user`, `--force` | `scope` and `installed`: each `{harness, skill, path, was}`; exit 1 when a copy was modified since install |
144
+ | `help --agent` | | `exit_codes` and `verbs` |
145
+
146
+ `analytics health` reads `GET /api/analytics/health[?hours=N][&stuck_hours=N]` (a window of 168 hours and a stuck
147
+ threshold of 24 by default), the Board's flow health from the history's lane changes. `states` lists each Board state
148
+ with `wip`, the tasks in it now, and for a state that is not final the `visits`, `mean_s` and `max_s` of the stays that
149
+ ended inside the window or are still going, `open` of them: a task still in its state counts to now, so its stay is
150
+ flagged rather than left out. `throughput` is `{count, per_day}`, the entries into a final state in the window.
151
+ `stuck` lists each task whose current stay in a state that is neither the first nor a final one has lasted
152
+ `stuck_hours` or longer, longest first, each `{task, state, since, dwell_s, counted_to_now}`. `warnings` carry each
153
+ gap the history recorded (`kind` `gap`: entries trimmed before it read them, so counts may miss them) and each lane
154
+ the Board machine has no state for. History starts at a task's first recorded lane change, so a stay before it is not
155
+ counted. The endpoint is 400 for an `hours` or `stuck_hours` that is no positive number, and 501 when the history
156
+ (a board adapter's own) does not list every task's lane changes through `lane_rows()` and `gaps()`.
157
+
158
+ A move carries the actor that makes it. The machine YAML names, per event, who fires it (`writers`), and each move
159
+ the server offers lists them as `writers`; `POST /api/move` takes `{task, to, actor}` with `actor` `operator` when
160
+ absent, which is what the page sends. The server refuses an actor outside the event's writers before the board
161
+ writer is asked, so a move declared for `operator` alone (sending an In Progress task back to Ready) is refused to
162
+ `starpulse task move`, which always moves as `agent`. A board adapter's `writer` receives that actor as its third
163
+ argument: `writer(task, status, actor)`. `/api/move` is unauthenticated on the LAN, so this stops an agent's
164
+ accident, not an adversary.
165
+
166
+ A move may also name the mover's session: `POST /api/move` takes `{task, to, actor, session}`, and `starpulse task move`
167
+ sends `--session`, else `STARPULSE_SESSION`. A move that names one calls the writer with it as a fourth argument,
168
+ `writer(task, status, actor, session)`; one that names none calls it with three, so a writer written before sessions
169
+ keeps working. The writer records the session as the task's holder when the move is a claim, `BoardTask.holder`
170
+ carries it back, and a `Written.advice` string rides in the move's answer (`advice` in the CLI document, empty when
171
+ the writer has none). The native and Backlog.md adapters' writers record the holder as a `**Holder:** <session>` line in the
172
+ task's notes, in the same edit as the `In Progress` status, because the Backlog CLI drops a frontmatter key it
173
+ does not know.
174
+
175
+ `doctor` runs every check even when one fails, so one call names every fault. The checks: `config` (the config file
176
+ loads), `server` (it answers `/api/snapshot`), `adapter:board` and one `adapter:<name>` per `[[runs]]` instance (it is
177
+ producing: the Board is read, the instance lists workflows and reports no error), and `gh` (installed and logged in,
178
+ which the pull request reader needs).
179
+
180
+ `skills` reads no server. The package bundles four skills: `operating-starpulse-board` (what to work next, why a task
181
+ cannot move, moving it), `authoring-starpulse-machines` (machine YAML, writers, guards, subflows, `machine validate`
182
+ and `machine import mermaid`), `writing-starpulse-adapters` (the board, machine-events and runs contracts, the adapter
183
+ kit, `emit`) and `setting-up-starpulse` (starting the server, config, `doctor`), and `skills install`
184
+ copies them to `.claude/skills` (`--claude`) or `.agents/skills` (`--codex`) in the working directory, or with `--user`
185
+ to `~/.claude/skills` or `~/.agents/skills`. An install records what it wrote, so a copy you edited afterwards is
186
+ refused (exit 1) until you pass `--force`, while one the package has since updated is replaced; `skills list` reports
187
+ which of the four each copy is.
188
+
189
+ `waiting_on` is the dependencies not yet completed. `prs` are the task's pull request links, each with the checks,
190
+ merged state and open review threads the server last read when it has them.
191
+
192
+ ## Configure
193
+
194
+ `starpulse serve --config starpulse.toml` reads one TOML file; `starpulse.toml` in the working directory is read
195
+ when it exists. Database credentials never go in it: they come from the driver's environment, such as `PGPASSWORD`.
196
+
197
+ The default SQLite store must be on a local disk because WAL mode does not work on network filesystems such as NFS
198
+ or SMB. When the project or config lives on network storage, set `database_url` to a Postgres database instead.
199
+
200
+ ```toml
201
+ # The tracker's web address: a task links to `<tracker_url>/tasks/<id>`. Unset, it opens in the page's own Kanban.
202
+ tracker_url = "https://tracker.example.com"
203
+
204
+ # History in Postgres instead of SQLite; install the extra with `uvx --from 'starpulse[postgres]' starpulse serve`.
205
+ database_url = "postgresql+psycopg://db.example.com/starpulse"
206
+
207
+ # The board adapter: a module under `starpulse`, or the dotted path of one an installed package provides.
208
+ # The rest of the table is that adapter's settings.
209
+ # With no [board] table the view draws its own Markdown board, `type = "native"`:
210
+ [board]
211
+ type = "native"
212
+ # path = ".starpulse/board" # the board's directory, relative to this file; created empty when absent
213
+ # machine = "board.yaml" # a machine file for the Board: its transitions and `writers` decide which moves are offered, and to whom
214
+
215
+ # To draw a Backlog.md project instead:
216
+ # [board]
217
+ # type = "upstream_backlog"
218
+ # path = "backlog" # the project's backlog/ directory, relative to this file
219
+ # command = "backlog" # the Backlog.md CLI that writes a move (`backlog task edit <id> -s <status>`)
220
+ # machine = "board.yaml" # as above
221
+
222
+ # One instance of a runs adapter, a module under `starpulse` or the dotted path of one an installed package provides
223
+ # (it offers `start(url)` and `follow(url, runs, log)`); its workflows are drawn as `<name>/<workflow>`.
224
+ [[runs]]
225
+ name = "dagu"
226
+ type = "dagu"
227
+ url = "http://dagu.example.com:8080"
228
+ run_safe = ["nightly"] # the workflows the page's Run now may start
229
+ domains = { Data = ["nightly", "etl"] } # how the page groups this instance's workflows
230
+ ```
231
+
232
+ ## Write a board adapter
233
+
234
+ A board adapter connects StarPulse to a tracker. It is a module with a `board(settings, base)` function that
235
+ returns a `starpulse.board.Board`: `settings` is the rest of the `[board]` table and `base` is the config file's
236
+ directory. The `Board` says:
237
+
238
+ - which lifecycle machines the page draws (`machines`),
239
+ - how the tracker's tasks reach the page (`start`, which feeds each task as it changes, and `keys`, the task
240
+ keys it recognizes),
241
+ - and optionally a `writer(task, status, actor)` for moves made on the page or by an agent (each task's `moves` may
242
+ list the `writers` the machine declares per event), an `assign` for assignee changes, a `read`, `edit` and `archive` for the full task record and guarded edits and
243
+ archives (`edit` needs `read`; the snapshot's `capabilities` says which the board has), a `create(title, details)` that
244
+ makes a task in the board's starting lane with the details the page filled (description, priority, labels,
245
+ milestone, assignee, dependencies, acceptance criteria) and answers with its id (`POST /api/tasks`;
246
+ `capabilities.create`), and its own `history`.
247
+
248
+ `starpulse.native`, the default, keeps tasks as Markdown files under `.starpulse/board/` and writes moves,
249
+ assignee changes and new tasks to them in Python. `starpulse.upstream_backlog` is the reference adapter for a tracker with its own
250
+ writer: it polls a Backlog.md project's Markdown files, takes
251
+ the machine from the project's own statuses (any lane reaches any other, unless `machine` names a machine file
252
+ whose states are those lanes and whose `writers` reserve a move to an actor, such as `operator`), and writes moves
253
+ with the `backlog` CLI, answering a failed write with the CLI's output. An adapter with a writer subclasses
254
+ `BoardAdapterKit` with `writer` set, and the kit then checks that a move the operator may make is written and one
255
+ the machine leaves to the operator is refused to the agent. Name your module in `[board] type` and StarPulse imports it.
256
+
257
+ ## Public surface
258
+
259
+ What a release keeps compatible; a minor `0.y` release may break it, and its notes say so. Everything else in the package is
260
+ internal and can change in any release.
261
+
262
+ - The `starpulse` command line: its verbs, their JSON output and their exit codes.
263
+ - The documented entry point `python -m starpulse.claude_code`.
264
+ - The config file's keys and the machine YAML with its JSON Schema (`machine.schema.json`).
265
+
266
+ The modules an adapter may import, each exporting exactly the names in its `__all__`:
267
+
268
+ - `starpulse.board`: the board adapter seam (`Board`, `Written` and the writer and task protocols).
269
+ - `starpulse.board_feed`: the feed a board adapter places tasks on (`BoardFeed`) and what following a stream needs of it.
270
+ - `starpulse.contracts`: the board, machine-event and runs records, their JSON Schemas, and `RunsSink`.
271
+ - `starpulse.adapter_kit`: the test kit an adapter author runs against their adapter, and the helpers that serve it.
272
+ - `starpulse.machine_definition`: loading and validating a machine, and the `Registry` of guards and actions.
273
+ - `starpulse.snapshot`: how a machine is described to the page, and how its workflows are named.
274
+ - `starpulse.history`: the history a board adapter may keep itself, and placing a task's events on a machine.
275
+ - `starpulse.event_log`: the database event log producers append to and readers tail.
276
+ - `starpulse.config`: loading the config file, and the runs adapter a `type` names.
277
+ - `starpulse.harnesses`: loading the harness file, the tiers and efforts an agent profile names.
278
+ - `starpulse.mermaid_import`: drafting a machine definition from a Mermaid state diagram.
279
+ - `starpulse.otlp`: decoding Claude Code's OpenTelemetry log export.
280
+
281
+ A package test pins this list and each module's names, so adding or removing one is a reviewed change.
282
+
283
+ ## Develop
284
+
285
+ ```sh
286
+ git clone https://github.com/trantor-org/starpulse && cd starpulse
287
+ uv sync
288
+ pnpm --dir starpulse/web install && pnpm --dir starpulse/web build # the page, built into starpulse/static
289
+ uv run starpulse serve
290
+ ```
291
+
292
+ `uv run pytest` runs the suite with Python alone. When Docker or Podman is available, it also runs the Postgres-backed
293
+ integration cases; otherwise those cases are skipped.
294
+ `pnpm --dir starpulse/web run check` typechecks, lints, tests and builds the page.
295
+ [`bench/`](bench/README.md) holds the hub-ingest and instance event-log benchmarks.
296
+
297
+ [`design/`](design/index.html) is the design mockup, a static page over a saved snapshot (`data.js`); view it with
298
+ `uv run python -m http.server 8781 --directory design`, and `uv run python -m starpulse.demo --mockup design --out
299
+ mockup.html` writes it as one scrubbed file. A pull request that changes the page (`starpulse/web/**`), the mockup or
300
+ the preview itself gets one UI-preview comment from
301
+ [`.github/workflows/ui-preview.yml`](.github/workflows/ui-preview.yml): screenshots of each changed surface's
302
+ scrubbed demo, built by [`ci/ui_preview.py`](ci/ui_preview.py) against the demo config
303
+ [`ci/preview.toml`](ci/preview.toml), with the demos published to `trantor-org/starpulse-demo` under `pr-<N>/`
304
+ while the pull request is open. The preview is review context and never gates the pull request. A push to `main`
305
+ that touches the same paths republishes both demos under `main/`, the live demo linked above.
306
+
307
+ ## Contributing
308
+
309
+ Open an [issue](https://github.com/trantor-org/starpulse/issues/new/choose) for a bug, a feature or a question.
310
+ Pull requests come from collaborators; [CONTRIBUTING.md](CONTRIBUTING.md) has how both work.
311
+
312
+ ## Releases
313
+
314
+ StarPulse is versioned `0.y.z` until its interfaces settle: a minor version can break the config file, the
315
+ adapter contract or the command line, and a patch release never does. Each release is a
316
+ [GitHub Release](https://github.com/trantor-org/starpulse/releases) whose notes list the pull requests it
317
+ contains, published to PyPI as `starpulse`.
318
+
319
+ ## License
320
+
321
+ MIT; see [LICENSE](LICENSE).