tickmarkr 1.30.0 → 1.33.1

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/README.md CHANGED
@@ -13,8 +13,7 @@ Green tasks consolidate onto a `tickmarkr/<runId>` **consolidation** branch; **s
13
13
  your mainline) is always the Partner's call, never the engine's. A fully green engagement is an
14
14
  **unqualified opinion** — every assertion independently ticked.
15
15
 
16
- > The package ships `tickmarkr` as the primary bin, plus `drovr` and a `drover` bin alias for
17
- > backward compatibility — all three invoke the same entry. New installs should call `tickmarkr`.
16
+ > The package ships `tickmarkr` and `tkr` only; `tkr` is the short alias.
18
17
 
19
18
  ## Vocabulary
20
19
 
@@ -53,34 +52,76 @@ These are law; the codebase fails closed around them:
53
52
  - worker/judge/review/consult prompts end with machine-parseable trailers
54
53
  (`DROVR_RESULT` / JSON verdicts); parse defensively, fail closed
55
54
 
56
- ## Install
55
+ Any agent (or human) operating this repo must respect the same five rules above — they are not
56
+ merely internal implementation details, they are the contract the gates enforce.
57
57
 
58
- Requirements: Node ≥ 20, git, and at least one agent CLI on PATH (`claude`, `codex`, `cursor-agent`,
59
- `opencode`, `grok`, or `pi`) authenticated through its own login.
58
+ ## Install
60
59
 
61
60
  ```bash
62
- npm install
63
- npm run build
64
- npm link # exposes the `tickmarkr` bin (and the `drovr` / `drover` compat alias)
61
+ npm i -g tickmarkr
65
62
  ```
66
63
 
64
+ This installs two identical bins: `tickmarkr` and its short alias `tkr` — use whichever you
65
+ prefer, every example below works with either.
66
+
67
+ **Requirements:**
68
+
69
+ - **macOS or Linux.** Every command shells out through `bash`, so native Windows is not
70
+ supported — use WSL (untested).
71
+ - Node ≥ 20 and `git` on PATH.
72
+ - At least one agent CLI on PATH (`claude`, `codex`, `cursor-agent`, `opencode`, `grok`, or
73
+ `pi`), authenticated through its own login. tickmarkr never handles vendor API keys itself.
74
+
67
75
  Then verify your fleet:
68
76
 
69
77
  ```bash
70
78
  tickmarkr doctor # probes installed adapters, herdr, auth; prints the capability matrix
71
79
  ```
72
80
 
73
- ## Quickstart
81
+ **Expect `doctor` (and `init`, which runs it) to take a while on first run:** auth detection is
82
+ one real, short LLM call per configured model per installed CLI — honest, but not instant. A
83
+ machine with three CLIs and several models each can take a minute or more; each probe is capped
84
+ at 30s.
85
+
86
+ ## Quickstart (5 minutes)
74
87
 
75
88
  ```bash
76
- tickmarkr init # guided setup + doctor; scaffolds .drovr/config.yaml and drovr.spec.md
77
- # edit drovr.spec.md # the native spec template, marked <!-- drovr:spec -->
78
- tickmarkr compile drovr.spec.md # spec → .drovr/graph.json (fails without acceptance criteria)
89
+ tickmarkr init # guided setup + doctor; scaffolds .drovr/config.yaml and tickmarkr.spec.md
90
+ # edit tickmarkr.spec.md # the native spec template, marked <!-- drovr:spec -->
91
+ tickmarkr compile tickmarkr.spec.md # spec → .drovr/graph.json (fails without acceptance criteria)
79
92
  tickmarkr plan # dry-run routing table + cost estimate + floor lints
80
93
  tickmarkr run # execute the graph (--concurrency N --driver herdr|subprocess --route-strict)
81
94
  tickmarkr report <runId> --md # engagement record in Markdown
82
95
  ```
83
96
 
