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
@@ -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;
@@ -53,4 +53,4 @@ export const QUOTA_RE = /rate.?limit|quota|usage limit|out of credits|insufficie
53
53
  // operator-facing lint text and persisted to doctor.json — defense-in-depth for MODEL-05 (config
54
54
  // suggestions that could reach a shell). Covers observed ids incl. zai-coding-plan/glm-5.2,
55
55
  // gpt-5.6-sol, composer-2.5, gpt-5.3-codex; non-conforming (ANSI/control/shell-metachar) ids dropped.
56
- export const MODEL_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._/:\[\]=,-]*$/;
56
+ export const MODEL_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._/:[\]=,-]*$/;
@@ -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,7 +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 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";
5
5
  export declare function dispatch(cmd: string | undefined, argv: string[], commands?: CommandMap): Promise<{
6
6
  out: string;
7
7
  code: number;
package/dist/cli/index.js CHANGED
@@ -17,11 +17,12 @@ export const COMMANDS = {
17
17
  init, doctor, compile, scope, plan, run, status, resume, report, profile, unlock, approve,
18
18
  };
19
19
  const HELP_CMDS = new Set(["help", "-h", "--help"]);
20
+ import { BANNER } from "../brand.js";
20
21
  export const USAGE = `tickmarkr — spec-driven orchestration harness for AI coding agents
21
22
  usage: tickmarkr <command>
22
23
  init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs
23
24
  doctor re-probe adapters, herdr, auth; print capability matrix
24
- compile <src> spec → .drovr/graph.json (fails without acceptance criteria)
25
+ compile <src> spec → .tickmarkr/graph.json (fails without acceptance criteria)
25
26
  scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)
26
27
  plan dry-run routing table + cost estimate + floor lints
27
28
  run execute the graph (--concurrency N --driver herdr|subprocess --route-strict)
@@ -32,14 +33,15 @@ usage: tickmarkr <command>
32
33
  unlock remove a stale/garbage run lock (refuses if the holder is alive)
33
34
  approve <id> <task> approve a parked human gate (--by <name> --reason <text>); takes effect on resume`;
34
35
  // pure, testable dispatcher: resolves a command, forwards argv, shapes the result — no side effects.
35
- // unknown/missing cmd → USAGE (exit 1 if a cmd was typed, 0 for bare `drovr`); a handler throw becomes
36
- // a one-line `drovr <cmd>: <message>` (never a raw stack) at exit 1.
36
+ // unknown/missing cmd → USAGE (exit 1 if a cmd was typed, 0 for bare `tickmarkr`); a handler throw becomes
37
+ // a one-line `tickmarkr <cmd>: <message>` (never a raw stack) at exit 1.
37
38
  export async function dispatch(cmd, argv, commands = COMMANDS) {
39
+ const usage = process.stdout.isTTY ? BANNER + USAGE : USAGE;
38
40
  if (!cmd || HELP_CMDS.has(cmd))
39
- return { out: USAGE, code: 0 };
41
+ return { out: usage, code: 0 };
40
42
  const fn = commands[cmd];
41
43
  if (!fn)
42
- return { out: USAGE, code: 1 };
44
+ return { out: usage, code: 1 };
43
45
  try {
44
46
  return { out: await fn(argv), code: 0 };
45
47
  }
@@ -49,7 +51,7 @@ export async function dispatch(cmd, argv, commands = COMMANDS) {
49
51
  }
50
52
  /* v8 ignore start -- binary entry: printing + process.exit side effects, not unit-testable (ROADMAP crit 2) */
51
53
  // node realpaths the main module (import.meta.url) but argv[1] keeps the symlink path —
52
- // a globally-linked `drovr` bin silently no-oped here (OBS-10); compare realpaths
54
+ // a globally-linked `tickmarkr` bin silently no-oped here (OBS-10); compare realpaths
53
55
  const argv1Real = (() => { try {
54
56
  return process.argv[1] ? realpathSync(process.argv[1]) : "";
55
57
  }
@@ -60,7 +62,7 @@ if (argv1Real && import.meta.url === pathToFileURL(argv1Real).href) {
60
62
  const [cmd, ...argv] = process.argv.slice(2);
61
63
  dispatch(cmd, argv).then(({ out, code }) => {
62
64
  // byte-identical streams: usage + success → stdout; a handler throw → stderr (original behavior)
63
- (out === USAGE || code === 0 ? console.log : console.error)(out);
65
+ (out.endsWith(USAGE) || code === 0 ? console.log : console.error)(out);
64
66
  if (code !== 0)
65
67
  process.exit(code);
66
68
  });
@@ -3,7 +3,7 @@ import { basename, dirname, isAbsolute, join, relative } from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { TIERS, validateGraph } from "../graph/schema.js";
5
5
  import { CompileError, assertWriteScope, inferShape, sha256 } from "./common.js";
6
- // GSD artifact front-end (spec v1.3): one GSD *plan* is one drovr *task* — a plan is
6
+ // GSD artifact front-end (spec v1.3): one GSD *plan* is one tickmarkr *task* — a plan is
7
7
  // worktree-sized; its inner <task> steps stay in the worker prompt via context[0] = the plan file.
8
8
  // Artifact-level only: parses .planning/ markdown, never GSD repo/command internals.
9
9
  const PLAN_SUFFIX = "-PLAN.md";
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { CompileError } from "./common.js";
4
4
  import { compileGsd, isGsdPhaseDir } from "./gsd.js";
5
- import { compileNative, NATIVE_MARKER } from "./native.js";
5
+ import { compileNative, TICKMARKR_NATIVE_MARKER } from "./native.js";
6
6
  import { compilePrd } from "./prd.js";
7
7
  import { compileSpecKit } from "./speckit.js";
8
8
  function detect(src) {
@@ -15,8 +15,14 @@ function detect(src) {
15
15
  }
16
16
  if (src.endsWith("-PLAN.md"))
17
17
  return "gsd"; // before the generic .md → prd rule
18
- if (src.endsWith(".md"))
19
- return existsSync(src) && NATIVE_MARKER.test(readFileSync(src, "utf8")) ? "native" : "prd";
18
+ if (src.endsWith(".md")) {
19
+ if (!existsSync(src))
20
+ return "prd";
21
+ const content = readFileSync(src, "utf8");
22
+ if (TICKMARKR_NATIVE_MARKER.test(content))
23
+ return "native";
24
+ return "prd";
25
+ }
20
26
  return null;
21
27
  }
22
28
  export function compileSource(src, type, root) {
@@ -1,4 +1,6 @@
1
1
  import { type RunGraph } from "../graph/schema.js";
2
+ export declare const LEGACY_PREFIX: string;
3
+ export declare const TICKMARKR_NATIVE_MARKER: RegExp;
2
4
  export declare const NATIVE_MARKER: RegExp;
3
5
  export declare function compileNative(file: string): RunGraph;
4
6
  export declare function specTemplate(): string;
@@ -1,7 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { GATE_NAMES, ORACLES, SHAPES, TIERS, validateGraph } from "../graph/schema.js";
3
3
  import { CompileError, inferShape, sha256 } from "./common.js";
4
- export const NATIVE_MARKER = /^<!--\s*(?:tickmarkr|drovr):spec(?:\s+v1)?\s*-->\s*$/m;
4
+ export const LEGACY_PREFIX = ["dro", "vr"].join("");
5
+ export const TICKMARKR_NATIVE_MARKER = /^<!--\s*tickmarkr:spec(?:\s+v1)?\s*-->\s*$/m;
6
+ export const NATIVE_MARKER = new RegExp(`^<!--\\s*(?:tickmarkr|${LEGACY_PREFIX}):spec(?:\\s+v1)?\\s*-->\\s*$`, "m");
5
7
  const HEAD_RE = /^## (T\d+):\s*(.+)$/;
6
8
  const FIELD_RE = /^- (\w+):\s*(.*)$/;
7
9
  const NESTED_RE = /^\s+- (.+)$/;
@@ -139,7 +141,7 @@ export function compileNative(file) {
139
141
  }
140
142
  return result;
141
143
  }
142
- // Commented native spec written to drovr.spec.md by `drovr init`. Documented via HTML comments (which the
144
+ // Commented native spec written to tickmarkr.spec.md by `tickmarkr init`. Documented via HTML comments (which the
143
145
  // parser ignores) so the template itself round-trips through compileSource() unchanged. Every field is
144
146
  // documented; the two example tasks illustrate plain vs deps+routing-hint, each with acceptance[].
145
147
  export function specTemplate() {
@@ -46,7 +46,7 @@ export declare const SubPricingSchema: z.ZodObject<{
46
46
  windowsPerMonthHigh: z.ZodNumber;
47
47
  }, z.core.$strip>;
48
48
  export type SubPricing = z.infer<typeof SubPricingSchema>;
49
- export declare const DrovrConfigSchema: z.ZodObject<{
49
+ export declare const TickmarkrConfigSchema: z.ZodObject<{
50
50
  concurrency: z.ZodNumber;
51
51
  driver: z.ZodEnum<{
52
52
  auto: "auto";
@@ -171,23 +171,23 @@ export declare const DrovrConfigSchema: z.ZodObject<{
171
171
  workersPerTab: z.ZodNumber;
172
172
  }, z.core.$strip>;
173
173
  }, z.core.$strip>;
174
- export type DrovrConfig = z.infer<typeof DrovrConfigSchema>;
174
+ export type TickmarkrConfig = z.infer<typeof TickmarkrConfigSchema>;
175
175
  export declare class ConfigError extends Error {
176
176
  constructor(message: string);
177
177
  }
178
- export declare const DEFAULT_CONFIG: DrovrConfig;
178
+ export declare const DEFAULT_CONFIG: TickmarkrConfig;
179
179
  export declare function globalConfigDir(): string;
180
180
  export declare function overlayPreferShapes(repoRoot: string, opts?: {
181
181
  globalDir?: string;
182
182
  }): ReadonlySet<string>;
183
183
  export declare function loadConfig(repoRoot: string, opts?: {
184
184
  globalDir?: string;
185
- }): DrovrConfig;
185
+ }): TickmarkrConfig;
186
186
  export type InitConfigOverlay = {
187
187
  concurrency?: number;
188
- driver?: DrovrConfig["driver"];
188
+ driver?: TickmarkrConfig["driver"];
189
189
  visibility?: {
190
- llm?: DrovrConfig["visibility"]["llm"];
190
+ llm?: TickmarkrConfig["visibility"]["llm"];
191
191
  };
192
192
  };
193
193
  export declare function configTemplate(overlay?: InitConfigOverlay): string;
@@ -63,7 +63,7 @@ const ShapeGateParticipationSchema = z
63
63
  });
64
64
  }
65
65
  });
66
- export const DrovrConfigSchema = z.object({
66
+ export const TickmarkrConfigSchema = z.object({
67
67
  concurrency: z.number().int().positive(),
68
68
  driver: z.enum(["auto", "herdr", "subprocess"]),
69
69
  integrationBranchPrefix: z
@@ -92,7 +92,7 @@ export const DrovrConfigSchema = z.object({
92
92
  tiers: z.record(z.string(), TierEntrySchema),
93
93
  pricing: z.record(z.string(), z.number()),
94
94
  // v1.20 REC-02: optional detailed price table for cost estimation. Distinct from `pricing` above
95
- // (the coarse per-task tier estimate `drovr plan` shows) — this one drives the usage/cost report.
95
+ // (the coarse per-task tier estimate `tickmarkr plan` shows) — this one drives the usage/cost report.
96
96
  // Absent ⇒ every channel reports "not measurable" (never $0). models keyed by model id (LiteLLM
97
97
  // convention); subs keyed by adapter id (subscription plans are per-account, not per-token).
98
98
  cost: z
@@ -136,7 +136,7 @@ export class ConfigError extends Error {
136
136
  export const DEFAULT_CONFIG = {
137
137
  concurrency: 3,
138
138
  driver: "auto",
139
- integrationBranchPrefix: "drovr/",
139
+ integrationBranchPrefix: "tickmarkr/",
140
140
  taskTimeoutMinutes: 30,
141
141
  contextWarnTokens: 170_000, // v1.23 T2: overseer ctx-watch.sh proven threshold
142
142
  routing: {
@@ -154,11 +154,11 @@ export const DEFAULT_CONFIG = {
154
154
  },
155
155
  // ROUTE-14 (2026-07-11, operator-adopted): learned reordering ON by default. Shipped OFF through v1.6/v1.7
156
156
  // as a preview-then-adopt trust ramp (ROUTE-09); the operator adopts it here after v1.8 made learning
157
- // auditable (VIS-05 `drovr report` learning section) and matured (decay ROUTE-11, scored utilization
157
+ // auditable (VIS-05 `tickmarkr report` learning section) and matured (decay ROUTE-11, scored utilization
158
158
  // ROUTE-12, escalation tiebreak ROUTE-13). SAFE BY CONSTRUCTION: an empty/cold profile ⇒ every
159
159
  // learnedScore returns exactly NEUTRAL ⇒ byte-identical v1.5 static routing, so ON only changes routing in
160
160
  // a workspace that has accumulated ≥MIN_SAMPLES warm telemetry per cell. Preview any workspace's effect
161
- // first with `drovr plan` / `drovr report`; flip to "off" to pin exact static routing (the kill switch stands).
161
+ // first with `tickmarkr plan` / `tickmarkr report`; flip to "off" to pin exact static routing (the kill switch stands).
162
162
  learned: "on",
163
163
  },
164
164
  // Seed table (spec §13). New models = edit this (or your config.yaml), never code.
@@ -185,12 +185,17 @@ export const DEFAULT_CONFIG = {
185
185
  // promoted from overlay 2026-07-11 (MODEL-10).
186
186
  // grok-4.5-fast → cheap: speed-optimized variant, no independent benchmark scores yet → floor tier.
187
187
  // RETIRED 2026-07-13: cursor-agent seeds grok-4.5-xhigh / grok-4.5-fast-xhigh — CLI no longer reports
188
- // either id (drovr plan lint, 2026-07-13). Tombstoned here; re-seed if cursor re-exposes them.
188
+ // either id (tickmarkr plan lint, 2026-07-13). Tombstoned here; re-seed if cursor re-exposes them.
189
189
  // (Benchmark provenance above still informs the native grok adapter seeds below.)
190
190
  "cursor-agent": {
191
191
  vendor: "cursor", channel: "sub",
192
192
  models: {
193
193
  "composer-2.5": "mid",
194
+ // composer-2.5-fast → cheap: speed-optimized variant, no independent benchmark scores yet → floor
195
+ // tier (same policy call as grok-composer-2.5-fast below). Id live-verified in doctor.json probe
196
+ // 2026-07-16 (cursor-agent 2026.07.09); gives cursor a cheap-tier channel so low-complexity shapes
197
+ // stop burning its mid. Operator-approved 2026-07-16.
198
+ "composer-2.5-fast": "cheap",
194
199
  },
195
200
  },
196
201
  // GLM-5.2 → mid per benchmark policy (2026-07): SWE-bench Pro 62.1 (> GPT-5.5 58.6), FrontierSWE 74.4 ≈ Opus 4.8;
@@ -255,12 +260,8 @@ function readYaml(path) {
255
260
  return parse(readFileSync(path, "utf8"));
256
261
  }
257
262
  export function globalConfigDir() {
258
- // read-old/write-new (operator-ordered rename 2026-07-15): prefer ~/.config/tickmarkr; an
259
- // existing ~/.config/drovr keeps working until the operator moves it. New scaffolds use tickmarkr.
260
263
  const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
261
- const next = join(base, "tickmarkr");
262
- const legacy = join(base, "drovr");
263
- return existsSync(join(next, "config.yaml")) || !existsSync(join(legacy, "config.yaml")) ? next : legacy;
264
+ return join(base, "tickmarkr");
264
265
  }
265
266
  export function overlayPreferShapes(repoRoot, opts = {}) {
266
267
  const shapes = new Set();
@@ -279,7 +280,7 @@ export function loadConfig(repoRoot, opts = {}) {
279
280
  const globalCfg = readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml"));
280
281
  const repoCfg = readYaml(join(repoRoot, stateDirName(repoRoot), "config.yaml"));
281
282
  const merged = deepMerge(deepMerge(structuredClone(DEFAULT_CONFIG), globalCfg), repoCfg);
282
- const r = DrovrConfigSchema.safeParse(merged);
283
+ const r = TickmarkrConfigSchema.safeParse(merged);
283
284
  if (!r.success)
284
285
  throw new ConfigError(z.prettifyError(r.error));
285
286
  return r.data;