hubkit-cli 0.1.0__py3-none-any.whl
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.
- hubcore/__init__.py +1 -0
- hubcore/cli.py +273 -0
- hubcore/compat.py +187 -0
- hubcore/config.py +107 -0
- hubcore/data/hub/LICENSE +202 -0
- hubcore/data/hub/SKILL.md +125 -0
- hubcore/data/hub/config.example.toml +84 -0
- hubcore/data/hub/docs/architecture.md +133 -0
- hubcore/data/hub/docs/commands.md +66 -0
- hubcore/data/hub/docs/configuration.md +151 -0
- hubcore/data/hub/docs/troubleshooting.md +21 -0
- hubcore/data/hub/intents.example.toml +74 -0
- hubcore/data/hub/references/design-sources.md +14 -0
- hubcore/data/hub/references/research.md +49 -0
- hubcore/data/hub/scripts/hubcore/__init__.py +1 -0
- hubcore/data/hub/scripts/hubcore/cli.py +273 -0
- hubcore/data/hub/scripts/hubcore/compat.py +187 -0
- hubcore/data/hub/scripts/hubcore/config.py +107 -0
- hubcore/data/hub/scripts/hubcore/gate.py +145 -0
- hubcore/data/hub/scripts/hubcore/hooks.py +326 -0
- hubcore/data/hub/scripts/hubcore/install.py +66 -0
- hubcore/data/hub/scripts/hubcore/learnings.py +232 -0
- hubcore/data/hub/scripts/hubcore/lessons.py +168 -0
- hubcore/data/hub/scripts/hubcore/orders.py +508 -0
- hubcore/data/hub/scripts/hubcore/redact.py +89 -0
- hubcore/data/hub/scripts/hubcore/routed.py +1169 -0
- hubcore/data/hub/scripts/hubcore/slow.py +531 -0
- hubcore/data/hub/scripts/hubcore/status.py +535 -0
- hubcore/data/hub/scripts/hubcore/store.py +172 -0
- hubcore/data/hub/scripts/hubcore/watcher.py +167 -0
- hubcore/data/hub/scripts/hubkit +37 -0
- hubcore/data/hub/scripts/learn.py +84 -0
- hubcore/data/hub-mep/SKILL.md +35 -0
- hubcore/data/hubkit-setup/SKILL.md +50 -0
- hubcore/gate.py +145 -0
- hubcore/hooks.py +326 -0
- hubcore/install.py +66 -0
- hubcore/learnings.py +232 -0
- hubcore/lessons.py +168 -0
- hubcore/orders.py +508 -0
- hubcore/redact.py +89 -0
- hubcore/routed.py +1169 -0
- hubcore/slow.py +531 -0
- hubcore/status.py +535 -0
- hubcore/store.py +172 -0
- hubcore/watcher.py +167 -0
- hubkit_cli-0.1.0.dist-info/METADATA +195 -0
- hubkit_cli-0.1.0.dist-info/RECORD +51 -0
- hubkit_cli-0.1.0.dist-info/WHEEL +4 -0
- hubkit_cli-0.1.0.dist-info/entry_points.txt +2 -0
- hubkit_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
hubcore/data/hub/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hub
|
|
3
|
+
description: Operating rules for the HUB, the one Claude Code session that coordinates peer sessions, background agents and machines for its user, often while the user is away. Use when the session is bound as the hub, or for "hub", "orchestrate", "coordinate sessions", "I'm away, keep work moving", "route this to the reviewer", urgent orders ("hotfix", "release", "merge", "code red"), "status", "eod", and before the hub runs a command, posts a message or calls something pending on the user. It tracks orders with a clock and evidence, tracks routed work, and learns from the user's corrections. Not for ordinary single-session coding.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hub
|
|
7
|
+
|
|
8
|
+
The hub coordinates. It does not work. Correct behaviour is the default: route first, act only through the gate below. `hubkit` below means `python3 ~/.claude/skills/hub/scripts/hubkit`; every verb is in `docs/commands.md`, the mechanics in `docs/architecture.md`.
|
|
9
|
+
|
|
10
|
+
**At session start and after every compaction:** the SessionStart hook prints a `hubkit:` block (live work, open orders with clocks, recent learnings). Read it, then the overlay (`hubkit doctor` prints its path; default `~/.config/hubkit/overlay.md`): the people, accounts, machines and routing that are personal to this user. The overlay wins over this file where they differ. A compaction summary is a hint, not evidence. If the block is missing, run `hubkit status` and `python3 ~/.claude/skills/hub/scripts/learn.py list --last 30`.
|
|
11
|
+
|
|
12
|
+
## 1. The role
|
|
13
|
+
|
|
14
|
+
Three verbs only: **Brief** (hand complete work to an agent or peer), **Read to decide** (one result, diff or thread), **Decide / report**. Anything else is work, and work goes to someone else.
|
|
15
|
+
|
|
16
|
+
## 2. Pre-action gate (before EVERY tool call; one failed check = stop and route)
|
|
17
|
+
|
|
18
|
+
- **G1 Is this mine?** Inline: one read to decide, `SendMessage` to a peer or running agent, a question to the user, an agent launch, the `hubkit` verbs. Everything else (edits, merges, posts, remote shells, test runs, status-by-thread-reading, deploys) is routed.
|
|
19
|
+
- **G2 Is it settled?** Before asking the user or listing something as pending on them: grep learnings and their own words in this session, then have an agent re-read the source. They often answer themselves.
|
|
20
|
+
- **G3 Is it a message to a person?** Needs the exact text, a typed yes (or a direct order naming recipient and act) and the right account (overlay).
|
|
21
|
+
- **G4 Does it touch a machine?** Owner known, one agent per machine, no deletes of toolchains or data (the overlay lists owners).
|
|
22
|
+
- **G5 Is it a review, merge, release or PR?** Needs the ask and the user's yes, quoted. Urgent orders (section 3) are that yes.
|
|
23
|
+
- **G6 Is an agent in flight?** Never stop it. Redirect with `SendMessage`.
|
|
24
|
+
- **G7 Am I about to report or ask?** Use the structured question tool for input; report per section 7.
|
|
25
|
+
- **G8 Is it the inbox?** Only through the watcher named in the overlay, run by an agent.
|
|
26
|
+
|
|
27
|
+
**Who gets the work.** Mechanical work (merges on order, posts on a yes, remote ops, deploys, bookkeeping, lookups): a cheap-tier agent. Judgement work (fixes, reviews, root cause, recon that needs reading code): a mid-tier agent. Role work with its own context (review, paired QA, a feature lane): a peer session. Model-tier ceilings, agent caps and banned commands are the user's rules (overlay). Every brief has Task, Context, Scope (do and do not), Output (at most 15 lines), Done-when, a banned-command list and "never print secrets". Find idle peers before launching agents (`ListAgents`, then the sessions directory).
|
|
28
|
+
|
|
29
|
+
## 3. Urgent orders: a record, a clock, evidence to close
|
|
30
|
+
|
|
31
|
+
A human prompt containing an order word (words come from `intents.toml`; hotfix, release, merge and code red by default) opens an **order**. The prompt hook prints one line: id, kind, the dispatch text for that kind and when the first action is due; later prompts print the status line of every live order.
|
|
32
|
+
|
|
33
|
+
- Peer messages and harness text never open orders, chases or corrections. An order opens only from an imperative sentence, never a question. `unconfirmed` orders (made by hand) have no clock and expire after `unconfirmed_ttl_min`: `hubkit order confirm <id>` after the first real action, or `hold`.
|
|
34
|
+
- **First action within the kind's limit**, then `hubkit order move <id> --note "<what moved>"` at each real step (merge done, run started, deploy green).
|
|
35
|
+
- **Verbs:** `order open|status|ls|confirm|move|block|result|hold|resume|cancel|close|metrics`. `hold` and `cancel` take the user's words (`--quote`); the prompt hook applies "don't do it yet" or "cancel" to the matching order itself, so check `order ls`. A hold is explicit ("hold the hotfix", "don't deploy yet"); bare "don't", "wait", "stop" are not, and urgent words ("now", "asap", "don't wait") keep or restart the clock.
|
|
36
|
+
- **Close needs evidence:** `hubkit order close <id> --evidence pr_merged=<url> --evidence prod_live=file:/path/to/log` with every name the kind's `done_when` lists; `file:` evidence must exist and be newer than the order. Every PR the order names must appear in the evidence; a shortfall is refused with the missing PRs named. A question never opens an order, even with an urgent word; if the user meant an order, open it by hand (`hubkit order open --kind K --quote "..."`). "Done" in a reply to the user is copied from `order status`, never from an agent's word.
|
|
37
|
+
- **Never hold an urgent order by asking.** The order is the yes. Dispatch first; ask only for a one-way door nobody ordered.
|
|
38
|
+
- **Metric: chases per order, goal zero.** A later short prompt such as "status?" or "what is the hold up" counts as a chase (`hubkit order metrics`). The hook line "chase recorded" means you missed a clock: act, then report with the generated status line. Three chases print a STOP-LOSS line: stop other work and do the blocking step; the Stop gate stays shut until `hubkit order result <id> --note "<what you did, what came of it>"` is recorded. A reply is not a result. `OVERDUE` lines at the top of a prompt's hook output are clock breaches: act before anything else.
|
|
39
|
+
|
|
40
|
+
**Solving blockers (core rules; a mistake of this kind is fixed here and in code, never only in a note).**
|
|
41
|
+
|
|
42
|
+
- **(a) A blocker on ordered work is the hub's to solve, not to report.** The moment something blocks an open order (no host, a tool down, an agent's report of a failure), start the fix on a free owner, then record it: `hubkit order block <id> --on "<what>" --fix "<action running now>" --holder <who>`, and confirm it ran: `hubkit order result <id> --note "<what came of it>"`. A blocker with no fix, or a fix with no result after `blocker_fix_min` (3), alerts and holds the Stop gate; notes (`order move`) do not reset the idle clock of a blocked order, only a result does. *Enforced in code.*
|
|
43
|
+
- **(a2) Saying it is blocked is not solving it.** A final message that calls an open order blocked, down, failed or waiting on a host, tool or person is held until a block record with a fix exists (word list: `intents.toml` `[stop_gate]`). *Enforced in code.*
|
|
44
|
+
- **(b) An urgent step never queues behind other work on a busy agent.** Send it to a free owner. The routed hook warns when a second job goes to an owner already holding a live job while an order is open. *Warning enforced in code; the choice of owner is prose.*
|
|
45
|
+
- **(f) Re-read his words before saying done.** `hubkit asks ls --since 12h` prints his recent prompts one per line; a final message that says done while his newest prompt is newer than the last `work done` and the last `asks ls` is held. *Enforced in code.*
|
|
46
|
+
- **(g) A merge is done on his order only.** `hubkit hook pretool` (register it as a PreToolUse hook on Bash) warns, never denies, on `gh pr merge` with no live order and none of his typed words naming that PR. *Warning only.*
|
|
47
|
+
- **(c) An agent's "that is a person's call" is checked, not relayed.** Read what the person already decided (the record, the order's words) before asking or telling anyone it needs them. *Checked in code:* a final message that relays "a person's call" with no Read/Grep/Glob/Bash call in that turn is held at the Stop gate (words: `intents.toml` `[stop_gate] unchecked_words`).
|
|
48
|
+
- **(d) Before saying something is gone or absent, read the code that writes it.** A missing file, row or log line is proof of nothing until you know what produces it and when. *Checked in code:* a final message that says something is gone, lost or absent, with no lookup in that turn, is held the same way.
|
|
49
|
+
- **(e) A brief must not forbid the one action that clears the blocker.** Before sending a brief, check its banned-command list against the step the order needs. *Checked in code:* the routed hook warns when a brief's "do not / never" line shares a key word with an open order's blocker or fix.
|
|
50
|
+
|
|
51
|
+
## 4. Live status and routed work
|
|
52
|
+
|
|
53
|
+
`hubkit work add --kind active|tracked|priority --title "..." [--owner X]`, `hubkit work done <id> --note "..."`, `hubkit work ls`. The work file holds only live items; finished ones move to `archive/archive_<day>.jsonl`. `hubkit status` rebuilds the block. Before saying anything is pending on the user, section 5 applies.
|
|
54
|
+
|
|
55
|
+
**Routed work is tracked automatically**, one outcome per (owner, PR number), by a hook on `SendMessage` and `Agent`. What counts, what does not and how a chase differs from a new ask are in `docs/architecture.md`. What you must know:
|
|
56
|
+
|
|
57
|
+
- A message naming a PR, or a long one, or a background agent brief that asks for a result, opens an item. A later message to that owner is a chase: it counts (`chased Nx`) and never resets the ask's clock. **Only a new brief (`Done-when`, `Output`) is a new ask.**
|
|
58
|
+
- A review item closes itself, PR by PR, when a non-author review lands at the PR's current head (on a PR written by `user.github_login`, a COMMENT review with a body counts); a review on an older head never closes it.
|
|
59
|
+
- By hand: `hubkit work add --kind routed --owner X --title ".." --pr URL --head SHA`; close with `hubkit work done <id> --note ".."`. A question that really blocks ordered work: `hubkit work add --kind ask --title ".." --note ".."` (his card answer closes it).
|
|
60
|
+
|
|
61
|
+
## 5. Before saying anything is pending on the user
|
|
62
|
+
|
|
63
|
+
- Have an agent re-read the thread or PR now; the compaction summary and the watcher are not evidence.
|
|
64
|
+
- Check the user's latest messages first. If they replied or acted, drop the item silently.
|
|
65
|
+
- PR status comes from `gh` (head sha, last commit age, last review age), not from agent words. No movement for the kind's `stall_min` on an urgent order: chase the agent.
|
|
66
|
+
- State the read time.
|
|
67
|
+
|
|
68
|
+
## 6. Watchdog and stop gate
|
|
69
|
+
|
|
70
|
+
Silence on routed work past `stall_min` (reviews: `review_stall_min`, hard `OVERDUE` at `review_overdue_min` from routing) climbs a ladder: step 1 a one-line state ask, step 2 a re-brief to another owner (never while the owner reported within the limit), step 3 tell the user. Every item shows one `NEXT:`; a chased item waits for the next step.
|
|
71
|
+
|
|
72
|
+
**Wake-up.** The hub runs `hubkit watch --interval 120 --max-cycles 30` in the background (one at a time). It exits 0 printing `ALERT ..` on a new alert or a backed-off repeat, which wakes you; it exits 3 after its cycles, so re-arm it. Cron runs `hubkit tick --if-bound` as a backstop; overdue orders also reach the hub at its next prompt.
|
|
73
|
+
|
|
74
|
+
**The Stop hook blocks the turn end** (JSON `decision: block`, shown to you) while: routed work is open and no watch runs; an item needs action; the message ends on a question to the user and no `hubkit work add --kind ask` item exists; or a sentence gives a verdict on a PR of a review item while GitHub shows no counting review at its head (non-author, or a COMMENT with a body on a PR written by `user.github_login`). Follow the `NEXT:` in the reason.
|
|
75
|
+
|
|
76
|
+
`hubkit doctor` prints the bind, heartbeat ages (`seen/cron` is touched by every `tick`, so it tells a dead cron from an idle one), hook errors and a count of secret-shaped strings in state (never the strings).
|
|
77
|
+
|
|
78
|
+
## 7. Reporting
|
|
79
|
+
|
|
80
|
+
- At most 15 lines. Lead with what changed or what the user must decide. Tag claims verified / read / inferred. Full URLs. Times in the user's zone.
|
|
81
|
+
- One line names what was left out and where it is. Input needed: the question tool after the report, recommended option first.
|
|
82
|
+
- Hand over all PRs of a wave in one list, only when reviewed and ready.
|
|
83
|
+
- Code or commands for the user go to a file, not a raw block (the overlay may name the clipboard step).
|
|
84
|
+
|
|
85
|
+
## 8. End of day
|
|
86
|
+
|
|
87
|
+
`hubkit eod [--note "<one self-improvement sentence>"]` writes `eod/eod_<day>.md` from the record, no model: the day at a glance (orders opened, closed, cancelled, held, open; chases per order; finished work; alerts; hook errors), a self-reflection (orders that needed a chase or are still open) and the learnings (new, repeated, addressed and holding, addressed but repeated, seen 2+ times and not addressed). Act on the last two the same day: promote the rule, add a check, then `learn.py address`. A repeat after `address` shows as regressed tomorrow. Combine with the user's own end-of-day skill if the overlay names one.
|
|
88
|
+
|
|
89
|
+
## 9. Adaptive learning loop
|
|
90
|
+
|
|
91
|
+
On every correction ("why did you...", "I told you...", a repeated instruction), the prompt hook injects a `CORRECTION` line with the quote. That turn:
|
|
92
|
+
|
|
93
|
+
1. **Quote** the exact words.
|
|
94
|
+
2. **Record** (a cheap agent runs it): `learn.py add --class <kebab> --gate <G1..G8> --quote "<words>" --situation "..." --wrong "..." --right "..." --evidence "<where>"`. Reuse an existing class for the same kind of mistake; a repeat appends a dated line.
|
|
95
|
+
3. **Promote.** `learn.py promote-candidates` lists classes seen 2+ times. For each: `learn.py promote <class> --gate <G> --rule "<one short line>"` writes the rule into the overlay under its gate (personal rules stay out of this file), then `learn.py address --class <c> --how "..."`. `address` refuses unless the overlay or this file changed since the entry was last seen and holds a rule line tagged `[<class>]`.
|
|
96
|
+
4. **Read** recent entries at session start and after compaction. The block shows at most five, only those seen in the last 30 days; an addressed class stays 14 days on probation, then drops.
|
|
97
|
+
|
|
98
|
+
If a rule here contradicts what the user says now, their latest words win; fix the rule.
|
|
99
|
+
|
|
100
|
+
## 10. Composition
|
|
101
|
+
|
|
102
|
+
| Need | Hand off to |
|
|
103
|
+
|---|---|
|
|
104
|
+
| Notes, tickets, inbox, asks, the user's own list | `hub-mep` (optional, thin; routes to the mep skills) |
|
|
105
|
+
| State across sessions, handover | the durable-notes skill named in the overlay |
|
|
106
|
+
| Any question, approval or report to the user | the human-loop skill named in the overlay |
|
|
107
|
+
| Release, hotfix, deploy | the release skills named in the overlay (the order's `dispatch` text names the entry point) |
|
|
108
|
+
| Build, review, QA, long unattended run | peer sessions or skills named in the overlay |
|
|
109
|
+
|
|
110
|
+
## 11. Configuration, redaction and install
|
|
111
|
+
|
|
112
|
+
Everything personal is in `~/.config/hubkit/`: `config.toml` (names, zone, tools, paths, private terms), `intents.toml` (order words, clocks, evidence names), `overlay.md` (rules), `ext/*.py` (optional extensions). Keys: `docs/configuration.md`. This file and the scripts carry no personal names; `hubkit doctor --check-clean` checks that against `private.terms`.
|
|
113
|
+
|
|
114
|
+
All state writes are redacted (8 token shapes, `key=value` secrets, long base64/hex runs). Natural-language secrets ("the password is hunter2") are not caught. State is in `$XDG_STATE_HOME/hubkit` (override `$HUBKIT_STATE`), refused if inside a git repo or this directory.
|
|
115
|
+
|
|
116
|
+
**Install** (details in `README.md`): hooks in `~/.claude/settings.json` run `hubkit hook <session|prompt|answer|routed|stop|record>`; bind once per hub session with `hubkit bind` (interactive sessions only; liveness is pid plus process start time, so a crashed hub frees the bind, and a resumed hub session re-binds itself without ever taking over a live hub). Hooks no-op in every session that is not the live bound hub and in headless runs, always exit 0, and show or log errors only for the hub. If hubkit fails to import, the hook exits 0 and appends to `import-errors.log` in the state dir. Deny hooks are not part of this version.
|
|
117
|
+
|
|
118
|
+
## Example
|
|
119
|
+
|
|
120
|
+
User, to the hub: "hotfix the billing bug now, the fix is PR 41."
|
|
121
|
+
|
|
122
|
+
1. The prompt hook prints `O-... hotfix [open] ... done needs: pr_merged,prod_live` and the dispatch text. The hub briefs a cheap agent: Task merge PR 41; Output at most 15 lines; Done-when merged; banned commands; never print secrets. No question back to the user (section 3).
|
|
123
|
+
2. Merge done: `hubkit order move O-... --note "PR 41 merged"`. Deploy green: move again.
|
|
124
|
+
3. `hubkit order close O-... --evidence pr_merged=https://github.com/acme/repo/pull/41 --evidence prod_live=file:/path/to/deploy.log`. If it is refused, the missing evidence is named; fetch it, do not claim done.
|
|
125
|
+
4. The user says "I told you to tell me when it is live." The hook injects `CORRECTION`; the hub has a cheap agent run `learn.py add --class report-when-live --gate G7 --quote "I told you to tell me when it is live" ...`. Seen twice, it becomes a rule via `learn.py promote` and `learn.py address`.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# hubkit config. Copy to ~/.config/hubkit/config.toml (or set $HUBKIT_CONFIG).
|
|
2
|
+
# Everything user-specific lives here, in intents.toml and in overlay.md. Nothing else.
|
|
3
|
+
# Every key is optional; missing keys use the defaults in scripts/hubcore/config.py.
|
|
4
|
+
|
|
5
|
+
[user]
|
|
6
|
+
name = "Alex" # how the hub block names you
|
|
7
|
+
timezone = "Europe/Berlin" # IANA name; times are stored as epoch and shown in this zone
|
|
8
|
+
github_login = "" # your GitHub login: on PRs you wrote, a COMMENT review at the head counts (the reviewer peer posts as you)
|
|
9
|
+
|
|
10
|
+
[hub]
|
|
11
|
+
name = "hub" # names the hub in the status block; also the session name used to re-bind a resumed hub
|
|
12
|
+
|
|
13
|
+
[paths]
|
|
14
|
+
# state_dir = "" # default $XDG_STATE_HOME/hubkit. Must NOT be inside a git repo.
|
|
15
|
+
# intents = "" # default <config dir>/intents.toml, else the skill's intents.example.toml
|
|
16
|
+
# overlay = "" # default <config dir>/overlay.md (your personal rules; see SKILL.md)
|
|
17
|
+
# learnings = "" # default <state dir>/learnings.md
|
|
18
|
+
# sessions_dir = "~/.claude/sessions"
|
|
19
|
+
# transcripts_glob = "~/.claude/projects/*/*.jsonl" # scanned for hook failures of our own hooks
|
|
20
|
+
# extensions_dir = "" # default <config dir>/ext (see Extension points)
|
|
21
|
+
|
|
22
|
+
[tools]
|
|
23
|
+
mep = "mep" # path or name of the miseenplace CLI
|
|
24
|
+
notify_cmd = "notify-send -u critical -a hubkit" # watchdog alerts: this command + message; "" = off
|
|
25
|
+
|
|
26
|
+
[mep]
|
|
27
|
+
# The optional `mep` task-list CLI. Set enabled = false if you do not use it.
|
|
28
|
+
enabled = false # default true
|
|
29
|
+
timeout_s = 30 # default 30
|
|
30
|
+
# data_dir = "~/Documents/mep-data" # exported as MISEENPLACE_DIR for the adapter
|
|
31
|
+
|
|
32
|
+
[watchdog]
|
|
33
|
+
transcript_scan_hours = 24
|
|
34
|
+
|
|
35
|
+
[stop_guard]
|
|
36
|
+
# Log only. A final message ending in "?" or containing one of these, while an order is open,
|
|
37
|
+
# writes a would_block row to <state>/stop-guard.jsonl. It never blocks.
|
|
38
|
+
phrases = ["waits for your go", "say go", "want me to", "shall i"]
|
|
39
|
+
|
|
40
|
+
[private]
|
|
41
|
+
# Words that must never appear in the skill's own files (`hubkit doctor --check-clean`).
|
|
42
|
+
# Keep your names, company and hosts here; this file stays on your machine.
|
|
43
|
+
terms = []
|
|
44
|
+
|
|
45
|
+
# Extension points (all optional):
|
|
46
|
+
# intents.toml - add kinds (words, clocks, done_when evidence names, movement_files globs).
|
|
47
|
+
# ext/*.py - in <config dir>/ext. Define any of:
|
|
48
|
+
# status_lines(cfg) -> list[str] extra lines in the status block
|
|
49
|
+
# tick_checks(cfg, live_orders) -> list[{"key","msg"}] extra watchdog alerts
|
|
50
|
+
# overlay.md - free text rules that are yours: people, channels, accounts, repos, boxes.
|
|
51
|
+
# Hooks (add to ~/.claude/settings.json; see the README "Hook setup"). Every key: docs/configuration.md.
|
|
52
|
+
|
|
53
|
+
[slow]
|
|
54
|
+
# Estimates and the slowness check (hub-internal). See docs/architecture.md.
|
|
55
|
+
min_samples = 3 # archived samples needed before the usual time replaces the stall limit
|
|
56
|
+
order_default_min = 30 # default order estimate while there is no history
|
|
57
|
+
floor_min = 2 # never SLOW under this many minutes
|
|
58
|
+
tool_min = 2 # a tool running this long is named in the why
|
|
59
|
+
scan_kb = 2048 # tail of a transcript read for the why
|
|
60
|
+
|
|
61
|
+
[watcher]
|
|
62
|
+
stuck_min = 30 # hub busy this long with live jobs = stuck
|
|
63
|
+
unhandled_min = 10 # an alert this old and no hub heartbeat since = unhandled
|
|
64
|
+
waiting_min = 3 # a peer waiting on a prompt this long = alert
|
|
65
|
+
active_s = 120 # a busy holder whose transcript grew this recently is working
|
|
66
|
+
watch_grace_min = 5 # live work with no watch running this long = alert
|
|
67
|
+
notify_user = true # platform default desktop notification for hub/peer alerts when notify_cmd is empty
|
|
68
|
+
|
|
69
|
+
[routed]
|
|
70
|
+
# repo_aliases = { fe = "owner/other-repo" } # "fe 123" / "fe#123" name a PR in that repo, not the default one
|
|
71
|
+
# Work the hub hands to peers/agents (SendMessage, Agent hooks). Also settable in intents.toml [routed].
|
|
72
|
+
stall_min = 15 # nothing from the owner this long = STALLED
|
|
73
|
+
review_words = ["review"] # items whose message/summary has one of these are reviews
|
|
74
|
+
review_stall_min = 5 # reviews: chase after this
|
|
75
|
+
review_overdue_min = 10 # reviews: hard OVERDUE this long after routing, whatever the owner said
|
|
76
|
+
skip_owners = ["inbox"] # never tracked
|
|
77
|
+
min_chars = 200 # a message without a PR is work only from this length
|
|
78
|
+
gh_timeout_s = 10
|
|
79
|
+
queued_wait_min = 60 # work queued "after X" has no review clock until this long after the ask (or a review shows)
|
|
80
|
+
reply_window_min = 60 # a hub reply to a peer that wrote this recently is not an ask
|
|
81
|
+
repo = "owner/name" # bare #N resolves against this
|
|
82
|
+
owner_logins = {} # owner name -> GitHub login, for peers who author PRs
|
|
83
|
+
reviewer_logins = [] # empty = any non-author review counts
|
|
84
|
+
skip_briefs = ["mep watch", "inbox watch"] # standing watchers are never routed work
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
hubkit is a set of Claude Code hooks plus a small CLI, all standard-library Python. Everything it knows is on disk in a state directory; the model is not trusted to remember.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
you (typed prompt, card answer) peers / agents (SendMessage, Agent)
|
|
7
|
+
| |
|
|
8
|
+
v v
|
|
9
|
+
UserPromptSubmit hook PostToolUse hook "routed"
|
|
10
|
+
(prompt) PostToolUse "answer" (SendMessage|Agent)
|
|
11
|
+
| |
|
|
12
|
+
v v
|
|
13
|
+
+----------------+ opens/chases +---------------------------+
|
|
14
|
+
| orders.jsonl |<--------------------| work.json (routed items) |
|
|
15
|
+
| order ledger | | one per (owner, PR) |
|
|
16
|
+
+-------+--------+ +-------------+-------------+
|
|
17
|
+
| |
|
|
18
|
+
| +-------------------------------+ | gh reads: head, reviews
|
|
19
|
+
+----->| tick / watch (watchdog) |<----+ transcript: last heard-from
|
|
20
|
+
| alerts.jsonl, status.json |
|
|
21
|
+
+---------------+---------------+
|
|
22
|
+
|
|
|
23
|
+
+----------------------+------------------------+
|
|
24
|
+
v v
|
|
25
|
+
SessionStart / prompt hooks print Stop hook = gate
|
|
26
|
+
the status block + OVERDUE / CORRECTION blocks the turn end while the hub owes
|
|
27
|
+
|
|
|
28
|
+
v
|
|
29
|
+
hub session --(learn.py add / promote / address)--> learnings.md --> overlay.md
|
|
30
|
+
^ |
|
|
31
|
+
+----------------------- rules read at the next session ----------------+
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## State and the bound hub
|
|
35
|
+
|
|
36
|
+
State lives in `$HUBKIT_STATE`, else `$XDG_STATE_HOME/hubkit` (default `~/.local/state/hubkit`). It is refused when inside a git repository or the skill directory, so it is never committed. Files are append-only JSONL or written by atomic replace under an `flock`; every write goes through the redactor (`scripts/hubcore/redact.py`).
|
|
37
|
+
|
|
38
|
+
`hubkit bind` records the hub as session id + pid + process start time (read from `/proc`), so a crashed hub frees the bind by itself. Every hook first asks `is_hub`: it is true only for the live, bound, interactive session. In any other session a hook prints nothing and exits 0. If hubkit fails to import, the hook exits 0 and appends to `import-errors.log`.
|
|
39
|
+
|
|
40
|
+
## Hooks (`scripts/hubcore/hooks.py`)
|
|
41
|
+
|
|
42
|
+
| Event | Hook | What it does |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| SessionStart | `session` | Prints the `hubkit:` status block. If the bind is dead and this session resumed the bound session id (or has the configured hub name in the bound cwd), re-binds and prints one line. Never takes over a live hub. |
|
|
45
|
+
| UserPromptSubmit | `prompt` | Runs the intake on the typed prompt (see Orders), prints the order lines, `OVERDUE` clock breaches, the status block, and a `CORRECTION` line when the learn loop applies. |
|
|
46
|
+
| PostToolUse `AskUserQuestion` | `answer` | A card answer goes through the same intake and human-only filter as a typed prompt, and closes `ask` work items. |
|
|
47
|
+
| PostToolUse `SendMessage\|Agent` | `routed` | Records or refreshes a routed-work item (see Routed work). One `gh` baseline read per named PR, 3 s in total. |
|
|
48
|
+
| Stop | `stop` | The gate. Prints JSON `{"decision": "block", "reason": ...}` while the hub owes something. |
|
|
49
|
+
| PreToolUse `Bash` | `pretool` | Warn-only merge guard: `gh pr merge` with no live order and none of his typed words naming the PR adds a warning as additional context. Never denies. Register it yourself in `settings.json`. |
|
|
50
|
+
| any | `record` | Schema canary. Every hub run writes the real payload's keys and value types (never values) to `payload-samples/`. |
|
|
51
|
+
|
|
52
|
+
A hook failure is shown and logged (`hook-errors.jsonl`) only for the hub. The stop gate's own blocks are not counted as failures.
|
|
53
|
+
|
|
54
|
+
## Orders (`orders.py`)
|
|
55
|
+
|
|
56
|
+
An order is a row family in `orders.jsonl` (`open`, `move`, `hold`, `resume`, `cancel`, `chase`, `close`, `expire`), folded into one record per id. The kinds, their words, limits and `done_when` evidence names come from `intents.toml`.
|
|
57
|
+
|
|
58
|
+
- **Intake.** Only human text counts: peer messages, task notifications and harness wrappers are filtered first (denylist of markers plus an `origin` check). A kind whose words match opens an order only from an imperative sentence (an `imperative` word present, not a question, not a `not_order_phrases` phrase). Otherwise the order is `unconfirmed`: no clock, expires after `unconfirmed_ttl_min` unless `hubkit order confirm`.
|
|
59
|
+
- **Clock.** A first action is due within the kind's `first_action_min`; after that, movement (`order move`, or a touched `movement_files` glob) is due every `stall_min`. A deadline in the prompt ("in 30 min", "by 17:00") is parsed too.
|
|
60
|
+
- **Holds.** Only explicit `hold_words` hold an order; `urgent_words` keep or restart the clock; `cancel_words` cancel. Long prompts (`verb_max_chars`) never hold or cancel.
|
|
61
|
+
- **Chases.** A short prompt matching `intake.chase` while an order is open is recorded as a chase. Three chases print a stop-loss line.
|
|
62
|
+
- **Close.** `order close` requires every evidence name the kind's `done_when` lists; `file:<path>` evidence must exist and be newer than the order. A closed order is archived under `archive/archive_<day>.jsonl`.
|
|
63
|
+
|
|
64
|
+
## Routed work (`routed.py`)
|
|
65
|
+
|
|
66
|
+
A routed item is one outcome per (owner, PR number), or per owner when no PR is named, in `work.json`.
|
|
67
|
+
|
|
68
|
+
- **Tracked:** a message naming a PR (URL, `PR 12`, bare `#123` resolved against `routed.repo`) or at least `min_chars` characters, and background `Agent` launches whose brief asks for a result (`Done-when`, `Output`, a PR). **Not tracked:** acknowledgements, short messages without a PR, `skip_owners`, foreground agents, standing watchers (`skip_briefs`), and a reply to a peer who wrote to the hub within `reply_window_min`.
|
|
69
|
+
- **Asks and chases.** Only a new brief is a new ask. Any later non-acknowledgement message to the same owner is a chase: it is counted, never resets the ask's clock, and clears a moved-head or old-head-verdict flag.
|
|
70
|
+
- **Reviews.** A message naming a PR and a `review_words` word ("review #N", "needs a review", "can I get a review on #N") makes a review item, unless it opens with a change verb (fix, address, resolve, apply, ...) and the review word names an existing review ("fix the findings in his review of #N"), on a short clock (`review_stall_min`, hard `OVERDUE` at `review_overdue_min`). It closes itself, PR by PR, when GitHub shows a review by someone other than the author at the PR's current head (on a PR written by `user.github_login`, a COMMENT review with a body counts too, because the reviewer peer posts as that account; an empty-body COMMENT is an inline-thread reply and does not). A review on an older head never closes it. One PR per clock: a message naming several PRs is a queue; "after X's queue" phrasing delays the clock by `queued_wait_min`.
|
|
71
|
+
- **Heard-from.** A peer message or task notification in the hub transcript, a review on the PR, or a background agent whose transcript or output file is still growing (`routed.activity_globs`) updates the last-heard time.
|
|
72
|
+
- **Ladder.** Silence past `stall_min` climbs: step 1 a one-line state ask, step 2 a re-brief to another owner (not while the owner reported within the limit), step 3 tell the user. Every item shows one `NEXT:` action.
|
|
73
|
+
|
|
74
|
+
## Watchdog (`status.py`)
|
|
75
|
+
|
|
76
|
+
`hubkit tick` evaluates: no first action, no movement, passed deadline, unconfirmed expiry, a hold longer than `held_review_min`, routed-item ladders, hook failures found by scanning transcripts for failures of hubkit's own hooks, and extension checks. It repairs half-archived orders, rewrites `status.json`, appends `alerts.jsonl` with back-off (one alert per condition per order, repeats at 10, 20, 30 minutes) and runs `tools.notify_cmd`. It runs from cron (`--if-bound`) and inside `hubkit watch`.
|
|
77
|
+
|
|
78
|
+
`hubkit watch` is the wake-up: the hub starts it in the background; it exits 0 printing `ALERT ...` on a new alert or backed-off repeat (which wakes an idle hub), and exits 3 after `--max-cycles` (re-arm it). A pidfile allows only one; a second exits 4.
|
|
79
|
+
|
|
80
|
+
## Job records, estimates and the slowness check (`slow.py`)
|
|
81
|
+
|
|
82
|
+
Internal to the hub; there is no report or verb for the user. Every live order and routed item has one record, rebuilt each tick into `status.json` and printed as one short `job ...` line in the `hubkit:` block (id, status, minutes in status / estimate, holder, and `SLOW: <why>` when over): the job, its status and when that status began, when it started, the estimate, the holder, and that it is not done.
|
|
83
|
+
|
|
84
|
+
- **Estimate.** A routed item's brief may carry a line `Estimate: <n>m`. Without it the hook fills in the usual time for the item's step (review, fix push, work) and says `DEFAULTED` in its output. An order gets its estimate at its first `order move`. Estimates are kept as a list on the record (`estimates`); a new one is added beside the old (`hubkit work estimate`), so end of day can show estimate vs actual and how often a job was re-estimated.
|
|
85
|
+
- **Usual time (baseline).** The median minutes per step over archived routed items and closed orders (`archive_*.jsonl`). With fewer than `slow.min_samples` samples it falls back to the configured stall limit (or `slow.order_default_min` for an order) and the line says `limit, N samples`.
|
|
86
|
+
- **Overrun.** Time on the job (an order from its opening, a routed item from its start, a "hub pickup" from the owner's report) is past the estimate and past `slow.floor_min`. The tick adds one `SLOW <id> <step> <m>m, estimate <e>m; holder <h>; why: <signals>. NEXT: <action>` alert to the existing wake-up path. The why uses only real signals: the holder's session `status` and `waitingFor`, the oldest tool started and not yet answered and for how long, queued messages not yet taken, PR head movement, CI state (`gh`), and whether the session or agent transcript is alive. Only tool names, ids, times and queue operations are read from a transcript, never its text. With no signal the why says `unknown`. The alert key carries the cause, so it repeats only with the usual back-off or when the cause changes.
|
|
87
|
+
- **Stop gate (the one exception to "the gate is unchanged").** A routed item or order with an estimate on record that is over it blocks the stop (`hubkit OVERRUN: SLOW ...`) until it is addressed: a chase to the holder, a re-brief of the same PRs to another owner, or `hubkit work estimate`. Items from before estimates existed alert but never block. A re-brief of an owner who reported within the limit is still advised against.
|
|
88
|
+
- **End of day.** The self-reflection lists estimate vs actual per step and kind: count, mean estimate, mean actual, over-estimate count, re-estimates.
|
|
89
|
+
|
|
90
|
+
## Watcher checks (`watcher.py`)
|
|
91
|
+
|
|
92
|
+
Every tick (cron with `--if-bound`, or inside `hubkit watch`) also checks things outside the hub session, with live jobs as the trigger:
|
|
93
|
+
|
|
94
|
+
- **Hub gone, stuck or not acting.** `hub:dead` (the bind is not a live process), `hub:stuck` (session `busy` longer than `watcher.stuck_min`), `hub:unhandled` (an alert older than `watcher.unhandled_min` and no hub heartbeat under `seen/` since).
|
|
95
|
+
- **Peer waiting.** A session holding a live item whose `waitingFor` is set (or whose status is a waiting state) for `watcher.waiting_min` raises `peer:<name>:waiting` with the holder and the minutes.
|
|
96
|
+
- **Watch and cron check each other.** A live job with no running watch raises `watch:dead` (the status block also tells the hub to re-arm it); a watch that finds `seen/cron` stale while the crontab runs hubkit raises `cron:dead`.
|
|
97
|
+
- **Blockers.** An order's `block` record (on, fix, holder) turns a report into work: no fix or a stale fix alerts and gates the stop; only an `order result` clears it, and `order move` notes do not reset the idle clock of a blocked order. A new job sent to an owner who already holds one, while an order is open, prints a queue warning.
|
|
98
|
+
- **Notification.** New alerts go to `tools.notify_cmd` when set. With none set, only `hub:*` and `peer:*` alerts use the platform default (`notify-send` on Linux, `osascript` on macOS, when installed). Nothing here ever posts to chat.
|
|
99
|
+
- **False alarms.** A silent item is not a stall while it is `blocked_on` another live job (`hubkit work block`), or while its holder's session is `busy` and its transcript is still growing (for up to 3x the limit). A head moved by the owner's own newest commit is progress, not a moved head. Every stall alert names the holder's state (`holder: ...`).
|
|
100
|
+
- **Short asks.** A short message to a peer is tracked when a request verb opens a sentence or it ends in a question; acknowledgements, status questions and thanks are not.
|
|
101
|
+
- **PRs are keyed by repo and number.** `owner/repo#N`, a full URL, or `<alias> N` for a `routed.repo_aliases` entry name their repo; only a bare `#N` falls back to `routed.repo`.
|
|
102
|
+
|
|
103
|
+
## Stop gate (`gate.py`)
|
|
104
|
+
|
|
105
|
+
The Stop hook blocks the end of the turn, with the exact next action, while any of these holds:
|
|
106
|
+
|
|
107
|
+
1. Routed work or an open order exists and no watch is running.
|
|
108
|
+
2. A routed item needs action now (stalled, overdue, moved head).
|
|
109
|
+
3. The final message gives a verdict on a PR of a review item while GitHub shows no counting review at its current head (a non-author review, or a COMMENT review with a body on a PR written by `user.github_login`). Awaiting wording ("waiting on the verdict") is not a verdict. Agent words are not evidence.
|
|
110
|
+
4. A job with an estimate on record is past it and not yet addressed (see the slowness check).
|
|
111
|
+
5. A blocker is not being solved (no fix running, fix past its limit), a STOP-LOSS has no result since, or the final message calls an open order blocked/down/failed/waiting while no order has a fix on record.
|
|
112
|
+
6. The final message says done while his newest prompt is newer than the last `work done` and the last `hubkit asks ls`; or it calls something gone/lost/absent, or "a person's call", with no Read/Grep/Glob/Bash call in the turn (transcript read).
|
|
113
|
+
7. The final message ends on a question (or holds a `stop_guard.phrases` phrase) and no `ask` work item says anything really waits on the user.
|
|
114
|
+
|
|
115
|
+
## The learn loop
|
|
116
|
+
|
|
117
|
+
This is the adaptive part. The mechanism, in the order it runs (`hooks.py`, `learnings.py`, `scripts/learn.py`):
|
|
118
|
+
|
|
119
|
+
1. **Detect.** The prompt hook injects a `CORRECTION` line, with the quote, when a typed prompt matches a `[corrections] patterns` regex or repeats a recent prompt (word-set similarity at least `repeat_similarity` within `repeat_window_min`).
|
|
120
|
+
2. **Record** (`learn.py add`). The hub records a class of mistake: kebab-case class, the gate it broke (G1 to G8 in SKILL.md), the exact quote, situation, wrong, right, evidence. A repeat of an existing class appends a dated `seen` line. Each entry stores a `basis` hash of the rule texts (overlay plus SKILL.md) at the time. Every field is redacted; edits are locked and atomic.
|
|
121
|
+
3. **Promote** (`learn.py promote-candidates`, then `learn.py promote <class> --gate G --rule "..."`). A class seen two or more times is a candidate. Promoting writes one short rule line tagged `[<class>]` into your overlay under its gate. Personal rules stay out of the shared `SKILL.md`.
|
|
122
|
+
4. **Address** (`learn.py address --class <c> --how "..."`). The class is marked fixed only if the rules changed since the entry was last seen **and** a rule line tagged `[<class>]` exists. Otherwise it is refused: a fix without a rule is not a fix.
|
|
123
|
+
5. **Probation.** The status block shows at most five learnings seen in the last 30 days; an addressed class stays visible for 14 days as "probation". If the class is seen again after it was addressed, `hubkit eod` and `learn.py review` list it as regressed (a fix that did not work), and it is to be fixed the same day.
|
|
124
|
+
|
|
125
|
+
hubkit does the detecting, storing, refusing and reporting; the hub model does the recording and the rule writing, so the loop only adapts as far as the hub follows it.
|
|
126
|
+
|
|
127
|
+
## End of day (`hubkit eod`)
|
|
128
|
+
|
|
129
|
+
Writes `eod/eod_<day>.md` from the record, with no model: orders opened, closed, cancelled, held and open; chases per order; finished work; alerts; hook errors; and the learnings between the archive and the new (new today, repeated, addressed and holding, addressed but repeated, seen twice and not addressed).
|
|
130
|
+
|
|
131
|
+
## Extension points
|
|
132
|
+
|
|
133
|
+
`<config dir>/ext/*.py` modules may define `status_lines(cfg) -> list[str]` (extra lines in the status block) and `tick_checks(cfg, live_orders) -> list[{"key", "msg"}]` (extra watchdog alerts). A broken extension is logged to `hook-errors.jsonl` and skipped.
|