tickmarkr 1.36.0 → 1.38.0
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.
- package/LICENSE +21 -0
- package/README.md +83 -207
- package/dist/adapters/claude-code.js +4 -4
- package/dist/adapters/codex.js +1 -1
- package/dist/adapters/fake.d.ts +2 -2
- package/dist/adapters/fake.js +8 -8
- package/dist/adapters/grok.js +5 -5
- package/dist/adapters/model-lints.d.ts +4 -4
- package/dist/adapters/model-lints.js +1 -1
- package/dist/adapters/prompt.js +8 -13
- package/dist/adapters/registry.d.ts +8 -8
- package/dist/adapters/registry.js +5 -5
- package/dist/adapters/types.d.ts +3 -3
- package/dist/adapters/types.js +1 -1
- package/dist/brand.d.ts +2 -0
- package/dist/brand.js +17 -0
- package/dist/cli/commands/approve.js +3 -3
- package/dist/cli/commands/compile.js +1 -1
- package/dist/cli/commands/doctor.js +3 -3
- package/dist/cli/commands/init.js +14 -9
- package/dist/cli/commands/profile.js +6 -6
- package/dist/cli/commands/status.js +2 -1
- package/dist/cli/commands/unlock.js +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +9 -7
- package/dist/compile/gsd.js +1 -1
- package/dist/compile/index.js +9 -3
- package/dist/compile/native.d.ts +2 -0
- package/dist/compile/native.js +4 -2
- package/dist/config/config.d.ts +6 -6
- package/dist/config/config.js +13 -12
- package/dist/drivers/herdr.js +5 -5
- package/dist/drivers/index.d.ts +2 -2
- package/dist/drivers/subprocess.js +3 -3
- package/dist/drivers/types.js +4 -6
- package/dist/gates/acceptance.js +1 -1
- package/dist/gates/baseline.d.ts +2 -2
- package/dist/gates/llm.js +12 -9
- package/dist/gates/review.d.ts +2 -2
- package/dist/gates/review.js +1 -1
- package/dist/gates/run-gates.d.ts +2 -2
- package/dist/graph/graph.d.ts +2 -2
- package/dist/graph/graph.js +4 -16
- package/dist/plan/prompt.js +1 -1
- package/dist/plan/scope.d.ts +2 -2
- package/dist/plan/scope.js +5 -5
- package/dist/route/preference.d.ts +4 -4
- package/dist/route/router.d.ts +3 -3
- package/dist/run/consult.d.ts +2 -2
- package/dist/run/consult.js +5 -5
- package/dist/run/daemon.d.ts +2 -0
- package/dist/run/daemon.js +48 -17
- package/dist/run/git.d.ts +1 -1
- package/dist/run/git.js +5 -9
- package/dist/run/journal.d.ts +2 -2
- package/dist/run/journal.js +9 -5
- package/dist/run/lock.js +11 -11
- package/dist/run/merge.d.ts +11 -2
- package/dist/run/merge.js +21 -3
- package/dist/run/reconcile.js +1 -1
- package/package.json +4 -2
- package/skills/tickmarkr-auto/SKILL.md +1 -1
- package/skills/tickmarkr-loop/SKILL.md +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Khalid Alzahrani
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,56 +1,24 @@
|
|
|
1
|
+
<p align="center"><img src="assets/wordmark-dark.png" alt="tickmarkr" width="640"></p>
|
|
2
|
+
|
|
1
3
|
# tickmarkr
|
|
2
4
|
|
|
3
5
|
**Assertions are free. Tickmarks are earned.**
|
|
4
6
|
|
|
5
|
-
tickmarkr is a spec-driven orchestration harness for AI coding agent CLIs
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
your mainline) is always the Partner's call, never the engine's. A fully green engagement is an
|
|
14
|
-
**unqualified opinion** — every assertion independently ticked.
|
|
15
|
-
|
|
16
|
-
<!-- tickmarkr:compat-legacy BEGIN: sanctioned naming residue, unchanged pending shim spec -->
|
|
17
|
-
**Legacy naming (unchanged):** tickmarkr reads existing state from `.drovr/` directories and resumes runs with legacy `drovr/<runId>` branch names recorded in their journals; new engagements write to `tickmarkr/<runId>`. Environment variables `DROVR_*` remain unchanged (e.g., `DROVR_FAKE_SCRIPT`, `DROVR_E2E`). Spec markers use the historical `<!-- drovr:spec -->` comment. These names are pending formalization in a future shim spec; no action required — existing runs and configurations remain fully compatible.
|
|
18
|
-
<!-- tickmarkr:compat-legacy END -->
|
|
19
|
-
|
|
20
|
-
> The package ships `tickmarkr` and `tkr` only; `tkr` is the short alias.
|
|
21
|
-
|
|
22
|
-
## Vocabulary
|
|
23
|
-
|
|
24
|
-
Audit voice in docs and reports maps to engine concepts exactly once:
|
|
25
|
-
|
|
26
|
-
| Audit term | Engine concept |
|
|
27
|
-
|---|---|
|
|
28
|
-
| Partner | Human operator |
|
|
29
|
-
| Manager | Overseer |
|
|
30
|
-
| tickmarkr | Orchestrator daemon |
|
|
31
|
-
| field teams / staff | Workers (agent CLIs in worktrees) |
|
|
32
|
-
| seniors | Watchers (status monitor, gate panes) |
|
|
33
|
-
| engagement | Run (`tickmarkr run` / `resume`) |
|
|
34
|
-
| assertion | Acceptance criterion (`acceptance[]` entry) |
|
|
35
|
-
| tickmark | Passed gate |
|
|
36
|
-
| audit evidence | Evidence gate (commits/diff exist) |
|
|
37
|
-
| audit trail | Journal (engagement event ledger) |
|
|
38
|
-
| the consolidation | Integration branch (`tickmarkr/<runId>`) |
|
|
39
|
-
| sign-off / issuance | Merge consolidation branch to main |
|
|
40
|
-
| unqualified opinion | Green engagement (all tasks merged) |
|
|
41
|
-
| qualified opinion | Failed or stalled engagement |
|
|
42
|
-
| EQR | Acceptance judge (LLM gate vs `acceptance[]`) |
|
|
43
|
-
| National Office | Consult (frontier-model escalation on deadlock) |
|
|
44
|
-
|
|
45
|
-
Commands below use the real CLI surface — audit terms never replace bin names in examples.
|
|
7
|
+
tickmarkr is a spec-driven orchestration harness for AI coding agent CLIs. You write a spec with
|
|
8
|
+
acceptance criteria; the engine routes tasks to the best installed agent CLI (claude-code, codex,
|
|
9
|
+
cursor-agent, opencode, grok, pi) by cost and capability, dispatches work in isolated git worktrees
|
|
10
|
+
— as interactive TUIs when running under [herdr](https://herdr.dev), headless subprocesses otherwise
|
|
11
|
+
— and independently verifies every result before merge. Green tasks consolidate onto a
|
|
12
|
+
`tickmarkr/<runId>` branch; merging to your mainline is always your call, never automated. Engage
|
|
13
|
+
with full visibility into routing decisions, worker progress, and gate verdicts — or run headless
|
|
14
|
+
and review the audit trail afterward.
|
|
46
15
|
|
|
47
16
|
## Invariants
|
|
48
17
|
|
|
49
18
|
These are law; the codebase fails closed around them:
|
|
50
19
|
|
|
51
20
|
- `acceptance[]` required on every task; compile fails without it
|
|
52
|
-
- new engagements consolidate to a `tickmarkr/<runId>` branch — never main
|
|
53
|
-
engagements keeps the branch names recorded in their journal)
|
|
21
|
+
- new engagements consolidate to a `tickmarkr/<runId>` branch — never main
|
|
54
22
|
- gates never trust worker claims; tickmarkr verifies everything itself
|
|
55
23
|
- state is files + git only; no DB, no services
|
|
56
24
|
- worker/judge/review/consult prompts end with machine-parseable trailers
|
|
@@ -91,17 +59,17 @@ at 30s.
|
|
|
91
59
|
|
|
92
60
|
```bash
|
|
93
61
|
tickmarkr init # guided setup + doctor; scaffolds config and spec template
|
|
94
|
-
# edit tickmarkr.spec.md #
|
|
95
|
-
tickmarkr compile tickmarkr.spec.md # spec →
|
|
96
|
-
tickmarkr plan # dry-run routing
|
|
97
|
-
tickmarkr run # execute
|
|
62
|
+
# edit tickmarkr.spec.md # write your tasks and acceptance criteria
|
|
63
|
+
tickmarkr compile tickmarkr.spec.md # spec → task graph (fails without acceptance criteria)
|
|
64
|
+
tickmarkr plan # dry-run routing decisions + cost estimate
|
|
65
|
+
tickmarkr run # execute, route to best CLI, gate every result (--concurrency N)
|
|
98
66
|
tickmarkr report <runId> --md # engagement record in Markdown
|
|
99
67
|
```
|
|
100
68
|
|
|
101
|
-
That's the
|
|
102
|
-
|
|
103
|
-
and
|
|
104
|
-
|
|
69
|
+
That's the flow: `init` scaffolds config, you write tasks with `acceptance[]` criteria, `compile`
|
|
70
|
+
validates and builds the graph, `plan` shows routing decisions and cost, `run` dispatches work to
|
|
71
|
+
installed CLIs and independently verifies every result, and `report` documents the outcome. All
|
|
72
|
+
green tasks land on `tickmarkr/<runId>` — merge to your mainline is your decision.
|
|
105
73
|
|
|
106
74
|
## Agent-ready repos: `tickmarkr init --agent`
|
|
107
75
|
|
|
@@ -126,33 +94,24 @@ Consent rules — every write is additive, never destructive:
|
|
|
126
94
|
- non-interactive shells (no TTY) never prompt: missing skills are installed, everything else is
|
|
127
95
|
skipped, and the summary names exactly what was skipped and which flag would enable it
|
|
128
96
|
|
|
129
|
-
|
|
97
|
+
## Monitor and supervise
|
|
130
98
|
|
|
131
99
|
```bash
|
|
132
|
-
tickmarkr status #
|
|
133
|
-
tickmarkr resume <runId> # continue an engagement from
|
|
134
|
-
tickmarkr approve <runId> <taskId> #
|
|
100
|
+
tickmarkr status # engagement state (--watch to follow live)
|
|
101
|
+
tickmarkr resume <runId> # continue an engagement from the audit trail
|
|
102
|
+
tickmarkr approve <runId> <taskId> # approve a parked task (--reason to document)
|
|
135
103
|
tickmarkr report <runId> # cost/quality report
|
|
136
104
|
tickmarkr profile # show the learned routing profile
|
|
137
|
-
tickmarkr unlock # remove a stale/garbage run lock (refuses if the holder is alive)
|
|
138
105
|
```
|
|
139
106
|
|
|
140
|
-
Green tasks land on
|
|
141
|
-
mainline is your call, never tickmarkr's.
|
|
142
|
-
|
|
143
|
-
### Escalation and National Office verdicts
|
|
144
|
-
|
|
145
|
-
When gates or field teams stall, tickmarkr escalates to a frontier-model **National Office** consult
|
|
146
|
-
for a structured verdict. The consult can recommend rerouting to a different adapter/model channel,
|
|
147
|
-
or human approval if deadlock persists. Reroute verdicts can target two scopes:
|
|
107
|
+
Green tasks land on `tickmarkr/<runId>`; merge to your mainline is always your call.
|
|
148
108
|
|
|
149
|
-
|
|
150
|
-
- **Adapter-level reroute**: exclude an entire adapter (e.g., `cursor-agent`) when the consult detects an environmental
|
|
151
|
-
CLI-level failure (blocked dialog, auth issue, timeout pattern). The daemon expands the task's exclusion list to cover
|
|
152
|
-
every available channel of that adapter, ensuring zero re-try of the same root cause
|
|
109
|
+
### Escalation and consults
|
|
153
110
|
|
|
154
|
-
|
|
155
|
-
|
|
111
|
+
When gates or tasks stall, tickmarkr escalates to a frontier-model consult for a structured verdict.
|
|
112
|
+
The consult can recommend rerouting (exclude a failed channel/adapter and try the next ranked option)
|
|
113
|
+
or human approval if deadlock persists. Exclusions persist across resume — a task that escalated
|
|
114
|
+
away from a failing adapter will never retry it in subsequent `tickmarkr resume` calls.
|
|
156
115
|
|
|
157
116
|
### Approving tasks
|
|
158
117
|
|
|
@@ -162,15 +121,9 @@ or adapters in a subsequent `tickmarkr resume`.
|
|
|
162
121
|
- The task finished with a result but gates require human judgment (`humanGate: true` in the spec)
|
|
163
122
|
- Approving records the Partner's verdict and the task proceeds to merge
|
|
164
123
|
|
|
165
|
-
**Attempt-
|
|
124
|
+
**Attempt-budget exhaustion**:
|
|
166
125
|
- The task has burned its full attempt budget without reaching a conclusive result
|
|
167
|
-
-
|
|
168
|
-
- Approving grants a **fresh attempt budget** with the same exclusion list — the task re-routes around all previously-burned channels
|
|
169
|
-
and adapters, giving it one more escalation ladder before exhaustion
|
|
170
|
-
|
|
171
|
-
Example: a task escalates away from cursor-agent due to a trust dialog (adapter-level exclusion), then away from all claude-code
|
|
172
|
-
models before hitting the attempt cap. `tickmarkr approve` grants a fresh ladder to try the frontier tier, still avoiding both
|
|
173
|
-
cursor-agent and claude-code entirely.
|
|
126
|
+
- Approving grants a fresh attempt budget, routing around all previously-failed channels and adapters
|
|
174
127
|
|
|
175
128
|
## Model scoping and auth detection
|
|
176
129
|
|
|
@@ -178,8 +131,8 @@ Each agent CLI exposes a list of available models. tickmarkr's routing works onl
|
|
|
178
131
|
|
|
179
132
|
**Model terminology**:
|
|
180
133
|
|
|
181
|
-
- **Listed models**: All models a CLI advertises as available (e.g., pi advertises both `
|
|
182
|
-
- **Scoped models**: Listed models with an explicit
|
|
134
|
+
- **Listed models**: All models a CLI advertises as available (e.g., pi advertises both `zai/glm-5.2` and `anthropic/claude-opus-4-8`, but not all are authed)
|
|
135
|
+
- **Scoped models**: Listed models with an explicit provider prefix (e.g., `zai/glm-5.2`, `zai-coding-plan/glm-5.2`). The design rule is to classify scoped models primarily, not unscoped listed models
|
|
183
136
|
- **Classified models**: Models you've entered into `tiers` — the routing-eligible set, regardless of scope
|
|
184
137
|
|
|
185
138
|
When you run `tickmarkr doctor`, it:
|
|
@@ -202,18 +155,17 @@ You can bench a classified model without removing it from tiers:
|
|
|
202
155
|
routing:
|
|
203
156
|
deny:
|
|
204
157
|
models:
|
|
205
|
-
- pi:glm-5.2
|
|
206
|
-
-
|
|
158
|
+
- pi:zai/glm-5.2 # stays in tiers, never routes (key is adapter:model)
|
|
159
|
+
- cursor-agent:composer-2.5 # reason in git commit
|
|
207
160
|
```
|
|
208
161
|
`tickmarkr plan` lints denied models identically to unauthed ones. Re-enable by deleting one line.
|
|
209
162
|
|
|
210
|
-
## Run output and
|
|
163
|
+
## Run output and audit trail
|
|
211
164
|
|
|
212
|
-
When you execute `tickmarkr run`, the daemon
|
|
213
|
-
gate
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
feed it to external tools; the narration is a convenience layer on top.
|
|
165
|
+
When you execute `tickmarkr run`, the daemon records every event in an append-only ledger (the audit trail):
|
|
166
|
+
task dispatch, gate verdicts, worker status, and merges. Narration streams to stdout; you can also
|
|
167
|
+
`--watch` or tail the ledger directly. If interrupted, `tickmarkr resume <runId>` replays the ledger
|
|
168
|
+
and continues from the last stable state — deterministic, not ephemeral.
|
|
217
169
|
|
|
218
170
|
## Usage and cost
|
|
219
171
|
|
|
@@ -243,9 +195,14 @@ and first-attempt success rate. Cost reporting follows strict honesty rules and
|
|
|
243
195
|
comments and LiteLLM's JSON file named as the copy-from source; tickmarkr never calls home to fetch rates
|
|
244
196
|
- **Attribution from journal**: token counts come from tickmarkr's own telemetry spans in the audit trail, never from provider invoices or dashboards
|
|
245
197
|
|
|
246
|
-
## Visibility: supervised
|
|
198
|
+
## Visibility: optional supervised workspace
|
|
199
|
+
|
|
200
|
+
When running under [herdr](https://herdr.dev), tickmarkr creates a labeled pane-and-tab workspace
|
|
201
|
+
for real-time visibility (optional — omit `--driver herdr` or run headless if preferred).
|
|
247
202
|
|
|
248
|
-
|
|
203
|
+
tickmarkr borrows audit-firm vocabulary for its roles: **you** are the *Partner* (final sign-off),
|
|
204
|
+
workers are the *field team*, the acceptance judge is the *EQR* (engagement quality reviewer), and
|
|
205
|
+
the frontier-model consult is the *National Office*. The terms below use that vocabulary:
|
|
249
206
|
|
|
250
207
|
### Tab vocabulary
|
|
251
208
|
|
|
@@ -289,7 +246,7 @@ Every pane and tab tickmarkr creates receives a **parseable ownership name** enc
|
|
|
289
246
|
- `watch` — status monitor pane
|
|
290
247
|
- `<role> · <taskId> · A<attempt> · R<runId>` — judge, review, consult, and worker panes (formats like `judge · task-abc123 · A1 · Rrun-20260713-175532`)
|
|
291
248
|
|
|
292
|
-
tickmarkr creates all owned panes only within the run's workspace; any tickmarkr-owned panes discovered outside the run's workspace (from prior runs or placement bugs) are reconciled and closed
|
|
249
|
+
tickmarkr creates all owned panes only within the run's workspace; any tickmarkr-owned panes discovered outside the run's workspace (from prior runs or placement bugs) are reconciled and closed. Any pane not matching the ownership contract is **foreign** — created by you or another tool — and is never closed automatically.
|
|
293
250
|
|
|
294
251
|
**Desired-state reconciliation**: A pure function computes the exact set of panes that should exist from the audit trail at any moment:
|
|
295
252
|
- Worker panes for all in-flight task attempts
|
|
@@ -305,138 +262,57 @@ The daemon reconciles at every safe point:
|
|
|
305
262
|
|
|
306
263
|
Reconciliation failures (herdr unavailable, a pane vanished mid-sweep) never fail the engagement — visibility is cosmetic, gates are law.
|
|
307
264
|
|
|
308
|
-
### Workspace
|
|
309
|
-
|
|
310
|
-
tickmarkr runs field teams in fresh git worktrees cloned from your repo. Before dispatch, every worker checks whether that repo is trusted by its CLI, fail-closed:
|
|
311
|
-
|
|
312
|
-
**Seeding trust at doctor time**:
|
|
313
|
-
- `tickmarkr doctor` probes each adapter's trust status for your repo root
|
|
314
|
-
- Adapters that can pre-seed trust (codex via `config.toml`, others if applicable) do so automatically; you see `seeded` in the doctor output
|
|
315
|
-
- Adapters requiring manual trust grant show `action-required` with the exact one-time command to run (e.g., `codex init /path/to/repo`)
|
|
316
|
-
- Adapters without a trust concept report `n/a`
|
|
317
|
-
|
|
318
|
-
**Auto-answering trust dialogs during runs**:
|
|
319
|
-
- Some CLIs (e.g., cursor-agent) cannot pre-seed trust and show an interactive "Workspace Trust Required" dialog the first time they touch the worktree
|
|
320
|
-
- For these CLIs, tickmarkr detects the trust dialog by its fingerprint and auto-answers it exactly once per slot — safe because tickmarkr created the worktree from your own repo
|
|
321
|
-
- Any other blocked dialog still pages the operator for manual approval
|
|
265
|
+
### Workspace trust
|
|
322
266
|
|
|
323
|
-
|
|
267
|
+
tickmarkr runs work in fresh git worktrees cloned from your repo. Before dispatch, workers check
|
|
268
|
+
whether the repo is trusted by their CLI:
|
|
324
269
|
|
|
325
|
-
tickmarkr
|
|
326
|
-
|
|
270
|
+
- `tickmarkr doctor` probes each CLI's trust status and auto-seeds trust where possible
|
|
271
|
+
- Some CLIs show a "Workspace Trust" dialog; tickmarkr detects and auto-answers it once per task
|
|
272
|
+
- Other blocked dialogs page you for manual approval
|
|
327
273
|
|
|
328
|
-
|
|
329
|
-
|---|---|---|
|
|
330
|
-
| **tickmarkr spec** (native format) | Native/default | `tickmarkr init` template with native spec marker |
|
|
331
|
-
| **Spec Kit** feature dir | Native | dir containing `tasks.md` |
|
|
332
|
-
| **GSD** phase dir | Native | dir containing `*-PLAN.md` |
|
|
333
|
-
| **Markdown PRD** | Native (universal adapter) | any `.md` with tasks + acceptance criteria |
|
|
334
|
-
| OpenSpec, BMAD, Superpowers, others | Not yet | render to a tickmarkr spec (with full Task surface), or contribute a front-end: one parser module behind `src/compile/index.ts`, same pattern as `gsd.ts` |
|
|
274
|
+
## Spec formats
|
|
335
275
|
|
|
336
|
-
|
|
276
|
+
tickmarkr compiles several formats into its internal task graph. The most common:
|
|
337
277
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
tickmarkr
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
↓ [tickmarkr scope: 3-question gate]
|
|
345
|
-
↓ [clarification ≤3 blocking Qs]
|
|
346
|
-
native spec (REQ-nn → oracle → task)
|
|
347
|
-
↓ [tickmarkr compile]
|
|
348
|
-
↓ [fails without acceptance criteria]
|
|
349
|
-
task graph + routing table
|
|
350
|
-
↓ [tickmarkr run]
|
|
351
|
-
↓ [gates never trust workers]
|
|
352
|
-
merged green tasks + engagement record
|
|
353
|
-
```
|
|
278
|
+
| Format | Example |
|
|
279
|
+
|---|---|
|
|
280
|
+
| **tickmarkr native** (default) | `tickmarkr init` template with native spec marker |
|
|
281
|
+
| **Markdown PRD** | Any `.md` file with task sections and `acceptance[]` criteria |
|
|
282
|
+
| **Spec Kit** | A [Spec Kit](https://github.com/github/spec-kit) spec directory |
|
|
283
|
+
| **GSD** | A GSD `.planning/` phase plan |
|
|
354
284
|
|
|
355
|
-
|
|
285
|
+
Compile fails loudly if it cannot recognize the format or if `acceptance[]` is missing. Use
|
|
286
|
+
`--type native|prd|speckit|gsd` to force a specific format.
|
|
356
287
|
|
|
357
|
-
|
|
288
|
+
## Acceptance criteria (assertions)
|
|
358
289
|
|
|
359
|
-
|
|
360
|
-
- `oracle: test <name>` — run a named test (must exist in the task's acceptance[] context)
|
|
361
|
-
- `oracle: judge <rubric>` — EQR verdict, fail-closed, never overrides failed command/test
|
|
290
|
+
Every task requires explicit `acceptance[]` criteria; compile fails without them. Three oracle types:
|
|
362
291
|
|
|
363
|
-
|
|
292
|
+
- **`command <cmd>`** — pass if exit code 0; most reliable
|
|
293
|
+
- **`test <name>`** — run a named test from your suite (must exist)
|
|
294
|
+
- **`judge <rubric>`** — LLM verdict against your rubric; fail-closed, never overrides failed command/test
|
|
364
295
|
|
|
365
|
-
|
|
296
|
+
At runtime, the **scope gate** verifies that workers only touched files declared in the spec. If a
|
|
297
|
+
worker must change out-of-scope paths, the task pauses as a human gate — your approval is required.
|
|
366
298
|
|
|
367
|
-
## Claude Code
|
|
299
|
+
## Claude Code integration
|
|
368
300
|
|
|
369
|
-
|
|
301
|
+
If you clone this repo and use Claude Code, project skills are installed in `.claude/skills/`:
|
|
370
302
|
|
|
371
|
-
- **`/tickmarkr-loop`** —
|
|
372
|
-
- **`/tickmarkr-auto`** —
|
|
373
|
-
executor (plans → compile → dry-run → run → merge → sync-back). Bindings are per-SDD by nature — the
|
|
374
|
-
sync-back writes that methodology's artifacts; other SDDs need their own thin binding, not this one.
|
|
375
|
-
- **`/tickmarkr-overseer`** — supervise an autonomous tickmarkr engagement from a Herdr workspace: two-tier visible hierarchy,
|
|
376
|
-
journal-based watchers, gate-failure triage, human-checkpoint routing. Requires `HERDR_ENV=1`.
|
|
303
|
+
- **`/tickmarkr-loop`** — compile a spec, review the routing plan, run the engagement, and commit the Markdown record
|
|
304
|
+
- **`/tickmarkr-auto`** — autonomous multi-phase runs (GSD milestones, etc.)
|
|
377
305
|
|
|
378
|
-
|
|
306
|
+
These are optional — the CLI works standalone. Skills are repo-scoped and ship in the npm tarball for agents working in projects that have run `tickmarkr init --agent`.
|
|
379
307
|
|
|
380
308
|
## Contributing
|
|
381
309
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
```bash
|
|
385
|
-
git clone <your fork>
|
|
386
|
-
cd tickmarkr
|
|
387
|
-
npm install
|
|
388
|
-
npm run build # tsc → dist/; the only enforced static check
|
|
389
|
-
npm link # exposes the tickmarkr/tkr bins from your checkout
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Tests are zero-token by design
|
|
393
|
-
|
|
394
|
-
```bash
|
|
395
|
-
npm test # vitest unit+integration — fake adapter only, spends no tokens, ever
|
|
396
|
-
npm run test:coverage # same suite, coverage floors enforced (see below)
|
|
397
|
-
npm run e2e # opt-in real-CLI end-to-end — DOES spend tokens, needs ≥1 agent CLI installed
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
`npm test` and `npm run test:coverage` must never call a real agent CLI or spend tokens —
|
|
401
|
-
test isolation uses deterministic fixtures. If a change needs a real CLI to verify, that test
|
|
402
|
-
belongs under `npm run e2e` (`tests/e2e/`), an opt-in test suite not run in CI.
|
|
403
|
-
|
|
404
|
-
Coverage floor: `src/{graph,route,gates,run}/**` must stay at lines 80% / functions 80% /
|
|
405
|
-
branches 70% — this is the CLAUDE.md invariant, enforced verbatim in `vitest.config.ts`.
|
|
406
|
-
`src/compile/**`, `src/adapters/**`, and `src/drivers/**` carry their own (higher) measured
|
|
407
|
-
thresholds in the same file.
|
|
408
|
-
|
|
409
|
-
### The spec-driven workflow — the gates are the review
|
|
410
|
-
|
|
411
|
-
Changes to tickmarkr itself land the same way tickmarkr expects consumer changes to land: write a
|
|
412
|
-
native spec (`specs/<version>-<name>.spec.md`, marked `<!-- tickmarkr:spec -->`), give every task
|
|
413
|
-
real `acceptance[]` criteria, then compile/plan/run it and let the gates (baseline, evidence, scope,
|
|
414
|
-
acceptance-judge, cross-vendor review) independently verify the diff before it merges. PRs are
|
|
415
|
-
welcome, but a PR that only "looks right" isn't the bar — a green, gated engagement is. If you're
|
|
416
|
-
proposing a non-trivial change, sketch it as a spec first; small fixes and docs can go straight to
|
|
417
|
-
a PR.
|
|
418
|
-
|
|
419
|
-
### Adding a worker adapter
|
|
420
|
-
|
|
421
|
-
Support for a new agent CLI is one new file implementing `WorkerAdapter`
|
|
422
|
-
(`src/adapters/types.ts`) — `id`, `vendor`, `probe()`, `channels()`, `headlessCommand()`,
|
|
423
|
-
`interactiveCommand()`, `invoke()`, `parse()`, plus the optional `listModels`/`resumeCommand`
|
|
424
|
-
capabilities other adapters use when the CLI supports them. Use an existing adapter as the
|
|
425
|
-
template (`src/adapters/codex.ts` is a good starting shape), then register it with one line in
|
|
426
|
-
`src/adapters/registry.ts`'s `allAdapters()` — append it, don't insert it in the middle, since
|
|
427
|
-
same-tier routing ties resolve by discovery order. No other file needs to know a new adapter
|
|
428
|
-
exists.
|
|
429
|
-
|
|
430
|
-
### Filing defects
|
|
431
|
-
|
|
432
|
-
Abnormalities found during a real engagement (not routine bugs — surprising or systemic
|
|
433
|
-
behavior) are logged as a numbered entry (`OBS-NN`) in `.planning/OBSERVATIONS.md`, with a status
|
|
434
|
-
(`OPEN` / `CLOSED-FIXED` / `CLOSED-MITIGATED`) and, once fixed, the spec/commit that closed it.
|
|
435
|
-
That file is the standing abnormality ledger for this repo — read it before assuming a rough edge
|
|
436
|
-
is new.
|
|
437
|
-
|
|
438
|
-
See the design spec in docs/superpowers/specs/.
|
|
310
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, the green bar (build/test/lint), and
|
|
311
|
+
design invariants. Pull requests welcome; non-trivial changes should include test coverage.
|
|
439
312
|
|
|
440
|
-
##
|
|
313
|
+
## Documentation
|
|
441
314
|
|
|
442
|
-
MIT
|
|
315
|
+
- **[LICENSE](LICENSE)** — MIT license
|
|
316
|
+
- **[CONTRIBUTING.md](CONTRIBUTING.md)** — development setup and contribution guidelines
|
|
317
|
+
- **[SECURITY.md](SECURITY.md)** — security policy and private vulnerability reporting
|
|
318
|
+
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** — Contributor Covenant
|
|
@@ -6,7 +6,7 @@ import { parseWorkerResult } from "./prompt.js";
|
|
|
6
6
|
import { channelsFromConfig, shq, TokenUsageSchema } from "./types.js";
|
|
7
7
|
// SPEND-01/SPEND-11: claude writes a per-session JSONL to ~/.claude/projects/<slug>/ where slug is the
|
|
8
8
|
// realpath'd cwd with every non-alphanumeric char replaced by "-" (verified 114/114 — 36-DIAGNOSIS.md).
|
|
9
|
-
// The old `/`-only formula missed the "." in `.
|
|
9
|
+
// The old `/`-only formula missed the "." in `.tickmarkr/worktrees/…` — ENOENT on every worktree dispatch.
|
|
10
10
|
// Each assistant message carries a per-record ISO `timestamp` and message.usage. We read it POST-HOC
|
|
11
11
|
// (never the pane, never the trailer) and sum
|
|
12
12
|
// tokens for records whose top-level cwd matches this task's worktree AND whose timestamp is at/after
|
|
@@ -43,9 +43,9 @@ export const claudeCode = {
|
|
|
43
43
|
channels: (cfg) => channelsFromConfig("claude-code", cfg),
|
|
44
44
|
// --strict-mcp-config --mcp-config '{"mcpServers":{}}': pin the MCP surface to empty so fresh-worktree
|
|
45
45
|
// workers/gates don't load project .mcp.json servers (herdr scrapes dialogs as idle — v1.4 incident,
|
|
46
|
-
// memory
|
|
46
|
+
// memory tickmarkr-worker-mcp-dialog-stall). Live-verified 2026-07-10 on claude 2.1.205 (operator check):
|
|
47
47
|
// headless (-p) fully suppressed — exit 0, no dialog. Interactive TUI STILL shows the project
|
|
48
|
-
// MCP-enable dialog (project trust/enablement, not config loading); Esc dismisses it, and
|
|
48
|
+
// MCP-enable dialog (project trust/enablement, not config loading); Esc dismisses it, and tickmarkr's
|
|
49
49
|
// blocked/idle paging surfaces the pane to the operator (same path as cursor's trust dialog).
|
|
50
50
|
// Gotchas (both bit the 2026-07-10 live check): bare '{}' is REJECTED ("mcpServers: expected record"),
|
|
51
51
|
// and --mcp-config is VARIADIC — a positional after it is eaten as a config-file path, so another
|
|
@@ -53,7 +53,7 @@ export const claudeCode = {
|
|
|
53
53
|
headlessCommand: (promptFile, model) => `claude -p "$(cat ${shq(promptFile)})" --model ${shq(model)} --permission-mode bypassPermissions --strict-mcp-config --mcp-config '{"mcpServers":{}}' --output-format text`,
|
|
54
54
|
// HYG-03: the residual first-entry dialog on an interactive TUI is the workspace TRUST dialog (not MCP
|
|
55
55
|
// config loading) — CLI-imposed, no flag to pre-accept, only store is claude's global last-writer-wins
|
|
56
|
-
// ~/.claude.json keyed on the exact path. Closed WON'T-FIX (decision B, 2026-07-10):
|
|
56
|
+
// ~/.claude.json keyed on the exact path. Closed WON'T-FIX (decision B, 2026-07-10): tickmarkr writes nothing
|
|
57
57
|
// to that file (a seed races claude's own writes, nondeterministically). Amortizes to one operator dismissal
|
|
58
58
|
// per stable worktree path; blocked-pane paging surfaces it. Do NOT change this command to "fix" the dialog —
|
|
59
59
|
// see .planning/REQUIREMENTS.md HYG-03 and 21-02-LIVE-CHECK.md. Revisit if upstream ships a --trust flag.
|
package/dist/adapters/codex.js
CHANGED
|
@@ -75,7 +75,7 @@ export function readCodexModelsCache(path) {
|
|
|
75
75
|
return { models: [] };
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
//
|
|
78
|
+
// tickmarkr worktrees keep their gitdir under the MAIN repo's .git/worktrees/<name> — outside the
|
|
79
79
|
// workspace-write sandbox root — so git commit dies on index.lock (incident run-20260709-104447
|
|
80
80
|
// P87-08: "Unable to create …/.git/worktrees/…/index.lock"). Expanded by the pane shell at the
|
|
81
81
|
// worktree cwd; in a plain repo it resolves to ./.git (already writable, harmless).
|
package/dist/adapters/fake.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TickmarkrConfig } from "../config/config.js";
|
|
2
2
|
import type { Task } from "../graph/schema.js";
|
|
3
3
|
import { type Assignment, type AuthHealth, type BillingChannel, type ContextUsage, type Invocation, type SessionRef, type TokenUsage, type WorkerAdapter, type WorkerResult } from "./types.js";
|
|
4
4
|
export interface FakeScript {
|
|
@@ -29,7 +29,7 @@ export declare class FakeAdapter implements WorkerAdapter {
|
|
|
29
29
|
private attempts;
|
|
30
30
|
constructor(scriptPath: string);
|
|
31
31
|
probe(): Promise<AuthHealth>;
|
|
32
|
-
channels(_cfg:
|
|
32
|
+
channels(_cfg: TickmarkrConfig): BillingChannel[];
|
|
33
33
|
private judgeIdx;
|
|
34
34
|
headlessCommand(promptFile: string, _model: string): string;
|
|
35
35
|
resumeCommand(_sessionId: string, promptFile: string, model: string): string;
|
package/dist/adapters/fake.js
CHANGED
|
@@ -22,14 +22,14 @@ export class FakeAdapter {
|
|
|
22
22
|
{ adapter: "fake", vendor: "fake-b", model: "fake-2", channel: "api", tier: "frontier" },
|
|
23
23
|
];
|
|
24
24
|
}
|
|
25
|
-
judgeIdx = 0; // Phase 47: per-instance judge verdict cursor — advances only on
|
|
25
|
+
judgeIdx = 0; // Phase 47: per-instance judge verdict cursor — advances only on TICKMARKR-JUDGE prompts
|
|
26
26
|
headlessCommand(promptFile, _model) {
|
|
27
27
|
// Phase 47 (GATE-09): detect role at COMMAND-BUILD time. runHeadless/runViaDriver write the prompt
|
|
28
28
|
// file BEFORE calling headlessCommand, so it exists here. The counter advances ONLY when the prompt
|
|
29
|
-
// contains
|
|
29
|
+
// contains TICKMARKR-JUDGE — a review/consult prompt never touches it (research Pitfall 3).
|
|
30
30
|
let isJudge = false;
|
|
31
31
|
try {
|
|
32
|
-
isJudge = /
|
|
32
|
+
isJudge = /TICKMARKR-JUDGE/.test(readFileSync(promptFile, "utf8"));
|
|
33
33
|
}
|
|
34
34
|
catch {
|
|
35
35
|
// unreadable promptFile: can't detect role; serve static values (legacy headless-call behavior)
|
|
@@ -45,7 +45,7 @@ export class FakeAdapter {
|
|
|
45
45
|
writeFileSync(p, JSON.stringify(val ?? {}, null, 1));
|
|
46
46
|
return p;
|
|
47
47
|
};
|
|
48
|
-
return `bash -c 'grep -q
|
|
48
|
+
return `bash -c 'grep -q TICKMARKR-JUDGE ${shq(promptFile)} && cat ${shq(serve("judge"))}; grep -q TICKMARKR-REVIEW ${shq(promptFile)} && cat ${shq(serve("review"))}; grep -q TICKMARKR-CONSULT ${shq(promptFile)} && cat ${shq(serve("consult"))}; true'`;
|
|
49
49
|
}
|
|
50
50
|
resumeCommand(_sessionId, promptFile, model) {
|
|
51
51
|
return this.interactiveCommand(promptFile, model) ?? this.headlessCommand(promptFile, model);
|
|
@@ -61,11 +61,11 @@ export class FakeAdapter {
|
|
|
61
61
|
// no ms clock) so the record lands at/after this attempt's dispatch and clears the sinceMs cursor.
|
|
62
62
|
// Written AFTER the step's `git add`/commit ⇒ stays untracked ⇒ invisible to the scope/evidence gates.
|
|
63
63
|
const usageWrite = step.usage
|
|
64
|
-
? `; node -e ${shq(`require("fs").writeFileSync(".
|
|
64
|
+
? `; node -e ${shq(`require("fs").writeFileSync(".tickmarkr-usage.json", JSON.stringify({timestamp:new Date().toISOString(), usage:${JSON.stringify(step.usage)}}))`)}`
|
|
65
65
|
: "";
|
|
66
66
|
const base = !step.result
|
|
67
67
|
? `bash -c ${shq(step.shell)}` // no trailer: scripted stall/quota
|
|
68
|
-
: `bash -c ${shq(step.shell)}; echo ${shq(`
|
|
68
|
+
: `bash -c ${shq(step.shell)}; echo ${shq(`TICKMARKR_RESULT_${nonce} ` + JSON.stringify({ deviations: [], ...step.result }))}`;
|
|
69
69
|
return base + usageWrite;
|
|
70
70
|
}
|
|
71
71
|
// SPEND-01: read the cwd-keyed usage record from DISK (never a pane), honoring the attempt cursor.
|
|
@@ -73,7 +73,7 @@ export class FakeAdapter {
|
|
|
73
73
|
// undefined ⇒ unmetered — never 0, never a thrown error that could fail a healthy task.
|
|
74
74
|
collectUsage(cwd, sinceMs) {
|
|
75
75
|
try {
|
|
76
|
-
const rec = JSON.parse(readFileSync(join(cwd, ".
|
|
76
|
+
const rec = JSON.parse(readFileSync(join(cwd, ".tickmarkr-usage.json"), "utf8"));
|
|
77
77
|
const ts = Date.parse(rec?.timestamp);
|
|
78
78
|
if (!Number.isFinite(ts) || ts < sinceMs)
|
|
79
79
|
return undefined; // cursor honored, fail-open
|
|
@@ -93,7 +93,7 @@ export class FakeAdapter {
|
|
|
93
93
|
// parseWorkerResult(output, nonce) succeeds exactly as before the nonce existed
|
|
94
94
|
nonceFor(promptFile) {
|
|
95
95
|
try {
|
|
96
|
-
return /
|
|
96
|
+
return /TICKMARKR_RESULT_([0-9a-z]+)/.exec(readFileSync(promptFile, "utf8"))?.[1] ?? "";
|
|
97
97
|
}
|
|
98
98
|
catch {
|
|
99
99
|
return "";
|
package/dist/adapters/grok.js
CHANGED
|
@@ -19,11 +19,11 @@ import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
|
19
19
|
// (4) `grok trace <id> --local` export and ~/.grok/logs/unified.jsonl (1,447 lines) — zero counters.
|
|
20
20
|
// (5) --debug-file traces DO carry real per-turn input_tokens=… output_tokens=… cache_read_tokens=…
|
|
21
21
|
// — a REAL source, REJECTED for stated reasons:
|
|
22
|
-
// (a) it exists ONLY because
|
|
23
|
-
//
|
|
22
|
+
// (a) it exists ONLY because tickmarkr would inject --debug-file into every dispatch — a side-channel
|
|
23
|
+
// tickmarkr creates, not a record the CLI keeps (same class as cursor's rejected --print --trust
|
|
24
24
|
// stdout tee, cursor-agent.ts:25-28);
|
|
25
25
|
// (b) DECISIVE: at DEBUG level the log verbatim-dumps the full OIDC bearer JWT and every MCP server
|
|
26
|
-
// env secret — an actual plaintext SUPABASE_ACCESS_TOKEN was observed —
|
|
26
|
+
// env secret — an actual plaintext SUPABASE_ACCESS_TOKEN was observed — tickmarkr forcing a
|
|
27
27
|
// per-attempt secret-spilling file onto disk to meter a sub channel fails its own security
|
|
28
28
|
// posture;
|
|
29
29
|
// (c) it is tracing diagnostics text with no format contract (xAI can reshape it in any patch).
|
|
@@ -102,11 +102,11 @@ export const grok = {
|
|
|
102
102
|
// 2026-07-11 (40-RESEARCH F-3): trailer intact + unwrapped, exit 0. "$(cat file)" matches every
|
|
103
103
|
// other adapter and is already quoting-proven. --permission-mode bypassPermissions is the
|
|
104
104
|
// autonomous-worker analog of claude's --dangerously-skip-permissions. NEVER pass grok's
|
|
105
|
-
// -w/--worktree flag — grok manages its OWN worktrees; a nested worktree inside
|
|
105
|
+
// -w/--worktree flag — grok manages its OWN worktrees; a nested worktree inside tickmarkr's worktree
|
|
106
106
|
// is scope-gate chaos (src/run/git.ts owns worktrees).
|
|
107
107
|
headlessCommand: (promptFile, model) => `grok -p "$(cat ${shq(promptFile)})" --model ${shq(model)} --permission-mode bypassPermissions --output-format plain`,
|
|
108
108
|
// GROK-02 interactive. Positional prompt seeds the TUI (40-CONTEXT D-01). Same permission flag;
|
|
109
|
-
// NEVER the -w/--worktree flag (see headless comment above —
|
|
109
|
+
// NEVER the -w/--worktree flag (see headless comment above — tickmarkr owns worktrees).
|
|
110
110
|
interactiveCommand: (promptFile, model) => `grok --model ${shq(model)} --permission-mode bypassPermissions "$(cat ${shq(promptFile)})"`,
|
|
111
111
|
invoke(task, _cwd, a, ctx) {
|
|
112
112
|
return { command: this.headlessCommand(ctx.promptFile, a.model) };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type TickmarkrConfig } from "../config/config.js";
|
|
2
2
|
import { type AuthHealth, type WorkerAdapter } from "./types.js";
|
|
3
3
|
export declare const SEED_STAMPED = "2026-07-09";
|
|
4
4
|
export declare const MODEL_STALE_DAYS = 30;
|
|
5
5
|
export declare const ttyVisual: () => boolean;
|
|
6
|
-
export declare function seedPreferLints(cfg:
|
|
7
|
-
export declare function modelLints(cfg:
|
|
6
|
+
export declare function seedPreferLints(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], overlayPreferShapes?: ReadonlySet<string>): string[];
|
|
7
|
+
export declare function modelLints(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], opts?: {
|
|
8
8
|
tty?: boolean;
|
|
9
9
|
stateDir?: string;
|
|
10
10
|
overlayPreferShapes?: ReadonlySet<string>;
|
|
@@ -14,4 +14,4 @@ export declare function formatModelAuthLine(excluded: {
|
|
|
14
14
|
reason: string;
|
|
15
15
|
probedAt: string;
|
|
16
16
|
}[], tty?: boolean, stateDir?: string): string;
|
|
17
|
-
export declare function suggestOverlay(cfg:
|
|
17
|
+
export declare function suggestOverlay(cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], stateDir?: string): string;
|
|
@@ -11,7 +11,7 @@ const DAY_MS = 86400000;
|
|
|
11
11
|
const LINT_VARIANT_RE = /^auto$|-(fast|minimal|low|medium|high|xhigh)$/;
|
|
12
12
|
const LINT_CAP = 5;
|
|
13
13
|
const TTY_LINT_CAP = 3;
|
|
14
|
-
const DEFAULT_STATE_DIR = ".
|
|
14
|
+
const DEFAULT_STATE_DIR = ".tickmarkr";
|
|
15
15
|
const doctorJsonRef = (stateDir) => ` — see ${stateDir}/doctor.json`;
|
|
16
16
|
export const ttyVisual = () => process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
17
17
|
const adapterHasAuthedChannel = (adapterId, shape, cfg, health, adapters) => {
|