tickmarkr 1.30.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 (123) hide show
  1. package/README.md +347 -0
  2. package/dist/adapters/claude-code.d.ts +4 -0
  3. package/dist/adapters/claude-code.js +191 -0
  4. package/dist/adapters/codex.d.ts +8 -0
  5. package/dist/adapters/codex.js +257 -0
  6. package/dist/adapters/cursor-agent.d.ts +4 -0
  7. package/dist/adapters/cursor-agent.js +62 -0
  8. package/dist/adapters/fake.d.ts +45 -0
  9. package/dist/adapters/fake.js +115 -0
  10. package/dist/adapters/grok.d.ts +4 -0
  11. package/dist/adapters/grok.js +122 -0
  12. package/dist/adapters/model-lints.d.ts +6 -0
  13. package/dist/adapters/model-lints.js +130 -0
  14. package/dist/adapters/opencode.d.ts +3 -0
  15. package/dist/adapters/opencode.js +108 -0
  16. package/dist/adapters/pi.d.ts +3 -0
  17. package/dist/adapters/pi.js +191 -0
  18. package/dist/adapters/prompt.d.ts +6 -0
  19. package/dist/adapters/prompt.js +84 -0
  20. package/dist/adapters/registry.d.ts +31 -0
  21. package/dist/adapters/registry.js +165 -0
  22. package/dist/adapters/types.d.ts +98 -0
  23. package/dist/adapters/types.js +56 -0
  24. package/dist/cli/commands/approve.d.ts +1 -0
  25. package/dist/cli/commands/approve.js +72 -0
  26. package/dist/cli/commands/compile.d.ts +1 -0
  27. package/dist/cli/commands/compile.js +23 -0
  28. package/dist/cli/commands/doctor.d.ts +2 -0
  29. package/dist/cli/commands/doctor.js +103 -0
  30. package/dist/cli/commands/init.d.ts +1 -0
  31. package/dist/cli/commands/init.js +32 -0
  32. package/dist/cli/commands/plan.d.ts +2 -0
  33. package/dist/cli/commands/plan.js +101 -0
  34. package/dist/cli/commands/profile.d.ts +1 -0
  35. package/dist/cli/commands/profile.js +56 -0
  36. package/dist/cli/commands/report.d.ts +4 -0
  37. package/dist/cli/commands/report.js +306 -0
  38. package/dist/cli/commands/resume.d.ts +1 -0
  39. package/dist/cli/commands/resume.js +11 -0
  40. package/dist/cli/commands/run.d.ts +1 -0
  41. package/dist/cli/commands/run.js +35 -0
  42. package/dist/cli/commands/scope.d.ts +3 -0
  43. package/dist/cli/commands/scope.js +22 -0
  44. package/dist/cli/commands/status.d.ts +9 -0
  45. package/dist/cli/commands/status.js +162 -0
  46. package/dist/cli/commands/unlock.d.ts +1 -0
  47. package/dist/cli/commands/unlock.js +11 -0
  48. package/dist/cli/index.d.ts +8 -0
  49. package/dist/cli/index.js +65 -0
  50. package/dist/compile/collateral.d.ts +6 -0
  51. package/dist/compile/collateral.js +128 -0
  52. package/dist/compile/common.d.ts +12 -0
  53. package/dist/compile/common.js +56 -0
  54. package/dist/compile/gsd.d.ts +3 -0
  55. package/dist/compile/gsd.js +222 -0
  56. package/dist/compile/index.d.ts +4 -0
  57. package/dist/compile/index.js +33 -0
  58. package/dist/compile/native.d.ts +4 -0
  59. package/dist/compile/native.js +196 -0
  60. package/dist/compile/prd.d.ts +2 -0
  61. package/dist/compile/prd.js +64 -0
  62. package/dist/compile/speckit.d.ts +2 -0
  63. package/dist/compile/speckit.js +66 -0
  64. package/dist/config/config.d.ts +184 -0
  65. package/dist/config/config.js +329 -0
  66. package/dist/drivers/herdr.d.ts +44 -0
  67. package/dist/drivers/herdr.js +394 -0
  68. package/dist/drivers/index.d.ts +3 -0
  69. package/dist/drivers/index.js +13 -0
  70. package/dist/drivers/subprocess.d.ts +25 -0
  71. package/dist/drivers/subprocess.js +106 -0
  72. package/dist/drivers/types.d.ts +61 -0
  73. package/dist/drivers/types.js +77 -0
  74. package/dist/gates/acceptance.d.ts +19 -0
  75. package/dist/gates/acceptance.js +92 -0
  76. package/dist/gates/baseline.d.ts +12 -0
  77. package/dist/gates/baseline.js +97 -0
  78. package/dist/gates/evidence.d.ts +4 -0
  79. package/dist/gates/evidence.js +12 -0
  80. package/dist/gates/llm.d.ts +25 -0
  81. package/dist/gates/llm.js +101 -0
  82. package/dist/gates/review.d.ts +12 -0
  83. package/dist/gates/review.js +80 -0
  84. package/dist/gates/run-gates.d.ts +34 -0
  85. package/dist/gates/run-gates.js +105 -0
  86. package/dist/gates/scope.d.ts +8 -0
  87. package/dist/gates/scope.js +32 -0
  88. package/dist/gates/types.d.ts +6 -0
  89. package/dist/gates/types.js +1 -0
  90. package/dist/graph/graph.d.ts +20 -0
  91. package/dist/graph/graph.js +148 -0
  92. package/dist/graph/schema.d.ts +185 -0
  93. package/dist/graph/schema.js +141 -0
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.js +1 -0
  96. package/dist/plan/prompt.d.ts +4 -0
  97. package/dist/plan/prompt.js +31 -0
  98. package/dist/plan/scope.d.ts +17 -0
  99. package/dist/plan/scope.js +141 -0
  100. package/dist/report/cost.d.ts +24 -0
  101. package/dist/report/cost.js +89 -0
  102. package/dist/route/preference.d.ts +27 -0
  103. package/dist/route/preference.js +49 -0
  104. package/dist/route/profile.d.ts +65 -0
  105. package/dist/route/profile.js +197 -0
  106. package/dist/route/router.d.ts +26 -0
  107. package/dist/route/router.js +200 -0
  108. package/dist/run/consult.d.ts +23 -0
  109. package/dist/run/consult.js +80 -0
  110. package/dist/run/daemon.d.ts +23 -0
  111. package/dist/run/daemon.js +728 -0
  112. package/dist/run/git.d.ts +12 -0
  113. package/dist/run/git.js +56 -0
  114. package/dist/run/journal.d.ts +91 -0
  115. package/dist/run/journal.js +287 -0
  116. package/dist/run/lock.d.ts +29 -0
  117. package/dist/run/lock.js +182 -0
  118. package/dist/run/merge.d.ts +12 -0
  119. package/dist/run/merge.js +44 -0
  120. package/dist/run/reconcile.d.ts +4 -0
  121. package/dist/run/reconcile.js +95 -0
  122. package/package.json +39 -0
  123. package/schema/rungraph.schema.json +306 -0
