tickmarkr 1.97.0 → 2.1.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 (47) hide show
  1. package/README.md +19 -1
  2. package/dist/brand.d.ts +28 -0
  3. package/dist/brand.js +41 -0
  4. package/dist/cli/commands/compile.js +32 -1
  5. package/dist/cli/commands/doctor.d.ts +19 -0
  6. package/dist/cli/commands/doctor.js +59 -0
  7. package/dist/cli/commands/init.js +5 -2
  8. package/dist/cli/commands/resume.js +25 -8
  9. package/dist/cli/commands/run.d.ts +61 -1
  10. package/dist/cli/commands/run.js +372 -18
  11. package/dist/cli/commands/status.js +145 -28
  12. package/dist/cli/index.d.ts +1 -1
  13. package/dist/cli/index.js +1 -1
  14. package/dist/compile/collateral.d.ts +25 -0
  15. package/dist/compile/collateral.js +46 -11
  16. package/dist/compile/native.js +10 -0
  17. package/dist/config/config.d.ts +1 -0
  18. package/dist/config/config.js +2 -2
  19. package/dist/drivers/herdr.d.ts +19 -13
  20. package/dist/drivers/herdr.js +90 -26
  21. package/dist/drivers/index.d.ts +5 -1
  22. package/dist/drivers/index.js +16 -1
  23. package/dist/drivers/orca.d.ts +189 -0
  24. package/dist/drivers/orca.js +879 -0
  25. package/dist/drivers/types.d.ts +2 -0
  26. package/dist/gates/acceptance.js +17 -7
  27. package/dist/gates/llm.d.ts +19 -0
  28. package/dist/gates/llm.js +104 -6
  29. package/dist/gates/run-gates.d.ts +18 -0
  30. package/dist/gates/run-gates.js +195 -29
  31. package/dist/gates/scope.d.ts +9 -1
  32. package/dist/gates/scope.js +22 -2
  33. package/dist/graph/graph.d.ts +1 -0
  34. package/dist/graph/graph.js +19 -2
  35. package/dist/report/compare.js +17 -2
  36. package/dist/run/daemon.d.ts +1 -8
  37. package/dist/run/daemon.js +231 -246
  38. package/dist/run/environment.d.ts +18 -1
  39. package/dist/run/environment.js +19 -2
  40. package/dist/run/journal.d.ts +50 -3
  41. package/dist/run/journal.js +181 -5
  42. package/dist/run/protocol.d.ts +4 -4
  43. package/dist/run/stall.d.ts +30 -0
  44. package/dist/run/stall.js +173 -0
  45. package/dist/tui/ink/init-app.js +14 -4
  46. package/package.json +1 -1
  47. package/skills/tickmarkr-overseer/SKILL.md +27 -15
@@ -1,3 +1,4 @@
1
+ import { classifyScopeOffenders } from "../compile/collateral.js";
1
2
  import { filesGlob } from "../graph/files-glob.js";
2
3
  import { shGitOk } from "../run/git.js";
3
4
  // OBS-61: the live integration tip can advance between attempts (sibling merge) while a resumed
@@ -22,7 +23,12 @@ export function dispositionOffenders(offenders, allowDeviations) {
22
23
  }
23
24
  return { hard, allowed };
24
25
  }
