tickmarkr 1.97.0 → 2.0.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/brand.d.ts +28 -0
- package/dist/brand.js +41 -0
- package/dist/cli/commands/compile.js +32 -1
- package/dist/cli/commands/resume.js +9 -3
- package/dist/cli/commands/run.d.ts +61 -1
- package/dist/cli/commands/run.js +368 -17
- package/dist/cli/commands/status.js +145 -28
- package/dist/compile/collateral.d.ts +25 -0
- package/dist/compile/collateral.js +46 -11
- package/dist/compile/native.js +10 -0
- package/dist/drivers/herdr.d.ts +19 -13
- package/dist/drivers/herdr.js +88 -26
- package/dist/drivers/types.d.ts +2 -0
- package/dist/gates/acceptance.js +17 -7
- package/dist/gates/llm.d.ts +19 -0
- package/dist/gates/llm.js +104 -6
- package/dist/gates/run-gates.d.ts +18 -0
- package/dist/gates/run-gates.js +195 -29
- package/dist/gates/scope.d.ts +9 -1
- package/dist/gates/scope.js +22 -2
- package/dist/graph/graph.d.ts +1 -0
- package/dist/graph/graph.js +19 -2
- package/dist/report/compare.js +17 -2
- package/dist/run/daemon.d.ts +1 -8
- package/dist/run/daemon.js +231 -246
- package/dist/run/environment.d.ts +18 -1
- package/dist/run/environment.js +19 -2
- package/dist/run/journal.d.ts +50 -3
- package/dist/run/journal.js +181 -5
- package/dist/run/protocol.d.ts +4 -4
- package/dist/run/stall.d.ts +30 -0
- package/dist/run/stall.js +173 -0
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +20 -15
package/dist/brand.d.ts
CHANGED
|
@@ -48,6 +48,34 @@ export declare const TOKENS: {
|
|
|
48
48
|
readonly dim: (s: string) => string;
|
|
49
49
|
readonly bold: (s: string) => string;
|
|
50
50
|
};
|
|
51
|
+
/** Exact operator-approved live colours. Values stay hex so the authority is inspectable. */
|
|
52
|
+
export declare const LIVE_PALETTE: {
|
|
53
|
+
/** muted teal — brand identity and pass */
|
|
54
|
+
readonly brand: "#90C4A4";
|
|
55
|
+
/** cornflower blue — running state and information */
|
|
56
|
+
readonly running: "#5A76AE";
|
|
57
|
+
/** ice — primary text */
|
|
58
|
+
readonly text: "#E6FDFF";
|
|
59
|
+
/** cloud — chrome and secondary text */
|
|
60
|
+
readonly chrome: "#D9D7DD";
|
|
61
|
+
/** amethyst — attention and failure */
|
|
62
|
+
readonly attention: "#B07BAC";
|
|
63
|
+
};
|
|
64
|
+
export type LiveRole = keyof typeof LIVE_PALETTE;
|
|
65
|
+
/** Semantic live tokens. Aliases are intentional; no token introduces a sixth colour. */
|
|
66
|
+
export declare const LIVE: {
|
|
67
|
+
readonly brand: (s: string) => string;
|
|
68
|
+
readonly pass: (s: string) => string;
|
|
69
|
+
readonly running: (s: string) => string;
|
|
70
|
+
readonly information: (s: string) => string;
|
|
71
|
+
readonly text: (s: string) => string;
|
|
72
|
+
readonly primaryText: (s: string) => string;
|
|
73
|
+
readonly chrome: (s: string) => string;
|
|
74
|
+
readonly secondaryText: (s: string) => string;
|
|
75
|
+
readonly attention: (s: string) => string;
|
|
76
|
+
readonly failure: (s: string) => string;
|
|
77
|
+
readonly chip: (s: string) => string;
|
|
78
|
+
};
|
|
51
79
|
/**
|
|
52
80
|
* The glyph vocabulary — plain characters only; color layers on via tokens so
|
|
53
81
|
* shape survives NO_COLOR. Bracket toggles ([x]/[ ]) are forbidden on every surface.
|
package/dist/brand.js
CHANGED
|
@@ -122,6 +122,47 @@ export const dim = sgr("2");
|
|
|
122
122
|
export const bold = sgr("1");
|
|
123
123
|
/** Every semantic color token, for sweeps: each is TTY-gated and NO_COLOR-aware. */
|
|
124
124
|
export const TOKENS = { brand, brandChip, ok, fail, warn, dim, bold };
|
|
125
|
+
// ── operator live-surface palette (v1.99) ───────────────────────────────────
|
|
126
|
+
// The tokens above remain the global design system for one-shot surfaces. LIVE is the closed
|
|
127
|
+
// five-colour system for long-running operator surfaces. Semantic aliases below deliberately share
|
|
128
|
+
// renderers: glyphs and words retain the distinction between pass/brand, running/information,
|
|
129
|
+
// chrome/secondary text, and attention/failure when colour is unavailable.
|
|
130
|
+
/** Exact operator-approved live colours. Values stay hex so the authority is inspectable. */
|
|
131
|
+
export const LIVE_PALETTE = {
|
|
132
|
+
/** muted teal — brand identity and pass */
|
|
133
|
+
brand: "#90C4A4",
|
|
134
|
+
/** cornflower blue — running state and information */
|
|
135
|
+
running: "#5A76AE",
|
|
136
|
+
/** ice — primary text */
|
|
137
|
+
text: "#E6FDFF",
|
|
138
|
+
/** cloud — chrome and secondary text */
|
|
139
|
+
chrome: "#D9D7DD",
|
|
140
|
+
/** amethyst — attention and failure */
|
|
141
|
+
attention: "#B07BAC",
|
|
142
|
+
};
|
|
143
|
+
const liveSgr = (hex) => {
|
|
144
|
+
const channels = hex.slice(1).match(/.{2}/gu).map((channel) => Number.parseInt(channel, 16));
|
|
145
|
+
return sgr(`38;2;${channels.join(";")}`);
|
|
146
|
+
};
|
|
147
|
+
const liveBrand = liveSgr(LIVE_PALETTE.brand);
|
|
148
|
+
const liveRunning = liveSgr(LIVE_PALETTE.running);
|
|
149
|
+
const liveText = liveSgr(LIVE_PALETTE.text);
|
|
150
|
+
const liveChrome = liveSgr(LIVE_PALETTE.chrome);
|
|
151
|
+
const liveAttention = liveSgr(LIVE_PALETTE.attention);
|
|
152
|
+
/** Semantic live tokens. Aliases are intentional; no token introduces a sixth colour. */
|
|
153
|
+
export const LIVE = {
|
|
154
|
+
brand: liveBrand,
|
|
155
|
+
pass: liveBrand,
|
|
156
|
+
running: liveRunning,
|
|
157
|
+
information: liveRunning,
|
|
158
|
+
text: liveText,
|
|
159
|
+
primaryText: liveText,
|
|
160
|
+
chrome: liveChrome,
|
|
161
|
+
secondaryText: liveChrome,
|
|
162
|
+
attention: liveAttention,
|
|
163
|
+
failure: liveAttention,
|
|
164
|
+
chip: liveBrand,
|
|
165
|
+
};
|
|
125
166
|
/**
|
|
126
167
|
* The glyph vocabulary — plain characters only; color layers on via tokens so
|
|
127
168
|
* shape survives NO_COLOR. Bracket toggles ([x]/[ ]) are forbidden on every surface.
|
|
@@ -3,8 +3,28 @@ import { parseArgs } from "node:util";
|
|
|
3
3
|
import { collateralLints, sourceScopeLints } from "../../compile/collateral.js";
|
|
4
4
|
import { compileSource } from "../../compile/index.js";
|
|
5
5
|
import { saveGraph, stateDirName } from "../../graph/graph.js";
|
|
6
|
+
import { formatPriorFindingEvidence, readPriorRunEvidence } from "../../run/journal.js";
|
|
7
|
+
import { shGit } from "../../run/git.js";
|
|
6
8
|
import { acquireRunLock, releaseRunLock } from "../../run/lock.js";
|
|
7
9
|
import { harnessLine, resolveHarness } from "../harness.js";
|
|
10
|
+
async function mergedPendingDiagnostics(cwd, pending, merges) {
|
|
11
|
+
const head = await shGit("git rev-parse HEAD", cwd);
|
|
12
|
+
const base = head.code === 0 ? head.stdout.trim() : "";
|
|
13
|
+
if (!/^[0-9a-f]{40}$/i.test(base))
|
|
14
|
+
return [];
|
|
15
|
+
const lines = [];
|
|
16
|
+
for (const taskId of pending) {
|
|
17
|
+
const candidates = merges.filter((merge) => merge.taskId === taskId).reverse();
|
|
18
|
+
for (const merge of candidates) {
|
|
19
|
+
const ancestor = await shGit(`git merge-base --is-ancestor ${merge.commit} ${base}`, cwd);
|
|
20
|
+
if (ancestor.code !== 0)
|
|
21
|
+
continue;
|
|
22
|
+
lines.push(`${taskId}: merged in run ${merge.runId}; compiles as pending (plan not marked done) — this dispatch rebuilds it`);
|
|
23
|
+
break; // one pure-information line per pending task, newest reachable merge wins
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lines;
|
|
27
|
+
}
|
|
8
28
|
// v1.89 T4: harnessFrom is the resolver's INPUT (see plan.ts); the default is the INVOKED entrypoint
|
|
9
29
|
// (`process.argv[1]`, the bin symlink), never this module's own url — that names an internal module.
|
|
10
30
|
export async function compile(argv, cwd = process.cwd(), harnessFrom = process.argv[1]) {
|
|
@@ -22,6 +42,11 @@ export async function compile(argv, cwd = process.cwd(), harnessFrom = process.a
|
|
|
22
42
|
// resolve against the target repo, not the process cwd (the CLI test passes a tmp repo)
|
|
23
43
|
// Both modes reach the same pure compiler; --dry-run only removes the lock/write side effect below.
|
|
24
44
|
const g = compileSource(isAbsolute(src) ? src : join(cwd, src), values.type, cwd);
|
|
45
|
+
// One bounded read supplies both cross-run surfaces: unresolved findings below and merge facts for
|
|
46
|
+
// the ancestry check. Neither fact mutates the compiled graph; status and every readiness predicate
|
|
47
|
+
// remain the source compiler's answer.
|
|
48
|
+
const prior = readPriorRunEvidence(cwd, g.tasks);
|
|
49
|
+
const mergedPending = await mergedPendingDiagnostics(cwd, new Set(g.tasks.filter((task) => task.status === "pending").map((task) => task.id)), prior.merges);
|
|
25
50
|
const stateDir = stateDirName(cwd);
|
|
26
51
|
if (!values["dry-run"]) {
|
|
27
52
|
// HARD-01 / Sol #3: hold the same link(2) run lock as the daemon around saveGraph so compile
|
|
@@ -41,5 +66,11 @@ export async function compile(argv, cwd = process.cwd(), harnessFrom = process.a
|
|
|
41
66
|
const diagnostics = scopeLints.length
|
|
42
67
|
? `\nscope lints:\n${scopeLints.map((lint) => ` ! ${lint}`).join("\n")}`
|
|
43
68
|
: "";
|
|
44
|
-
|
|
69
|
+
const priorFindings = prior.findings.length
|
|
70
|
+
? `\nprior-run evidence:\n${prior.findings.map((finding) => ` ${formatPriorFindingEvidence(finding)}`).join("\n")}`
|
|
71
|
+
: "";
|
|
72
|
+
const mergeHistory = mergedPending.length
|
|
73
|
+
? `\nmerge history:\n${mergedPending.map((line) => ` ${line}`).join("\n")}`
|
|
74
|
+
: "";
|
|
75
|
+
return `${harnessLine(resolveHarness(harnessFrom))}\n${summary}${diagnostics}${priorFindings}${mergeHistory}`;
|
|
45
76
|
}
|
|
@@ -2,8 +2,8 @@ import { loadConfig } from "../../config/config.js";
|
|
|
2
2
|
import { pickDriver } from "../../drivers/index.js";
|
|
3
3
|
import { loadGraph } from "../../graph/graph.js";
|
|
4
4
|
import { formatSummary, runDaemon } from "../../run/daemon.js";
|
|
5
|
-
import { formatJournalNarration } from "../../run/journal.js";
|
|
6
5
|
import { denyPreferCollisionLine, denyPreferCollisions } from "../../route/preference.js";
|
|
6
|
+
import { narrationSink, bindNarration } from "./run.js";
|
|
7
7
|
const summaryGreen = (s) => s.failed.length === 0 && s.human.length === 0 && s.blocked.length === 0 && s.pending.length === 0
|
|
8
8
|
&& s.tipVerify !== "failed";
|
|
9
9
|
export async function resume(argv, cwd = process.cwd()) {
|
|
@@ -25,13 +25,19 @@ export async function resume(argv, cwd = process.cwd()) {
|
|
|
25
25
|
if (collisions.length) {
|
|
26
26
|
throw new Error(collisions.map(denyPreferCollisionLine).join("; "));
|
|
27
27
|
}
|
|
28
|
+
const narrate = narrationSink(runId);
|
|
28
29
|
const s = await runDaemon(cwd, {
|
|
29
30
|
runId,
|
|
30
31
|
resume: true,
|
|
31
32
|
graphChanged,
|
|
32
33
|
retryFailed,
|
|
33
|
-
driver
|
|
34
|
-
|
|
34
|
+
// bound to the same sink the daemon gets, so a driver-journaled recovery reaches this rail too
|
|
35
|
+
driver: bindNarration(pickDriver(cfg), narrate),
|
|
36
|
+
// v1.99 T2: the ONE narration sink — the quiet rail on a TTY, the raw journal formatter on a
|
|
37
|
+
// pipe. A resumed run meets the same surface a fresh one does; printing the raw formatter here
|
|
38
|
+
// would leave `resume` as the last place the old unfiltered dump survives. Bound to the run id
|
|
39
|
+
// the operator named, so a resumed run's lifecycle rows name THIS run and not a generic word.
|
|
40
|
+
narrate,
|
|
35
41
|
});
|
|
36
42
|
const out = `resumed ${s.runId} — ${formatSummary(s)}`;
|
|
37
43
|
return { out, code: summaryGreen(s) ? 0 : 2 };
|
|
@@ -1,6 +1,66 @@
|
|
|
1
|
+
import type { ExecutorDriver } from "../../drivers/types.js";
|
|
1
2
|
import { type JournalEvent } from "../../run/journal.js";
|
|
2
|
-
|
|
3
|
+
/** Closed repetitive set the rail suppresses on a TTY. An ungated `phase-start` joins them below —
|
|
4
|
+
* it is a phase counter, while a phase-start CARRYING a gate is the gate start the rail draws. */
|
|
5
|
+
export declare const TTY_NOISE_EVENTS: readonly ["worker-contact", "worker-status"];
|
|
6
|
+
type RailTone = "pass" | "fail" | "attention" | "active" | "neutral";
|
|
7
|
+
/** Closed retained set: the short operator label and the row's default tone. Labels are the rail's
|
|
8
|
+
* own vocabulary — a raw journal event name is what this surface exists to stop printing. A `pass`
|
|
9
|
+
* or `ok` datum on the event overrides the default tone, so one gate row can read either way.
|
|
10
|
+
*
|
|
11
|
+
* MEMBERSHIP RULE — the daemon journals far more than this, and an allowlist built from whatever
|
|
12
|
+
* the tests happened to cover masks real events. An event earns a row when it changes what the run
|
|
13
|
+
* will DO next or reports an OUTCOME of it: a routing decision, a worker result, a gate start or
|
|
14
|
+
* verdict, a repair, an escalation, a merge, a run lifecycle step. Everything else — how the daemon
|
|
15
|
+
* got there (worktree setup, launch mechanics, baseline and routing lints) and every poll-time
|
|
16
|
+
* observation (contact reads, quota banners, held dead-verdicts, context samples) — stays off the
|
|
17
|
+
* rail and on the pipe, where it is byte-identical to the raw journal.
|
|
18
|
+
*
|
|
19
|
+
* Applying that rule is what put `graph-rehash` and the worker-nudge family here: a rehash is the
|
|
20
|
+
* operator's audited `--graph-changed` release, journaled by the resumed run through THIS sink, and
|
|
21
|
+
* a nudge is a decision the daemon takes on the operator's behalf (it contacts the worker and arms a
|
|
22
|
+
* grace deadline that force-concludes the wait), whose answered/failed/expired rows are that
|
|
23
|
+
* decision's outcome. Neither is mechanics, and both reach this process's narrate callback. */
|
|
24
|
+
export declare const RAIL_ROWS: Record<string, {
|
|
25
|
+
label: string;
|
|
26
|
+
tone: RailTone;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* One rail row, or null when the TTY rail suppresses this event. Never wider than `columns`: the
|
|
30
|
+
* identity and the label are clipped SEPARATELY and the detail takes only what they leave, so a row
|
|
31
|
+
* can never wrap into a second line and can never lose its meaning to a long identity.
|
|
32
|
+
*/
|
|
33
|
+
export declare function narrationRow(event: JournalEvent, runId: string, columns?: number): string | null;
|
|
34
|
+
/** The narration line for one event of the run named by `runId`: the raw journal formatter on a pipe
|
|
35
|
+
* (byte-identical, every event), the quiet rail on a TTY. Null means the rail suppressed it — the
|
|
36
|
+
* caller prints nothing. */
|
|
37
|
+
export declare const narrationLine: (event: JournalEvent, runId: string) => string | null;
|
|
38
|
+
/**
|
|
39
|
+
* The daemon's narration sink, BOUND TO THE RUN IT NARRATES: the rail on a TTY, the raw journal
|
|
40
|
+
* formatter on a pipe, and nothing at all for an event the rail suppressed. EVERY command that drives
|
|
41
|
+
* a daemon owes its narration to this sink - a second call site that prints `formatJournalNarration`
|
|
42
|
+
* itself is a surface where the rail does not exist, and the operator meets the old unfiltered dump
|
|
43
|
+
* under the newly stacked board.
|
|
44
|
+
*
|
|
45
|
+
* The binding is what puts a real identity on the run-scoped rows: the daemon's `narrate` callback is
|
|
46
|
+
* handed one event and nothing else, and the lifecycle events carry no run id of their own, so the
|
|
47
|
+
* run id can only come from the command that owns the run. Both daemon-driving commands supply it:
|
|
48
|
+
* `run` below mints the id it then passes to the daemon, and `resume` (src/cli/commands/resume.ts)
|
|
49
|
+
* carries the id the operator named.
|
|
50
|
+
*/
|
|
51
|
+
export declare const narrationSink: (runId: string) => (event: JournalEvent) => void;
|
|
52
|
+
/**
|
|
53
|
+
* The run's driver, BOUND to the run's narration sink.
|
|
54
|
+
*
|
|
55
|
+
* A driver journals events of its own that the daemon never sees: `dispatch-retry` is appended by
|
|
56
|
+
* HerdrDriver from inside a pane recovery, through a Journal it opens itself (src/drivers/herdr.ts).
|
|
57
|
+
* Unbound, that event lands in the file and on the pipe while the rail — the operator's only live
|
|
58
|
+
* surface — stays silent about a redispatch that already happened. Both daemon-driving commands
|
|
59
|
+
* wrap their driver here so neither can forget the binding.
|
|
60
|
+
*/
|
|
61
|
+
export declare function bindNarration<D extends ExecutorDriver>(driver: D, narrate: (event: JournalEvent) => void): D;
|
|
3
62
|
export declare function run(argv: string[], cwd?: string): Promise<{
|
|
4
63
|
out: string;
|
|
5
64
|
code: number;
|
|
6
65
|
}>;
|
|
66
|
+
export {};
|