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/dist/cli/index.js CHANGED
@@ -17,22 +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
- // TTY-only pixel-tick logo (assets/mark.svg is the image twin — same block geometry).
21
- // Never printed to pipes — the non-TTY stdout surface is byte-pinned by tests and consumed by machines.
22
- const B = "\x1b[1m", R = "\x1b[0m";
23
- const g = (n, s) => `\x1b[38;5;${n}m${s}${R}`; // 256-color green ramp, bright → deep
24
- export const BANNER = [
25
- ` ${g(84, "▄▄████")}`,
26
- ` ${g(78, "▄▄████▀▀")}`,
27
- `${g(41, "████▄▄▄▄████▀▀")} ${B}tickmarkr${R}`,
28
- ` ${g(35, "▀▀████▀▀")} spec in, verified work out.`,
29
- "",
30
- ].join("\n");
20
+ import { BANNER } from "../brand.js";
31
21
  export const USAGE = `tickmarkr — spec-driven orchestration harness for AI coding agents
32
22
  usage: tickmarkr <command>
33
23
  init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs
34
24
  doctor re-probe adapters, herdr, auth; print capability matrix
35
- compile <src> spec → .drovr/graph.json (fails without acceptance criteria)
25
+ compile <src> spec → .tickmarkr/graph.json (fails without acceptance criteria)
36
26
  scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)
37
27
  plan dry-run routing table + cost estimate + floor lints
38
28
  run execute the graph (--concurrency N --driver herdr|subprocess --route-strict)
@@ -43,8 +33,8 @@ usage: tickmarkr <command>
43
33
  unlock remove a stale/garbage run lock (refuses if the holder is alive)
44
34
  approve <id> <task> approve a parked human gate (--by <name> --reason <text>); takes effect on resume`;
45
35
  // pure, testable dispatcher: resolves a command, forwards argv, shapes the result — no side effects.
