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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -207
  3. package/dist/adapters/claude-code.js +4 -4
  4. package/dist/adapters/codex.js +1 -1
  5. package/dist/adapters/fake.d.ts +2 -2
  6. package/dist/adapters/fake.js +8 -8
  7. package/dist/adapters/grok.js +5 -5
  8. package/dist/adapters/model-lints.d.ts +4 -4
  9. package/dist/adapters/model-lints.js +1 -1
  10. package/dist/adapters/prompt.js +8 -13
  11. package/dist/adapters/registry.d.ts +8 -8
  12. package/dist/adapters/registry.js +5 -5
  13. package/dist/adapters/types.d.ts +3 -3
  14. package/dist/adapters/types.js +1 -1
  15. package/dist/brand.d.ts +2 -0
  16. package/dist/brand.js +17 -0
  17. package/dist/cli/commands/approve.js +3 -3
  18. package/dist/cli/commands/compile.js +1 -1
  19. package/dist/cli/commands/doctor.js +3 -3
  20. package/dist/cli/commands/init.js +14 -9
  21. package/dist/cli/commands/profile.js +6 -6
  22. package/dist/cli/commands/status.js +2 -1
  23. package/dist/cli/commands/unlock.js +1 -1
  24. package/dist/cli/index.d.ts +1 -1
  25. package/dist/cli/index.js +9 -7
  26. package/dist/compile/gsd.js +1 -1
  27. package/dist/compile/index.js +9 -3
  28. package/dist/compile/native.d.ts +2 -0
  29. package/dist/compile/native.js +4 -2
  30. package/dist/config/config.d.ts +6 -6
  31. package/dist/config/config.js +13 -12
  32. package/dist/drivers/herdr.js +5 -5
  33. package/dist/drivers/index.d.ts +2 -2
  34. package/dist/drivers/subprocess.js +3 -3
  35. package/dist/drivers/types.js +4 -6
  36. package/dist/gates/acceptance.js +1 -1
  37. package/dist/gates/baseline.d.ts +2 -2
  38. package/dist/gates/llm.js +12 -9
  39. package/dist/gates/review.d.ts +2 -2
  40. package/dist/gates/review.js +1 -1
  41. package/dist/gates/run-gates.d.ts +2 -2
  42. package/dist/graph/graph.d.ts +2 -2
  43. package/dist/graph/graph.js +4 -16
  44. package/dist/plan/prompt.js +1 -1
  45. package/dist/plan/scope.d.ts +2 -2
  46. package/dist/plan/scope.js +5 -5
  47. package/dist/route/preference.d.ts +4 -4
  48. package/dist/route/router.d.ts +3 -3
  49. package/dist/run/consult.d.ts +2 -2
  50. package/dist/run/consult.js +5 -5
  51. package/dist/run/daemon.d.ts +2 -0
  52. package/dist/run/daemon.js +48 -17
  53. package/dist/run/git.d.ts +1 -1
  54. package/dist/run/git.js +5 -9
  55. package/dist/run/journal.d.ts +2 -2
  56. package/dist/run/journal.js +9 -5
  57. package/dist/run/lock.js +11 -11
  58. package/dist/run/merge.d.ts +11 -2
  59. package/dist/run/merge.js +21 -3
  60. package/dist/run/reconcile.js +1 -1
  61. package/package.json +4 -2
  62. package/skills/tickmarkr-auto/SKILL.md +1 -1
  63. 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 framed as an external
