tickmarkr 1.37.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 (58) hide show
  1. package/README.md +1 -6
  2. package/dist/adapters/claude-code.js +4 -4
  3. package/dist/adapters/codex.js +1 -1
  4. package/dist/adapters/fake.d.ts +2 -2
  5. package/dist/adapters/fake.js +8 -8
  6. package/dist/adapters/grok.js +5 -5
  7. package/dist/adapters/model-lints.d.ts +4 -4
  8. package/dist/adapters/model-lints.js +1 -1
  9. package/dist/adapters/prompt.js +8 -13
  10. package/dist/adapters/registry.d.ts +8 -8
  11. package/dist/adapters/registry.js +5 -5
  12. package/dist/adapters/types.d.ts +3 -3
  13. package/dist/brand.d.ts +2 -0
  14. package/dist/brand.js +17 -0
  15. package/dist/cli/commands/approve.js +3 -3
  16. package/dist/cli/commands/compile.js +1 -1
  17. package/dist/cli/commands/doctor.js +3 -3
  18. package/dist/cli/commands/init.js +14 -9
  19. package/dist/cli/commands/profile.js +6 -6
  20. package/dist/cli/commands/status.js +2 -1
  21. package/dist/cli/commands/unlock.js +1 -1
  22. package/dist/cli/index.d.ts +1 -2
  23. package/dist/cli/index.js +5 -15
  24. package/dist/compile/gsd.js +1 -1
  25. package/dist/compile/index.js +9 -3
  26. package/dist/compile/native.d.ts +2 -0
  27. package/dist/compile/native.js +4 -2
  28. package/dist/config/config.d.ts +6 -6
  29. package/dist/config/config.js +8 -12
  30. package/dist/drivers/herdr.js +5 -5
  31. package/dist/drivers/index.d.ts +2 -2
  32. package/dist/drivers/subprocess.js +3 -3
  33. package/dist/drivers/types.js +4 -6
  34. package/dist/gates/acceptance.js +1 -1
  35. package/dist/gates/baseline.d.ts +2 -2
  36. package/dist/gates/llm.js +12 -9
  37. package/dist/gates/review.d.ts +2 -2
  38. package/dist/gates/review.js +1 -1
  39. package/dist/gates/run-gates.d.ts +2 -2
  40. package/dist/graph/graph.d.ts +2 -2
  41. package/dist/graph/graph.js +4 -16
  42. package/dist/plan/prompt.js +1 -1
  43. package/dist/plan/scope.d.ts +2 -2
  44. package/dist/plan/scope.js +5 -5
  45. package/dist/route/preference.d.ts +4 -4
  46. package/dist/route/router.d.ts +3 -3
  47. package/dist/run/consult.d.ts +2 -2
  48. package/dist/run/consult.js +5 -5
  49. package/dist/run/daemon.js +13 -13
  50. package/dist/run/git.d.ts +1 -1
  51. package/dist/run/git.js +5 -9
  52. package/dist/run/journal.d.ts +2 -2
  53. package/dist/run/journal.js +4 -4
  54. package/dist/run/lock.js +11 -11
  55. package/dist/run/merge.d.ts +2 -2
  56. package/dist/run/merge.js +3 -3
  57. package/dist/run/reconcile.js +1 -1
  58. package/package.json +2 -2
package/README.md CHANGED
@@ -13,17 +13,12 @@ cursor-agent, opencode, grok, pi) by cost and capability, dispatches work in iso
13
13
  with full visibility into routing decisions, worker progress, and gate verdicts — or run headless
14
14
  and review the audit trail afterward.
