tickmarkr 1.50.0 → 1.52.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/dist/adapters/codex.js +3 -0
- package/dist/adapters/model-lints.js +2 -2
- package/dist/adapters/registry.js +56 -37
- package/dist/adapters/types.d.ts +1 -0
- package/dist/cli/commands/fleet.js +93 -33
- package/dist/cli/commands/plan.d.ts +1 -1
- package/dist/cli/commands/plan.js +49 -6
- package/dist/cli/commands/run.js +31 -12
- package/dist/compile/native.js +18 -2
- package/dist/config/config.d.ts +37 -10
- package/dist/config/config.js +175 -38
- package/dist/graph/schema.d.ts +6 -0
- package/dist/graph/schema.js +6 -0
- package/dist/route/profile.d.ts +12 -0
- package/dist/route/profile.js +31 -0
- package/dist/route/router.d.ts +1 -0
- package/dist/route/router.js +6 -6
- package/dist/run/daemon.d.ts +16 -0
- package/dist/run/daemon.js +55 -11
- package/dist/run/git.d.ts +2 -0
- package/dist/run/git.js +10 -1
- package/package.json +1 -1
package/dist/run/daemon.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { shq } from "../adapters/types.js";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
6
|
+
import { stringify } from "yaml";
|
|
5
7
|
import { trailerPattern, writePrompt } from "../adapters/prompt.js";
|
|
6
8
|
import { allAdapters, discoverChannels, getAdapter, probeAll, readDoctor } from "../adapters/registry.js";
|
|
7
9
|
import { addUsage, channelKey, matchesTrustDialog, QUOTA_RE } from "../adapters/types.js";
|
|
8
10
|
import { bannerShell } from "../brand.js";
|
|
9
|
-
import {
|
|
11
|
+
import { globalConfigDir, loadConfigWithMode, readOverlayFile, repoOverlayPath, } from "../config/config.js";
|
|
10
12
|
import { herdrSealShellPrefix, SubprocessDriver } from "../drivers/subprocess.js";
|
|
11
13
|
import { formatOwnedName } from "../drivers/types.js";
|
|
12
14
|
import { captureBaseline, detectGateCommands } from "../gates/baseline.js";
|
|
@@ -17,8 +19,41 @@ import { cleanupRunWorktrees, gitHead, linkNodeModules, sh, shGit, WORKTREE_LAYO
|
|
|
17
19
|
import { classifyWorkerResultCause, engagementComparable, Journal, loadRoutingProfile, newRunId } from "./journal.js";
|
|
18
20
|
import { acquireRunLock, releaseRunLock } from "./lock.js";
|
|
19
21
|
import { ensureIntegration, integrationBranch, integrationHead, mergeTask, verifyIntegrationTip } from "./merge.js";
|
|
20
|
-
import { nextChannel, route } from "../route/router.js";
|
|
22
|
+
import { nextChannel, QUALITY_ENV, route } from "../route/router.js";
|
|
21
23
|
import { desiredPanes } from "./reconcile.js";
|
|
24
|
+
const MODE_RANK = { "staff-led": 0, "risk-based": 1, "partner-led": 2 };
|
|
25
|
+
// An override (flag/spec) re-resolves through loadConfigWithMode itself, via a synthesized repo overlay
|
|
26
|
+
// carrying routing.mode — floors, explore, lints, and provenance all come from config.ts's preset
|
|
27
|
+
// compiler, never duplicated mode math here (the quality-silently-loses defense holds by construction).
|
|
28
|
+
function withOverlayMode(repoRoot, mode, globalDir) {
|
|
29
|
+
const overlay = readOverlayFile(repoOverlayPath(repoRoot));
|
|
30
|
+
const tmp = mkdtempSync(join(tmpdir(), "tickmarkr-mode-"));
|
|
31
|
+
try {
|
|
32
|
+
mkdirSync(join(tmp, ".tickmarkr"), { recursive: true });
|
|
33
|
+
writeFileSync(join(tmp, ".tickmarkr", "config.yaml"), stringify({ ...overlay, routing: { ...overlay.routing, mode } }));
|
|
34
|
+
return loadConfigWithMode(tmp, { globalDir });
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function resolveRunMode(repoRoot, opts = {}) {
|
|
41
|
+
const overlayMode = (path) => readOverlayFile(path).routing?.mode;
|
|
42
|
+
const source = opts.flag !== undefined ? "run flag"
|
|
43
|
+
: opts.spec !== undefined ? "spec"
|
|
44
|
+
: overlayMode(repoOverlayPath(repoRoot)) !== undefined ? "repo config"
|
|
45
|
+
: overlayMode(join(opts.globalDir ?? globalConfigDir(), "config.yaml")) !== undefined ? "global config"
|
|
46
|
+
: "default";
|
|
47
|
+
const override = opts.flag ?? opts.spec;
|
|
48
|
+
const base = loadConfigWithMode(repoRoot, { globalDir: opts.globalDir });
|
|
49
|
+
const resolved = override === undefined || override === base.mode.mode
|
|
50
|
+
? base
|
|
51
|
+
: withOverlayMode(repoRoot, override, opts.globalDir);
|
|
52
|
+
const conflict = opts.flag !== undefined && opts.spec !== undefined && MODE_RANK[opts.flag] < MODE_RANK[opts.spec]
|
|
53
|
+
? `mode conflict: run flag ${opts.flag} selects a mode below the spec-declared ${opts.spec} — the run flag wins this run`
|
|
54
|
+
: undefined;
|
|
55
|
+
return { cfg: resolved.cfg, mode: resolved.mode, source, ...(conflict ? { conflict } : {}) };
|
|
56
|
+
}
|
|
22
57
|
// VIS-01: one formatter, four readers (run-end journal event, run/resume CLI, run-end notify).
|
|
23
58
|
// Parity by construction — every caller renders the same complete bucket line.
|
|
24
59
|
export function formatSummary(s) {
|
|
@@ -54,14 +89,11 @@ async function cherryPickCommits(wt, commits) {
|
|
|
54
89
|
return carried;
|
|
55
90
|
}
|
|
56
91
|
export async function runDaemon(repoRoot, opts = {}) {
|
|
57
|
-
|
|
92
|
+
// v1.51 T2: retired --quality env seam. Mode resolution owns premium routing now; an exported
|
|
93
|
+
// TICKMARKR_QUALITY must not resurrect the old one-band floor raise in daemon dispatches.
|
|
94
|
+
delete process.env[QUALITY_ENV];
|
|
58
95
|
const adapters = opts.adapters ?? allAdapters();
|
|
59
96
|
const health = readDoctor(repoRoot) ?? (await probeAll(adapters));
|
|
60
|
-
const channels = discoverChannels(cfg, adapters, health);
|
|
61
|
-
// v1.6 ROUTE-06: build the learned profile ONCE at startup (never per task, never in the comparator).
|
|
62
|
-
// No preview — the daemon honors routing.learned:off and gets undefined; this snapshot is immutable
|
|
63
|
-
// for the run, so this run's own telemetry never feeds back into its own routing.
|
|
64
|
-
const profile = loadRoutingProfile(repoRoot, cfg);
|
|
65
97
|
const driver = opts.driver ?? new SubprocessDriver();
|
|
66
98
|
// HARD-01/02: hold the run lock across the whole read-modify-write of graph.json. Acquire
|
|
67
99
|
// BEFORE loadGraph; release in the finally below (every exit path, incl. throws).
|
|
@@ -72,6 +104,18 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
72
104
|
const lock = acquireRunLock(repoRoot, runId);
|
|
73
105
|
try {
|
|
74
106
|
let graph = loadGraph(repoRoot);
|
|
107
|
+
// v1.51 T2: the routing mode resolves BEFORE any routing input is built — run flag > spec front-matter
|
|
108
|
+
// > repo > global > default. The resolved cfg carries mode-compiled floors; route() never sees the mode.
|
|
109
|
+
const rm = resolveRunMode(repoRoot, { flag: opts.mode, spec: graph.mode, globalDir: opts.globalDir });
|
|
110
|
+
const cfg = rm.cfg;
|
|
111
|
+
// v1.51 T4: every dispatch provenance line begins with the mode and its source; when a pin won
|
|
112
|
+
// the route (the final "→ " segment is a pin, not a degraded-to-auto tail) it names the mode it bypassed.
|
|
113
|
+
const dispatchProvenance = (p) => `mode ${rm.mode.mode} (${rm.source})${p.split("→ ").pop().startsWith("pin ") ? ` — pin bypasses mode ${rm.mode.mode}` : ""} · ${p}`;
|
|
114
|
+
const channels = discoverChannels(cfg, adapters, health);
|
|
115
|
+
// v1.6 ROUTE-06: build the learned profile ONCE at startup (never per task, never in the comparator).
|
|
116
|
+
// No preview — the daemon honors routing.learned:off and gets undefined; this snapshot is immutable
|
|
117
|
+
// for the run, so this run's own telemetry never feeds back into its own routing.
|
|
118
|
+
const profile = loadRoutingProfile(repoRoot, cfg);
|
|
75
119
|
const journal = opts.resume ? Journal.open(repoRoot, runId, opts.narrate) : Journal.create(repoRoot, runId, opts.narrate);
|
|
76
120
|
const branchEvent = opts.resume
|
|
77
121
|
? [...journal.read()].reverse().find((e) => (e.event === "run-start" || e.event === "run-end" || e.event === "merge") && typeof e.data.branch === "string")
|
|
@@ -131,7 +175,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
131
175
|
baseRef = await gitHead(repoRoot);
|
|
132
176
|
baseline = await captureBaseline(repoRoot, commands);
|
|
133
177
|
writeFileSync(join(journal.dir, "baseline.json"), JSON.stringify(baseline, null, 2));
|
|
134
|
-
journal.append("run-start", undefined, { pid: process.pid, baseRef, commands, channels: channels.map(channelKey), branch, graphDefinitionHash: graphDefinitionHash(graph) }); // graphDefinitionHash: T3 engagement identity (status+resume share it); pid: v1.13 (VIS-11) liveness
|
|
178
|
+
journal.append("run-start", undefined, { pid: process.pid, baseRef, commands, channels: channels.map(channelKey), branch, graphDefinitionHash: graphDefinitionHash(graph), mode: rm.mode.mode, modeSource: rm.source }); // graphDefinitionHash: T3 engagement identity (status+resume share it); pid: v1.13 (VIS-11) liveness; mode/modeSource: v1.51 T2
|
|
135
179
|
}
|
|
136
180
|
// T6: open the narrator AFTER run-start/run-resume is journaled so the watch surface has a run to
|
|
137
181
|
// show. driver.narrator is undefined on subprocess → no-op (subprocess spawns nothing). Swallowed:
|
|
@@ -389,7 +433,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
389
433
|
lastContextTokens = undefined;
|
|
390
434
|
graph = setStatus(graph, t.id, "running");
|
|
391
435
|
saveGraph(repoRoot, graph);
|
|
392
|
-
journal.append("task-dispatch", t.id, { assignment, attempt, provenance: r.provenance, retryMode });
|
|
436
|
+
journal.append("task-dispatch", t.id, { assignment, attempt, provenance: dispatchProvenance(r.provenance), retryMode });
|
|
393
437
|
const taskBase = await integrationHead(intWt); // deps are merged → visible to this task
|
|
394
438
|
const taskBranch = `${branch}--${t.id}`; // "--": a ref can't nest under the existing integration branch (locked decision 10)
|
|
395
439
|
const priorWt = worktreePath(repoRoot, taskBranch);
|
package/dist/run/git.d.ts
CHANGED
package/dist/run/git.js
CHANGED
|
@@ -3,14 +3,23 @@ import { existsSync, lstatSync, readlinkSync, rmSync, symlinkSync } from "node:f
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { shq } from "../adapters/types.js";
|
|
5
5
|
import { tickmarkrDir } from "../graph/graph.js";
|
|
6
|
+
import { ROUTING_ENV_SEAMS } from "../route/router.js";
|
|
7
|
+
export { ROUTING_ENV_SEAMS };
|
|
6
8
|
// stdin "ignore": same class as HARD-05 / SubprocessDriver — never leave an open pipe a child can block on
|
|
7
9
|
// (pi -p / codex exec wait for stdin EOF). timedOut distinguishes SIGKILL-timeout from a real nonzero exit.
|
|
8
10
|
function shell(cmd, cwd, timeoutMs, login) {
|
|
11
|
+
// OBS-74: scrub tickmarkr's own routing env seams from every child — a daemon carrying
|
|
12
|
+
// TICKMARKR_QUALITY leaked it into baseline/gate/tip-verify children, turning a dogfood
|
|
13
|
+
// repo's route() tests red inside the gates. Scrub a copy at this one choke point so
|
|
14
|
+
// children are hermetic by construction; the daemon's own process.env stays unchanged.
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
for (const k of ROUTING_ENV_SEAMS)
|
|
17
|
+
delete env[k];
|
|
9
18
|
return new Promise((resolve) => {
|
|
10
19
|
// detached: bash gets its own process group so a timeout can kill the whole tree —
|
|
11
20
|
// SIGKILLing bash alone orphans grandchildren (codex/pi) that hold the stdio pipes
|
|
12
21
|
// open, so "close" never fires and the promise wedges forever (v1.33.1 init hang).
|
|
13
|
-
const p = spawn("bash", [login ? "-lc" : "-c", cmd], { cwd, stdio: ["ignore", "pipe", "pipe"], detached: true });
|
|
22
|
+
const p = spawn("bash", [login ? "-lc" : "-c", cmd], { cwd, env, stdio: ["ignore", "pipe", "pipe"], detached: true });
|
|
14
23
|
let stdout = "", stderr = "";
|
|
15
24
|
let timedOut = false, done = false;
|
|
16
25
|
const finish = (code, err) => {
|