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.
- package/README.md +347 -0
- package/dist/adapters/claude-code.d.ts +4 -0
- package/dist/adapters/claude-code.js +191 -0
- package/dist/adapters/codex.d.ts +8 -0
- package/dist/adapters/codex.js +257 -0
- package/dist/adapters/cursor-agent.d.ts +4 -0
- package/dist/adapters/cursor-agent.js +62 -0
- package/dist/adapters/fake.d.ts +45 -0
- package/dist/adapters/fake.js +115 -0
- package/dist/adapters/grok.d.ts +4 -0
- package/dist/adapters/grok.js +122 -0
- package/dist/adapters/model-lints.d.ts +6 -0
- package/dist/adapters/model-lints.js +130 -0
- package/dist/adapters/opencode.d.ts +3 -0
- package/dist/adapters/opencode.js +108 -0
- package/dist/adapters/pi.d.ts +3 -0
- package/dist/adapters/pi.js +191 -0
- package/dist/adapters/prompt.d.ts +6 -0
- package/dist/adapters/prompt.js +84 -0
- package/dist/adapters/registry.d.ts +31 -0
- package/dist/adapters/registry.js +165 -0
- package/dist/adapters/types.d.ts +98 -0
- package/dist/adapters/types.js +56 -0
- package/dist/cli/commands/approve.d.ts +1 -0
- package/dist/cli/commands/approve.js +72 -0
- package/dist/cli/commands/compile.d.ts +1 -0
- package/dist/cli/commands/compile.js +23 -0
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +103 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +32 -0
- package/dist/cli/commands/plan.d.ts +2 -0
- package/dist/cli/commands/plan.js +101 -0
- package/dist/cli/commands/profile.d.ts +1 -0
- package/dist/cli/commands/profile.js +56 -0
- package/dist/cli/commands/report.d.ts +4 -0
- package/dist/cli/commands/report.js +306 -0
- package/dist/cli/commands/resume.d.ts +1 -0
- package/dist/cli/commands/resume.js +11 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +35 -0
- package/dist/cli/commands/scope.d.ts +3 -0
- package/dist/cli/commands/scope.js +22 -0
- package/dist/cli/commands/status.d.ts +9 -0
- package/dist/cli/commands/status.js +162 -0
- package/dist/cli/commands/unlock.d.ts +1 -0
- package/dist/cli/commands/unlock.js +11 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +65 -0
- package/dist/compile/collateral.d.ts +6 -0
- package/dist/compile/collateral.js +128 -0
- package/dist/compile/common.d.ts +12 -0
- package/dist/compile/common.js +56 -0
- package/dist/compile/gsd.d.ts +3 -0
- package/dist/compile/gsd.js +222 -0
- package/dist/compile/index.d.ts +4 -0
- package/dist/compile/index.js +33 -0
- package/dist/compile/native.d.ts +4 -0
- package/dist/compile/native.js +196 -0
- package/dist/compile/prd.d.ts +2 -0
- package/dist/compile/prd.js +64 -0
- package/dist/compile/speckit.d.ts +2 -0
- package/dist/compile/speckit.js +66 -0
- package/dist/config/config.d.ts +184 -0
- package/dist/config/config.js +329 -0
- package/dist/drivers/herdr.d.ts +44 -0
- package/dist/drivers/herdr.js +394 -0
- package/dist/drivers/index.d.ts +3 -0
- package/dist/drivers/index.js +13 -0
- package/dist/drivers/subprocess.d.ts +25 -0
- package/dist/drivers/subprocess.js +106 -0
- package/dist/drivers/types.d.ts +61 -0
- package/dist/drivers/types.js +77 -0
- package/dist/gates/acceptance.d.ts +19 -0
- package/dist/gates/acceptance.js +92 -0
- package/dist/gates/baseline.d.ts +12 -0
- package/dist/gates/baseline.js +97 -0
- package/dist/gates/evidence.d.ts +4 -0
- package/dist/gates/evidence.js +12 -0
- package/dist/gates/llm.d.ts +25 -0
- package/dist/gates/llm.js +101 -0
- package/dist/gates/review.d.ts +12 -0
- package/dist/gates/review.js +80 -0
- package/dist/gates/run-gates.d.ts +34 -0
- package/dist/gates/run-gates.js +105 -0
- package/dist/gates/scope.d.ts +8 -0
- package/dist/gates/scope.js +32 -0
- package/dist/gates/types.d.ts +6 -0
- package/dist/gates/types.js +1 -0
- package/dist/graph/graph.d.ts +20 -0
- package/dist/graph/graph.js +148 -0
- package/dist/graph/schema.d.ts +185 -0
- package/dist/graph/schema.js +141 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plan/prompt.d.ts +4 -0
- package/dist/plan/prompt.js +31 -0
- package/dist/plan/scope.d.ts +17 -0
- package/dist/plan/scope.js +141 -0
- package/dist/report/cost.d.ts +24 -0
- package/dist/report/cost.js +89 -0
- package/dist/route/preference.d.ts +27 -0
- package/dist/route/preference.js +49 -0
- package/dist/route/profile.d.ts +65 -0
- package/dist/route/profile.js +197 -0
- package/dist/route/router.d.ts +26 -0
- package/dist/route/router.js +200 -0
- package/dist/run/consult.d.ts +23 -0
- package/dist/run/consult.js +80 -0
- package/dist/run/daemon.d.ts +23 -0
- package/dist/run/daemon.js +728 -0
- package/dist/run/git.d.ts +12 -0
- package/dist/run/git.js +56 -0
- package/dist/run/journal.d.ts +91 -0
- package/dist/run/journal.js +287 -0
- package/dist/run/lock.d.ts +29 -0
- package/dist/run/lock.js +182 -0
- package/dist/run/merge.d.ts +12 -0
- package/dist/run/merge.js +44 -0
- package/dist/run/reconcile.d.ts +4 -0
- package/dist/run/reconcile.js +95 -0
- package/package.json +39 -0
- 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
|
+
}
|