6
- audit engagement. You scope assertions (acceptance criteria); the engine earns tickmarks (passed
7
- gates) only after independent verification. An **engagement** (`tickmarkr run`) compiles a spec
8
- into a task graph, routes work to the best installed agent CLI (claude-code, codex, cursor-agent,
9
- opencode, grok, pi) by cost within capability floors, dispatches **field teams** in isolated git
10
- worktrees as real visible TUIs when running under [herdr](https://herdr.dev), headless
11
- subprocesses otherwise and records every event in the **audit trail** (engagement state directory).
12
- Green tasks consolidate onto a `tickmarkr/<runId>` **consolidation** branch; **sign-off** (merge to
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 (resume of older
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 # the native spec template
95
- tickmarkr compile tickmarkr.spec.md # spec → engagement graph (fails without acceptance criteria)
96
- tickmarkr plan # dry-run routing table + cost estimate + floor lints
97
- tickmarkr run # execute the graph (--concurrency N --driver herdr|subprocess --route-strict)
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 whole loop: `init` scaffolds, you write the spec's tasks and `acceptance[]`, `compile`
102
- turns it into a task graph, `plan` shows you the routing/cost dry run, `run` dispatches field teams
103
- and gates every result, and `report` writes the engagement record. Nothing merges to your mainline
104
- without your sign-off.
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
- Supervise and finish:
97
+ ## Monitor and supervise
130
98
 
131
99
  ```bash
132
- tickmarkr status # live engagement state (--watch to follow)
133
- tickmarkr resume <runId> # continue an engagement from its audit trail
134
- tickmarkr approve <runId> <taskId> # Partner sign-off on a parked task (--by <name> --reason <text>)
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 the engagement's `tickmarkr/<runId>` consolidation branch; sign-off to your
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
- - **Channel-level reroute**: exclude a single channel (e.g., `claude-code:opus-4-8`) and try the next ranked channel
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
- Both exclusion types persist across resume a task that failed due to a consult reroute will never re-try excluded channels
155
- or adapters in a subsequent `tickmarkr resume`.
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-cap parks** (attempt budget exhausted):
124
+ **Attempt-budget exhaustion**:
166
125
  - The task has burned its full attempt budget without reaching a conclusive result
167
- - All channels (including adapter exclusions from prior consults) remain excluded from retry
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 `pi:glm-5.2` and `pi:anthropic-claude-opus-4-8`, but not all are authed)
182
- - **Scoped models**: Listed models with an explicit CLI namespace prefix (e.g., `pi:glm-5.2`, `openai:gpt-4o`). The design rule is to classify scoped models primarily, not unscoped listed models
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 # stays in tiers, never routes
206
- - openai:gpt-4o # reason in git commit
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 narration
163
+ ## Run output and audit trail
211
164
 
212
- When you execute `tickmarkr run`, the daemon streams live narration of each audit-trail event to stdout — task status changes,
213
- gate results (tickmarks), worker dispatch, and merge commits. This narration is derived from the append-only event ledger,
214
- which remains the authoritative source of truth for resumability. If an engagement is interrupted, `tickmarkr resume <runId>`
215
- replays the ledger and picks up where it left off. You can also tail the ledger directly for raw event inspection or
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 fleet UX
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
- When running under [herdr](https://herdr.dev), tickmarkr creates a visible, labeled pane-and-tab workspace:
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 along with their emptied tabs. Any pane not matching the ownership contract is **foreign** — created by the operator or another tool — and is never a candidate for closing.
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-trust pre-flight
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
- ## Spec front-ends (what compiles today)
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 is SDD-agnostic downstream of `compile` routing, gates, and merges never know which methodology
326
- produced the graph. Upstream, these front-ends exist:
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
- | Spec source | Support | How |
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
- Compile fails loudly on anything it can't recognize (`--type native|speckit|prd|gsd` to force).
276
+ tickmarkr compiles several formats into its internal task graph. The most common:
337
277
 
338
- ## Planning layer: intent → spec → engagement
339
-
340
- tickmarkr pairs a lightweight planning step (`tickmarkr scope`) with the existing compile/run flow. The flow is:
341
-
342
- ```
343
- intent file (prose)
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
- **Clarification gate**: The `tickmarkr scope` CLI enforces ≤3 blocking questions. Drafting refuses until all are answered; residual uncertainty becomes explicit assumptions in the spec.
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
- **Oracle types** assertions are typed for deterministic verification:
288
+ ## Acceptance criteria (assertions)
358
289
 
359
- - `oracle: command <cmd>` exit code (0 = pass); most reliable
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
- Untyped acceptance strings in existing specs compile as `oracle: judge` + warning (read-old/write-new compat, same as the rename precedent).
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
- **Scope semantics**: Plan-time blast radius is advisory + structured `{paths, confidence, reason}` — never authoritative. The run-time scope gate verifies actual file changes; if a worker requests widening, the daemon pauses as a designed human gate for operator approval.
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 skills (ship with the repo)
299
+ ## Claude Code integration
368
300
 
369
- Cloning this repo gives Claude Code project skills under `.claude/skills/`:
301
+ If you clone this repo and use Claude Code, project skills are installed in `.claude/skills/`:
370
302
 
371
- - **`/tickmarkr-loop`** — the SDD-agnostic loop: compile one spec, read the plan, run from the journal, merge green work, and commit only the Markdown execution record beside that spec. For GSD bookkeeping, use `/tickmarkr-auto`.
372
- - **`/tickmarkr-auto`** — the **GSD binding**: run remaining GSD milestone phases autonomously with tickmarkr as the
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
- Repo-scoped: available automatically in Claude Code sessions inside this repo; not installed by npm.
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
- ### Dev setup
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
- ## License
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 `.drovr/worktrees/…` — ENOENT on every worktree dispatch.
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 drovr-worker-mcp-dialog-stall). Live-verified 2026-07-10 on claude 2.1.205 (operator check):
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 drovr's
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): drovr writes nothing
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.
@@ -75,7 +75,7 @@ export function readCodexModelsCache(path) {
75
75
  return { models: [] };
76
76
  }
77
77
  }