46
- // unknown/missing cmd → USAGE (exit 1 if a cmd was typed, 0 for bare `drovr`); a handler throw becomes
47
- // 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.
48
38
  export async function dispatch(cmd, argv, commands = COMMANDS) {
49
39
  const usage = process.stdout.isTTY ? BANNER + USAGE : USAGE;
50
40
  if (!cmd || HELP_CMDS.has(cmd))
@@ -61,7 +51,7 @@ export async function dispatch(cmd, argv, commands = COMMANDS) {
61
51
  }
62
52
  /* v8 ignore start -- binary entry: printing + process.exit side effects, not unit-testable (ROADMAP crit 2) */
63
53
  // node realpaths the main module (import.meta.url) but argv[1] keeps the symlink path —
64
- // 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
65
55
  const argv1Real = (() => { try {
66
56
  return process.argv[1] ? realpathSync(process.argv[1]) : "";
67
57
  }
@@ -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,7 +185,7 @@ 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",
@@ -260,12 +260,8 @@ function readYaml(path) {
260
260
  return parse(readFileSync(path, "utf8"));
261
261
  }
262
262
  export function globalConfigDir() {
263
- // read-old/write-new (operator-ordered rename 2026-07-15): prefer ~/.config/tickmarkr; an
264
- // existing ~/.config/drovr keeps working until the operator moves it. New scaffolds use tickmarkr.
265
263
  const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
266
- const next = join(base, "tickmarkr");
267
- const legacy = join(base, "drovr");
268
- return existsSync(join(next, "config.yaml")) || !existsSync(join(legacy, "config.yaml")) ? next : legacy;
264
+ return join(base, "tickmarkr");
269
265
  }
270
266
  export function overlayPreferShapes(repoRoot, opts = {}) {
271
267
  const shapes = new Set();
@@ -284,7 +280,7 @@ export function loadConfig(repoRoot, opts = {}) {
284
280
  const globalCfg = readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml"));
285
281
  const repoCfg = readYaml(join(repoRoot, stateDirName(repoRoot), "config.yaml"));
286
282
  const merged = deepMerge(deepMerge(structuredClone(DEFAULT_CONFIG), globalCfg), repoCfg);
287
- const r = DrovrConfigSchema.safeParse(merged);
283
+ const r = TickmarkrConfigSchema.safeParse(merged);
288
284
  if (!r.success)
289
285
  throw new ConfigError(z.prettifyError(r.error));
290
286
  return r.data;
@@ -74,7 +74,7 @@ export class HerdrDriver {
74
74
  }
75
75
  async slot(cwd, name, opts) {
76
76
  // T1 ownership contract: `opts.owned` (T2 call sites) names the pane canonically —
77
- // drovr:<role>:<taskId>:<attempt>:<runId>. Without it, `name` passes through byte-identical
77
+ // tickmarkr:<role>:<taskId>:<attempt>:<runId>. Without it, `name` passes through byte-identical
78
78
  // (today's legacy daemon/gates/consult shapes) — canonicalizeLegacyName (types.ts) is what lets
79
79
  // reconcile.ts and this driver's own renameGroupTab/glyphFor decode role/taskId/attempt from
80
80
  // those shapes without a call-site migration; T2 retires this branch by always passing `owned`.
@@ -92,7 +92,7 @@ export class HerdrDriver {
92
92
  // named after a dead cursor worker and the operator read it as a mislabeled agent)
93
93
  async tabSlot(cwd, name, label = name) {
94
94
  // tab-per-slot: concurrent agents in one tab split it into sliver columns — TUIs exit or
95
- // hard-wrap at COLUMNS≈2, shredding even the DROVR_RESULT marker (v1.4 phase-1 incident).
95
+ // hard-wrap at COLUMNS≈2, shredding even the TICKMARKR_RESULT marker (v1.4 phase-1 incident).
96
96
  // A dedicated named tab gives every agent a full-width pane; tab close() reaps it.
97
97
  // VIS-10 (operator ruling 2026-07-11): "pane placed by focus heuristic" is a DEFECT CLASS.
98
98
  // Fail closed at every step — env unset, tab-create non-zero, unparseable stdout, or a parsed
@@ -100,7 +100,7 @@ export class HerdrDriver {
100
100
  if (!this.ws)
101
101
  throw new Error("herdr placement requires HERDR_WORKSPACE_ID — refusing untargeted pane (VIS-10: fail closed, never place by focus)");
102
102
  // pin the tab to the RUN's workspace, UNCONDITIONALLY (inherited via HERDR_WORKSPACE_ID), never the
103
- // operator's focused one (Intl-Dossier run-20260709-104447 incident: worker tabs opened in the drovr repo workspace)
103
+ // operator's focused one (Intl-Dossier run-20260709-104447 incident: worker tabs opened in the tickmarkr repo workspace)
104
104
  const t = await this.herdr(`tab create --label ${shq(label)} --no-focus --workspace ${shq(this.ws)}`);
105
105
  if (t.code !== 0)
106
106
  throw new Error(`herdr tab create failed (exit ${t.code}, refusing untargeted placement): ${t.stderr || t.stdout}`);
@@ -139,7 +139,7 @@ export class HerdrDriver {
139
139
  if (typeof id !== "string" || !id)
140
140
  throw new Error(`herdr agent start returned no pane id: ${r.stdout}`);
141
141
  // tab create auto-spawns a root shell pane and agent start --tab adds the agent as a SECOND
142
- // pane — reap the idle shell so no tab shows a dead "drovr git:" prompt beside its agent
142
+ // pane — reap the idle shell so no tab shows a dead "tickmarkr git:" prompt beside its agent
143
143
  // (VIS-04 orphan fix). Best-effort: a failed reap costs cosmetics only.
144
144
  if (typeof rootPane === "string" && rootPane && rootPane !== id) {
145
145
  await this.herdr(`pane close ${shq(rootPane)}`);
@@ -444,7 +444,7 @@ export class HerdrDriver {
444
444
  return s;
445
445
  });
446
446
  }
447
- // OBS-17 T2 / v1.22b T1: close every drovr-owned pane that should not exist (superseded attempts,
447
+ // OBS-17 T2 / v1.22b T1: close every tickmarkr-owned pane that should not exist (superseded attempts,
448
448
  // killed-daemon orphans, leftovers from OLDER runs) — in this run's workspace OR misplaced in any
449
449
  // other one — then reap the tabs those closes emptied. Ownership is decided ONLY by parseOwnedName
450
450
  // (drivers/types.ts panesToClose) — foreign names never become candidates, in any workspace; a pane
@@ -1,3 +1,3 @@
1
- import type { DrovrConfig } from "../config/config.js";
1
+ import type { TickmarkrConfig } from "../config/config.js";
2
2
  import type { ExecutorDriver } from "./types.js";
3
- export declare function pickDriver(cfg: DrovrConfig, override?: "auto" | "herdr" | "subprocess"): ExecutorDriver;
3
+ export declare function pickDriver(cfg: TickmarkrConfig, override?: "auto" | "herdr" | "subprocess"): ExecutorDriver;
@@ -1,8 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createWorktree } from "../run/git.js";
3
- // HARD-03: cap retained worker output so a chatty worker can't grow drovr's heap unbounded.
3
+ // HARD-03: cap retained worker output so a chatty worker can't grow tickmarkr's heap unbounded.
4
4
  // 2MB is safe against BOTH consumers: the largest read() call site asks for 1000 lines
5
- // (daemon.ts:217/247), and every marker (DROVR_RESULT, DROVR_EXIT_<nonce>) is emitted at the
5
+ // (daemon.ts:217/247), and every marker (TICKMARKR_RESULT, TICKMARKR_EXIT_<nonce>) is emitted at the
6
6
  // END of output — a trailer would only be evicted if >2MB arrived AFTER it inside one 200ms
7
7
  // waitOutput poll (~10MB/s sustained, far beyond agent-CLI rates). Tail-truncate, never head:
8
8
  // consumers only ever tail-read.
@@ -43,7 +43,7 @@ export class SubprocessDriver {
43
43
  }
44
44
  async run(slot, cmd) {
45
45
  const s = this.state(slot);
46
- // HARD-05: interactive=false — no operator, so an open stdin pipe is a promise drovr can never
46
+ // HARD-05: interactive=false — no operator, so an open stdin pipe is a promise tickmarkr can never
47
47
  // keep; codex exec appends a piped stdin as a <stdin> block (`codex exec --help`) and blocks on a
48
48
  // read that never EOFs. One spawn site covers every adapter (D-06).
49
49
  // v1.22 T3: seal herdr control vars so worker/judge/review/consult children cannot reach the
@@ -1,15 +1,13 @@
1
1
  // ---- Ownership contract (T1: OBS-17 pane-hygiene) --------------------------------------------
2
- // Every pane/tab drovr creates is identified by exactly one parseable token:
3
- // drovr:<role>:<taskId>:<attempt>:<runId>
2
+ // Every pane/tab tickmarkr creates is identified by exactly one parseable token:
3
+ // tickmarkr:<role>:<taskId>:<attempt>:<runId>
4
4
  // role ∈ OWNED_ROLES; attempt is a non-negative integer; taskId/runId never contain ":" (task and
5
5
  // run ids are alphanumeric/dash by construction elsewhere). formatOwnedName/parseOwnedName round-trip
6
6
  // exactly. parseOwnedName (or isForeignName) is the ONLY way reconcile.ts may decide a live pane name
7
- // is drovr-owned — anything that doesn't parse is foreign and is never a candidate for closing.
7
+ // is tickmarkr-owned — anything that doesn't parse is foreign and is never a candidate for closing.
8
8
  export const OWNED_ROLES = ["worker", "judge", "review", "consult", "watch", "other"];
9
- // read-old/write-new: new panes are named tickmarkr:*, but reconcile still recognizes (and may
10
- // close/reuse) drovr:* panes left by pre-rename runs — resumed old runs keep working.
11
9
  const OWNED_PREFIX = "tickmarkr";
12
- const OWNED_RE = new RegExp(`^(?:${OWNED_PREFIX}|drovr):(${OWNED_ROLES.join("|")}):([^:]+):(\\d+):([^:]+)$`);
10
+ const OWNED_RE = new RegExp(`^${OWNED_PREFIX}:(${OWNED_ROLES.join("|")}):([^:]+):(\\d+):([^:]+)$`);
13
11
  export function formatOwnedName(o) {
14
12
  return `${OWNED_PREFIX}:${o.role}:${o.taskId}:${o.attempt}:${o.runId}`;
15
13
  }
@@ -58,7 +58,7 @@ export async function acceptanceGate(task, worktree, baseRef, judge, via, opts =
58
58
  ? "WARNING: only judge oracles — no deterministic command/test oracle guards this task (deterministic preferred, spec §2).\n"
59
59
  : "";
60
60
  const diff = (await shOk(`git diff '${baseRef}..HEAD'`, worktree)).slice(0, DIFF_CAP);
61
- const prompt = `DROVR-JUDGE
61
+ const prompt = `TICKMARKR-JUDGE
62
62
  You are a strict acceptance judge. Decide whether the diff satisfies EVERY acceptance criterion.
63
63
  Judge only what the diff proves — plausible-but-wrong must fail. Do not award partial credit.
64
64
  Deterministic command/test oracles have already passed mechanically; judge ONLY the rubric items below.
@@ -1,4 +1,4 @@
1
- import type { DrovrConfig } from "../config/config.js";
1
+ import type { TickmarkrConfig } from "../config/config.js";
2
2
  import type { GateResult } from "./types.js";
3
3
  export interface Baseline {
4
4
  commands: Record<string, {
@@ -7,6 +7,6 @@ export interface Baseline {
7
7
  }>;
8
8
  }
9
9
  export declare function fingerprint(output: string): string[];
10
- export declare function detectGateCommands(repoRoot: string, cfg: DrovrConfig): Record<string, string>;
10
+ export declare function detectGateCommands(repoRoot: string, cfg: TickmarkrConfig): Record<string, string>;
11
11
  export declare function captureBaseline(cwd: string, commands: Record<string, string>): Promise<Baseline>;
12
12
  export declare function compareToBaseline(cwd: string, commands: Record<string, string>, baseline: Baseline, enabled: string[]): Promise<GateResult[]>;
package/dist/gates/llm.js CHANGED
@@ -2,6 +2,7 @@ import { mkdtempSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { formatOwnedName, parseOwnedName } from "../drivers/types.js";
5
+ import { bannerShell } from "../brand.js";
5
6
  import { sh } from "../run/git.js";
6
7
  export const GATE_PANE_SEP = " · ";
7
8
  /** T8: role-first pane name for fleet visibility — judge · T4, review · T3, consult · T2. */
@@ -12,7 +13,7 @@ export function gatePaneName(role, taskId, suffix = "") {
12
13
  // via.name remains the fallback for non-gate runViaDriver callers and carries -r1 retry suffixes.
13
14
  // T2 ownership contract: a canonical owned fallback (the daemon's nameFor now emits one) passes
14
15
  // through untouched; run-gates' "-r1" judge-retry suffix becomes attempt+1 so the retry pane's name
15
- // stays contract-parseable (drovr:judge:<task>:1:<runId>) instead of a corrupted-runId shape.
16
+ // stays contract-parseable (tickmarkr:judge:<task>:1:<runId>) instead of a corrupted-runId shape.
16
17
  export function rolePaneNameFromPrompt(prompt, fallback) {
17
18
  const retry = fallback.endsWith("-r1");
18
19
  const base = retry ? fallback.slice(0, -3) : fallback;
@@ -22,14 +23,14 @@ export function rolePaneNameFromPrompt(prompt, fallback) {
22
23
  const id = prompt.match(/## Task ([^\n:]+):/)?.[1];
23
24
  if (!id)
24
25
  return fallback;
25
- if (prompt.startsWith("DROVR-JUDGE"))
26
+ if (prompt.startsWith("TICKMARKR-JUDGE"))
26
27
  return gatePaneName("judge", id, retry ? "-r1" : "");
27
- if (prompt.startsWith("DROVR-REVIEW"))
28
+ if (prompt.startsWith("TICKMARKR-REVIEW"))
28
29
  return gatePaneName("review", id, retry ? "-r1" : "");
29
30
  return fallback;
30
31
  }
31
32
  export async function runHeadless(adapter, model, prompt, cwd, timeoutMs = 300000) {
32
- const pf = join(mkdtempSync(join(tmpdir(), "drovr-llm-")), "prompt.md");
33
+ const pf = join(mkdtempSync(join(tmpdir(), "tickmarkr-llm-")), "prompt.md");
33
34
  writeFileSync(pf, prompt);
34
35
  const r = await sh(adapter.headlessCommand(pf, model), cwd, timeoutMs);
35
36
  return r.stdout + "\n" + r.stderr;
@@ -37,15 +38,17 @@ export async function runHeadless(adapter, model, prompt, cwd, timeoutMs = 30000
37
38
  // v1.1 default path: the same headless CLI call, but dispatched through the driver
38
39
  // as a visible named agent (herdr pane), with the quote-split completion wrapper.
39
40
  export async function runViaDriver(adapter, model, prompt, cwd, via, timeoutMs = 300000) {
40
- const pf = join(mkdtempSync(join(tmpdir(), "drovr-llm-")), "prompt.md");
41
+ const pf = join(mkdtempSync(join(tmpdir(), "tickmarkr-llm-")), "prompt.md");
41
42
  writeFileSync(pf, prompt);
42
43
  const slot = await via.driver.slot(cwd, rolePaneNameFromPrompt(prompt, via.name), via.label ? { label: via.label } : undefined);
43
44
  via.onSlot?.(slot);
44
- await via.driver.run(slot, `${adapter.headlessCommand(pf, model)}; printf '\\nDROVR_''EXIT:%s\\n' $?`);
45
- // wait for the digit-suffixed marker (a real $? exit code), never a bare "DROVR_EXIT:" that a
45
+ // brand banner at pane top — gate panes run headless-print (no alt screen), so it stays visible;
46
+ // extractJson is anchored to JSON braces and the exit marker to TICKMARKR_EXIT:\d, neither matches banner text
47
+ await via.driver.run(slot, `${bannerShell()}; ${adapter.headlessCommand(pf, model)}; printf '\\nTICKMARKR_''EXIT:%s\\n' $?`);
48
+ // wait for the digit-suffixed marker (a real $? exit code), never a bare "TICKMARKR_EXIT:" that a
46
49
  // self-referential diff under review merely DISPLAYS — same guard the worker path uses (daemon.ts:193).
47
- // Without it, a judge/review pane echoing drovr's own source false-completes before its verdict lands.
48
- await via.driver.waitOutput(slot, "DROVR_EXIT:\\d", timeoutMs, { regex: true });
50
+ // Without it, a judge/review pane echoing tickmarkr's own source false-completes before its verdict lands.
51
+ await via.driver.waitOutput(slot, "TICKMARKR_EXIT:\\d", timeoutMs, { regex: true });
49
52
  const out = await via.driver.read(slot, 400);
50
53
  if (!via.keep)
51
54
  await via.driver.close(slot);
@@ -1,5 +1,5 @@
1
1
  import { type Assignment, type BillingChannel, type WorkerAdapter } from "../adapters/types.js";
2
- import { type DrovrConfig } from "../config/config.js";
2
+ import { type TickmarkrConfig } from "../config/config.js";
3
3
  import { type Task } from "../graph/schema.js";
4
4
  import { type GateVia } from "./llm.js";
5
5
  import type { GateResult } from "./types.js";
@@ -9,4 +9,4 @@ export interface ReviewVerdict {
9
9
  }
10
10
  export declare function modelId(model: string): string;
11
11
  export declare function pickReviewer(author: Assignment, channels: BillingChannel[], exclude?: string[]): BillingChannel | null;
12
- export declare function reviewGate(task: Task, worktree: string, baseRef: string, author: Assignment, channels: BillingChannel[], adapters: WorkerAdapter[], cfg: DrovrConfig, via?: GateVia, excludeReviewers?: string[]): Promise<GateResult>;
12
+ export declare function reviewGate(task: Task, worktree: string, baseRef: string, author: Assignment, channels: BillingChannel[], adapters: WorkerAdapter[], cfg: TickmarkrConfig, via?: GateVia, excludeReviewers?: string[]): Promise<GateResult>;
@@ -39,7 +39,7 @@ export async function reviewGate(task, worktree, baseRef, author, channels, adap
39
39
  : { gate: "review", pass: true, details: "WARNING: no cross-vendor reviewer available — review waived by config" };
40
40
  }
41
41
  const diff = (await shOk(`git diff '${baseRef}..HEAD'`, worktree)).slice(0, DIFF_CAP);
42
- const prompt = `DROVR-REVIEW
42
+ const prompt = `TICKMARKR-REVIEW
43
43
  You are a skeptical cross-vendor code reviewer. Another agent (vendor: ${author.adapter}) authored this diff.
44
44
  Look for correctness bugs, security issues, and acceptance-criteria gaps. Approve only if you would merge it.
45
45
 
@@ -1,5 +1,5 @@
1
1
  import { type Assignment, type BillingChannel, type WorkerAdapter, type WorkerResult } from "../adapters/types.js";
2
- import { type DrovrConfig } from "../config/config.js";
2
+ import { type TickmarkrConfig } from "../config/config.js";
3
3
  import { type GateName, type Task } from "../graph/schema.js";
4
4
  import { type Baseline } from "./baseline.js";
5
5
  import type { GateVia } from "./llm.js";
@@ -23,7 +23,7 @@ export interface GateContext {
23
23
  baseline: Baseline;
24
24
  channels: BillingChannel[];
25
25
  adapters: WorkerAdapter[];
26
- cfg: DrovrConfig;
26
+ cfg: TickmarkrConfig;
27
27
  via?: GateVia;
28
28
  excludeReviewers?: string[];
29
29
  onGate?: (e: GateEvent) => void | Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import { type RunGraph, type Task, type TaskStatus } from "./schema.js";
2
- export declare function stateDirName(repoRoot: string): string;
2
+ export declare function stateDirName(_repoRoot: string): string;
3
3
  export declare function graphPath(repoRoot: string): string;
4
- export declare function drovrDir(repoRoot: string): string;
4
+ export declare function tickmarkrDir(repoRoot: string): string;
5
5
  export declare function loadGraph(repoRoot: string): RunGraph;
6
6
  export declare function saveGraph(repoRoot: string, g: RunGraph): void;
7
7
  export declare function getTask(g: RunGraph, id: string): Task;
@@ -1,25 +1,13 @@
1
1
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { validateGraph } from "./schema.js";
4
- const stateDirs = new Map();
5
- export function stateDirName(repoRoot) {
6
- const cached = stateDirs.get(repoRoot);
7
- if (cached)
8
- return cached;
9
- const name = existsSync(join(repoRoot, ".tickmarkr"))
10
- ? ".tickmarkr"
11
- : existsSync(join(repoRoot, ".drovr"))
12
- ? ".drovr"
13
- : existsSync(join(repoRoot, ".drover"))
14
- ? ".drover"
15
- : ".tickmarkr";
16
- stateDirs.set(repoRoot, name);
17
- return name;
4
+ export function stateDirName(_repoRoot) {
5
+ return ".tickmarkr";
18
6
  }
19
7
  export function graphPath(repoRoot) {
20
8
  return join(repoRoot, stateDirName(repoRoot), "graph.json");
21
9
  }
22
- export function drovrDir(repoRoot) {
10
+ export function tickmarkrDir(repoRoot) {
23
11
  const dir = join(repoRoot, stateDirName(repoRoot));
24
12
  mkdirSync(dir, { recursive: true });
25
13
  const gi = join(dir, ".gitignore");
@@ -34,7 +22,7 @@ export function loadGraph(repoRoot) {
34
22
  return validateGraph(JSON.parse(readFileSync(p, "utf8")));
35
23
  }
36
24
  export function saveGraph(repoRoot, g) {
37
- drovrDir(repoRoot);
25
+ tickmarkrDir(repoRoot);
38
26
  const p = graphPath(repoRoot);
39
27
  // Temp file MUST be a sibling of graph.json: rename(2) is atomic only within one filesystem
40
28
  // (never os.tmpdir()). pid-suffix so a racing writer can't clobber our in-flight temp (HARD-04).
@@ -1,5 +1,5 @@
1
1
  export function scopePrompt(intent, repair) {
2
- return `DROVR-JUDGE
2
+ return `TICKMARKR-JUDGE
3
3
  You are drafting a tickmarkr native spec from an answered intent. Return only the Markdown spec, with no commentary or code fence.
4
4
 
5
5
  The draft must:
@@ -1,9 +1,9 @@
1
1
  import type { WorkerAdapter } from "../adapters/types.js";
2
- import type { DrovrConfig } from "../config/config.js";
2
+ import type { TickmarkrConfig } from "../config/config.js";
3
3
  import type { ExecutorDriver } from "../drivers/types.js";
4
4
  export declare function clarificationGate(intent: string): string[];
5
5
  export interface ScopeOptions {
6
- cfg: DrovrConfig;
6
+ cfg: TickmarkrConfig;
7
7
  adapters: WorkerAdapter[];
8
8
  driver?: ExecutorDriver;
9
9
  force?: boolean;
@@ -2,7 +2,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "no
2
2
  import { tmpdir } from "node:os";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
4
  import { discoverChannels, getAdapter, probeAll } from "../adapters/registry.js";
5
- import { compileNative, NATIVE_MARKER } from "../compile/native.js";
5
+ import { compileNative, LEGACY_PREFIX } from "../compile/native.js";
6
6
  import { pickDriver } from "../drivers/index.js";
7
7
  import { extractJson, runLlm } from "../gates/llm.js";
8
8
  import { TaskSchema } from "../graph/schema.js";
@@ -48,7 +48,7 @@ function section(source, name) {
48
48
  return lines.slice(start + 1, end === -1 ? undefined : end).join("\n");
49
49
  }
50
50
  function extractDraft(raw) {
51
- const clean = raw.replace(/\nDROVR_EXIT:\d[\s\S]*$/, "").trim();
51
+ const clean = raw.replace(/\nTICKMARKR_EXIT:\d[\s\S]*$/, "").trim();
52
52
  try {
53
53
  const value = JSON.parse(clean);
54
54
  if (typeof value === "string")
@@ -64,18 +64,18 @@ function extractDraft(raw) {
64
64
  const fenced = [...clean.matchAll(/```(?:markdown|md)?\s*\n([\s\S]*?)```/gi)].at(-1)?.[1];
65
65
  if (fenced)
66
66
  return fenced;
67
- const marker = clean.search(/<!--\s*(?:tickmarkr|drovr):spec/);
67
+ const marker = clean.search(new RegExp(`<!--\\s*(?:tickmarkr|${LEGACY_PREFIX}):spec`));
68
68
  return (marker === -1 ? clean : clean.slice(marker)).trimEnd() + "\n";
69
69
  }
70
70
  function validateDraft(draft) {
71
- if (!NATIVE_MARKER.test(draft))
71
+ if (!/^<!--\s*tickmarkr:spec/.test(draft))
72
72
  throw new Error("draft is missing the tickmarkr native marker");
73
73
  if (!section(draft, "Assumptions").trim())
74
74
  throw new Error("draft is missing explicit assumptions");
75
75
  const requirements = [...new Set(section(draft, "Requirements").match(/\bREQ-\d{2}\b/g) ?? [])];
76
76
  if (!requirements.length)
77
77
  throw new Error("draft has no REQ-nn requirements");
78
- const dir = mkdtempSync(join(tmpdir(), "drovr-scope-compile-"));
78
+ const dir = mkdtempSync(join(tmpdir(), "tickmarkr-scope-compile-"));
79
79
  const file = join(dir, "draft.spec.md");
80
80
  try {
81
81
  writeFileSync(file, draft);
@@ -1,10 +1,10 @@
1
1
  import { type AuthHealth } from "../adapters/types.js";
2
- import type { DrovrConfig } from "../config/config.js";
2
+ import type { TickmarkrConfig } from "../config/config.js";
3
3
  export interface Disallowed {
4
4
  by: "deny" | "allow";
5
5
  entry: string;
6
6
  }
7
- export declare function excludedChannels(cfg: DrovrConfig, adapters: {
7
+ export declare function excludedChannels(cfg: TickmarkrConfig, adapters: {
8
8
  id: string;
9
9
  }[] | string[], health: Record<string, AuthHealth>): {
10
10
  key: string;
@@ -17,11 +17,11 @@ export declare function exclusionLine(excluded: {
17
17
  export declare function preferRanks(c: {
18
18
  adapter: string;
19
19
  model: string;
20
- }, cfg: DrovrConfig): {
20
+ }, cfg: TickmarkrConfig): {
21
21
  shape: string;
22
22
  rank: number;
23
23
  }[];
24
24
  export declare function disallowedBy(c: {
25
25
  adapter: string;
26
26
  model: string;
27
- }, routing: DrovrConfig["routing"]): Disallowed | null;
27
+ }, routing: TickmarkrConfig["routing"]): Disallowed | null;
@@ -1,5 +1,5 @@
1
1
  import { type Assignment, type BillingChannel } from "../adapters/types.js";
2
- import { type DrovrConfig } from "../config/config.js";
2
+ import { type TickmarkrConfig } from "../config/config.js";
3
3
  import type { Task } from "../graph/schema.js";
4
4
  import { type RoutingProfile } from "./profile.js";
5
5
  export type LadderStep = "retry" | "escalate" | "consult" | "human";
@@ -30,5 +30,5 @@ export declare class RoutingError extends Error {
30
30
  constructor(msg: string);
31
31
  }
32
32
  export declare function marginalCostRank(c: BillingChannel): number;
33
- export declare function route(task: Task, cfg: DrovrConfig, channels: BillingChannel[], profile?: RoutingProfile, preferCtx?: RoutingPreferContext): Route;
34
- export declare function nextChannel(current: Assignment, task: Task, cfg: DrovrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile): Assignment | null;
33
+ export declare function route(task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], profile?: RoutingProfile, preferCtx?: RoutingPreferContext): Route;
34
+ export declare function nextChannel(current: Assignment, task: Task, cfg: TickmarkrConfig, channels: BillingChannel[], tried: string[], profile?: RoutingProfile): Assignment | null;
@@ -1,5 +1,5 @@
1
1
  import type { WorkerAdapter } from "../adapters/types.js";
2
- import type { DrovrConfig } from "../config/config.js";
2
+ import type { TickmarkrConfig } from "../config/config.js";
3
3
  import type { ExecutorDriver, Slot } from "../drivers/types.js";
4
4
  import type { GateResult } from "../gates/types.js";
5
5
  export interface ConsultVerdict {
@@ -16,7 +16,7 @@ export interface Dossier {
16
16
  gates: GateResult[];
17
17
  }
18
18
  export declare function buildDossierPrompt(d: Dossier): string;
19
- export declare function consult(d: Dossier, cfg: DrovrConfig, adapters: WorkerAdapter[], driver: ExecutorDriver, cwd: string, runDir: string, opts?: {
19
+ export declare function consult(d: Dossier, cfg: TickmarkrConfig, adapters: WorkerAdapter[], driver: ExecutorDriver, cwd: string, runDir: string, opts?: {
20
20
  keep?: boolean;
21
21
  onSlot?: (slot: Slot) => void;
22
22
  runId?: string;