97
+ That's the whole loop: `init` scaffolds, you write the spec's tasks and `acceptance[]`, `compile`
98
+ turns it into a task graph, `plan` shows you the routing/cost dry run, `run` dispatches field teams
99
+ and gates every result, and `report` writes the engagement record. Nothing merges to your mainline
100
+ without your sign-off.
101
+
102
+ ## Agent-ready repos: `tickmarkr init --agent`
103
+
104
+ `tickmarkr init --agent` composes with the base `init` above and additionally installs the
105
+ consumer-facing skills (`tickmarkr-loop`, `tickmarkr-auto`) that ship in the npm tarball, so a
106
+ coding agent working in your repo knows how to drive tickmarkr without reading its source:
107
+
108
+ ```bash
109
+ tickmarkr init --agent # installs .claude/skills/tickmarkr-{loop,auto}, offers CLAUDE.md/AGENTS.md notes
110
+ tickmarkr init --agent --force # also overwrite skill files that already exist
111
+ tickmarkr init --agent --docs # also append the agent-docs section without an interactive prompt
112
+ ```
113
+
114
+ Consent rules — every write is additive, never destructive:
115
+
116
+ - an existing `.claude/skills/tickmarkr-{loop,auto}/SKILL.md` is left untouched unless you answer
117
+ yes to a per-file prompt, or pass `--force`
118
+ - a short "tickmarkr" section (the loop commands + the invariants above) is appended to `CLAUDE.md`
119
+ — or `AGENTS.md` if that exists and `CLAUDE.md` doesn't — only after you say yes, or pass `--docs`;
120
+ it's wrapped in `<!-- tickmarkr:agent-docs begin/end -->` markers and never inserted twice
121
+ - declining a prompt still lets the rest of `init` complete
122
+ - non-interactive shells (no TTY) never prompt: missing skills are installed, everything else is
123
+ skipped, and the summary names exactly what was skipped and which flag would enable it
124
+
84
125
  Supervise and finish:
85
126
 
86
127
  ```bash
@@ -282,11 +323,11 @@ produced the graph. Upstream, these front-ends exist:
282
323
 
283
324
  | Spec source | Support | How |
284
325
  |---|---|---|
285
- | **drovr spec** (native format) | Native/default | `tickmarkr init` template with the `<!-- drovr:spec -->` marker |
326
+ | **tickmarkr spec** (native format) | Native/default | `tickmarkr init` template with the `<!-- drovr:spec -->` marker |
286
327
  | **Spec Kit** feature dir | Native | dir containing `tasks.md` |
287
328
  | **GSD** phase dir | Native | dir containing `*-PLAN.md` |
288
329
  | **Markdown PRD** | Native (universal adapter) | any `.md` with tasks + acceptance criteria |
289
- | OpenSpec, BMAD, Superpowers, others | Not yet | render to a drovr spec (the `<!-- drovr:spec -->` marker plus the full Task surface), or contribute a front-end: one parser module behind `src/compile/index.ts`, same pattern as `gsd.ts` |
330
+ | OpenSpec, BMAD, Superpowers, others | Not yet | render to a tickmarkr spec (the `<!-- drovr:spec -->` marker plus the full Task surface), or contribute a front-end: one parser module behind `src/compile/index.ts`, same pattern as `gsd.ts` |
290
331
 
291
332
  Compile fails loudly on anything it can't recognize (`--type native|speckit|prd|gsd` to force).
292
333
 
@@ -323,8 +364,8 @@ Untyped acceptance strings in existing specs compile as `oracle: judge` + warnin
323
364
 
324
365
  Cloning this repo gives Claude Code project skills under `.claude/skills/`:
325
366
 
326
- - **`/drovr-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 `/drovr-auto`.
327
- - **`/drovr-auto`** — the **GSD binding**: run remaining GSD milestone phases autonomously with tickmarkr as the
367
+ - **`/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`.
368
+ - **`/tickmarkr-auto`** — the **GSD binding**: run remaining GSD milestone phases autonomously with tickmarkr as the
328
369
  executor (plans → compile → dry-run → run → merge → sync-back). Bindings are per-SDD by nature — the
329
370
  sync-back writes that methodology's artifacts; other SDDs need their own thin binding, not this one.
330
371
  - **`/overseer`** — supervise an autonomous tickmarkr engagement from a Herdr workspace: two-tier visible hierarchy,
@@ -332,14 +373,65 @@ Cloning this repo gives Claude Code project skills under `.claude/skills/`:
332
373
 
333
374
  Repo-scoped: available automatically in Claude Code sessions inside this repo; not installed by npm.
