tickmarkr 1.47.0 → 1.48.1
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/kimi.d.ts +5 -0
- package/dist/adapters/kimi.js +79 -0
- package/dist/adapters/registry.d.ts +11 -0
- package/dist/adapters/registry.js +41 -5
- package/dist/cli/commands/doctor.js +7 -3
- package/dist/cli/commands/init.js +3 -4
- package/dist/config/config.js +12 -0
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +2 -2
- package/skills/tickmarkr-loop/SKILL.md +2 -2
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type WorkerAdapter, type WorkerResult } from "./types.js";
|
|
2
|
+
export declare function kimiAuthed(credentialsText: string, nowMs: number): boolean;
|
|
3
|
+
export declare function parseKimiModels(raw: string): string[];
|
|
4
|
+
export declare function parseKimiResult(raw: string, nonce: string): WorkerResult;
|
|
5
|
+
export declare const kimi: WorkerAdapter;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { probeVersion } from "./claude-code.js";
|
|
6
|
+
import { parseWorkerResult } from "./prompt.js";
|
|
7
|
+
import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
8
|
+
// KIMI-03: collectUsage is DELIBERATELY ABSENT — documented won't-implement, consistent with the
|
|
9
|
+
// metering-honesty invariant (adapters/types.ts). Sessions live under ~/.kimi-code/sessions/ with
|
|
10
|
+
// kimi export (ZIP) + session_index.jsonl; no harness-readable per-attempt token counter was found
|
|
11
|
+
// in the session store (research F-6, 2026-07-17). Consequence: kimi channels report honestly
|
|
12
|
+
// `unmetered` — same class as grok (grok.ts:11-39). Revisit if kimi ships usage in the
|
|
13
|
+
// `-p --output-format stream-json` envelope or a counter in the session store.
|
|
14
|
+
const CREDENTIALS_PATH = join(homedir(), ".kimi-code", "credentials", "kimi-code.json");
|
|
15
|
+
// KIMI-01. Auth verdict from ~/.kimi-code/credentials/kimi-code.json ONLY — flat JSON, never a
|
|
16
|
+
// network call. expires_at is epoch SECONDS (not ISO like grok). Non-empty refresh_token dominates
|
|
17
|
+
// an expired expires_at (device-code flow auto-refreshes). Missing/unreadable/garbage ⇒ authed:false.
|
|
18
|
+
export function kimiAuthed(credentialsText, nowMs) {
|
|
19
|
+
try {
|
|
20
|
+
const j = JSON.parse(credentialsText);
|
|
21
|
+
if (typeof j.refresh_token === "string" && j.refresh_token.length > 0)
|
|
22
|
+
return true;
|
|
23
|
+
const exp = typeof j.expires_at === "number" ? j.expires_at * 1000 : Number(j.expires_at) * 1000;
|
|
24
|
+
return Number.isFinite(exp) && exp > nowMs;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// KIMI-04 listModels parse: `kimi provider list --json` models map keys. Fail-open to [].
|
|
31
|
+
export function parseKimiModels(raw) {
|
|
32
|
+
try {
|
|
33
|
+
const j = JSON.parse(raw);
|
|
34
|
+
if (!j.models || typeof j.models !== "object")
|
|
35
|
+
return [];
|
|
36
|
+
return Object.keys(j.models).filter((id) => id.length > 0 && MODEL_ID_RE.test(id));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// KIMI-02 parse: kimi -p text output prefixes thinking/answer lines with `• `. Strip before trailer
|
|
43
|
+
// scan so JSON.parse succeeds (research F-4 fixture). Resume line after the trailer is ignored by
|
|
44
|
+
// last-valid-trailer-wins in parseWorkerResult.
|
|
45
|
+
export function parseKimiResult(raw, nonce) {
|
|
46
|
+
const stripped = raw.split("\n").map((l) => l.replace(/^[\s]*[•*-]\s+/, "")).join("\n");
|
|
47
|
+
return parseWorkerResult(stripped, nonce);
|
|
48
|
+
}
|
|
49
|
+
export const kimi = {
|
|
50
|
+
id: "kimi",
|
|
51
|
+
vendor: "moonshot",
|
|
52
|
+
probeCwd: "neutral",
|
|
53
|
+
probe: async () => {
|
|
54
|
+
const h = probeVersion("kimi");
|
|
55
|
+
if (!h.installed)
|
|
56
|
+
return h;
|
|
57
|
+
let authed = false;
|
|
58
|
+
try {
|
|
59
|
+
authed = kimiAuthed(readFileSync(CREDENTIALS_PATH, "utf8"), Date.now());
|
|
60
|
+
}
|
|
61
|
+
catch { /* missing/unreadable ⇒ truthfully unauthed */ }
|
|
62
|
+
return authed
|
|
63
|
+
? { ...h, note: "auth verified via ~/.kimi-code/credentials/kimi-code.json (free, no network)" }
|
|
64
|
+
: { ...h, authed: false, note: "no valid ~/.kimi-code/credentials/kimi-code.json (kimi login to fix)" };
|
|
65
|
+
},
|
|
66
|
+
channels: (cfg) => channelsFromConfig("kimi", cfg),
|
|
67
|
+
// KIMI-02 headless. -p prompt mode + -y yolo + explicit model. kimi 0.27.0 rejects -p combined
|
|
68
|
+
// with -y at parse time — tickmarkr still emits both per harness contract; interactive uses -y.
|
|
69
|
+
headlessCommand: (promptFile, model) => `kimi -p "$(cat ${shq(promptFile)})" -y --model ${shq(model)} --output-format text`,
|
|
70
|
+
interactiveCommand: (promptFile, model) => `kimi -y --model ${shq(model)} "$(cat ${shq(promptFile)})"`,
|
|
71
|
+
invoke(task, _cwd, a, ctx) {
|
|
72
|
+
return { command: this.headlessCommand(ctx.promptFile, a.model) };
|
|
73
|
+
},
|
|
74
|
+
parse: parseKimiResult,
|
|
75
|
+
listModels: async () => {
|
|
76
|
+
const r = spawnSync("kimi", ["provider", "list", "--json"], { encoding: "utf8", timeout: 15000 });
|
|
77
|
+
return r.error || r.status !== 0 ? [] : parseKimiModels(r.stdout || "");
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type TickmarkrConfig } from "../config/config.js";
|
|
2
2
|
import { type RoutingProfile } from "../route/profile.js";
|
|
3
3
|
import { type AuthHealth, type BillingChannel, type WorkerAdapter } from "./types.js";
|
|
4
|
+
export declare const CANDIDATE_CLI_CATALOG: readonly ["kimi", "gemini", "qwen", "aider", "goose", "amp", "droid", "auggie", "crush"];
|
|
5
|
+
export declare const REGISTERED_ADAPTER_BINARIES: readonly ["claude", "codex", "cursor-agent", "opencode", "pi", "grok"];
|
|
6
|
+
export type CandidateCliDetection = {
|
|
7
|
+
binary: string;
|
|
8
|
+
version: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function probeCandidateVersion(bin: string): string | undefined;
|
|
11
|
+
export declare function detectCandidateClis(opts?: {
|
|
12
|
+
pathEnv?: string;
|
|
13
|
+
}): CandidateCliDetection[];
|
|
14
|
+
export declare function formatCandidateCliRow({ binary, version }: CandidateCliDetection): string;
|
|
4
15
|
export declare function allAdapters(opts?: {
|
|
5
16
|
fakeScriptPath?: string;
|
|
6
17
|
}): WorkerAdapter[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { existsSync, mkdtempSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
@@ -14,14 +15,49 @@ import { codex } from "./codex.js";
|
|
|
14
15
|
import { cursorAgent } from "./cursor-agent.js";
|
|
15
16
|
import { FakeAdapter } from "./fake.js";
|
|
16
17
|
import { grok } from "./grok.js";
|
|
18
|
+
import { kimi } from "./kimi.js";
|
|
17
19
|
import { opencode } from "./opencode.js";
|
|
18
20
|
import { pi } from "./pi.js";
|
|
19
21
|
import { channelKey, modelAuthed, QUOTA_RE } from "./types.js";
|
|
22
|
+
// Agent-CLI binary names with no tickmarkr adapter — doctor sweeps these advisory-only (v1.48 T1).
|
|
23
|
+
export const CANDIDATE_CLI_CATALOG = ["kimi", "gemini", "qwen", "aider", "goose", "amp", "droid", "auggie", "crush"];
|
|
24
|
+
// Binaries probed by registered adapters — kept beside the catalog so a test can forbid overlap.
|
|
25
|
+
export const REGISTERED_ADAPTER_BINARIES = ["claude", "codex", "cursor-agent", "opencode", "pi", "grok"];
|
|
26
|
+
function candidateOnPath(bin, pathEnv) {
|
|
27
|
+
const r = spawnSync("which", [bin], { encoding: "utf8", env: { ...process.env, PATH: pathEnv } });
|
|
28
|
+
return r.status === 0 && r.stdout.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
// Fail open: a broken candidate binary never throws and never fails doctor.
|
|
31
|
+
export function probeCandidateVersion(bin) {
|
|
32
|
+
try {
|
|
33
|
+
const r = spawnSync(bin, ["--version"], { encoding: "utf8", timeout: 10000 });
|
|
34
|
+
if (r.error || r.status !== 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
return (r.stdout || r.stderr).trim().split("\n")[0];
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function detectCandidateClis(opts = {}) {
|
|
43
|
+
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const bin of CANDIDATE_CLI_CATALOG) {
|
|
46
|
+
if (!candidateOnPath(bin, pathEnv))
|
|
47
|
+
continue;
|
|
48
|
+
out.push({ binary: bin, version: probeCandidateVersion(bin) });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
export function formatCandidateCliRow({ binary, version }) {
|
|
53
|
+
const ver = version ?? "version unknown";
|
|
54
|
+
return ` ! ${binary.padEnd(14)} detected: ${ver} (no tickmarkr adapter — not routable)`;
|
|
55
|
+
}
|
|
20
56
|
export function allAdapters(opts = {}) {
|
|
21
|
-
// pi + grok appended LAST: same-tier ties resolve by discovery order (Phase 6 D2), so
|
|
22
|
-
// keeps the Phase 6 shape→channel matrix byte-identical; inserting anywhere else
|
|
23
|
-
// reassigns shapes on same-tier ties.
|
|
24
|
-
const real = [claudeCode, codex, cursorAgent, opencode, pi, grok];
|
|
57
|
+
// pi + grok + kimi appended LAST: same-tier ties resolve by discovery order (Phase 6 D2), so
|
|
58
|
+
// appending keeps the Phase 6 shape→channel matrix byte-identical; inserting anywhere else
|
|
59
|
+
// silently reassigns shapes on same-tier ties. kimi is appended AFTER grok for the same reason.
|
|
60
|
+
const real = [claudeCode, codex, cursorAgent, opencode, pi, grok, kimi];
|
|
25
61
|
const fakePath = opts.fakeScriptPath ?? process.env.TICKMARKR_FAKE_SCRIPT;
|
|
26
62
|
return fakePath ? [new FakeAdapter(fakePath), ...real] : real;
|
|
27
63
|
}
|
|
@@ -112,7 +148,7 @@ export async function probeModels(cfg, repoRoot, adapters, health, onProgress) {
|
|
|
112
148
|
if (!h?.installed)
|
|
113
149
|
return;
|
|
114
150
|
// Tests inject FakeAdapter; never let an incidental default adapter spend a real token in the suite.
|
|
115
|
-
if (process.env.VITEST && [claudeCode, codex, cursorAgent, opencode, pi, grok].includes(a))
|
|
151
|
+
if (process.env.VITEST && [claudeCode, codex, cursorAgent, opencode, pi, grok, kimi].includes(a))
|
|
116
152
|
return;
|
|
117
153
|
const verdicts = {};
|
|
118
154
|
const priorModelAuth = priorHealth?.[a.id]?.modelAuth;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { allAdapters, probeAll, probeModels, readAutoPrefer, servableExclusions, servabilityLine, writeDoctor } from "../../adapters/registry.js";
|
|
3
|
+
import { allAdapters, detectCandidateClis, formatCandidateCliRow, probeAll, probeModels, readAutoPrefer, servableExclusions, servabilityLine, writeDoctor } from "../../adapters/registry.js";
|
|
4
4
|
import { BANNER } from "../../brand.js";
|
|
5
5
|
import { tickmarkrDir, stateDirName } from "../../graph/graph.js";
|
|
6
6
|
import { declaredModelWindow, hasWindowsConfig, modelLints, suggestOverlay, ttyVisual } from "../../adapters/model-lints.js";
|
|
@@ -33,6 +33,9 @@ function stylize(out) {
|
|
|
33
33
|
}
|
|
34
34
|
export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(), opts = {}) {
|
|
35
35
|
const cfg = loadConfig(cwd);
|
|
36
|
+
// banner at START — the logo greets the operator before the ~60s probe wait, never trailing it (operator report 2026-07-17)
|
|
37
|
+
if (opts.banner !== false && visual())
|
|
38
|
+
process.stdout.write(BANNER);
|
|
36
39
|
// stderr, live: auth probes are real LLM calls (up to 30s per configured model) and the CLI
|
|
37
40
|
// otherwise prints nothing until the end — silence here reads as a hang (v1.33.1)
|
|
38
41
|
console.error("probing installed agent CLIs — one short LLM call per configured model, may take a minute...");
|
|
@@ -62,6 +65,8 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
62
65
|
const state = !h.installed ? "not installed" : `${h.version ?? "installed"}${h.note ? ` (${h.note})` : ""}`;
|
|
63
66
|
return ` ${h.installed ? "✓" : "✗"} ${a.id.padEnd(14)} ${state}`;
|
|
64
67
|
});
|
|
68
|
+
// v1.48 T1: advisory sweep for known agent CLIs with no adapter — never written to doctor.json health.
|
|
69
|
+
rows.push(...detectCandidateClis().map(formatCandidateCliRow));
|
|
65
70
|
rows.push(` ${HerdrDriver.available() ? "✓" : "✗"} herdr ${HerdrDriver.available() ? "driver available (HERDR_ENV=1)" : "not detected — subprocess driver will be used"}`);
|
|
66
71
|
// v1.22 T5: workspace-trust pre-flight — per installed adapter: trusted | seeded | action-required | n/a.
|
|
67
72
|
// action-required names the exact one-time command (or dialog) the operator must run once.
|
|
@@ -163,6 +168,5 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
163
168
|
const modelSummary = modelStatus.length || preferStatus.length
|
|
164
169
|
? `\nmodel status:\n${[...modelStatus, ...preferStatus].join("\n")}`
|
|
165
170
|
: "";
|
|
166
|
-
|
|
167
|
-
return opts.banner !== false && visual() ? BANNER + body : body;
|
|
171
|
+
return stylize(`tickmarkr doctor — capability matrix:\n${rows.join("\n")}${modelSummary}${drift}\nwrote ${stateDirName(cwd)}/doctor.json`);
|
|
168
172
|
}
|
|
@@ -210,6 +210,8 @@ export async function init(argv, cwd = process.cwd()) {
|
|
|
210
210
|
yes: { type: "boolean" },
|
|
211
211
|
},
|
|
212
212
|
});
|
|
213
|
+
// banner at START on the visual surface — every init path, not just the wizard, and never trailing the probe (operator report 2026-07-17)
|
|
214
|
+
emitBanner();
|
|
213
215
|
const gdir = values["global-dir"] ?? globalConfigDir();
|
|
214
216
|
mkdirSync(gdir, { recursive: true });
|
|
215
217
|
const notes = [];
|
|
@@ -262,8 +264,5 @@ export async function init(argv, cwd = process.cwd()) {
|
|
|
262
264
|
}
|
|
263
265
|
if (values.agent)
|
|
264
266
|
await installAgentFiles(cwd, values.force ?? false, values.docs ?? false, notes);
|
|
265
|
-
|
|
266
|
-
if (visual() && !bannerEmitted)
|
|
267
|
-
return BANNER + body;
|
|
268
|
-
return body;
|
|
267
|
+
return `${notes.join("\n")}\n${doc}\n${nextSteps(cwd, SCAFFOLD_SPEC)}\n${ENVIRONMENTS_FOOTER}`;
|
|
269
268
|
}
|
package/dist/config/config.js
CHANGED
|
@@ -244,6 +244,18 @@ export const DEFAULT_CONFIG = {
|
|
|
244
244
|
vendor: "xai", channel: "sub",
|
|
245
245
|
models: { "grok-4.5": "mid", "grok-composer-2.5-fast": "cheap" },
|
|
246
246
|
},
|
|
247
|
+
// Native kimi CLI (Phase 48). kimi-code/k3 → frontier: 81.2 FrontierSWE, 88.3 Terminal-Bench 2.1,
|
|
248
|
+
// top-3 across six coding benchmarks (research 2026-07-17, K3 released 2026-07-16).
|
|
249
|
+
// kimi-code/kimi-for-coding → mid (K2.7 Coding, 262k ctx). kimi-code/kimi-for-coding-highspeed →
|
|
250
|
+
// cheap (fast K2.7 variant). Ids live-verified `kimi provider list --json` 2026-07-17, kimi 0.27.0.
|
|
251
|
+
kimi: {
|
|
252
|
+
vendor: "moonshot", channel: "sub",
|
|
253
|
+
models: {
|
|
254
|
+
"kimi-code/k3": "frontier",
|
|
255
|
+
"kimi-code/kimi-for-coding": "mid",
|
|
256
|
+
"kimi-code/kimi-for-coding-highspeed": "cheap",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
247
259
|
},
|
|
248
260
|
pricing: { cheap: 0.1, mid: 0.5, frontier: 2.5 },
|
|
249
261
|
gates: { diffCap: DEFAULT_DIFF_CAP },
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ When working in a multi-agent terminal environment, decide your role before star
|
|
|
14
14
|
|
|
15
15
|
- **Orchestrator:** your session was started to execute the mission. Rename your own tab/pane `ORCH · <version>` (short labels: ≤20 chars, `ROLE · token`) and run the loop below.
|
|
16
16
|
- **Supervisor with a live orchestrator:** do not start a second run. Relay the mission to the existing orchestrator with a [verified handoff](#verified-handoffs-agent-to-agent-messaging), then supervise it as OVERSEER.
|
|
17
|
-
- **Primary session without an orchestrator:** rename your own tab `OVERSEER · <version>` and your agent `overseer`, spawn one child orchestration session with `--permission-mode bypassPermissions`, label its tab `ORCH · <version>` and name its agent, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself. Before spawning, confirm any PREVIOUS orchestrator has stood down (monitors stopped, input box empty) and close its tab — the journal, records, and ledger hold the story; scrollback is disposable.
|
|
17
|
+
- **Primary session without an orchestrator:** rename your own tab `OVERSEER · <version>` and your agent `overseer`, spawn one child orchestration session with `--permission-mode bypassPermissions`, label its tab `ORCH · <version>` and name its agent, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself. Before spawning, confirm any PREVIOUS orchestrator has stood down (monitors stopped, input box empty — dim ghost-text suggestions are UI, not queued input; ANSI-verify before alarming) and close its tab — the journal, records, and ledger hold the story; scrollback is disposable.
|
|
18
18
|
|
|
19
19
|
Outside a multi-agent terminal environment, run the loop directly.
|
|
20
20
|
|
|
@@ -60,7 +60,7 @@ After sending, **confirm delivery** by reading the target pane and verifying the
|
|
|
60
60
|
1. **Prepare** — confirm the target list. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
61
61
|
2. **Compile** — run `tickmarkr compile <spec-or-directory>`. Fix source-spec defects instead of editing the generated graph.
|
|
62
62
|
3. **Plan** — run `tickmarkr plan`. Review routes, capability-floor warnings, and human gates before execution.
|
|
63
|
-
4. **Run** — run `tickmarkr run`. Watch the run journal for its terminal event rather than polling agents. Resolve blocked interactions in the relevant agent session.
|
|
63
|
+
4. **Run** — run `tickmarkr run`. Watch the run journal for its terminal event rather than polling agents — a self-terminating poll (`until grep -q '"event":"run-end"' <state-dir>/runs/<runId>/journal.jsonl; do sleep 20; done`), never `tail -F | grep -m1` (wedges on the journal's final line) and never a pane-level done wait (turn-end flaps). Resolve blocked interactions in the relevant agent session.
|
|
64
64
|
5. **Verify and consolidate** — continue only after a green run. A run is green when the run-end event exists in the journal AND the tip verify is not "failed". Tickmarkr consolidates accepted work on `tickmarkr/<runId>` and never signs off to the main branch. A human controls any later release merge.
|
|
65
65
|
6. **Record** — write `tickmarkr report <runId> --md` beside the source spec and commit the execution record when the repository tracks those records.
|
|
66
66
|
7. **Continue** — move to the next requested target. If a target fails or is parked, stop with the journal evidence rather than silently skipping it.
|
|
@@ -53,13 +53,13 @@ After sending, **confirm delivery** by reading the target pane and verifying the
|
|
|
53
53
|
## Stand-down (mission end and retirement)
|
|
54
54
|
|
|
55
55
|
- **Orchestrator, on terminal state** (green, failed, or parked), after the record commit and operator notification: stop every monitor and background task you started, print one final stand-down line, and leave NOTHING queued in your input box. A finished session with an armed watcher or pre-filled input is a loaded gun — a retired v1.40 orchestrator sat idle with "merge … tag, publish" unsent in its input; one stray Enter would have shipped a duplicate release.
|
|
56
|
-
- **Supervisor, when a mission completes** (and always before spawning the next orchestrator): verify the orchestrator stood down, then close its tab. The journal, execution record, OBS ledger, and memory hold the story; pane scrollback is disposable. Never leave a retired agent idle with watchers armed.
|
|
56
|
+
- **Supervisor, when a mission completes** (and always before spawning the next orchestrator): verify the orchestrator stood down, then close its tab. Seeming input-box text in a retired pane can be the TUI's dim ghost-text suggestion, not queued input — confirm with an ANSI read (dim escape around the text) or type-one-char-and-read-back before treating it as the loaded gun; close the tab either way. The journal, execution record, OBS ledger, and memory hold the story; pane scrollback is disposable. Never leave a retired agent idle with watchers armed.
|
|
57
57
|
|
|
58
58
|
## The loop
|
|
59
59
|
|
|
60
60
|
1. **Prepare** — start from the requested spec. Run the [binary preflight](#binary-preflight-before-compile-or-run). Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
61
61
|
2. **Compile** — run `tickmarkr compile <spec>`. Correct compilation errors in the spec, never in the generated graph.
|
|
62
62
|
3. **Plan** — run `tickmarkr plan`. Review the routing table, capability-floor warnings, and every human gate, including work that each gate blocks.
|
|
63
|
-
4. **Run** — run `tickmarkr run`. Watch the run journal for its terminal event rather than repeatedly polling agents. Resolve blocked interactions in the agent session; do not turn them into proxy questions.
|
|
63
|
+
4. **Run** — run `tickmarkr run`. Watch the run journal for its terminal event rather than repeatedly polling agents. Use a self-terminating poll — `until grep -q '"event":"run-end"' <state-dir>/runs/<runId>/journal.jsonl; do sleep 20; done` — never `tail -F | grep -m1` (run-end is the journal's last line, so tail never notices the broken pipe and the watcher hangs forever) and never a pane-level done wait (it fires on every agent turn end, not mission end). Resolve blocked interactions in the agent session; do not turn them into proxy questions.
|
|
64
64
|
5. **Verify and consolidate** — accept only a green run. A run is green when the run-end event exists in the journal AND the tip verify is not "failed". Tickmarkr consolidates accepted task work on `tickmarkr/<runId>`; it never signs off to the main branch. A human may later merge that integration branch through the repository's normal release process.
|
|
65
65
|
6. **Record** — write `tickmarkr report <runId> --md` beside the source spec and commit the execution record when the repository tracks those records. Then [stand down](#stand-down-mission-end-and-retirement).
|