15
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`). New specs use the `<!-- tickmarkr:spec -->` marker; the historical `<!-- drovr:spec -->` marker is still accepted. 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
16
  ## Invariants
21
17
 
22
18
  These are law; the codebase fails closed around them:
23
19
 
24
20
  - `acceptance[]` required on every task; compile fails without it
25
- - new engagements consolidate to a `tickmarkr/<runId>` branch — never main (resume of older
26
- engagements keeps the branch names recorded in their journal)
21
+ - new engagements consolidate to a `tickmarkr/<runId>` branch — never main
27
22
  - gates never trust worker claims; tickmarkr verifies everything itself
28
23
  - state is files + git only; no DB, no services
29
24
  - worker/judge/review/consult prompts end with machine-parseable trailers
@@ -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) => {
@@ -18,7 +18,7 @@ ${task.files.length ? `\n## File scope — touch ONLY paths matching:\n${list(ta
18
18
  - Do not ask questions; you are unattended. Make the smallest correct change.
19
19
  ${feedback ? `\n## Previous attempt failed gates — fix these specifically\n${feedback}\n` : ""}
20
20
  When finished, end your final message with exactly one line (no code fence):
21
- DROVR_RESULT_${nonce} {"ok":true|false,"summary":"<one sentence>","deviations":["<path or reason>"]}
21
+ TICKMARKR_RESULT_${nonce} {"ok":true|false,"summary":"<one sentence>","deviations":["<path or reason>"]}
22
22
  `;
23
23
  }
24
24
  export function writePrompt(dir, task, attempt, feedback = "", nonce = "") {
@@ -31,26 +31,21 @@ export function writePrompt(dir, task, attempt, feedback = "", nonce = "") {
31
31
  // across lines by a TUI renderer ([\s\S] bridges newlines between tokens) — but never the template
32
32
  // line above, whose literal `true|false` is not a bool followed by , or }. v1.4: the run-tagged nonce
33
33
  // anchors it so displayed source/diffs (which can't know the nonce) can never premature-harvest.
34
- // v1.16 (REN-02): dual-parse — accepts both the new marker spelling and the legacy alternation, so a
35
- // worker/fixture still emitting the old marker keeps parsing through the rename.
36
34
  export function trailerPattern(nonce) {
37
- return `(?:DROVR|DROVER)_RESULT_${nonce} \\{[\\s\\S]{0,120}?"ok":\\s*(true|false)\\s*[,}]`;
35
+ return `TICKMARKR_RESULT_${nonce} \\{[\\s\\S]{0,120}?"ok":\\s*(true|false)\\s*[,}]`;
38
36
  }
39
- // all start positions of either marker spelling, ascending — merged so "last occurrence" and
40
- // "scan backward for the first parseable one" work identically across both spellings.
41
37
  function trailerTokenPositions(raw, nonce) {
38
+ const token = `TICKMARKR_RESULT_${nonce}`;
42
39
  const positions = [];
43
- for (const token of [`DROVR_RESULT_${nonce}`, `DROVER_RESULT_${nonce}`]) {
44
- for (let i = raw.indexOf(token); i !== -1; i = raw.indexOf(token, i + 1))
45
- positions.push(i);
46
- }
47
- return positions.sort((a, b) => a - b);
40
+ for (let i = raw.indexOf(token); i !== -1; i = raw.indexOf(token, i + 1))
41
+ positions.push(i);
42
+ return positions;
48
43
  }
49
44
  export function parseWorkerResult(raw, nonce) {
50
45
  const fail = (summary) => ({ ok: false, summary, deviations: [], raw });
51
46
  const positions = trailerTokenPositions(raw, nonce);
52
47
  if (positions.length === 0)
53
- return fail("worker produced no DROVR_RESULT trailer");
48
+ return fail("worker produced no TICKMARKR_RESULT trailer");
54
49
  // TUIs echo the prompt template, redraw lines, and HARD-wrap the JSON with per-line margins
55
50
  // (cursor does; recent-unwrapped can't rejoin hard newlines). Scan occurrences backward — last
56
51
  // parseable wins — joining wrapped lines, stripping margin/box chrome, and growing the candidate
@@ -80,5 +75,5 @@ export function parseWorkerResult(raw, nonce) {
80
75
  }
81
76
  }
82
77
  }
83
- return fail("unparseable DROVR_RESULT trailer");
78
+ return fail("unparseable TICKMARKR_RESULT trailer");
84
79
  }
@@ -1,4 +1,4 @@
1
- import { type DrovrConfig } from "../config/config.js";
1
+ import { type TickmarkrConfig } from "../config/config.js";
2
2
  import { type RoutingProfile } from "../route/profile.js";
3
3
  import { type AuthHealth, type BillingChannel, type WorkerAdapter } from "./types.js";
4
4
  export declare function allAdapters(opts?: {
@@ -14,20 +14,20 @@ export interface AutoPreferDoc {
14
14
  }
15
15
  export declare const pendingAutoPreferKey: unique symbol;
16
16
  export declare const DOCTOR_ROUTING_STALE_MS: number;
17
- export declare function deriveAutoPrefer(cfg: DrovrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>, profile?: RoutingProfile): AutoPreferDoc;
17
+ export declare function deriveAutoPrefer(cfg: TickmarkrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>, profile?: RoutingProfile): AutoPreferDoc;
18
18
  export declare function readAutoPrefer(repoRoot: string): AutoPreferDoc | null;
19
- export declare function routingPreferContext(repoRoot: string, cfg: DrovrConfig, opts?: {
19
+ export declare function routingPreferContext(repoRoot: string, cfg: TickmarkrConfig, opts?: {
20
20
  globalDir?: string;
21
21
  }): {
22
22
  autoPrefer?: AutoPreferDoc;
23
23
  doctorFresh: boolean;
24
24
  overlayPreferShapes: ReadonlySet<string>;
25
25
  };
26
- export declare function probeModels(cfg: DrovrConfig, repoRoot: string, adapters: WorkerAdapter[], health: Record<string, AuthHealth>, onProgress?: ProbeModelProgress): Promise<void>;
26
+ export declare function probeModels(cfg: TickmarkrConfig, repoRoot: string, adapters: WorkerAdapter[], health: Record<string, AuthHealth>, onProgress?: ProbeModelProgress): Promise<void>;
27
27
  export declare function writeDoctor(repoRoot: string, health: Record<string, AuthHealth>): void;
28
28
  export declare function readDoctor(repoRoot: string): Record<string, AuthHealth> | null;
29
- export declare function discoverChannels(cfg: DrovrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): BillingChannel[];
30
- export declare function servableExclusions(cfg: DrovrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): {
29
+ export declare function discoverChannels(cfg: TickmarkrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): BillingChannel[];
30
+ export declare function servableExclusions(cfg: TickmarkrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): {
31
31
  key: string;
32
32
  adapter: string;
33
33
  }[];
@@ -35,7 +35,7 @@ export declare function servabilityLine(excluded: {
35
35
  key: string;
36
36
  adapter: string;
37
37
  }[]): string;
38
- export declare function modelAuthExclusions(cfg: DrovrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): {
38
+ export declare function modelAuthExclusions(cfg: TickmarkrConfig, adapters: WorkerAdapter[], health: Record<string, AuthHealth>): {
39
39
  key: string;
40
40
  adapter: string;
41
41
  reason: string;
@@ -54,6 +54,6 @@ export declare function initDoctorReuse(repoRoot: string, fresh: boolean): {
54
54
  ageMs: number | null;
55
55
  health: Record<string, AuthHealth> | null;
56
56
  };
57
- export declare function formatDoctorReport(cwd: string, cfg: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], opts?: {
57
+ export declare function formatDoctorReport(cwd: string, cfg: TickmarkrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[], opts?: {
58
58
  wrote?: boolean;
59
59
  }): string;
@@ -4,7 +4,7 @@ import { join } from "node:path";
4
4
  import { overlayPreferShapes, TIER_RANK } from "../config/config.js";
5
5
  import { modelLints, suggestOverlay } from "./model-lints.js";
6
6
  import { HerdrDriver } from "../drivers/herdr.js";
7
- import { drovrDir, stateDirName } from "../graph/graph.js";
7
+ import { tickmarkrDir, stateDirName } from "../graph/graph.js";
8
8
  import { disallowedBy, excludedChannels, exclusionLine, preferRanks } from "../route/preference.js";
9
9
  import { learnedScore } from "../route/profile.js";
10
10
  import { marginalCostRank } from "../route/router.js";
@@ -22,7 +22,7 @@ export function allAdapters(opts = {}) {
22
22
  // keeps the Phase 6 shape→channel matrix byte-identical; inserting anywhere else silently
23
23
  // reassigns shapes on same-tier ties. grok is appended AFTER pi for the same reason.
24
24
  const real = [claudeCode, codex, cursorAgent, opencode, pi, grok];
25
- const fakePath = opts.fakeScriptPath ?? process.env.DROVR_FAKE_SCRIPT ?? process.env.DROVER_FAKE_SCRIPT;
25
+ const fakePath = opts.fakeScriptPath ?? process.env.TICKMARKR_FAKE_SCRIPT;
26
26
  return fakePath ? [new FakeAdapter(fakePath), ...real] : real;
27
27
  }
28
28
  export function getAdapter(id, adapters) {
@@ -131,7 +131,7 @@ export async function probeModels(cfg, repoRoot, adapters, health, onProgress) {
131
131
  verdict = { authed: false, reason: "headless probe unavailable", probedAt, durationMs: Date.now() - t0 };
132
132
  }
133
133
  else {
134
- const promptFile = join(mkdtempSync(join(tmpdir(), "drovr-auth-")), "probe.md");
134
+ const promptFile = join(mkdtempSync(join(tmpdir(), "tickmarkr-auth-")), "probe.md");
135
135
  writeFileSync(promptFile, MODEL_PROBE_PROMPT);
136
136
  const r = await sh(a.headlessCommand(promptFile, model), probeRoot, MODEL_PROBE_TIMEOUT_MS);
137
137
  if (r.timedOut && priorTimedOut) {
@@ -181,7 +181,7 @@ function readDoctorFile(repoRoot) {
181
181
  return existsSync(doctorPath(repoRoot)) ? JSON.parse(readFileSync(doctorPath(repoRoot), "utf8")) : null;
182
182
  }
183
183
  export function writeDoctor(repoRoot, health) {
184
- drovrDir(repoRoot);
184
+ tickmarkrDir(repoRoot);
185
185
  const pending = health[pendingAutoPreferKey];
186
186
  const payload = { ...health };
187
187
  delete payload[pendingAutoPreferKey];
@@ -327,7 +327,7 @@ export function formatDoctorReport(cwd, cfg, health, adapters, opts = {}) {
327
327
  let drift = "";
328
328
  if (frag) {
329
329
  if (visual) {
330
- const overlayPath = join(drovrDir(cwd), "doctor-overlay.yaml");
330
+ const overlayPath = join(tickmarkrDir(cwd), "doctor-overlay.yaml");
331
331
  writeFileSync(overlayPath, frag);
332
332
  drift = `\nmodel drift: unclassified models detected — paste-ready overlay written to ${overlayPath} (advisory; tickmarkr never applies it)`;
333
333
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { DrovrConfig, Tier } from "../config/config.js";
2
+ import type { TickmarkrConfig, Tier } from "../config/config.js";
3
3
  import type { Task } from "../graph/schema.js";
4
4
  export declare const TokenUsageSchema: z.ZodObject<{
5
5
  input: z.ZodNumber;
@@ -74,7 +74,7 @@ export interface WorkerAdapter {
74
74
  vendor: string;
75
75
  probeCwd?: "repo" | "neutral";
76
76
  probe(): Promise<AuthHealth>;
77
- channels(cfg: DrovrConfig): BillingChannel[];
77
+ channels(cfg: TickmarkrConfig): BillingChannel[];
78
78
  headlessCommand(promptFile: string, model: string): string;
79
79
  interactiveCommand(promptFile: string, model: string): string | null;
80
80
  resumeCommand?(sessionId: string, promptFile: string, model: string): string;
@@ -89,7 +89,7 @@ export interface WorkerAdapter {
89
89
  trust?(repoRoot: string): TrustVerdict;
90
90
  trustDialog?: TrustDialog;
91
91
  }
92
- export declare function channelsFromConfig(adapterId: string, cfg: DrovrConfig): BillingChannel[];
92
+ export declare function channelsFromConfig(adapterId: string, cfg: TickmarkrConfig): BillingChannel[];
93
93
  export declare function channelKey(c: {
94
94
  adapter: string;
95
95
  model: string;
@@ -0,0 +1,2 @@
1
+ export declare const BANNER: string;
2
+ export declare function bannerShell(): string;
package/dist/brand.js ADDED
@@ -0,0 +1,17 @@
1
+ // TTY-only pixel-tick logo (assets/mark.svg is the image twin — same block geometry).
2
+ // Never printed to pipes — the non-TTY stdout surface is byte-pinned by tests and consumed by machines.
3
+ const B = "\x1b[1m", R = "\x1b[0m";
4
+ const g = (n, s) => `\x1b[38;5;${n}m${s}${R}`; // 256-color green ramp, bright → deep
5
+ export const BANNER = [
6
+ ` ${g(84, "▄▄████")}`,
7
+ ` ${g(78, "▄▄████▀▀")}`,
8
+ `${g(41, "████▄▄▄▄████▀▀")} ${B}tickmarkr${R}`,
9
+ ` ${g(35, "▀▀████▀▀")} spec in, verified work out.`,
10
+ "",
11
+ ].join("\n");
12
+ // Shell one-liner that prints the banner inside a pane before a gate command runs. ESC bytes are
13
+ // carried as printf %b escapes (never raw control bytes in a command string crossing the herdr socket).
14
+ export function bannerShell() {
15
+ const printable = BANNER.replaceAll("\x1b", "\\033").replaceAll("\n", "\\n");
16
+ return `printf '%b\\n' '${printable}'`;
17
+ }
@@ -1,9 +1,9 @@
1
1
  import { userInfo } from "node:os";
2
2
  import { ATTEMPT_CAP_RELEASE, Journal } from "../../run/journal.js";
3
- // GATE-08 (v1.12): approve a parked human gate so the next `drovr resume <runId>` dispatches it.
3
+ // GATE-08 (v1.12): approve a parked human gate so the next `tickmarkr resume <runId>` dispatches it.
4
4
  //
5
5
  // The approval is a JOURNAL EVENT (task-approved) carrying who and when — it touches ONLY the
6
- // append-only journal. Writing it into drovr's compiled graph artifact would be silently erased by
6
+ // append-only journal. Writing it into tickmarkr's compiled graph artifact would be silently erased by
7
7
  // the next recompile (which re-emits humanGate:true from the plan frontmatter) — Phase 42 D-02.
8
8
  //
9
9
  // v1.24 OBS-18: when the park was attempt-cap (not a humanGate pre-dispatch park), the event also
@@ -13,7 +13,7 @@ import { ATTEMPT_CAP_RELEASE, Journal } from "../../run/journal.js";
13
13
  //
14
14
  // Fail-closed (D-05): unknown runId, unknown taskId, a not-parked task, and a double-approve are all
15
15
  // LOUD refusals that name the reason and append NO event — never a silent no-op. A handler throw
16
- // becomes `drovr approve: <message>` at exit 1 (src/cli/index.ts dispatch).
16
+ // becomes `tickmarkr approve: <message>` at exit 1 (src/cli/index.ts dispatch).
17
17
  //
18
18
  // Who/when is truthful, not dressed-up auth (D-03): default actor os.userInfo().username; --by overrides
19
19
  // for delegated approval; optional --reason; the event's ts (stamped by Journal.append) is the when.
@@ -18,7 +18,7 @@ export async function compile(argv, cwd = process.cwd()) {
18
18
  // as a second daemon. Read-only check — compile never acquires/holds (it's instantaneous).
19
19
  const stateDir = stateDirName(cwd);
20
20
  if (isRunLockLive(cwd))
21
- throw new Error(`${stateDir}/graph.lock is held by another drovr run, or is a stale/garbage lock — refusing to overwrite graph.json. If no run is active, run \`drovr unlock\`.`);
21
+ throw new Error(`${stateDir}/graph.lock is held by another tickmarkr run, or is a stale/garbage lock — refusing to overwrite graph.json. If no run is active, run \`tickmarkr unlock\`.`);
22
22
  saveGraph(cwd, g);
