tickmarkr 1.30.0 → 1.33.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/README.md +112 -20
- package/dist/adapters/cursor-agent.js +1 -1
- package/dist/adapters/model-lints.js +3 -3
- package/dist/adapters/prompt.js +1 -1
- package/dist/cli/commands/approve.js +4 -4
- package/dist/cli/commands/compile.js +1 -1
- package/dist/cli/commands/doctor.js +44 -2
- package/dist/cli/commands/init.js +79 -8
- package/dist/cli/commands/plan.js +2 -2
- package/dist/cli/commands/profile.js +1 -1
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/resume.js +1 -1
- package/dist/cli/commands/scope.js +1 -1
- package/dist/cli/commands/status.d.ts +0 -4
- package/dist/cli/commands/status.js +116 -59
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +4 -4
- package/dist/compile/gsd.js +3 -3
- package/dist/compile/native.js +6 -6
- package/dist/compile/speckit.js +1 -1
- package/dist/config/config.js +5 -5
- package/dist/drivers/herdr.d.ts +5 -0
- package/dist/drivers/herdr.js +107 -16
- package/dist/drivers/subprocess.js +2 -2
- package/dist/drivers/types.js +4 -3
- package/dist/gates/baseline.js +6 -2
- package/dist/graph/graph.js +1 -1
- package/dist/plan/prompt.js +2 -2
- package/dist/plan/scope.js +4 -4
- package/dist/run/consult.js +1 -1
- package/dist/run/daemon.js +13 -21
- package/dist/run/git.js +26 -5
- package/dist/run/journal.d.ts +3 -1
- package/dist/run/journal.js +8 -2
- package/dist/run/reconcile.js +1 -0
- package/package.json +4 -4
- package/skills/tickmarkr-auto/SKILL.md +41 -0
- package/skills/tickmarkr-loop/SKILL.md +39 -0
package/dist/graph/graph.js
CHANGED
|
@@ -24,7 +24,7 @@ export function drovrDir(repoRoot) {
|
|
|
24
24
|
export function loadGraph(repoRoot) {
|
|
25
25
|
const p = graphPath(repoRoot);
|
|
26
26
|
if (!existsSync(p))
|
|
27
|
-
throw new Error(`no graph at ${p} — run \`
|
|
27
|
+
throw new Error(`no graph at ${p} — run \`tickmarkr compile <src>\` first`);
|
|
28
28
|
return validateGraph(JSON.parse(readFileSync(p, "utf8")));
|
|
29
29
|
}
|
|
30
30
|
export function saveGraph(repoRoot, g) {
|
package/dist/plan/prompt.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export function scopePrompt(intent, repair) {
|
|
2
2
|
return `DROVR-JUDGE
|
|
3
|
-
You are drafting a
|
|
3
|
+
You are drafting a tickmarkr native spec from an answered intent. Return only the Markdown spec, with no commentary or code fence.
|
|
4
4
|
|
|
5
5
|
The draft must:
|
|
6
|
-
- start with <!--
|
|
6
|
+
- start with <!-- tickmarkr:spec -->
|
|
7
7
|
- include a Requirements section whose ids are sequential REQ-01, REQ-02, ...
|
|
8
8
|
- include an Assumptions section that makes every residual uncertainty explicit
|
|
9
9
|
- include a Traceability section mapping every REQ-nn to at least one Tn task
|
package/dist/plan/scope.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "no
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { basename, dirname, extname, join } from "node:path";
|
|
4
4
|
import { discoverChannels, getAdapter, probeAll } from "../adapters/registry.js";
|
|
5
|
-
import { compileNative } from "../compile/native.js";
|
|
5
|
+
import { compileNative, NATIVE_MARKER } from "../compile/native.js";
|
|
6
6
|
import { pickDriver } from "../drivers/index.js";
|
|
7
7
|
import { extractJson, runLlm } from "../gates/llm.js";
|
|
8
8
|
import { TaskSchema } from "../graph/schema.js";
|
|
@@ -64,12 +64,12 @@ function extractDraft(raw) {
|
|
|
64
64
|
const fenced = [...clean.matchAll(/```(?:markdown|md)?\s*\n([\s\S]*?)```/gi)].at(-1)?.[1];
|
|
65
65
|
if (fenced)
|
|
66
66
|
return fenced;
|
|
67
|
-
const marker = clean.
|
|
67
|
+
const marker = clean.search(/<!--\s*(?:tickmarkr|drovr):spec/);
|
|
68
68
|
return (marker === -1 ? clean : clean.slice(marker)).trimEnd() + "\n";
|
|
69
69
|
}
|
|
70
70
|
function validateDraft(draft) {
|
|
71
|
-
if (
|
|
72
|
-
throw new Error("draft is missing the
|
|
71
|
+
if (!NATIVE_MARKER.test(draft))
|
|
72
|
+
throw new Error("draft is missing the tickmarkr native marker");
|
|
73
73
|
if (!section(draft, "Assumptions").trim())
|
|
74
74
|
throw new Error("draft is missing explicit assumptions");
|
|
75
75
|
const requirements = [...new Set(section(draft, "Requirements").match(/\bREQ-\d{2}\b/g) ?? [])];
|
package/dist/run/consult.js
CHANGED
|
@@ -6,7 +6,7 @@ import { sh } from "./git.js";
|
|
|
6
6
|
const ACTIONS = ["retry", "reroute", "decompose", "human"];
|
|
7
7
|
export function buildDossierPrompt(d) {
|
|
8
8
|
return `DROVR-CONSULT
|
|
9
|
-
You are a senior engineering consult for the
|
|
9
|
+
You are a senior engineering consult for the tickmarkr orchestrator. A worker task hit trouble.
|
|
10
10
|
Read the dossier and return a verdict. Be decisive; cost is the lowest priority, quality the highest.
|
|
11
11
|
|
|
12
12
|
## Task: ${d.taskId} — trigger: ${d.trigger}
|
package/dist/run/daemon.js
CHANGED
|
@@ -38,9 +38,8 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
38
38
|
// BEFORE loadGraph; release in the finally below (every exit path, incl. throws).
|
|
39
39
|
const runId = opts.runId ?? newRunId();
|
|
40
40
|
// T6 narrator: one live status surface per run (herdr only — driver.narrator is undefined on
|
|
41
|
-
// subprocess, so the optional-chain open below is a no-op there).
|
|
42
|
-
//
|
|
43
|
-
let narrator = null;
|
|
41
|
+
// subprocess, so the optional-chain open below is a no-op there). Cosmetic-only: any failure is
|
|
42
|
+
// swallowed (never affects the run); the operator closes a surviving watch pane.
|
|
44
43
|
const lock = acquireRunLock(repoRoot, runId);
|
|
45
44
|
try {
|
|
46
45
|
let graph = loadGraph(repoRoot);
|
|
@@ -91,10 +90,10 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
91
90
|
// show. driver.narrator is undefined on subprocess → no-op (subprocess spawns nothing). Swallowed:
|
|
92
91
|
// a failed-to-open or later-dead watch pane never affects the run.
|
|
93
92
|
try {
|
|
94
|
-
|
|
93
|
+
await driver.narrator?.(repoRoot, "tickmarkr status --watch", runId);
|
|
95
94
|
}
|
|
96
95
|
catch {
|
|
97
|
-
|
|
96
|
+
/* cosmetic-only — the run proceeds without a live surface */
|
|
98
97
|
}
|
|
99
98
|
const intWt = await ensureIntegration(repoRoot, branch, baseRef);
|
|
100
99
|
const concurrency = opts.concurrency ?? cfg.concurrency;
|
|
@@ -139,7 +138,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
139
138
|
// merges are serialized — two concurrent `git merge`s in one worktree would corrupt each other
|
|
140
139
|
let mergeChain = Promise.resolve();
|
|
141
140
|
const mergeSerial = (taskBranch, t, gated) => {
|
|
142
|
-
const next = mergeChain.then(() => mergeTask(intWt, taskBranch, `
|
|
141
|
+
const next = mergeChain.then(() => mergeTask(intWt, taskBranch, `tickmarkr: merge ${t.id} ${t.title}`, gated));
|
|
143
142
|
mergeChain = next.catch(() => undefined);
|
|
144
143
|
return next;
|
|
145
144
|
};
|
|
@@ -153,7 +152,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
153
152
|
journal.telemetry({ taskId: t.id, shape: t.shape, adapter: assignment.adapter, model: assignment.model, channel: assignment.channel, attempts, outcome: "human", durationMs: Date.now() - startMs, parkKind: kind ?? undefined, gateFails, consults, tokens, meteredAttempts: tokens ? metered : undefined, retryMode });
|
|
154
153
|
}
|
|
155
154
|
await reconcile({ spareLiveLlm: true }); // task-human is a terminal event — sweep, sparing sibling tasks' live LLM panes
|
|
156
|
-
await driver.notify(`
|
|
155
|
+
await driver.notify(`tickmarkr ${runId}: ${t.id} needs a human — ${reason}`, { tier: "attention" });
|
|
157
156
|
};
|
|
158
157
|
const execTask = async (t) => {
|
|
159
158
|
const startMs = Date.now();
|
|
@@ -252,7 +251,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
252
251
|
action: v.action, notes: v.notes,
|
|
253
252
|
...(v.excludeAdapter ? { excludeAdapter: v.excludeAdapter } : {}),
|
|
254
253
|
});
|
|
255
|
-
await driver.notify(`
|
|
254
|
+
await driver.notify(`tickmarkr ${runId}: ${t.id} consult verdict: ${v.action}`, { tier: "attention" });
|
|
256
255
|
if (v.action === "retry") {
|
|
257
256
|
feedback = v.notes || feedback;
|
|
258
257
|
return true;
|
|
@@ -386,7 +385,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
386
385
|
threshold: cfg.contextWarnTokens,
|
|
387
386
|
attempt,
|
|
388
387
|
});
|
|
389
|
-
await driver.notify(`
|
|
388
|
+
await driver.notify(`tickmarkr ${runId}: ${t.id} context ${usage.tokens} tokens ≥ ${cfg.contextWarnTokens}`, { tier: "attention" });
|
|
390
389
|
};
|
|
391
390
|
let finished;
|
|
392
391
|
let output;
|
|
@@ -449,7 +448,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
449
448
|
}
|
|
450
449
|
paged = true; // page once — the visible pane is the operator's to unblock; task timeout is the backstop
|
|
451
450
|
const why = st === "blocked" ? "is blocked on a prompt — approve in its pane" : "looks idle without finishing — check its pane";
|
|
452
|
-
await driver.notify(`
|
|
451
|
+
await driver.notify(`tickmarkr ${runId}: ${slot.name} ${why}`, { tier: "attention" });
|
|
453
452
|
}
|
|
454
453
|
// a dead pane or a false-positive marker display returns fast — sleep the unspent slice, never hot-spin
|
|
455
454
|
const spent = Date.now() - sliceStart;
|
|
@@ -500,7 +499,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
500
499
|
const next = failover("quota-failover");
|
|
501
500
|
journal.append("quota-failover", t.id, { from: channelKey(assignment), to: next ? channelKey(next) : null });
|
|
502
501
|
if (next) {
|
|
503
|
-
await driver.notify(`
|
|
502
|
+
await driver.notify(`tickmarkr ${runId}: ${t.id} quota failover`, { tier: "attention" });
|
|
504
503
|
// OBS-17 T2: the superseded slot's pane closes AT REROUTE TIME — it holds a throttled
|
|
505
504
|
// dead-end, not failure context; the next safe-point reconcile catches a missed close.
|
|
506
505
|
if (!keepForever) {
|
|
@@ -562,7 +561,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
562
561
|
});
|
|
563
562
|
}
|
|
564
563
|
}
|
|
565
|
-
journal.append("gate-result", t.id, { gate: g.gate, pass: g.pass, details: g.details });
|
|
564
|
+
journal.append("gate-result", t.id, { gate: g.gate, pass: g.pass, details: g.details, ...(g.meta?.skipped === true ? { skipped: true } : {}) });
|
|
566
565
|
// v1.1 failover: never re-ask a reviewer channel that produced garbage for this task
|
|
567
566
|
if (g.gate === "review" && !g.pass && /unparseable/.test(g.details) && typeof g.meta?.reviewer === "string") {
|
|
568
567
|
badReviewers.push(g.meta.reviewer);
|
|
@@ -642,7 +641,7 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
642
641
|
feedback = results.filter((g) => !g.pass).map((g) => `${g.gate}: ${g.details}`).join("\n\n");
|
|
643
642
|
const step = r.ladder[Math.min(ladderIdx++, r.ladder.length - 1)];
|
|
644
643
|
journal.append("escalation", t.id, { step, attempt: attempt + 1 });
|
|
645
|
-
await driver.notify(`
|
|
644
|
+
await driver.notify(`tickmarkr ${runId}: ${t.id} escalation: ${step}`, { tier: "attention" });
|
|
646
645
|
if (step === "retry")
|
|
647
646
|
continue;
|
|
648
647
|
if (step === "escalate") {
|
|
@@ -712,17 +711,10 @@ export async function runDaemon(repoRoot, opts = {}) {
|
|
|
712
711
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
713
712
|
.map(([root, count]) => `${count} blocked behind ${root}`)
|
|
714
713
|
.join(", ");
|
|
715
|
-
await driver.notify(`
|
|
714
|
+
await driver.notify(`tickmarkr ${runId}: ${summary.done.length} done, ${summary.failed.length} failed, ${summary.human.length} awaiting human, ${summary.blocked.length} blocked, ${summary.pending.length} pending${attribution ? ` (${attribution})` : ""} — integration branch ${branch} (merge to main is yours)`, { tier: "attention" });
|
|
716
715
|
return summary;
|
|
717
716
|
}
|
|
718
717
|
finally {
|
|
719
|
-
// T6: close the narrator at run end — cosmetic, swallowed (never blocks lock release)
|
|
720
|
-
if (narrator) {
|
|
721
|
-
try {
|
|
722
|
-
await driver.close(narrator);
|
|
723
|
-
}
|
|
724
|
-
catch { /* cosmetic */ }
|
|
725
|
-
}
|
|
726
718
|
releaseRunLock(repoRoot);
|
|
727
719
|
}
|
|
728
720
|
}
|
package/dist/run/git.js
CHANGED
|
@@ -7,14 +7,35 @@ import { drovrDir } from "../graph/graph.js";
|
|
|
7
7
|
// (pi -p / codex exec wait for stdin EOF). timedOut distinguishes SIGKILL-timeout from a real nonzero exit.
|
|
8
8
|
export function sh(cmd, cwd, timeoutMs = 600000) {
|
|
9
9
|
return new Promise((resolve) => {
|
|
10
|
-
|
|
10
|
+
// detached: bash gets its own process group so a timeout can kill the whole tree —
|
|
11
|
+
// SIGKILLing bash alone orphans grandchildren (codex/pi) that hold the stdio pipes
|
|
12
|
+
// open, so "close" never fires and the promise wedges forever (v1.33.1 init hang).
|
|
13
|
+
const p = spawn("bash", ["-lc", cmd], { cwd, stdio: ["ignore", "pipe", "pipe"], detached: true });
|
|
11
14
|
let stdout = "", stderr = "";
|
|
12
|
-
let timedOut = false;
|
|
13
|
-
const
|
|
15
|
+
let timedOut = false, done = false;
|
|
16
|
+
const finish = (code, err) => {
|
|
17
|
+
if (done)
|
|
18
|
+
return;
|
|
19
|
+
done = true;
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
resolve({ code, stdout, stderr: err ?? stderr, timedOut });
|
|
22
|
+
};
|
|
23
|
+
const timer = setTimeout(() => {
|
|
24
|
+
timedOut = true;
|
|
25
|
+
try {
|
|
26
|
+
process.kill(-p.pid, "SIGKILL");
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
p.kill("SIGKILL");
|
|
30
|
+
}
|
|
31
|
+
}, timeoutMs);
|
|
14
32
|
p.stdout.on("data", (d) => (stdout += d));
|
|
15
33
|
p.stderr.on("data", (d) => (stderr += d));
|
|
16
|
-
p.on("error", (e) =>
|
|
17
|
-
p.on("close", (code) =>
|
|
34
|
+
p.on("error", (e) => finish(127, String(e)));
|
|
35
|
+
p.on("close", (code) => finish(code ?? 1));
|
|
36
|
+
// "close" waits for stdio to drain; a surviving pipe-holder must not outlive the timeout
|
|
37
|
+
p.on("exit", (code) => { if (timedOut)
|
|
38
|
+
finish(code ?? 1); });
|
|
18
39
|
});
|
|
19
40
|
}
|
|
20
41
|
export async function shOk(cmd, cwd) {
|
package/dist/run/journal.d.ts
CHANGED
|
@@ -80,7 +80,9 @@ export declare class Journal {
|
|
|
80
80
|
private constructor();
|
|
81
81
|
static create(repoRoot: string, runId: string, narrate?: (event: JournalEvent) => void): Journal;
|
|
82
82
|
static open(repoRoot: string, runId: string, narrate?: (event: JournalEvent) => void): Journal;
|
|
83
|
-
static latestRunId(repoRoot: string
|
|
83
|
+
static latestRunId(repoRoot: string, opts?: {
|
|
84
|
+
withJournal?: boolean;
|
|
85
|
+
}): string | null;
|
|
84
86
|
private get journalPath();
|
|
85
87
|
append(event: string, taskId?: string, data?: Record<string, unknown>): void;
|
|
86
88
|
read(): JournalEvent[];
|
package/dist/run/journal.js
CHANGED
|
@@ -157,10 +157,16 @@ export class Journal {
|
|
|
157
157
|
throw new Error(`no journal for ${runId} at ${dir}`);
|
|
158
158
|
return new Journal(dir, runId, narrate);
|
|
159
159
|
}
|
|
160
|
-
|
|
160
|
+
// withJournal: journal.jsonl appears at first append, after Journal.create mkdirs — a caller that
|
|
161
|
+
// will Journal.open the result (status, report) must fall back to the newest run that is actually
|
|
162
|
+
// readable, not throw on the mkdir-to-first-append window. Raw default stays for telemetry-scoped
|
|
163
|
+
// callers (profile cursor), where a journal-less run dir still counts.
|
|
164
|
+
static latestRunId(repoRoot, opts = {}) {
|
|
161
165
|
if (!existsSync(runsDir(repoRoot)))
|
|
162
166
|
return null;
|
|
163
|
-
const ids = readdirSync(runsDir(repoRoot))
|
|
167
|
+
const ids = readdirSync(runsDir(repoRoot))
|
|
168
|
+
.filter((d) => d.startsWith("run-") && (!opts.withJournal || existsSync(join(runsDir(repoRoot), d, "journal.jsonl"))))
|
|
169
|
+
.sort();
|
|
164
170
|
return ids.at(-1) ?? null;
|
|
165
171
|
}
|
|
166
172
|
get journalPath() {
|
package/dist/run/reconcile.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tickmarkr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.1",
|
|
4
4
|
"description": "Spec in, verified work out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
11
|
"tickmarkr": "dist/cli/index.js",
|
|
12
|
-
"
|
|
13
|
-
"drover": "dist/cli/index.js"
|
|
12
|
+
"tkr": "dist/cli/index.js"
|
|
14
13
|
},
|
|
15
14
|
"files": [
|
|
16
15
|
"dist",
|
|
17
|
-
"schema"
|
|
16
|
+
"schema",
|
|
17
|
+
"skills"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc -p tsconfig.json",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tickmarkr-auto
|
|
3
|
+
description: 'Run a repository’s requested specs autonomously with tickmarkr. Triggers: "/tickmarkr-auto", "run these specs with tickmarkr", "autonomous tickmarkr run".'
|
|
4
|
+
argument-hint: "[spec-or-directory ...]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# tickmarkr-auto — run repository specs autonomously
|
|
8
|
+
|
|
9
|
+
Use this to execute a requested sequence of repository specs. It is SDD-agnostic: each target can be any file or directory accepted by `tickmarkr compile`.
|
|
10
|
+
|
|
11
|
+
## Two-tier by default — role check before the loop
|
|
12
|
+
|
|
13
|
+
When working in a multi-agent terminal environment, decide your role before starting:
|
|
14
|
+
|
|
15
|
+
- **Orchestrator:** your session was started to execute the mission. Run the loop below.
|
|
16
|
+
- **Supervisor with a live orchestrator:** do not start a second run. Relay the mission to the existing orchestrator with a verified send, then supervise it.
|
|
17
|
+
- **Primary session without an orchestrator:** create one child orchestration session, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself.
|
|
18
|
+
|
|
19
|
+
Outside a multi-agent terminal environment, run the loop directly.
|
|
20
|
+
|
|
21
|
+
## Invariants
|
|
22
|
+
|
|
23
|
+
- Never run two tickmarkr runs in the same repository concurrently.
|
|
24
|
+
- Never let tickmarkr merge work to the main branch. New work consolidates on `tickmarkr/<runId>`.
|
|
25
|
+
- Do not edit a generated graph to force an outcome; correct the source spec and compile again.
|
|
26
|
+
- Gates independently verify evidence, scope, acceptance criteria, and review. A worker's completion claim is not evidence.
|
|
27
|
+
- Treat missing or unparseable machine results and verdicts as failures. Never bypass a failed gate or merge partial work without a human decision.
|
|
28
|
+
|
|
29
|
+
## Act by default
|
|
30
|
+
|
|
31
|
+
Run every requested target in order without seeking routine confirmation. Stop only for a blocked agent interaction, a genuinely unresolved stalled task, a designed human gate, or a failed run that needs a human decision. Diagnose from the journal and evidence first; self-release and resume only after fixing and verifying a harness defect.
|
|
32
|
+
|
|
33
|
+
## Per-spec loop
|
|
34
|
+
|
|
35
|
+
1. **Prepare** — confirm the target list, check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
36
|
+
2. **Compile** — run `tickmarkr compile <spec-or-directory>`. Fix source-spec defects instead of editing the generated graph.
|
|
37
|
+
3. **Plan** — run `tickmarkr plan`. Review routes, capability-floor warnings, and human gates before execution.
|
|
38
|
+
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.
|
|
39
|
+
5. **Verify and consolidate** — continue only after a green run. Tickmarkr consolidates accepted work on `tickmarkr/<runId>` and never signs off to the main branch. A human controls any later release merge.
|
|
40
|
+
6. **Record** — write `tickmarkr report <runId> --md` beside the source spec and commit the execution record when the repository tracks those records.
|
|
41
|
+
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.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tickmarkr-loop
|
|
3
|
+
description: 'Run one repository spec autonomously with tickmarkr. Triggers: "/tickmarkr-loop", "run this spec with tickmarkr", "tickmarkr the spec".'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# tickmarkr-loop — run one spec autonomously
|
|
7
|
+
|
|
8
|
+
Use this for any spec that `tickmarkr compile` accepts. It is SDD-agnostic: use the repository's requested spec format and keep the execution record beside that source spec.
|
|
9
|
+
|
|
10
|
+
## Two-tier by default — role check before the loop
|
|
11
|
+
|
|
12
|
+
When working in a multi-agent terminal environment, decide your role before starting:
|
|
13
|
+
|
|
14
|
+
- **Orchestrator:** your session was started to execute the mission. Run the loop below.
|
|
15
|
+
- **Supervisor with a live orchestrator:** do not start a second run. Relay the mission to the existing orchestrator with a verified send, then supervise it.
|
|
16
|
+
- **Primary session without an orchestrator:** create one child orchestration session, give it the mission and these rules verbatim, then supervise it. Do not drive a duplicate single-tier run yourself.
|
|
17
|
+
|
|
18
|
+
Outside a multi-agent terminal environment, run the loop directly.
|
|
19
|
+
|
|
20
|
+
## Invariants
|
|
21
|
+
|
|
22
|
+
- Never run two tickmarkr runs in the same repository concurrently.
|
|
23
|
+
- Never let tickmarkr merge work to the main branch. New work consolidates on `tickmarkr/<runId>`.
|
|
24
|
+
- Do not edit the compiled graph to force an outcome; fix the source spec and compile again.
|
|
25
|
+
- Gates verify commits, diffs, acceptance criteria, and reviews independently. Never trust a worker's claim that work is complete.
|
|
26
|
+
- Treat missing or unparseable machine results and verdicts as failures. Do not release, resume, or merge around failed gates.
|
|
27
|
+
|
|
28
|
+
## Act by default
|
|
29
|
+
|
|
30
|
+
Proceed through the loop without seeking routine confirmation. Stop only for a blocked agent interaction, a genuinely unresolved stalled task, or a designed human gate. Diagnose from the journal and available evidence before escalating; if a harness defect is fixed and verified, resume the run.
|
|
31
|
+
|
|
32
|
+
## The loop
|
|
33
|
+
|
|
34
|
+
1. **Prepare** — start from the requested spec. Check `git status`, confirm no tickmarkr run is active, and work from a non-main branch.
|
|
35
|
+
2. **Compile** — run `tickmarkr compile <spec>`. Correct compilation errors in the spec, never in the generated graph.
|
|
36
|
+
3. **Plan** — run `tickmarkr plan`. Review the routing table, capability-floor warnings, and every human gate, including work that each gate blocks.
|
|
37
|
+
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.
|
|
38
|
+
5. **Verify and consolidate** — accept only a green run. 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.
|
|
39
|
+
6. **Record** — write `tickmarkr report <runId> --md` beside the source spec and commit the execution record when the repository tracks those records.
|