334
375
 
335
- ## Development
376
+ ## Contributing
377
+
378
+ ### Dev setup
336
379
 
337
380
  ```bash
338
- npm test # vitest unit+integration — zero tokens, fake adapter only
339
- npm run test:coverage # coverage floors enforced on core dirs
340
- npm run e2e # real-CLI end-to-end (spends tokens; needs ≥1 agent CLI)
381
+ git clone <your fork>
382
+ cd tickmarkr
383
+ npm install
384
+ npm run build # tsc → dist/; the only enforced static check
385
+ npm link # exposes the tickmarkr/tkr bins from your checkout
341
386
  ```
342
387
 
388
+ ### Tests are zero-token by design
389
+
390
+ ```bash
391
+ npm test # vitest unit+integration — fake adapter only, spends no tokens, ever
392
+ npm run test:coverage # same suite, coverage floors enforced (see below)
393
+ npm run e2e # opt-in real-CLI end-to-end — DOES spend tokens, needs ≥1 agent CLI installed
394
+ ```
395
+
396
+ `npm test` and `npm run test:coverage` must never call a real agent CLI or spend tokens — that's
397
+ what `DROVR_FAKE_SCRIPT` and the `FakeAdapter` (`src/adapters/fake.ts`) exist for. If a change
398
+ needs a real CLI to verify, that test belongs under `npm run e2e` (`tests/e2e/`), gated behind
399
+ `DROVR_E2E=1`, never in the default suite.
400
+
401
+ Coverage floor: `src/{graph,route,gates,run}/**` must stay at lines 80% / functions 80% /
402
+ branches 70% — this is the CLAUDE.md invariant, enforced verbatim in `vitest.config.ts`.
403
+ `src/compile/**`, `src/adapters/**`, and `src/drivers/**` carry their own (higher) measured
404
+ thresholds in the same file.
405
+
406
+ ### The spec-driven workflow — the gates are the review
407
+
408
+ Changes to tickmarkr itself land the same way tickmarkr expects consumer changes to land: write a
409
+ native spec (`specs/<version>-<name>.spec.md`, marked `<!-- tickmarkr:spec -->`), give every task
410
+ real `acceptance[]` criteria, then compile/plan/run it and let the gates (baseline, evidence, scope,
411
+ acceptance-judge, cross-vendor review) independently verify the diff before it merges. PRs are
412
+ welcome, but a PR that only "looks right" isn't the bar — a green, gated engagement is. If you're
413
+ proposing a non-trivial change, sketch it as a spec first; small fixes and docs can go straight to
414
+ a PR.
415
+
416
+ ### Adding a worker adapter
417
+
418
+ Support for a new agent CLI is one new file implementing `WorkerAdapter`
419
+ (`src/adapters/types.ts`) — `id`, `vendor`, `probe()`, `channels()`, `headlessCommand()`,
420
+ `interactiveCommand()`, `invoke()`, `parse()`, plus the optional `listModels`/`resumeCommand`
421
+ capabilities other adapters use when the CLI supports them. Use an existing adapter as the
422
+ template (`src/adapters/codex.ts` is a good starting shape), then register it with one line in
423
+ `src/adapters/registry.ts`'s `allAdapters()` — append it, don't insert it in the middle, since
424
+ same-tier routing ties resolve by discovery order. No other file needs to know a new adapter
425
+ exists.
426
+
427
+ ### Filing defects
428
+
429
+ Abnormalities found during a real engagement (not routine bugs — surprising or systemic
430
+ behavior) are logged as a numbered entry (`OBS-NN`) in `.planning/OBSERVATIONS.md`, with a status
431
+ (`OPEN` / `CLOSED-FIXED` / `CLOSED-MITIGATED`) and, once fixed, the spec/commit that closed it.
432
+ That file is the standing abnormality ledger for this repo — read it before assuming a rough edge
433
+ is new.
434
+
343
435
  Design spec: `docs/superpowers/specs/2026-07-07-drover-design.md`.
344
436
 
345
437
  ## License