23
23
  return `compiled ${src} → ${stateDir}/graph.json (${g.tasks.length} tasks, source ${g.spec.source}, hash ${g.spec.hash.slice(0, 12)})`;
24
24
  }
@@ -1,7 +1,7 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { allAdapters, probeAll, probeModels, readAutoPrefer, servableExclusions, servabilityLine, writeDoctor } from "../../adapters/registry.js";
4
- import { drovrDir, stateDirName } from "../../graph/graph.js";
4
+ import { tickmarkrDir, stateDirName } from "../../graph/graph.js";
5
5
  import { modelLints, suggestOverlay, ttyVisual } from "../../adapters/model-lints.js";
6
6
  import { DEFAULT_CONFIG, loadConfig, overlayPreferShapes } from "../../config/config.js";
7
7
  import { HerdrDriver } from "../../drivers/herdr.js";
@@ -99,14 +99,14 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
99
99
  const servable = servableExclusions(cfg, adapters, health);
100
100
  if (servable.length)
101
101
  rows.push(` ! ${servabilityLine(servable)}`);
102
- // MODEL-05/06: print-only drift fragment; advisory, whole-line-commented additions, drovr NEVER applies it.
102
+ // MODEL-05/06: print-only drift fragment; advisory, whole-line-commented additions, tickmarkr NEVER applies it.
103
103
  // TTY gets a one-line summary + the fragment as a file (the full dump drowned everything else,