25
- export async function scopeGate(worktree, integrationTip, files, result, allowDeviations = []) {
26
+ /**
27
+ * OBS-547: `collateral` is this task's slice of the run's ONE full prediction map (computed at run
28
+ * start, uncapped — src/compile/collateral.ts). Absent ⇒ no classification, today's behaviour; the
29
+ * gate never computes a map of its own, so what it classifies on is always what the run predicted.
30
+ */
31
+ export async function scopeGate(worktree, integrationTip, files, result, allowDeviations = [], collateral) {
26
32
  if (!files.length)
27
33
  return { gate: "scope", pass: true, details: "no file scope declared — unrestricted" };
28
34
  const baseRef = await scopeDiffBase(worktree, integrationTip);
@@ -38,5 +44,19 @@ export async function scopeGate(worktree, integrationTip, files, result, allowDe
38
44
  if (!hard.length) {
39
45
  return { gate: "scope", pass: true, details: `out-of-scope but operator-allowlisted:\n${allowed.join("\n")}${note}` };
40
46
  }
41
- return { gate: "scope", pass: false, details: `out-of-scope edits not covered by scope.allowDeviations:\n${hard.join("\n")}${note}` };
47
+ // OBS-547: cross-reference at the red — the red supplies the paths, the prediction the classification.
48
+ const verdict = collateral
49
+ ? classifyScopeOffenders(collateral.taskId, hard, collateral.predicted)
50
+ : undefined;
51
+ const classification = verdict?.authoring
52
+ ? `\nauthoring defect (OBS-547): the collateral lint named every one of these paths before dispatch. Repair:\n${verdict.repair}`
53
+ : verdict && verdict.missed.length
54
+ ? `\ncollateral lint did not predict: ${verdict.missed.join(", ")}`
55
+ : "";
56
+ return {
57
+ gate: "scope",
58
+ pass: false,
59
+ details: `out-of-scope edits not covered by scope.allowDeviations:\n${hard.join("\n")}${note}${classification}`,
60
+ ...(verdict ? { meta: { collateral: verdict } } : {}),
61
+ };
42
62
  }
@@ -2,6 +2,7 @@ import { type RunGraph, type Task, type TaskStatus } from "./schema.js";
2
2
  export declare function stateDirName(_repoRoot: string): string;
3
3
  export declare function graphPath(repoRoot: string): string;
4
4
  export declare function graphDefinitionHash(g: RunGraph): string;
5
+ export declare function taskContentDigest(task: Pick<Task, "goal" | "files" | "acceptance">): string;
5
6
  export declare function tickmarkrDir(repoRoot: string): string;
6
7
  export declare function loadGraph(repoRoot: string): RunGraph;
7
8
  export declare function saveGraph(repoRoot: string, g: RunGraph): void;
@@ -19,6 +19,16 @@ export function graphDefinitionHash(g) {
19
19
  const definitions = g.tasks.map(({ status: _status, evidence: _evidence, ...def }) => def);
20
20
  return createHash("sha256").update(JSON.stringify({ version: g.version, spec: g.spec, tasks: definitions })).digest("hex").slice(0, 16);
21
21
  }
22
+ // OBS-543: cross-run evidence belongs to the artifact one task describes, not to the whole compiled
23
+ // graph. A sibling task, dependency, routing hint or status change therefore cannot expire a useful
24
+ // finding; changing the goal, write surface or acceptance contract does. Keep the full digest here:
25
+ // unlike graphDefinitionHash this value is persisted beside evidence and is the fail-closed join a
26
+ // later run uses, so there is no benefit in making collision diagnosis less explicit.
27
+ export function taskContentDigest(task) {
28
+ return createHash("sha256")
29
+ .update(JSON.stringify({ goal: task.goal, files: task.files, acceptance: task.acceptance }))
30
+ .digest("hex");
31
+ }
22
32
  export function tickmarkrDir(repoRoot) {
23
33
  const dir = join(repoRoot, stateDirName(repoRoot));
24
34
  mkdirSync(dir, { recursive: true });
@@ -59,7 +69,14 @@ export function setStatus(g, id, status) {
59
69
  return { ...g, tasks: g.tasks.map((t) => (t.id === id ? { ...t, status } : t)) };
60
70
  }
61
71
  export function addEvidence(g, id, patch) {
62
- getTask(g, id);
72
+ const subject = getTask(g, id);
73
+ const digest = taskContentDigest(subject);
74
+ // This is the graph-evidence boundary where a gate result meets the task it measured. Stamp a
75
+ // copy, never mutate the gate result runGates returned: callers still use that live object for
76
+ // predicates, while durable evidence gains the content identity a later run can compare.
77
+ const gateResults = (patch.gateResults ?? []).map((result) => result !== null && typeof result === "object" && !Array.isArray(result)
78
+ ? { ...result, taskContentDigest: digest }
79
+ : result);
63
80
  return {
64
81
  ...g,
65
82
  tasks: g.tasks.map((t) => t.id === id
@@ -68,7 +85,7 @@ export function addEvidence(g, id, patch) {
68
85
  evidence: {
69
86
  commits: [...t.evidence.commits, ...(patch.commits ?? [])],
70
87
  artifacts: [...t.evidence.artifacts, ...(patch.artifacts ?? [])],
71
- gateResults: [...t.evidence.gateResults, ...(patch.gateResults ?? [])],
88
+ gateResults: [...t.evidence.gateResults, ...gateResults],
72
89
  },
73
90
  }
74
91
  : t),
@@ -21,7 +21,21 @@ export function recordedEnvironment(events) {
21
21
  return undefined;
22
22
  adapterVersions[k] = v;
23
23
  }
24
- return { tickmarkrVersion: o.tickmarkrVersion, configHash: o.configHash, adapterVersions };
24
+ // Capacity arrived in v2.0 as a pair. Two absent fields are an honest older record; one without
25
+ // the other is a malformed new record and fails closed instead of being compared as legacy.
26
+ const hasCores = Object.hasOwn(o, "cores");
27
+ const hasForkCap = Object.hasOwn(o, "forkCap");
28
+ if (hasCores !== hasForkCap)
29
+ return undefined;
30
+ if (hasCores && (typeof o.cores !== "number" || !Number.isFinite(o.cores) || o.cores <= 0
31
+ || typeof o.forkCap !== "number" || !Number.isFinite(o.forkCap) || o.forkCap <= 0))
32
+ return undefined;
33
+ return {
34
+ tickmarkrVersion: o.tickmarkrVersion,
35
+ configHash: o.configHash,
36
+ adapterVersions,
37
+ ...(hasCores ? { cores: o.cores, forkCap: o.forkCap } : {}),
38
+ };
25
39
  }
26
40
  return undefined;
27
41
  }
@@ -31,7 +45,8 @@ function envFingerprint(env) {
31
45
  return env.configHash;
32
46
  }
33
47
  function envEqual(a, b) {
34
- if (a.tickmarkrVersion !== b.tickmarkrVersion || a.configHash !== b.configHash)
48
+ if (a.tickmarkrVersion !== b.tickmarkrVersion || a.configHash !== b.configHash
49
+ || a.cores !== b.cores || a.forkCap !== b.forkCap)
35
50
  return false;
36
51
  const ak = Object.keys(a.adapterVersions).sort();
37
52
  const bk = Object.keys(b.adapterVersions).sort();
@@ -4,6 +4,7 @@ import { type ExecutorDriver } from "../drivers/types.js";
4
4
  import { type Baseline } from "../gates/baseline.js";
5
5
  import type { GateResult } from "../gates/types.js";
6
6
  import { Journal, type JournalEvent } from "./journal.js";
7
+ export { harvestCpuFlatWindowMs, resetHarvestCpuFlatMsForTests, setHarvestCpuFlatMsForTests, workerTreeCpuMs, } from "./stall.js";
7
8
  export interface RunOptions {
8
9
  runId?: string;
9
10
  resume?: boolean;
@@ -117,10 +118,6 @@ export declare function resetDeadChannelFastKillMsForTests(): void;
117
118
  /** Test seam — shrink the harvest silence gate without minute-long sleeps. */
118
119
  export declare function setHarvestSilentMsForTests(ms: number): void;
119
120
  export declare function resetHarvestSilentMsForTests(): void;
120
- export declare function harvestCpuFlatWindowMs(resolutionMs: number): number;
121
- /** Test seam — pin the flat window so a probe case need not sit through a real one. */
122
- export declare function setHarvestCpuFlatMsForTests(ms: number): void;
123
- export declare function resetHarvestCpuFlatMsForTests(): void;
124
121
  export declare const HARVESTED_RESULT_SUMMARY = "harvested: the worktree carries committed work; the worker emitted no TICKMARKR_RESULT trailer";
125
122
  /** T4 (OBS-266): identity of the command SET a tip verify ran — a changed command is a different verify. */
126
123
  export declare function commandsHash(commands: Record<string, string>): string;
@@ -139,10 +136,6 @@ export declare function verifyIntegrationTipCached(intWt: string, commands: Reco
139
136
  lastMergedTask?: string;
140
137
  baseline?: Baseline;
141
138
  }): Promise<boolean>;
142
- export declare function workerTreeCpuMs(marker: string, cwd: string): Promise<{
143
- ms: number;
144
- resolutionMs: number;
145
- } | undefined>;
146
139
  /** Test seam — exercise the production observer's total read bound with a small real tree. */
147
140
  export declare function setObserveBudgetBytesForTests(bytes: number): void;
148
141
  export declare function resetObserveBudgetBytesForTests(): void;