78
- // drovr worktrees keep their gitdir under the MAIN repo's .git/worktrees/<name> — outside the
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).
@@ -1,4 +1,4 @@
1
- import type { DrovrConfig } from "../config/config.js";
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: DrovrConfig): BillingChannel[];
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;
@@ -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 DROVR-JUDGE prompts
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 DROVR-JUDGE — a review/consult prompt never touches it (research Pitfall 3).
29
+ // contains TICKMARKR-JUDGE — a review/consult prompt never touches it (research Pitfall 3).
30
30
  let isJudge = false;
31
31
  try {
32
- isJudge = /DROVR-JUDGE/.test(readFileSync(promptFile, "utf8"));
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 DROVR-JUDGE ${shq(promptFile)} && cat ${shq(serve("judge"))}; grep -q DROVR-REVIEW ${shq(promptFile)} && cat ${shq(serve("review"))}; grep -q DROVR-CONSULT ${shq(promptFile)} && cat ${shq(serve("consult"))}; true'`;
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(".drovr-usage.json", JSON.stringify({timestamp:new Date().toISOString(), usage:${JSON.stringify(step.usage)}}))`)}`
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(`DROVR_RESULT_${nonce} ` + JSON.stringify({ deviations: [], ...step.result }))}`;
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, ".drovr-usage.json"), "utf8"));
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 /DROVR_RESULT_([0-9a-z]+)/.exec(readFileSync(promptFile, "utf8"))?.[1] ?? "";
96
+ return /TICKMARKR_RESULT_([0-9a-z]+)/.exec(readFileSync(promptFile, "utf8"))?.[1] ?? "";
97
97
  }
98
98
  catch {
99
99
  return "";
@@ -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 drovr would inject --debug-file into every dispatch — a side-channel
23
- // drovr creates, not a record the CLI keeps (same class as cursor's rejected --print --trust
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 — drovr forcing a
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 drovr's worktree
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 — drovr owns worktrees).
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 DrovrConfig } from "../config/config.js";
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: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], overlayPreferShapes?: ReadonlySet<string>): string[];
7
- export declare function modelLints(cfg: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], opts?: {
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: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], stateDir?: string): string;
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 = ".drovr";
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) => {