104
104
  // v1.33.1 onboarding); machine/CI surface keeps the inline dump — layout is pinned by tests.
105
105
  const frag = suggestOverlay(cfg, health, adapters, stateDirName(cwd));
106
106
  let drift = "";
107
107
  if (frag) {
108
108
  if (visual()) {
109
- const overlayPath = join(drovrDir(cwd), "doctor-overlay.yaml");
109
+ const overlayPath = join(tickmarkrDir(cwd), "doctor-overlay.yaml");
110
110
  writeFileSync(overlayPath, frag);
111
111
  drift = `\nmodel drift: unclassified models detected — paste-ready overlay written to ${overlayPath} (advisory; tickmarkr never applies it)`;
112
112
  }
@@ -4,8 +4,8 @@ import { createInterface } from "node:readline/promises";
4
4
  import { parseArgs } from "node:util";
5
5
  import { allAdapters, formatDoctorAgeForInit, formatDoctorReport, initDoctorReuse } from "../../adapters/registry.js";
6
6
  import { configTemplate, DEFAULT_CONFIG, globalConfigDir, loadConfig } from "../../config/config.js";
7
- import { specTemplate } from "../../compile/native.js";
8
- import { drovrDir } from "../../graph/graph.js";
7
+ import { LEGACY_PREFIX, specTemplate } from "../../compile/native.js";
8
+ import { tickmarkrDir } from "../../graph/graph.js";
9
9
  import { doctor } from "./doctor.js";
10
10
  const AGENT_SKILLS = ["tickmarkr-loop", "tickmarkr-auto"];
11
11
  const DOCS_BEGIN = "<!-- tickmarkr:agent-docs begin -->";
@@ -52,7 +52,8 @@ async function installAgentFiles(cwd, force, docs, notes) {
52
52
  const agents = join(cwd, "AGENTS.md");
53
53
  const docPath = existsSync(claude) || !existsSync(agents) ? claude : agents;
54
54
  const current = existsSync(docPath) ? readFileSync(docPath, "utf8") : "";
55
- if (current.includes(DOCS_BEGIN) || current.includes(DOCS_END)) {
55
+ if (current.includes(DOCS_BEGIN) || current.includes(`<!-- ${LEGACY_PREFIX}:agent-docs begin -->`)
56
+ || current.includes(DOCS_END) || current.includes(`<!-- ${LEGACY_PREFIX}:agent-docs end -->`)) {
56
57
  notes.push(`kept existing tickmarkr agent docs in ${docPath}`);
57
58
  }
58
59
  else if (docs || await confirm(`Append tickmarkr agent docs to ${docPath}?`)) {
@@ -126,7 +127,7 @@ export async function init(argv, cwd = process.cwd()) {
126
127
  else {
127
128
  notes.push(`kept existing ${globalPath}`);
128
129
  }
129
- const repoConfigPath = join(drovrDir(cwd), "config.yaml");
130
+ const repoConfigPath = join(tickmarkrDir(cwd), "config.yaml");
130
131
  const repoConfigExists = existsSync(repoConfigPath);
131
132
  if (repoConfigExists)
132
133
  notes.push(`kept existing ${repoConfigPath}`);
@@ -134,12 +135,16 @@ export async function init(argv, cwd = process.cwd()) {
134
135
  if (existsSync(specPath)) {
135
136
  notes.push(`kept existing ${specPath}`);
136
137
  }
137
- else if (existsSync(join(cwd, "drovr.spec.md"))) {
138
- notes.push(`kept existing ${join(cwd, "drovr.spec.md")}`);
139
- }
140
138
  else {
141
- writeFileSync(specPath, specTemplate());
142
- notes.push(`wrote ${specPath}`);
139
+ const legacySpec = join(cwd, `${LEGACY_PREFIX}.spec.md`);
140
+ if (existsSync(legacySpec)) {
141
+ writeFileSync(specPath, readFileSync(legacySpec, "utf8"));
142
+ notes.push(`wrote ${specPath}`);
143
+ }
144
+ else {
145
+ writeFileSync(specPath, specTemplate());
146
+ notes.push(`wrote ${specPath}`);
147
+ }
143
148
  }
144
149
  const fresh = values.fresh ?? false;
145
150
  const { reuse, ageMs, health } = initDoctorReuse(cwd, fresh);
@@ -1,22 +1,22 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loadConfig } from "../../config/config.js";
4
- import { drovrDir, stateDirName } from "../../graph/graph.js";
4
+ import { tickmarkrDir, stateDirName } from "../../graph/graph.js";
5
5
  import { learnedScore, MIN_SAMPLES, cellsOf } from "../../route/profile.js";
6
6
  import { Journal, loadRoutingProfile, readProfileCursor, RUNS_WINDOW } from "../../run/journal.js";
7
- // drovr profile — inspect (show) and forget (reset) the derived learned-routing profile (VIS-03).
7
+ // tickmarkr profile — inspect (show) and forget (reset) the derived learned-routing profile (VIS-03).
8
8
  // Read-only class like plan/status/report: no run lock. `reset` writes ONE cursor scalar, nothing else.
9
9
  export async function profile(argv, cwd = process.cwd()) {
10
10
  return argv[0] === "reset" ? reset(cwd) : show(cwd);
11
11
  }
12
- // Non-destructive reset (T-13-06): a one-line runId cutoff at .drovr/profile-since. NEVER deletes,
12
+ // Non-destructive reset (T-13-06): a one-line runId cutoff at .tickmarkr/profile-since. NEVER deletes,
13
13
  // truncates, or rotates any telemetry — the profile is derived (PROF-01), so there is nothing to delete;
14
- // the cursor only bounds loadRoutingProfile's telemetry window. drovrDir guarantees .drovr exists and
14
+ // the cursor only bounds loadRoutingProfile's telemetry window. tickmarkrDir guarantees .tickmarkr exists and
15
15
  // blanket-gitignores it, so the cursor is never git-addable. Opaque string: used only in a runId > compare.
16
16
  function reset(cwd) {
17
17
  const cursor = Journal.latestRunId(cwd) ?? ""; // empty repo ⇒ empty cursor ⇒ readProfileCursor === undefined
18
18
  const stateDir = stateDirName(cwd);
19
- const path = join(drovrDir(cwd), "profile-since");
19
+ const path = join(tickmarkrDir(cwd), "profile-since");
20
20
  writeFileSync(path, cursor + "\n");
21
21
  return [
22
22
  cursor
@@ -27,7 +27,7 @@ function reset(cwd) {
27
27
  ].join("\n");
28
28
  }
29
29
  // Inspection surface: preview:true bypasses the routing.learned:off short-circuit so `show` renders the
30
- // profile even under the default off — same trust ramp as `drovr plan`. The routing path stays inert.
30
+ // profile even under the default off — same trust ramp as `tickmarkr plan`. The routing path stays inert.
31
31
  function show(cwd) {
32
32
  const cfg = loadConfig(cwd);
33
33
  const cursor = readProfileCursor(cwd);
@@ -1,3 +1,4 @@
1
+ import { BANNER } from "../../brand.js";
1
2
  import { blockedTasks, loadGraph, pendingTasks } from "../../graph/graph.js";
2
3
  import { GATE_NAMES } from "../../graph/schema.js";
3
4
  import { Journal } from "../../run/journal.js";
@@ -211,7 +212,7 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
211
212
  for (let i = 0; i < iterations; i++) {
212
213
  const frame = renderFrame(cwd);
213
214
  if (tty)
214
- process.stdout.write(`\x1b[2J\x1b[H${frame}\n\x1b[2m watching · refresh ${REFRESH_MS / 1000}s · ^C to quit\x1b[0m`);
215
+ process.stdout.write(`\x1b[2J\x1b[H${BANNER}${frame}\n\x1b[2m watching · refresh ${REFRESH_MS / 1000}s · ^C to quit\x1b[0m`);
215
216
  else
216
217
  process.stdout.write(frame + sep);
217
218
  if (bounded)
@@ -1,6 +1,6 @@
1
1
  import { unlockRun } from "../../run/lock.js";
2
2
  // LOCK-03: thin formatter over unlockRun. A live-holder refusal propagates as the throw — the
3
- // dispatcher prints `drovr unlock: …` and exits 1 (src/cli/index.ts).
3
+ // dispatcher prints `tickmarkr unlock: …` and exits 1 (src/cli/index.ts).
4
4
  export async function unlock(_argv, cwd = process.cwd()) {
5
5
  const r = unlockRun(cwd);
6
6
  if (!r.held)
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  export type CommandMap = Record<string, (argv: string[]) => Promise<string>>;
3
3
  export declare const COMMANDS: CommandMap;
4
- export declare const BANNER: string;
5
- export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix\n compile <src> spec \u2192 .drovr/graph.json (fails without acceptance criteria)\n scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n run execute the graph (--concurrency N --driver herdr|subprocess --route-strict)\n status live run state\n resume <id> continue a run from its journal\n report <id> cost/quality report (--md for committable execution record)\n profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)\n unlock remove a stale/garbage run lock (refuses if the holder is alive)\n approve <id> <task> approve a parked human gate (--by <name> --reason <text>); takes effect on resume";
4
+ export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix\n compile <src> spec \u2192 .tickmarkr/graph.json (fails without acceptance criteria)\n scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n run execute the graph (--concurrency N --driver herdr|subprocess --route-strict)\n status live run state\n resume <id> continue a run from its journal\n report <id> cost/quality report (--md for committable execution record)\n profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)\n unlock remove a stale/garbage run lock (refuses if the holder is alive)\n approve <id> <task> approve a parked human gate (--by <name> --reason <text>); takes effect on resume";
6
5
  export declare function dispatch(cmd: string | undefined, argv: string[], commands?: CommandMap): Promise<{
7
6
  out: string;
8
7
  code: number;