@@ -0,0 +1,184 @@
1
+ import { z } from "zod";
2
+ declare const TierEnum: z.ZodEnum<{
3
+ cheap: "cheap";
4
+ mid: "mid";
5
+ frontier: "frontier";
6
+ }>;
7
+ export type Tier = z.infer<typeof TierEnum>;
8
+ export declare const TIER_RANK: Record<Tier, number>;
9
+ export declare const MapEntrySchema: z.ZodObject<{
10
+ pin: z.ZodOptional<z.ZodObject<{
11
+ via: z.ZodString;
12
+ model: z.ZodString;
13
+ }, z.core.$strip>>;
14
+ tier: z.ZodOptional<z.ZodEnum<{
15
+ cheap: "cheap";
16
+ mid: "mid";
17
+ frontier: "frontier";
18
+ }>>;
19
+ prefer: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
+ escalate: z.ZodOptional<z.ZodBoolean>;
21
+ }, z.core.$strip>;
22
+ export type MapEntry = z.infer<typeof MapEntrySchema>;
23
+ export declare const TierEntrySchema: z.ZodObject<{
24
+ vendor: z.ZodString;
25
+ channel: z.ZodEnum<{
26
+ sub: "sub";
27
+ api: "api";
28
+ }>;
29
+ models: z.ZodRecord<z.ZodString, z.ZodEnum<{
30
+ cheap: "cheap";
31
+ mid: "mid";
32
+ frontier: "frontier";
33
+ }>>;
34
+ }, z.core.$strip>;
35
+ export type TierEntry = z.infer<typeof TierEntrySchema>;
36
+ export declare const ModelPricingSchema: z.ZodObject<{
37
+ inPerMtok: z.ZodNumber;
38
+ outPerMtok: z.ZodNumber;
39
+ cacheReadPerMtok: z.ZodOptional<z.ZodNumber>;
40
+ rateDate: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strip>;
42
+ export type ModelPricing = z.infer<typeof ModelPricingSchema>;
43
+ export declare const SubPricingSchema: z.ZodObject<{
44
+ planMonthly: z.ZodNumber;
45
+ windowsPerMonthLow: z.ZodNumber;
46
+ windowsPerMonthHigh: z.ZodNumber;
47
+ }, z.core.$strip>;
48
+ export type SubPricing = z.infer<typeof SubPricingSchema>;
49
+ export declare const DrovrConfigSchema: z.ZodObject<{
50
+ concurrency: z.ZodNumber;
51
+ driver: z.ZodEnum<{
52
+ auto: "auto";
53
+ herdr: "herdr";
54
+ subprocess: "subprocess";
55
+ }>;
56
+ integrationBranchPrefix: z.ZodString;
57
+ taskTimeoutMinutes: z.ZodNumber;
58
+ contextWarnTokens: z.ZodNumber;
59
+ setup: z.ZodOptional<z.ZodString>;
60
+ routing: z.ZodObject<{
61
+ map: z.ZodRecord<z.ZodString, z.ZodObject<{
62
+ pin: z.ZodOptional<z.ZodObject<{
63
+ via: z.ZodString;
64
+ model: z.ZodString;
65
+ }, z.core.$strip>>;
66
+ tier: z.ZodOptional<z.ZodEnum<{
67
+ cheap: "cheap";
68
+ mid: "mid";
69
+ frontier: "frontier";
70
+ }>>;
71
+ prefer: z.ZodOptional<z.ZodArray<z.ZodString>>;
72
+ escalate: z.ZodOptional<z.ZodBoolean>;
73
+ }, z.core.$strip>>;
74
+ floors: z.ZodRecord<z.ZodString, z.ZodEnum<{
75
+ cheap: "cheap";
76
+ mid: "mid";
77
+ frontier: "frontier";
78
+ }>>;
79
+ learned: z.ZodEnum<{
80
+ on: "on";
81
+ off: "off";
82
+ }>;
83
+ learnedTuning: z.ZodOptional<z.ZodObject<{
84
+ halfLifeRuns: z.ZodOptional<z.ZodNumber>;
85
+ availWeight: z.ZodOptional<z.ZodNumber>;
86
+ }, z.core.$strip>>;
87
+ allow: z.ZodOptional<z.ZodObject<{
88
+ adapters: z.ZodOptional<z.ZodArray<z.ZodString>>;
89
+ models: z.ZodOptional<z.ZodArray<z.ZodString>>;
90
+ }, z.core.$strip>>;
91
+ deny: z.ZodOptional<z.ZodObject<{
92
+ adapters: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ models: z.ZodOptional<z.ZodArray<z.ZodString>>;
94
+ }, z.core.$strip>>;
95
+ }, z.core.$strip>;
96
+ tiers: z.ZodRecord<z.ZodString, z.ZodObject<{
97
+ vendor: z.ZodString;
98
+ channel: z.ZodEnum<{
99
+ sub: "sub";
100
+ api: "api";
101
+ }>;
102
+ models: z.ZodRecord<z.ZodString, z.ZodEnum<{
103
+ cheap: "cheap";
104
+ mid: "mid";
105
+ frontier: "frontier";
106
+ }>>;
107
+ }, z.core.$strip>>;
108
+ pricing: z.ZodRecord<z.ZodString, z.ZodNumber>;
109
+ cost: z.ZodOptional<z.ZodObject<{
110
+ models: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
111
+ inPerMtok: z.ZodNumber;
112
+ outPerMtok: z.ZodNumber;
113
+ cacheReadPerMtok: z.ZodOptional<z.ZodNumber>;
114
+ rateDate: z.ZodOptional<z.ZodString>;
115
+ }, z.core.$strip>>>;
116
+ subs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
117
+ planMonthly: z.ZodNumber;
118
+ windowsPerMonthLow: z.ZodNumber;
119
+ windowsPerMonthHigh: z.ZodNumber;
120
+ }, z.core.$strip>>>;
121
+ }, z.core.$strip>>;
122
+ gates: z.ZodObject<{
123
+ build: z.ZodOptional<z.ZodString>;
124
+ test: z.ZodOptional<z.ZodString>;
125
+ lint: z.ZodOptional<z.ZodString>;
126
+ byShape: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodEnum<{
127
+ plan: "plan";
128
+ spec: "spec";
129
+ implement: "implement";
130
+ tests: "tests";
131
+ docs: "docs";
132
+ migration: "migration";
133
+ ui: "ui";
134
+ refactor: "refactor";
135
+ chore: "chore";
136
+ }> & z.core.$partial, z.ZodObject<{
137
+ acceptance: z.ZodOptional<z.ZodBoolean>;
138
+ review: z.ZodOptional<z.ZodBoolean>;
139
+ }, z.core.$loose>>>>;
140
+ }, z.core.$strip>;
141
+ scope: z.ZodOptional<z.ZodObject<{
142
+ allowDeviations: z.ZodOptional<z.ZodArray<z.ZodString>>;
143
+ }, z.core.$strip>>;
144
+ judge: z.ZodObject<{
145
+ adapter: z.ZodString;
146
+ model: z.ZodString;
147
+ }, z.core.$strip>;
148
+ review: z.ZodObject<{
149
+ complexityThreshold: z.ZodNumber;
150
+ required: z.ZodBoolean;
151
+ }, z.core.$strip>;
152
+ consult: z.ZodObject<{
153
+ adapter: z.ZodString;
154
+ model: z.ZodString;
155
+ stallMinutes: z.ZodNumber;
156
+ }, z.core.$strip>;
157
+ visibility: z.ZodObject<{
158
+ llm: z.ZodEnum<{
159
+ pane: "pane";
160
+ headless: "headless";
161
+ }>;
162
+ keepPanes: z.ZodEnum<{
163
+ run: "run";
164
+ attempt: "attempt";
165
+ forever: "forever";
166
+ }>;
167
+ worker: z.ZodEnum<{
168
+ interactive: "interactive";
169
+ print: "print";
170
+ }>;
171
+ workersPerTab: z.ZodNumber;
172
+ }, z.core.$strip>;
173
+ }, z.core.$strip>;
174
+ export type DrovrConfig = z.infer<typeof DrovrConfigSchema>;
175
+ export declare class ConfigError extends Error {
176
+ constructor(message: string);
177
+ }
178
+ export declare const DEFAULT_CONFIG: DrovrConfig;
179
+ export declare function globalConfigDir(): string;
180
+ export declare function loadConfig(repoRoot: string, opts?: {
181
+ globalDir?: string;
182
+ }): DrovrConfig;
183
+ export declare function configTemplate(): string;
184
+ export {};
@@ -0,0 +1,329 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { parse } from "yaml";
5
+ import { z } from "zod";
6
+ import { stateDirName } from "../graph/graph.js";
7
+ import { SHAPES, TIERS } from "../graph/schema.js";
8
+ const TierEnum = z.enum(TIERS, {
9
+ error: (iss) => `Invalid option: expected one of "cheap"|"mid"|"frontier" (got ${JSON.stringify(iss.input)})`,
10
+ });
11
+ export const TIER_RANK = { cheap: 0, mid: 1, frontier: 2 };
12
+ export const MapEntrySchema = z.object({
13
+ pin: z.object({ via: z.string(), model: z.string() }).optional(),
14
+ tier: TierEnum.optional(),
15
+ prefer: z.array(z.string()).optional(),
16
+ escalate: z.boolean().optional(),
17
+ });
18
+ export const TierEntrySchema = z.object({
19
+ vendor: z.string(),
20
+ channel: z.enum(["sub", "api"]),
21
+ models: z.record(z.string(), TierEnum),
22
+ });
23
+ // v1.10 FLEET-06: optional routing.allow/deny fleet preference; absent blocks ⇒ byte-identical routing/discovery.
24
+ // deny wins on conflict; presence of allow (even {} / empty arrays) activates allowlist (fail-closed).
25
+ const PrefBlockSchema = z.object({
26
+ adapters: z.array(z.string()).optional(),
27
+ models: z.array(z.string()).optional(),
28
+ });
29
+ // v1.20 REC-02: operator-maintained price table for the usage/cost report (src/report/cost.ts). Two
30
+ // channel economics, never conflated (spec cost model): API = tokens × per-Mtok rate; sub = flat plan
31
+ // amortized over a windows/month RANGE (+ an API-equivalent counterfactual when the model has a rate).
32
+ // ALL OPTIONAL: absent pricing ⇒ the estimator reports "not measurable" — never a crash or a fake $0.
33
+ // Rate source to copy from: LiteLLM's model_prices_and_context_window.json (github.com/BerriAI/litellm),
34
+ // the de-facto machine-readable price table ccusage/tokonomics both consume.
35
+ export const ModelPricingSchema = z.object({
36
+ inPerMtok: z.number().nonnegative(),
37
+ outPerMtok: z.number().nonnegative(),
38
+ cacheReadPerMtok: z.number().nonnegative().optional(),
39
+ rateDate: z.string().optional(), // ISO date the rate was verified; echoed in every estimate's basis
40
+ });
41
+ export const SubPricingSchema = z
42
+ .object({
43
+ planMonthly: z.number().positive(),
44
+ windowsPerMonthLow: z.number().int().positive(),
45
+ windowsPerMonthHigh: z.number().int().positive(),
46
+ })
47
+ .refine((s) => s.windowsPerMonthLow <= s.windowsPerMonthHigh, "windowsPerMonthLow must be ≤ windowsPerMonthHigh");
48
+ const ShapeGateParticipationSchema = z
49
+ .object({ acceptance: z.boolean().optional(), review: z.boolean().optional() })
50
+ .passthrough()
51
+ .superRefine((entry, ctx) => {
52
+ for (const gate of Object.keys(entry)) {
53
+ if (gate === "acceptance" || gate === "review")
54
+ continue;
55
+ const invariant = gate === "build" || gate === "test" || gate === "lint" ? "baseline" : gate;
56
+ const mandatory = invariant === "baseline" || invariant === "evidence" || invariant === "scope";
57
+ ctx.addIssue({
58
+ code: "custom",
59
+ path: [gate],
60
+ message: mandatory
61
+ ? `${invariant} is a mandatory fail-closed gate invariant and cannot be configured per shape`
62
+ : `gates.byShape may configure only acceptance and review (not ${gate})`,
63
+ });
64
+ }
65
+ });
66
+ export const DrovrConfigSchema = z.object({
67
+ concurrency: z.number().int().positive(),
68
+ driver: z.enum(["auto", "herdr", "subprocess"]),
69
+ integrationBranchPrefix: z
70
+ .string()
71
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "must be branch-safe (letters/digits/._/-, no spaces or shell metacharacters)")
72
+ .refine((v) => !v.includes(".."), "may not contain '..'"),
73
+ taskTimeoutMinutes: z.number().positive(),
74
+ // v1.23 T2: warn the operator once per attempt when a live worker's context fill crosses this
75
+ // token count (sampled at existing poll seams). Telemetry only — never blocks or kills a worker.
76
+ // Default 170_000 matches the overseer's proven ctx-watch.sh threshold.
77
+ contextWarnTokens: z.number().int().positive(),
78
+ setup: z.string().optional(),
79
+ routing: z.object({
80
+ map: z.record(z.string(), MapEntrySchema),
81
+ floors: z.record(z.string(), TierEnum),
82
+ learned: z.enum(["on", "off"]), // v1.6 ROUTE-09 kill switch; a typo (offf) fails loud via safeParse
83
+ // v1.9 ROUTE-15 — optional overrides for profile.ts HALF_LIFE_RUNS/AVAIL_WEIGHT; absent ⇒ byte-identical defaults.
84
+ // SIBLING of learned (not nested): routing.learned is the on/off enum switch.
85
+ learnedTuning: z.object({
86
+ halfLifeRuns: z.number().positive().optional(),
87
+ availWeight: z.number().nonnegative().optional(),
88
+ }).optional(),
89
+ allow: PrefBlockSchema.optional(),
90
+ deny: PrefBlockSchema.optional(),
91
+ }),
92
+ tiers: z.record(z.string(), TierEntrySchema),
93
+ pricing: z.record(z.string(), z.number()),
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.
96
+ // Absent ⇒ every channel reports "not measurable" (never $0). models keyed by model id (LiteLLM
97
+ // convention); subs keyed by adapter id (subscription plans are per-account, not per-token).
98
+ cost: z
99
+ .object({
100
+ models: z.record(z.string(), ModelPricingSchema).optional(),
101
+ subs: z.record(z.string(), SubPricingSchema).optional(),
102
+ })
103
+ .optional(),
104
+ gates: z.object({
105
+ build: z.string(),
106
+ test: z.string(),
107
+ lint: z.string(),
108
+ byShape: z.partialRecord(z.enum(SHAPES), ShapeGateParticipationSchema).optional(),
109
+ }).partial(),
110
+ scope: z.object({
111
+ // HARD-08: the ONLY authority that can excuse an out-of-scope edit. Globs, matched with the
112
+ // same picomatch options as files[] ({ dot: true }). Default [] = no excuses (fail-closed).
113
+ allowDeviations: z.array(z.string()),
114
+ }).partial().optional(),
115
+ judge: z.object({ adapter: z.string(), model: z.string() }),
116
+ review: z.object({ complexityThreshold: z.number(), required: z.boolean() }),
117
+ consult: z.object({ adapter: z.string(), model: z.string(), stallMinutes: z.number().positive() }),
118
+ visibility: z.object({
119
+ llm: z.enum(["pane", "headless"]),
120
+ keepPanes: z.enum(["run", "attempt", "forever"]),
121
+ worker: z.enum(["interactive", "print"]),
122
+ // VIS-09 item 2 (43-CONTEXT D-03): cap concurrent worker panes per WORKERS tab; the cap+1'th member
123
+ // overflows to a WORKERS-2 tab instead of further splitting tab 1 (bounded height, zero width risk).
124
+ // Positive int: zero/negative reddens the schema. Overlays omitting it parse (deepMerge over the
125
+ // default 3). Default 3: at a ~50-row terminal that's ~16 rows/pane (readable); the operator's
126
+ // 5-pane incident was ~10 rows each.
127
+ workersPerTab: z.number().int().positive(),
128
+ }),
129
+ });
130
+ export class ConfigError extends Error {
131
+ constructor(message) {
132
+ super(message);
133
+ this.name = "ConfigError";
134
+ }
135
+ }
136
+ export const DEFAULT_CONFIG = {
137
+ concurrency: 3,
138
+ driver: "auto",
139
+ integrationBranchPrefix: "drovr/",
140
+ taskTimeoutMinutes: 30,
141
+ contextWarnTokens: 170_000, // v1.23 T2: overseer ctx-watch.sh proven threshold
142
+ routing: {
143
+ map: {
144
+ plan: { pin: { via: "claude-code", model: "fable" } },
145
+ spec: { pin: { via: "claude-code", model: "fable" } },
146
+ implement: { tier: "mid", prefer: ["cursor-agent", "codex"] },
147
+ tests: { tier: "cheap", prefer: ["opencode"] },
148
+ docs: { tier: "cheap" },
149
+ },
150
+ floors: {
151
+ plan: "frontier", spec: "frontier", migration: "frontier",
152
+ implement: "mid", ui: "mid", refactor: "mid",
153
+ tests: "cheap", docs: "cheap", chore: "cheap",
154
+ },
155
+ // ROUTE-14 (2026-07-11, operator-adopted): learned reordering ON by default. Shipped OFF through v1.6/v1.7
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
158
+ // ROUTE-12, escalation tiebreak ROUTE-13). SAFE BY CONSTRUCTION: an empty/cold profile ⇒ every
159
+ // learnedScore returns exactly NEUTRAL ⇒ byte-identical v1.5 static routing, so ON only changes routing in
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).
162
+ learned: "on",
163
+ },
164
+ // Seed table (spec §13). New models = edit this (or your config.yaml), never code.
165
+ tiers: {
166
+ "claude-code": {
167
+ vendor: "anthropic", channel: "sub",
168
+ models: { fable: "frontier", opus: "frontier", sonnet: "mid", haiku: "cheap" },
169
+ },
170
+ // ids verified against installed CLI models_cache.json (codex 0.144.0, fetched 2026-07-09);
171
+ // gpt-5.4 / gpt-5.4-mini removed — OpenAI retirement 2026-07-23; sol=frontier / terra=mid / luna=cheap per OpenAI tier framing.
172
+ // MODEL-08 resolution (Phase 23, 23-LIVE-CHECK.md Finding 1): the v1.7 finding that codex refused
173
+ // gpt-5.6-sol was a codex 0.143.0 CLIENT-VERSION gate ("requires a newer version of Codex"), NOT a
174
+ // retired id. Installed codex 0.144.1 lists it (models_cache.json client_version 0.144.0, fetched
175
+ // 2026-07-10) AND a live runtime probe (`codex exec --model gpt-5.6-sol` → "OK", 2026-07-10) confirmed
176
+ // it runs. Reseeding away would be a regression — the CLI upgrade closed the gate. Seed stays.
177
+ codex: {
178
+ vendor: "openai", channel: "sub",
179
+ models: { "gpt-5.6-sol": "frontier", "gpt-5.5": "frontier", "gpt-5.6-terra": "mid", "gpt-5.6-luna": "cheap" },
180
+ },
181
+ // grok-4.5 (xAI, released 2026-07-08) → mid: AA Intelligence 54 (#4), Terminal-Bench 2.1 83.3%
182
+ // (≈ GPT-5.5 83.4, Fable 5 84.3), SWE-bench Pro 64.7%, ~4.2× more token-efficient than Opus 4.8
183
+ // on SWE-b Pro (15,954 vs 67,020 output tokens). Near-frontier on terminal work at cheap price;
184
+ // conservative mid per policy — learned routing / overlays may raise. Researched 2026-07-10,
185
+ // promoted from overlay 2026-07-11 (MODEL-10).
186
+ // grok-4.5-fast → cheap: speed-optimized variant, no independent benchmark scores yet → floor tier.
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.
189
+ // (Benchmark provenance above still informs the native grok adapter seeds below.)
190
+ "cursor-agent": {
191
+ vendor: "cursor", channel: "sub",
192
+ models: {
193
+ "composer-2.5": "mid",
194
+ },
195
+ },
196
+ // 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;
197
+ // no independent Terminal-Bench score → conservative mid, overlays may raise.
198
+ // MODEL-09 reseed (Phase 23, 23-LIVE-CHECK.md Finding 2): opencode 1.17.15 renamed the provider prefix
199
+ // opencode/ → zai-coding-plan/; the old id opencode/glm-5.2 is live-confirmed ABSENT and the new id
200
+ // zai-coding-plan/glm-5.2 present (`opencode models`, 2026-07-10). Tier stays mid — same model, benchmark
201
+ // unchanged (GLM-5.2 SWE-bench Pro 62.1).
202
+ opencode: {
203
+ vendor: "mixed", channel: "sub",
204
+ models: { "zai-coding-plan/glm-5.2": "mid" },
205
+ },
206
+ // GLM-5.2 via pi/ZAI Coding Plan (sub, flat-rate). mid per benchmark policy (2026-07):
207
+ // SWE-bench Pro 62.1, FrontierSWE 74.4 — same rationale as the opencode glm-5.2 seed above.
208
+ // id "zai/glm-5.2" verified via pi --list-models v0.80.3, 2026-07-10 (provider zai, model glm-5.2);
209
+ // provider-qualified so the channel never depends on ~/.pi/agent/settings.json defaults.
210
+ // Also exists as opencode:zai-coding-plan/glm-5.2 — two channels, one model, distinct channelKeys, intentional
211
+ // (opencode zen vs ZAI Coding Plan billing paths); review-diversity hole (zhipu vs mixed = same model
212
+ // cross-harness) is pre-existing, FLEET-05 owns it.
213
+ pi: {
214
+ vendor: "zhipu", channel: "sub",
215
+ models: { "zai/glm-5.2": "mid" },
216
+ },
217
+ // Native grok CLI (Phase 40). grok-4.5 → mid: same benchmark provenance as the retired cursor-agent
218
+ // grok-4.5-xhigh seed above (AA 54, TB2.1 83.3%, SWE-b Pro 64.7% — researched 2026-07-10).
219
+ // grok-composer-2.5-fast → cheap: fast variant, no independent benchmark scores yet → floor tier
220
+ // (same policy call as grok-4.5-fast, above). Ids live-verified `grok models` 2026-07-11, grok 0.2.93.
221
+ // Distinct channel from any former cursor-agent grok seed; channelKey() disambiguates (40-CONTEXT D-06).
222
+ grok: {
223
+ vendor: "xai", channel: "sub",
224
+ models: { "grok-4.5": "mid", "grok-composer-2.5-fast": "cheap" },
225
+ },
226
+ },
227
+ pricing: { cheap: 0.1, mid: 0.5, frontier: 2.5 },
228
+ gates: {},
229
+ judge: { adapter: "claude-code", model: "fable" },
230
+ review: { complexityThreshold: 7, required: true },
231
+ consult: { adapter: "claude-code", model: "fable", stallMinutes: 15 },
232
+ // v1.4: gate LLM calls (judge/review/consult) run headless by default; pane opts back into visible agents.
233
+ // v1.2: workers are the real agent TUI in the pane; "print" restores the -p-rendered-in-pane path.
234
+ visibility: { llm: "headless", keepPanes: "run", worker: "interactive", workersPerTab: 3 },
235
+ };
236
+ function deepMerge(base, over) {
237
+ if (over === undefined || over === null)
238
+ return base;
239
+ if (Array.isArray(base) || Array.isArray(over) || typeof base !== "object" || typeof over !== "object" || base === null) {
240
+ return over;
241
+ }
242
+ const out = { ...base };
243
+ for (const [k, v] of Object.entries(over)) {
244
+ if (v === null) {
245
+ delete out[k]; // v1.1 tombstone: an explicit null in an overlay removes the key (e.g. stale tiers model ids)
246
+ continue;
247
+ }
248
+ out[k] = k in out ? deepMerge(out[k], v) : v;
249
+ }
250
+ return out;
251
+ }
252
+ function readYaml(path) {
253
+ if (!existsSync(path))
254
+ return undefined;
255
+ return parse(readFileSync(path, "utf8"));
256
+ }
257
+ export function globalConfigDir() {
258
+ return process.env.XDG_CONFIG_HOME
259
+ ? join(process.env.XDG_CONFIG_HOME, "drovr")
260
+ : join(homedir(), ".config", "drovr");
261
+ }
262
+ export function loadConfig(repoRoot, opts = {}) {
263
+ const globalCfg = readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml"));
264
+ const repoCfg = readYaml(join(repoRoot, stateDirName(repoRoot), "config.yaml"));
265
+ const merged = deepMerge(deepMerge(structuredClone(DEFAULT_CONFIG), globalCfg), repoCfg);
266
+ const r = DrovrConfigSchema.safeParse(merged);
267
+ if (!r.success)
268
+ throw new ConfigError(z.prettifyError(r.error));
269
+ return r.data;
270
+ }
271
+ export function configTemplate() {
272
+ return `# drovr config overlay — merges over built-in defaults (repo beats global beats defaults)
273
+ # concurrency: 3
274
+ # driver: auto # auto | herdr | subprocess
275
+ # taskTimeoutMinutes: 30
276
+ # contextWarnTokens: 170000 # v1.23: journal+notify once per attempt when live worker context crosses this (status shows the sample)
277
+ # setup: npm ci --prefer-offline # run in each fresh task worktree before dispatch
278
+ # routing:
279
+ # map: # per-shape routing; pin an exact CLI+model, or tier+prefer
280
+ # implement: { tier: mid, prefer: [cursor-agent, codex] }
281
+ # migration: { pin: { via: claude-code, model: fable } }
282
+ # floors: # advisory minimum tiers; 'drovr plan' lints violations
283
+ # migration: frontier
284
+ # learned: on # default ON (ROUTE-14); cold profile = exact v1.5 static routing, warms per workspace. Set 'off' to pin static routing; preview with 'drovr plan'
285
+ # learnedTuning: { halfLifeRuns: 5, availWeight: 0.05 } # optional; defaults byte-identical
286
+ # allow: { adapters: [claude-code, codex] } # optional fleet allowlist; presence activates even if empty (fail-closed)
287
+ # deny: { models: [codex:gpt-5.5] } # optional fleet denylist; deny beats allow on conflict
288
+ # # entry grammar: adapter id | model id | adapter:model (entries in either list accept all three forms)
289
+ # # a hint pinning a denied channel FAILS at plan time (RoutingError) — never silent reroute
290
+ # # tombstone: deny: null in a repo overlay removes a global deny (arrays replace wholesale, never merge)
291
+ # tiers: # model → capability band; extend when new models ship
292
+ # claude-code: { vendor: anthropic, channel: sub, models: { fable: frontier } }
293
+ # codex: { models: { gpt-5.2-codex: null } } # null = tombstone: removes a stale seed id
294
+ # visibility:
295
+ # llm: headless # headless (default): judge/review/consult run silently | pane: visible agents
296
+ # keepPanes: run # run (default): ephemeral judge/review/consult panes close when read; a merged task's worker pane closes on done; other worker panes persist until run end | attempt | forever (keep everything for debugging)
297
+ # worker: interactive # interactive (default): workers run the real agent TUI | print
298
+ # workersPerTab: 3 # cap concurrent worker panes per WORKERS tab; overflow opens WORKERS-2 (VIS-09). Default 3
299
+ # scope:
300
+ # allowDeviations: [] # globs an operator permits out-of-scope edits into, e.g. ["package-lock.json"]
301
+ # gates: # override auto-detected commands; per-shape participation may only skip LLM gates
302
+ # test: npm test
303
+ # byShape:
304
+ # docs: { acceptance: false, review: false } # baseline, evidence, and scope are mandatory
305
+ # review: { complexityThreshold: 7, required: true }
306
+ # consult: { adapter: claude-code, model: fable, stallMinutes: 15 }
307
+ # cost: # v1.20 REC-02 — OPTIONAL price table for the usage/cost report. Absent ⇒ every
308
+ # # channel reports "not measurable" (never a crash or a fake $0). Two economics,
309
+ # # never conflated: API = tokens × per-Mtok rate; sub = flat plan amortized over a
310
+ # # windows/month range, plus an API-equivalent counterfactual when the model has a rate.
311
+ # models: # per-Mtok API rates by model id. Copy from LiteLLM's
312
+ # # model_prices_and_context_window.json (github.com/BerriAI/litellm) — the de-facto
313
+ # # price table ccusage/tokonomics consume. Stamp rateDate; refresh on vendor changes.
314
+ # opus: { inPerMtok: 5.0, outPerMtok: 25.0, cacheReadPerMtok: 0.5, rateDate: 2026-07-13 }
315
+ # sonnet: { inPerMtok: 3.0, outPerMtok: 15.0, rateDate: 2026-07-13 }
316
+ # subs: # subscription plans by adapter id (plans are per-account, not per-token).
317
+ # # windowsPerMonthLow/High is a RANGE (time-varying quotas, e.g. GLM peak 3×).
318
+ # claude-code: { planMonthly: 200, windowsPerMonthLow: 400, windowsPerMonthHigh: 1200 }
319
+ # NOTE: claude workers/judges run with --strict-mcp-config --mcp-config '{"mcpServers":{}}' — project
320
+ # .mcp.json servers (e.g. supabase) are UNAVAILABLE in worktrees. Deliberate: prevents the MCP-enable
321
+ # dialog stalling unattended HEADLESS runs (live-verified on claude 2.1.205). Interactive TUIs still
322
+ # show a first-entry dialog — but it is the workspace TRUST dialog, not MCP config loading (live-drilled
323
+ # claude 2.1.206, 2026-07-10). There is NO CLI flag to pre-accept trust; the only store is claude's global
324
+ # ~/.claude.json keyed on the EXACT worktree path. drovr does NOT write it (HYG-03 won't-fix, decision B:
325
+ # claude's own last-writer-wins persistence races any seed). Cost is ~one dismissal per worktree path,
326
+ # ever — drovr reuses stable worktree paths so the accept persists across runs; blocked-pane paging
327
+ # surfaces each first-time dialog.
328
+ `;
329
+ }
@@ -0,0 +1,44 @@
1
+ import { type ExecutorDriver, type NotifyOpts, type Slot, type SlotOpts } from "./types.js";
2
+ export declare const TRAILER_SAFE_FLOOR_COLS = 108;
3
+ export declare const TRAILER_WIDTH_MARGIN = 2;
4
+ /** First-generation join direction from measured trailer-safe floor (43-MEASUREMENT.md). */
5
+ export declare function workerSplitDirection(paneCols: number | null, safeFloor?: number, margin?: number): "right" | "down";
6
+ export declare class HerdrDriver implements ExecutorDriver {
7
+ private bin;
8
+ private workersPerTab;
9
+ id: string;
10
+ interactive: boolean;
11
+ private groups;
12
+ private groupSerial;
13
+ private ws;
14
+ constructor(bin?: string, workersPerTab?: number);
15
+ private serial;
16
+ static available(): boolean;
17
+ private herdr;
18
+ private paneId;
19
+ private paneWidth;
20
+ slot(cwd: string, name: string, opts?: SlotOpts): Promise<Slot>;
21
+ private tabSlot;
22
+ private groupSlot;
23
+ private newGeneration;
24
+ private renameGroupTab;
25
+ private glyphFor;
26
+ private joinGroup;
27
+ run(slot: Slot, cmd: string): Promise<void>;
28
+ waitOutput(slot: Slot, pattern: string, timeoutMs: number, opts?: {
29
+ regex?: boolean;
30
+ }): Promise<boolean>;
31
+ waitAgentStatus(slot: Slot, status: string, timeoutMs: number): Promise<boolean>;
32
+ status(slot: Slot): Promise<string>;
33
+ private statusByName;
34
+ read(slot: Slot, lines: number): Promise<string>;
35
+ sendKey(slot: Slot, key: string): Promise<void>;
36
+ notify(msg: string, opts?: NotifyOpts): Promise<void>;
37
+ close(slot: Slot): Promise<void>;
38
+ private closeGrouped;
39
+ narrator(cwd: string, command: string, runId?: string): Promise<Slot>;
40
+ reconcile(desired: Set<string>, runId: string, opts?: {
41
+ spareLiveLlm?: boolean;
42
+ }): Promise<void>;
43
+ worktree(repo: string, branch: string, baseRef: string): Promise<string>;
44
+ }