@@ -49,7 +49,7 @@ export const cursorAgent = {
49
49
  // print-only). Doctor names the dialog; the daemon auto-answers it via trustDialog once per slot.
50
50
  trust: () => ({
51
51
  status: "action-required",
52
- command: 'accept the cursor-agent "Workspace Trust Required" dialog (Enter) — drovr auto-answers once per slot',
52
+ command: 'accept the cursor-agent "Workspace Trust Required" dialog (Enter) — tickmarkr auto-answers once per slot',
53
53
  }),
54
54
  trustDialog: CURSOR_TRUST_DIALOG,
55
55
  // v1.5 MODEL-01: fail OPEN to [] (advisory detection, unlike gates). --list-models may touch the
@@ -1,7 +1,7 @@
1
1
  import { MODEL_ID_RE } from "./types.js";
2
2
  // date the tiers seeds were last live-verified (Phase 6/7 reseed) — surfaced when an adapter has no list surface.
3
3
  export const SEED_STAMPED = "2026-07-09";
4
- // knowledge past this age gets a "rerun drovr doctor" nudge (BLOCKED_POLL_MS-style named constant).
4
+ // knowledge past this age gets a "rerun tickmarkr doctor" nudge (BLOCKED_POLL_MS-style named constant).
5
5
  export const MODEL_STALE_DAYS = 30;
6
6
  const DAY_MS = 86400000;
7
7
  // cursor-agent 2026.07.08 reports 193 mostly-parameterized ids (e.g. gpt-5.3-codex-high-fast); filter the `auto`
@@ -26,7 +26,7 @@ export function modelLints(cfg, health, adapters) {
26
26
  const detected = h?.models ?? []; // MANDATORY default: pre-v1.5 files lack a populated models array
27
27
  if (detected.length === 0) {
28
28
  if (h?.installed)
29
- lints.push(`${id}: no detection data — run drovr doctor`);
29
+ lints.push(`${id}: no detection data — run tickmarkr doctor`);
30
30
  continue; // no data to diff or age
31
31
  }
32
32
  const configured = Object.keys(cfg.tiers[id].models);
@@ -45,7 +45,7 @@ export function modelLints(cfg, health, adapters) {
45
45
  if (at) {
46
46
  const days = Math.floor((Date.now() - Date.parse(at)) / DAY_MS); // completed days — never overstate age
47
47
  if (days >= MODEL_STALE_DAYS)
48
- lints.push(`${id}: model knowledge is ${days} days old — rerun drovr doctor`);
48
+ lints.push(`${id}: model knowledge is ${days} days old — rerun tickmarkr doctor`);
49
49
  }
50
50
  }
51
51
  return lints;
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { renderAcceptanceItem } from "../graph/schema.js";
4
4
  export function buildTaskPrompt(task, feedback = "", nonce = "") {
5
5
  const list = (xs) => xs.map((x) => `- ${x}`).join("\n");
6
- return `You are an autonomous coding worker dispatched by drovr into an isolated git worktree.
6
+ return `You are an autonomous coding worker dispatched by tickmarkr into an isolated git worktree.
7
7
 
8
8
  ## Task ${task.id}: ${task.title}
9
9
  Goal: ${task.goal}
@@ -40,7 +40,7 @@ export async function approve(argv, cwd = process.cwd()) {
40
40
  via: "cli",
41
41
  ...(capPark ? { release: ATTEMPT_CAP_RELEASE } : {}),
42
42
  });
43
- return `approved ${taskId} in ${runId} — by ${by}; run \`drovr resume ${runId}\` to dispatch it`;
43
+ return `approved ${taskId} in ${runId} — by ${by}; run \`tickmarkr resume ${runId}\` to dispatch it`;
44
44
  }
45
45
  // hand-parsed argv — no CLI framework (house style). Flags --by <name> and --reason <text>; positionals
46
46
  // are runId then taskId. Throws usage on missing positionals (mirrors resume.ts/unlock.ts).
@@ -53,12 +53,12 @@ function parseArgs(argv) {
53
53
  if (a === "--by") {
54
54
  by = argv[++i];
55
55
  if (!by)
56
- throw new Error("usage: drovr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
56
+ throw new Error("usage: tickmarkr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
57
57
  }
58
58
  else if (a === "--reason") {
59
59
  reason = argv[++i];
60
60
  if (!reason)
61
- throw new Error("usage: drovr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
61
+ throw new Error("usage: tickmarkr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
62
62
  }
63
63
  else {
64
64
  positionals.push(a);
@@ -66,7 +66,7 @@ function parseArgs(argv) {
66
66
  }
67
67
  const [runId, taskId] = positionals;
68
68
  if (!runId || !taskId) {
69
- throw new Error("usage: drovr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
69
+ throw new Error("usage: tickmarkr approve <run-id> <task-id> [--by <name>] [--reason <text>]");
70
70
  }
71
71
  return { runId, taskId, by: by ?? userInfo().username, reason };
72
72
  }
@@ -11,7 +11,7 @@ export async function compile(argv, cwd = process.cwd()) {
11
11
  });
12
12
  const src = positionals[0];
13
13
  if (!src)
14
- throw new Error("usage: drovr compile <spec-dir-or-md> [--type speckit|prd|gsd|native]");
14
+ throw new Error("usage: tickmarkr compile <spec-dir-or-md> [--type speckit|prd|gsd|native]");
15
15
  // resolve against the target repo, not the process cwd (the CLI test passes a tmp repo)
16
16
  const g = compileSource(isAbsolute(src) ? src : join(cwd, src), values.type, cwd);
17
17
  // HARD-01: a compile clobbering a running daemon's graph.json is the same last-write-wins race
@@ -1,10 +1,40 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { allAdapters, probeAll, probeModels, servableExclusions, servabilityLine, writeDoctor } from "../../adapters/registry.js";
4
+ import { drovrDir } from "../../graph/graph.js";
2
5
  import { modelLints, suggestOverlay } from "../../adapters/model-lints.js";
3
6
  import { loadConfig } from "../../config/config.js";
4
7
  import { HerdrDriver } from "../../drivers/herdr.js";
5
8
  import { disallowedBy, excludedChannels, exclusionLine, preferRanks } from "../../route/preference.js";
9
+ // same gate + palette as status's audit-ledger frame: styled only on a real TTY, plain otherwise
10
+ const visual = () => process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
11
+ const color = (text, code) => `\x1b[${code}m${text}\x1b[0m`;
12
+ // TTY-only line pass: color the verdict symbols, dim the chrome (section titles, parentheticals,
13
+ // n/a rows). Padding is untouched — every rewrite swaps equal-width text or wraps it in ANSI.
14
+ function stylize(out) {
15
+ if (!visual())
16
+ return out;
17
+ const rule = color("─".repeat(Math.min(process.stdout.columns ?? 80, 100)), 2);
18
+ return out.split("\n").map((line) => {
19
+ if (line.startsWith("tickmarkr doctor"))
20
+ return ` ${color("✓", 32)} ${color("tickmarkr", 1)} doctor ${color("· capability matrix", 2)}\n${rule}`;
21
+ if (/^(workspace trust|model status|model drift)/.test(line))
22
+ return color(line, 2);
23
+ return line
24
+ .replace(/^(\s+)✓ /, (_, s) => `${s}${color("✓", 32)} `)
25
+ .replace(/^(\s+)✗ /, (_, s) => `${s}${color("✗", 31)} `)
26
+ .replace(/^(\s+)! /, (_, s) => `${s}${color("!", 33)} `)
27
+ .replace(/^(\s+)= (.*)$/, (_, s, rest) => `${s}${color(`= ${rest}`, 2)}`)
28
+ .replace(/(\(auth assumed; verified at dispatch \(failover on auth\/quota errors\)\))/, (m) => color(m, 2))
29
+ .replace(/\bauthed\b(?!:)/, color("authed", 32))
30
+ .replace(/\bunauthed:/, color("unauthed:", 33));
31
+ }).join("\n");
32
+ }
6
33
  export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters()) {
7
34
  const cfg = loadConfig(cwd);
35
+ // stderr, live: auth probes are real LLM calls (up to 30s per configured model) and the CLI
36
+ // otherwise prints nothing until the end — silence here reads as a hang (v1.33.1)
37
+ console.error("probing installed agent CLIs — one short LLM call per configured model, may take a minute...");
8
38
  const health = await probeAll(adapters);
9
39
  // MODEL-02: detect models where the adapter exposes a list surface, BEFORE writing doctor.json (write once, below).
10
40
  // Fail OPEN — the inverse of gates' fail-closed: detection is advisory, so a broken list surface NEVER fails doctor.
@@ -67,8 +97,20 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
67
97
  if (servable.length)
68
98
  rows.push(` ! ${servabilityLine(servable)}`);
69
99
  // MODEL-05/06: print-only drift fragment; advisory, whole-line-commented additions, drovr NEVER applies it.
100
+ // TTY gets a one-line summary + the fragment as a file (the full dump drowned everything else,
101
+ // v1.33.1 onboarding); machine/CI surface keeps the inline dump — layout is pinned by tests.
70
102
  const frag = suggestOverlay(cfg, health, adapters);
71
- const drift = frag ? `\nmodel drift — paste-ready overlay (advisory; drovr never applies):\n${frag}` : "";
103
+ let drift = "";
104
+ if (frag) {
105
+ if (visual()) {
106
+ const overlayPath = join(drovrDir(cwd), "doctor-overlay.yaml");
107
+ writeFileSync(overlayPath, frag);
108
+ drift = `\nmodel drift: unclassified models detected — paste-ready overlay written to ${overlayPath} (advisory; tickmarkr never applies it)`;
109
+ }
110
+ else {
111
+ drift = `\nmodel drift — paste-ready overlay (advisory; tickmarkr never applies):\n${frag}`;
112
+ }
113
+ }
72
114
  // T4: model-status table — one row per CLASSIFIED model (tiers config) with tier, auth verdict
73
115
  // (reason + date when unauthed), operator-deny flag, and prefer rank across the routing map.
74
116
  // Unclassified listed models (detected via listModels but never tiered) compress to one count line
@@ -99,5 +141,5 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
99
141
  return rows;
100
142
  });
101
143
  const modelSummary = modelStatus.length ? `\nmodel status:\n${modelStatus.join("\n")}` : "";
102
- return `drovr doctor — capability matrix:\n${rows.join("\n")}${modelSummary}${drift}\nwrote .drovr/doctor.json`;
144
+ return stylize(`tickmarkr doctor — capability matrix:\n${rows.join("\n")}${modelSummary}${drift}\nwrote .drovr/doctor.json`);
103
145
  }
@@ -1,12 +1,78 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
+ import { createInterface } from "node:readline/promises";
3
4
  import { parseArgs } from "node:util";
4
5
  import { configTemplate, globalConfigDir } from "../../config/config.js";
5
6
  import { specTemplate } from "../../compile/native.js";
6
7
  import { drovrDir } from "../../graph/graph.js";
7
8
  import { doctor } from "./doctor.js";
9
+ const AGENT_SKILLS = ["tickmarkr-loop", "tickmarkr-auto"];
10
+ const DOCS_BEGIN = "<!-- tickmarkr:agent-docs begin -->";
11
+ const DOCS_END = "<!-- tickmarkr:agent-docs end -->";
12
+ const AGENT_DOCS = `${DOCS_BEGIN}
13
+ ## tickmarkr
14
+
15
+ tickmarkr turns repository specs into isolated, independently verified agent work. Use \`/tickmarkr-loop\` for one spec or \`/tickmarkr-auto\` for several.
16
+
17
+ Loop: \`tickmarkr compile <spec>\` → \`tickmarkr plan\` → \`tickmarkr run\` → \`tickmarkr report <runId> --md\`.
18
+
19
+ - Never run two tickmarkr runs in the same repository concurrently.
20
+ - Never merge tickmarkr work directly to main; new runs consolidate on \`tickmarkr/<runId>\`.
21
+ - Fix source specs instead of editing generated graphs.
22
+ - Never trust a worker's completion claim; gates independently verify the work.
23
+ - Treat missing or unparseable results as failures.
24
+ ${DOCS_END}
25
+ `;
26
+ async function installAgentFiles(cwd, force, docs, notes) {
27
+ const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
28
+ let prompt;
29
+ const confirm = async (question) => {
30
+ if (!interactive)
31
+ return false;
32
+ prompt ??= createInterface({ input: process.stdin, output: process.stdout });
33
+ return /^(?:y|yes)$/i.test((await prompt.question(`${question} [y/N] `)).trim());
34
+ };
35
+ try {
36
+ for (const skill of AGENT_SKILLS) {
37
+ const dest = join(cwd, ".claude", "skills", skill, "SKILL.md");
38
+ const exists = existsSync(dest);
39
+ if (exists && !force && !(await confirm(`Overwrite ${dest}?`))) {
40
+ notes.push(`skipped existing ${dest}; pass --force to overwrite it`);
41
+ continue;
42
+ }
43
+ mkdirSync(join(cwd, ".claude", "skills", skill), { recursive: true });
44
+ writeFileSync(dest, readFileSync(new URL(`../../../skills/${skill}/SKILL.md`, import.meta.url)), exists ? undefined : { flag: "wx" });
45
+ notes.push(`${exists ? "overwrote" : "wrote"} ${dest}`);
46
+ }
47
+ const claude = join(cwd, "CLAUDE.md");
48
+ const agents = join(cwd, "AGENTS.md");
49
+ const docPath = existsSync(claude) || !existsSync(agents) ? claude : agents;
50
+ const current = existsSync(docPath) ? readFileSync(docPath, "utf8") : "";
51
+ if (current.includes(DOCS_BEGIN) || current.includes(DOCS_END)) {
52
+ notes.push(`kept existing tickmarkr agent docs in ${docPath}`);
53
+ }
54
+ else if (docs || await confirm(`Append tickmarkr agent docs to ${docPath}?`)) {
55
+ appendFileSync(docPath, `${current ? current.endsWith("\n") ? "\n" : "\n\n" : ""}${AGENT_DOCS}`);
56
+ notes.push(`appended tickmarkr agent docs to ${docPath}`);
57
+ }
58
+ else {
59
+ notes.push(`skipped agent docs for ${docPath}; pass --docs to append them`);
60
+ }
61
+ }
62
+ finally {
63
+ prompt?.close();
64
+ }
65
+ }
8
66
  export async function init(argv, cwd = process.cwd()) {
9
- const { values } = parseArgs({ args: argv, options: { "global-dir": { type: "string" } } });
67
+ const { values } = parseArgs({
68
+ args: argv,
69
+ options: {
70
+ "global-dir": { type: "string" },
71
+ agent: { type: "boolean" },
72
+ force: { type: "boolean" },
73
+ docs: { type: "boolean" },
74
+ },
75
+ });
10
76
  const gdir = values["global-dir"] ?? globalConfigDir();
11
77
  mkdirSync(gdir, { recursive: true });
12
78
  const notes = [];
@@ -19,14 +85,19 @@ export async function init(argv, cwd = process.cwd()) {
19
85
  notes.push(`kept existing ${p}`);
20
86
  }
21
87
  }
22
- const specPath = join(cwd, "drovr.spec.md");
23
- if (!existsSync(specPath)) {
24
- writeFileSync(specPath, specTemplate());
25
- notes.push(`wrote ${specPath}`);
88
+ const specPath = join(cwd, "tickmarkr.spec.md");
89
+ if (existsSync(specPath)) {
90
+ notes.push(`kept existing ${specPath}`);
91
+ }
92
+ else if (existsSync(join(cwd, "drovr.spec.md"))) {
93
+ notes.push(`kept existing ${join(cwd, "drovr.spec.md")}`);
26
94
  }
27
95
  else {
28
- notes.push(`kept existing ${specPath}`);
96
+ writeFileSync(specPath, specTemplate());
97
+ notes.push(`wrote ${specPath}`);
29
98
  }
99
+ if (values.agent)
100
+ await installAgentFiles(cwd, values.force ?? false, values.docs ?? false, notes);
30
101
  const doc = await doctor([], cwd);
31
- return `${notes.join("\n")}\n${doc}\nnext: edit drovr.spec.md, then drovr compile drovr.spec.md && drovr plan && drovr run`;
102
+ return `${notes.join("\n")}\n${doc}\nnext: edit tickmarkr.spec.md, then tickmarkr compile tickmarkr.spec.md && tickmarkr plan && tickmarkr run`;
32
103
  }
@@ -21,7 +21,7 @@ export async function plan(_argv, cwd = process.cwd(), adapters = allAdapters())
21
21
  // output byte-identical to today.
22
22
  const profile = loadRoutingProfile(cwd, cfg, { preview: true });
23
23
  let deviations = 0;
24
- const lines = [`drovr plan — dry run (${channels.length} channels available)`, ""];
24
+ const lines = [`tickmarkr plan — dry run (${channels.length} channels available)`, ""];
25
25
  const excluded = excludedChannels(cfg, adapters, health);
26
26
  if (excluded.length)
27
27
  lines.push(exclusionLine(excluded), "");
@@ -38,7 +38,7 @@ export async function plan(_argv, cwd = process.cwd(), adapters = allAdapters())
38
38
  if (cached) {
39
39
  const age = doctorAgeMs(cwd);
40
40
  if (age !== null && age > DOCTOR_STALE_MS) {
41
- lines.push(`doctor.json is ${Math.floor(age / 3_600_000)}h old — run 'drovr doctor' to refresh (servability/auth may have changed)`, "");
41
+ lines.push(`doctor.json is ${Math.floor(age / 3_600_000)}h old — run 'tickmarkr doctor' to refresh (servability/auth may have changed)`, "");
42
42
  }
43
43
  }
44
44
  // HYG-07(a)/T2 pin honesty: append the drop reason when a pin/miss message names a filtered channel —
@@ -32,7 +32,7 @@ function show(cwd) {
32
32
  const cursor = readProfileCursor(cwd);
33
33
  const p = loadRoutingProfile(cwd, cfg, { preview: true });
34
34
  const header = [
35
- `drovr profile`,
35
+ `tickmarkr profile`,
36
36
  ` routing.learned: ${cfg.routing.learned}${cfg.routing.learned === "off" ? " (preview — routing is inert)" : ""}`,
37
37
  ` runs window: ${RUNS_WINDOW}`,
38
38
  ...(cursor ? [` reset cursor: ${cursor}`] : []),
@@ -293,9 +293,9 @@ export async function report(argv, cwd = process.cwd()) {
293
293
  options: { md: { type: "boolean" } },
294
294
  allowPositionals: true,
295
295
  });
296
- const runId = positionals[0] ?? Journal.latestRunId(cwd);
296
+ const runId = positionals[0] ?? Journal.latestRunId(cwd, { withJournal: true });
297
297
  if (!runId)
298
- throw new Error("no runs found — usage: drovr report <run-id> [--md]");
298
+ throw new Error("no runs found — usage: tickmarkr report <run-id> [--md]");
299
299
  const j = Journal.open(cwd, runId);
300
300
  const events = j.read();
301
301
  if (values.md) {
@@ -5,7 +5,7 @@ import { formatJournalNarration } from "../../run/journal.js";
5
5
  export async function resume(argv, cwd = process.cwd()) {
6
6
  const runId = argv[0];
7
7
  if (!runId)
8
- throw new Error("usage: drovr resume <run-id>");
8
+ throw new Error("usage: tickmarkr resume <run-id>");
9
9
  const s = await runDaemon(cwd, { runId, resume: true, driver: pickDriver(loadConfig(cwd)), narrate: (event) => console.log(formatJournalNarration(event)) });
10
10
  return `resumed ${s.runId} — ${formatSummary(s)}`;
11
11
  }
@@ -10,7 +10,7 @@ export async function scope(argv, cwd = process.cwd(), adapters = allAdapters(),
10
10
  allowPositionals: true,
11
11
  });
12
12
  if (positionals.length !== 1)
13
- throw new Error("usage: drovr scope <intent-file> [--force]");
13
+ throw new Error("usage: tickmarkr scope <intent-file> [--force]");
14
14
  const source = positionals[0];
15
15
  const intentFile = isAbsolute(source) ? source : join(cwd, source);
16
16
  const result = await scopeIntent(intentFile, cwd, {
@@ -1,9 +1,5 @@
1
- import { type Task } from "../../graph/schema.js";
2
- import { type JournalEvent } from "../../run/journal.js";
3
1
  export type StatusOpts = {
4
2
  iterations?: number;
5
3
  sleep?: (ms: number) => Promise<void>;
6
4
  };
7
- /** Derive the live phase column for one task. Counts only that task's events since its last dispatch/escalation. */
8
- export declare const derivePhase: (task: Task, events: JournalEvent[], channel?: string) => string;
9
5
  export declare function status(argv: string[], cwd?: string, opts?: StatusOpts): Promise<string>;