tickmarkr 2.2.1 → 2.3.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/README.md +10 -9
- package/dist/adapters/types.d.ts +20 -1
- package/dist/adapters/types.js +42 -2
- package/dist/cli/commands/approve.js +5 -4
- package/dist/cli/commands/beat.js +7 -4
- package/dist/cli/commands/doctor.d.ts +5 -1
- package/dist/cli/commands/doctor.js +67 -7
- package/dist/cli/commands/init.js +36 -21
- package/dist/cli/commands/plan.js +16 -2
- package/dist/cli/commands/report.js +37 -1
- package/dist/cli/commands/verify.d.ts +5 -0
- package/dist/cli/commands/verify.js +140 -25
- package/dist/compile/collateral.js +15 -9
- package/dist/compile/native.js +3 -3
- package/dist/config/config.js +1 -1
- package/dist/drivers/index.d.ts +6 -0
- package/dist/drivers/index.js +19 -4
- package/dist/drivers/orca.d.ts +22 -1
- package/dist/drivers/orca.js +147 -6
- package/dist/drivers/subprocess.d.ts +3 -3
- package/dist/drivers/subprocess.js +16 -9
- package/dist/drivers/types.d.ts +2 -0
- package/dist/gates/baseline.d.ts +2 -0
- package/dist/gates/baseline.js +21 -4
- package/dist/gates/llm.d.ts +6 -0
- package/dist/gates/llm.js +25 -9
- package/dist/gates/review.d.ts +3 -1
- package/dist/gates/review.js +40 -12
- package/dist/gates/run-gates.js +17 -10
- package/dist/gates/verdict-cause.d.ts +6 -2
- package/dist/gates/verdict-cause.js +8 -4
- package/dist/run/consult.js +5 -1
- package/dist/run/daemon.d.ts +12 -0
- package/dist/run/daemon.js +218 -25
- package/dist/run/git.d.ts +1 -0
- package/dist/run/git.js +4 -0
- package/dist/run/journal.d.ts +15 -2
- package/dist/run/journal.js +70 -12
- package/dist/run/supervision.d.ts +6 -0
- package/dist/run/supervision.js +29 -1
- package/dist/tui/ink/init-app.js +4 -4
- package/package.json +1 -1
- package/skills/tickmarkr-overseer/SKILL.md +77 -18
- package/skills/tickmarkr-overseer/scripts/seat-send.sh +88 -18
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +36 -2
- package/skills/tickmarkr-overseer/scripts/watch-contamination.sh +36 -15
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +33 -7
- package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +32 -8
package/README.md
CHANGED
|
@@ -13,8 +13,9 @@ tickmarkr is a spec-driven orchestration harness for AI coding agent CLIs. You w
|
|
|
13
13
|
acceptance criteria; the engine routes tasks to the best installed agent CLI (claude-code, codex,
|
|
14
14
|
cursor-agent, opencode, grok, pi, kimi) by cost and capability, dispatches work in git worktrees for
|
|
15
15
|
change isolation — as interactive TUIs when running under [herdr](https://herdr.dev), headless
|
|
16
|
-
subprocesses otherwise, or in [Orca](https://onorca.dev) terminals when
|
|
17
|
-
|
|
16
|
+
subprocesses otherwise, or in [Orca](https://onorca.dev) terminals when auto detects both Orca
|
|
17
|
+
markers (name that driver explicitly outside one) — and independently verifies each committed
|
|
18
|
+
result by checking for no new
|
|
18
19
|
baseline failures per task, then strictly verifying the integration tip. Green tasks consolidate onto a
|
|
19
20
|
`tickmarkr/<runId>` branch; merging to your mainline is always your call, never automated. Engage
|
|
20
21
|
with full visibility into routing decisions, worker progress, and gate verdicts — or run headless
|
|
@@ -251,14 +252,14 @@ and first-attempt success rate. Cost reporting follows strict honesty rules and
|
|
|
251
252
|
When running under [herdr](https://herdr.dev), tickmarkr creates a labeled pane-and-tab workspace
|
|
252
253
|
for real-time visibility (optional — omit `--driver herdr` or run headless if preferred).
|
|
253
254
|
|
|
254
|
-
### Orca:
|
|
255
|
+
### Orca: a detected-or-named execution surface
|
|
255
256
|
|
|
256
|
-
[Orca](https://onorca.dev) is the third execution surface
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
257
|
+
[Orca](https://onorca.dev) is the third execution surface. `auto` resolves herdr first when
|
|
258
|
+
`HERDR_ENV=1`, then Orca only when both Orca-authored markers `TERM_PROGRAM=Orca` and
|
|
259
|
+
`ORCA_TERMINAL_HANDLE` are present, then subprocess. This environment-only choice executes no
|
|
260
|
+
binary or runtime probe. Outside an Orca terminal, name it explicitly with `--driver orca` or
|
|
261
|
+
`driver: orca` in config. Once selected either way, an unreachable Orca stays a loud Orca driver
|
|
262
|
+
failure and is never silently replaced by a hidden subprocess worker.
|
|
262
263
|
|
|
263
264
|
What Orca supplies is terminals. What tickmarkr keeps is everything that decides whether work
|
|
264
265
|
ships: **it creates and owns the git worktree** for every task (Orca is told which checkout to bind
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -116,7 +116,23 @@ export declare const TrustDialogSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
116
116
|
reason: z.ZodString;
|
|
117
117
|
}, z.core.$strict>]>;
|
|
118
118
|
export declare function matchesTrustDialog(paneText: string, dialog: TrustDialog): dialog is CapturedTrustDialog;
|
|
119
|
+
export declare const ADAPTER_PROMPT_GLYPHS: {
|
|
120
|
+
readonly "claude-code": "❯";
|
|
121
|
+
readonly codex: "›";
|
|
122
|
+
readonly "cursor-agent": ">";
|
|
123
|
+
readonly opencode: ">";
|
|
124
|
+
readonly pi: ">";
|
|
125
|
+
readonly grok: ">";
|
|
126
|
+
readonly kimi: ">";
|
|
127
|
+
readonly omp: ">";
|
|
128
|
+
readonly agy: ">";
|
|
129
|
+
readonly "prime-agent": ">";
|
|
130
|
+
readonly fake: ">";
|
|
131
|
+
};
|
|
132
|
+
export type PromptGlyph = (typeof ADAPTER_PROMPT_GLYPHS)[keyof typeof ADAPTER_PROMPT_GLYPHS];
|
|
133
|
+
export declare function declaredPromptGlyphForAdapter(adapterId: string): PromptGlyph | undefined;
|
|
119
134
|
export interface InputBox {
|
|
135
|
+
promptGlyph?: PromptGlyph;
|
|
120
136
|
fingerprint: string;
|
|
121
137
|
match?(paneText: string): boolean;
|
|
122
138
|
emptyMatch?(paneText: string): boolean;
|
|
@@ -124,8 +140,11 @@ export interface InputBox {
|
|
|
124
140
|
launchCommand?(command: string): boolean;
|
|
125
141
|
readinessTimeoutMs?: number;
|
|
126
142
|
}
|
|
127
|
-
export declare function declareInputBox(adapterId: string, inputBox: InputBox
|
|
143
|
+
export declare function declareInputBox(adapterId: string, inputBox: Omit<InputBox, "promptGlyph"> & {
|
|
144
|
+
promptGlyph?: PromptGlyph;
|
|
145
|
+
}): InputBox;
|
|
128
146
|
export declare function declaredInputBoxForWorkerName(workerName: string): InputBox | undefined;
|
|
147
|
+
export declare function declaredPromptGlyphForWorkerName(workerName: string): PromptGlyph | undefined;
|
|
129
148
|
export declare function matchesInputBox(paneText: string, inputBox: InputBox): boolean;
|
|
130
149
|
export declare function matchesEmptyInputBox(paneText: string, inputBox: InputBox): boolean;
|
|
131
150
|
export declare function matchesOccupiedInputBox(paneText: string, inputBox: InputBox): boolean;
|
package/dist/adapters/types.js
CHANGED
|
@@ -129,15 +129,55 @@ export function matchesTrustDialog(paneText, dialog) {
|
|
|
129
129
|
return false;
|
|
130
130
|
return dialog.fingerprint.trim().length > 0 && paneText.includes(dialog.fingerprint);
|
|
131
131
|
}
|
|
132
|
+
// v1.75 T1 / OBS-136: an adapter whose steady-state TUI presents a bordered input box declares
|
|
133
|
+
// one distinctive pane-text fingerprint. The herdr driver associates declarations with worker
|
|
134
|
+
// slots by the existing adapter-bearing dispatch name before that name is canonicalized.
|
|
135
|
+
// v1.77 / OBS-142: launchCommand identifies the adapter-owned bootstrap command that necessarily
|
|
136
|
+
// precedes the box; that command settles on a clean shell line, while every other delivery waits
|
|
137
|
+
// for the declared box itself. readinessTimeoutMs bounds that evidence loop per adapter.
|
|
138
|
+
// v1.85 T5 / OBS-140: typed delivery is licensed by DECLARED states, never by transcript shape.
|
|
139
|
+
// `match` is the box painted, `emptyMatch` the box carrying nothing, `occupiedMatch` the box still
|
|
140
|
+
// holding a prompt. An adapter may pin `occupiedMatch` directly, or leave it derived from the other
|
|
141
|
+
// two — but an adapter that declares neither cannot acknowledge a submission and is refused.
|
|
142
|
+
// OBS-620: prompt glyphs are adapter facts, just like the input-box matchers below. Keep the shipped
|
|
143
|
+
// set in one shell-readable table: the overseer receipt cannot import TypeScript, but it reads these
|
|
144
|
+
// exact declarations from src/ in a checkout or dist/ in an installed package. A newly shipped
|
|
145
|
+
// adapter therefore has one visible place where omission can be enumerated and refused.
|
|
146
|
+
export const ADAPTER_PROMPT_GLYPHS = {
|
|
147
|
+
"claude-code": "❯",
|
|
148
|
+
"codex": "›",
|
|
149
|
+
"cursor-agent": ">",
|
|
150
|
+
"opencode": ">",
|
|
151
|
+
"pi": ">",
|
|
152
|
+
"grok": ">",
|
|
153
|
+
"kimi": ">",
|
|
154
|
+
"omp": ">",
|
|
155
|
+
"agy": ">",
|
|
156
|
+
"prime-agent": ">",
|
|
157
|
+
// Test-only, but declared so a fake interactive slot exercises the same fail-closed contract.
|
|
158
|
+
"fake": ">",
|
|
159
|
+
};
|
|
160
|
+
export function declaredPromptGlyphForAdapter(adapterId) {
|
|
161
|
+
return ADAPTER_PROMPT_GLYPHS[adapterId];
|
|
162
|
+
}
|
|
132
163
|
const inputBoxes = new Map();
|
|
133
164
|
export function declareInputBox(adapterId, inputBox) {
|
|
134
|
-
|
|
135
|
-
|
|
165
|
+
const promptGlyph = inputBox.promptGlyph ?? declaredPromptGlyphForAdapter(adapterId);
|
|
166
|
+
// The shipped-adapter registry is audited separately against ADAPTER_PROMPT_GLYPHS. Keep this
|
|
167
|
+
// declaration helper open to synthetic and extension adapters: Herdr's input-state tests register
|
|
168
|
+
// those at module load, and prompt glyphs are not part of its typed-delivery decision.
|
|
169
|
+
const declared = promptGlyph === undefined ? inputBox : { ...inputBox, promptGlyph };
|
|
170
|
+
inputBoxes.set(adapterId, declared);
|
|
171
|
+
return declared;
|
|
136
172
|
}
|
|
137
173
|
export function declaredInputBoxForWorkerName(workerName) {
|
|
138
174
|
const adapterId = /^.+-worker-(.+)-a\d+-.+$/.exec(workerName)?.[1];
|
|
139
175
|
return adapterId === undefined ? undefined : inputBoxes.get(adapterId);
|
|
140
176
|
}
|
|
177
|
+
export function declaredPromptGlyphForWorkerName(workerName) {
|
|
178
|
+
const adapterId = /^.+-worker-(.+)-a\d+-.+$/.exec(workerName)?.[1];
|
|
179
|
+
return adapterId === undefined ? undefined : declaredPromptGlyphForAdapter(adapterId);
|
|
180
|
+
}
|
|
141
181
|
export function matchesInputBox(paneText, inputBox) {
|
|
142
182
|
return inputBox.match?.(paneText) ?? paneText.includes(inputBox.fingerprint);
|
|
143
183
|
}
|
|
@@ -11,7 +11,7 @@ export const APPROVAL_DISPOSITIONS = ["dispatch", "waive-gate", "re-dispatch", "
|
|
|
11
11
|
export const APPROVAL_ENACTS = {
|
|
12
12
|
dispatch: "dispatch it",
|
|
13
13
|
"waive-gate": "continue past the approved gate",
|
|
14
|
-
"re-dispatch": "re-dispatch against the full gate suite",
|
|
14
|
+
"re-dispatch": "re-dispatch against the full gate suite only if re-running the whole declared battery on the parked commit before any worker is red",
|
|
15
15
|
"fund-fixed-attempt": "dispatch a fixed attempt carrying the findings",
|
|
16
16
|
"fresh-budget": "dispatch it on a fresh attempt budget",
|
|
17
17
|
};
|
|
@@ -85,6 +85,7 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
85
85
|
const lastHuman = events[lastHumanIndex];
|
|
86
86
|
const capPark = lastHuman?.data.kind === ATTEMPT_CAP_RELEASE;
|
|
87
87
|
const gateFailPark = lastHuman?.data.kind === "gate-fail";
|
|
88
|
+
const infraPark = lastHuman?.data.kind === "infra";
|
|
88
89
|
const failedGate = gateFailPark ? failedGateForNewestPark(events, taskId, lastHumanIndex) : undefined;
|
|
89
90
|
if (gateFailPark && !failedGate) {
|
|
90
91
|
throw new Error(`task ${taskId} is parked on gate-fail but has no failed gate result on the newest park — refusing to infer one`);
|
|
@@ -104,8 +105,8 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
104
105
|
return disposition(cwd, runId, "fund-fixed-attempt", `upheld the reviewer for ${taskId} in ${runId} — by ${by}`, serialization.contended);
|
|
105
106
|
}
|
|
106
107
|
if (recheck) {
|
|
107
|
-
if (!gateFailPark || !failedGate) {
|
|
108
|
-
throw new Error(`--recheck applies to a gate-fail park; ${taskId}'s newest park is ${String(lastHuman?.data.kind ?? "none")} with failed gate ${failedGate ?? "none"} — refusing`);
|
|
108
|
+
if ((!gateFailPark || !failedGate) && !infraPark) {
|
|
109
|
+
throw new Error(`--recheck applies to a gate-fail or infra park; ${taskId}'s newest park is ${String(lastHuman?.data.kind ?? "none")} with failed gate ${failedGate ?? "none"} — refusing`);
|
|
109
110
|
}
|
|
110
111
|
journal.append("task-approved", taskId, {
|
|
111
112
|
by,
|
|
@@ -114,7 +115,7 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
114
115
|
release: RECHECK_RELEASE,
|
|
115
116
|
...(reviewRoundCeiling === undefined ? {} : { reviewRoundCeiling }),
|
|
116
117
|
});
|
|
117
|
-
return disposition(cwd, runId, "re-dispatch", `re-checking ${taskId} in ${runId} — by ${by}; failed gate ${failedGate}; no gate marked satisfied`, serialization.contended);
|
|
118
|
+
return disposition(cwd, runId, "re-dispatch", `re-checking ${taskId} in ${runId} — by ${by}; ${failedGate ? `failed gate ${failedGate}` : "infra park"}; no gate marked satisfied`, serialization.contended);
|
|
118
119
|
}
|
|
119
120
|
if (waive) {
|
|
120
121
|
if (!gateFailPark || !failedGate) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { tickmarkrDir } from "../../graph/graph.js";
|
|
4
|
-
import { SUPERVISION_BEAT_MS, SUPERVISION_DEFAULT_THRESHOLD_PCT, SUPERVISION_STALE_MS, SUPERVISION_TIERS, beatSupervision, supervisionStandDownPath, } from "../../run/supervision.js";
|
|
4
|
+
import { SUPERVISION_BEAT_MS, SUPERVISION_DEFAULT_THRESHOLD_PCT, SUPERVISION_STALE_MS, SUPERVISION_TIERS, beatSupervision, resolveSupervisionRoot, supervisionStandDownPath, } from "../../run/supervision.js";
|
|
5
5
|
// SUP-04: the writer side of supervision, as a VERB. `beatSupervision` and `SUPERVISION_BEAT_MS` shipped
|
|
6
6
|
// with exactly one in-repo caller — the daemon, on one tier — so `status` printed
|
|
7
7
|
// `orchestrator ARMED / overseer ABSENT / watch ABSENT` while a real overseer worked the run: two thirds
|
|
@@ -41,14 +41,17 @@ export async function beat(argv, cwd = process.cwd()) {
|
|
|
41
41
|
throw new Error(`${named} needs --seat <identity> — a beat that names no seat arms a tier nobody occupies` +
|
|
42
42
|
" (pass the seat's own pane id or agent name)");
|
|
43
43
|
}
|
|
44
|
+
// A beat names repository state, never the caller's incidental directory. Resolution is read-only
|
|
45
|
+
// and happens before every write, so a non-repository invocation cannot create the state it claims.
|
|
46
|
+
const repoRoot = resolveSupervisionRoot(cwd);
|
|
44
47
|
if (standDown)
|
|
45
|
-
return standDownTier(
|
|
48
|
+
return standDownTier(repoRoot, named, seat);
|
|
46
49
|
// Clear a stand-down marker left by an earlier session BEFORE beating: a valid marker outranks every
|
|
47
50
|
// beat that does not strictly follow it, and two writes landing in the same millisecond do not. An
|
|
48
51
|
// uncleared marker would render DISARMED while this verb claimed ARMED, so the removal is unguarded
|
|
49
52
|
// too — `force` makes the ordinary "no marker" case a no-op, and anything else is a real failure.
|
|
50
|
-
rmSync(supervisionStandDownPath(
|
|
51
|
-
beatSupervision(
|
|
53
|
+
rmSync(supervisionStandDownPath(repoRoot, named), { force: true, recursive: true });
|
|
54
|
+
beatSupervision(repoRoot, named, seat, pct === undefined ? undefined : { armId: armId ?? seat, pct, thresholdPct });
|
|
52
55
|
return `${named} ARMED as ${seat} — beat again every ${SUPERVISION_BEAT_MS / 1_000}s; the tier reads STALE ${SUPERVISION_STALE_MS / 1_000}s after the last beat`;
|
|
53
56
|
}
|
|
54
57
|
/** `--seat <identity>` or `--seat=<identity>`; blank and missing are the same answer — none. */
|
|
@@ -19,6 +19,10 @@ export type DoctorOpts = {
|
|
|
19
19
|
compact?: boolean;
|
|
20
20
|
/** Test seam for the same `orca status --json` transport production invokes. */
|
|
21
21
|
orcaStatusProbe?: (cwd: string, binary: string) => Promise<ShResult>;
|
|
22
|
+
/** Test seam for Orca's authoritative per-agent hook coverage listing. */
|
|
23
|
+
orcaHooksStatusProbe?: (cwd: string, binary: string) => Promise<ShResult>;
|
|
24
|
+
/** Environment used only to explain whether auto-selection applies in this terminal. */
|
|
25
|
+
orcaEnv?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
22
26
|
/** Test seam for shell-path discovery; absence remains a normal doctor row, never an exception. */
|
|
23
27
|
resolveOrcaBinary?: (cwd: string) => string | undefined;
|
|
24
28
|
/** Test seam for the runner-owned JSON listing used by the acceptance-oracle report row. */
|
|
@@ -36,7 +40,7 @@ type OrcaCapability = {
|
|
|
36
40
|
* Capability-row `detail` stays hermetic — never `OrcaError.message`, which embeds volatile CLI
|
|
37
41
|
* stderr (Electron timestamps), so the row is byte-stable across runs.
|
|
38
42
|
*/
|
|
39
|
-
export declare function probeOrcaCapability(cwd: string, opts?: Pick<DoctorOpts, "orcaStatusProbe" | "resolveOrcaBinary">): Promise<OrcaCapability>;
|
|
43
|
+
export declare function probeOrcaCapability(cwd: string, opts?: Pick<DoctorOpts, "orcaStatusProbe" | "orcaHooksStatusProbe" | "resolveOrcaBinary" | "orcaEnv">): Promise<OrcaCapability>;
|
|
40
44
|
export declare function runnerIgnoreFinding(cwd: string): {
|
|
41
45
|
verdict: "pass" | "warn";
|
|
42
46
|
detail: string;
|
|
@@ -25,6 +25,62 @@ export const LIVEBENCH_TABLE_MAX_AGE_DAYS = 90;
|
|
|
25
25
|
const visual = () => process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
26
26
|
const alignedStatusRow = (verdict, key, value) => ` ${statusRow(verdict, kvRow(key, value).slice(2))}`;
|
|
27
27
|
const attentionRow = (text) => ` ${statusRow("warn", text)}`;
|
|
28
|
+
const ORCA_HOOK_ADAPTERS = {
|
|
29
|
+
claude: "claude-code",
|
|
30
|
+
"claude-code": "claude-code",
|
|
31
|
+
codex: "codex",
|
|
32
|
+
cursor: "cursor-agent",
|
|
33
|
+
"cursor-agent": "cursor-agent",
|
|
34
|
+
grok: "grok",
|
|
35
|
+
kimi: "kimi",
|
|
36
|
+
opencode: "opencode",
|
|
37
|
+
pi: "pi",
|
|
38
|
+
};
|
|
39
|
+
function orcaSelectionDetail(env) {
|
|
40
|
+
const inside = env.TERM_PROGRAM === "Orca" && !!env.ORCA_TERMINAL_HANDLE?.trim();
|
|
41
|
+
return inside
|
|
42
|
+
? "auto picks orca in this Orca terminal"
|
|
43
|
+
: "not an Orca terminal; auto picks orca only inside one; use --driver orca";
|
|
44
|
+
}
|
|
45
|
+
async function orcaHookCoverage(cwd, binary, opts) {
|
|
46
|
+
// A supplied status transport is a hermetic boundary: do not escape it to a real binary for the
|
|
47
|
+
// second command. Tests (and embedders) that want coverage supply the matching hook transport.
|
|
48
|
+
if (opts.orcaHooksStatusProbe === undefined && opts.orcaStatusProbe !== undefined) {
|
|
49
|
+
return "hooks status unavailable";
|
|
50
|
+
}
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await (opts.orcaHooksStatusProbe ?? ((probeCwd, executable) => sh(`${shq(executable)} agent hooks status --json`, probeCwd, 10_000)))(cwd, binary);
|
|
54
|
+
if (response.code !== 0 || response.timedOut)
|
|
55
|
+
return "hooks status unavailable";
|
|
56
|
+
// Until the driver's response-family expansion lands, this still uses its one strict envelope
|
|
57
|
+
// parser. Coverage comes only from this command's `statuses[].state`; managedHooksPresent and
|
|
58
|
+
// local agent config files are deliberately not alternative oracles.
|
|
59
|
+
const envelope = parseEnvelope("status", response.stdout);
|
|
60
|
+
const statuses = envelope.result.statuses;
|
|
61
|
+
if (!Array.isArray(statuses))
|
|
62
|
+
return "hooks status unavailable";
|
|
63
|
+
const coverage = new Map();
|
|
64
|
+
for (const raw of statuses) {
|
|
65
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
66
|
+
continue;
|
|
67
|
+
const row = raw;
|
|
68
|
+
const adapter = typeof row.agent === "string" ? ORCA_HOOK_ADAPTERS[row.agent] : undefined;
|
|
69
|
+
if (!adapter)
|
|
70
|
+
continue;
|
|
71
|
+
if (row.state === "installed")
|
|
72
|
+
coverage.set(adapter, "hooked");
|
|
73
|
+
else if (row.state === "not_installed")
|
|
74
|
+
coverage.set(adapter, "unhooked");
|
|
75
|
+
}
|
|
76
|
+
return coverage.size
|
|
77
|
+
? `hooks: ${[...coverage].map(([adapter, state]) => `${adapter} ${state}`).join(", ")}`
|
|
78
|
+
: "hooks: no tickmarkr adapter status reported";
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return "hooks status unavailable";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
28
84
|
/**
|
|
29
85
|
* Orca's status body is deliberately interpreted by T1's one shared envelope parser. Doctor owns
|
|
30
86
|
* only capability presentation: it may classify an absent executable, but it never invents a second
|
|
@@ -63,13 +119,15 @@ export async function probeOrcaCapability(cwd, opts = {}) {
|
|
|
63
119
|
? runtime.reachable
|
|
64
120
|
: undefined;
|
|
65
121
|
if (reachable === true) {
|
|
122
|
+
const hooks = await orcaHookCoverage(cwd, binary, opts);
|
|
123
|
+
const selection = orcaSelectionDetail(opts.orcaEnv ?? process.env);
|
|
66
124
|
const appVersion = typeof runtime === "object" && runtime !== null && !Array.isArray(runtime)
|
|
67
125
|
? runtime.appVersion
|
|
68
126
|
: undefined;
|
|
69
127
|
if (typeof appVersion !== "string" || !appVersion) {
|
|
70
|
-
return { verdict: "warn", detail: `runtime reachable (${envelope.runtimeId}, appVersion absent; fixture pin ${ORCA_FIXTURE_VERSION})` };
|
|
128
|
+
return { verdict: "warn", detail: `runtime reachable (${envelope.runtimeId}, appVersion absent; fixture pin ${ORCA_FIXTURE_VERSION}); ${hooks} — ${selection}` };
|
|
71
129
|
}
|
|
72
|
-
const detail = `runtime reachable (${envelope.runtimeId}, appVersion ${appVersion}; fixture pin ${ORCA_FIXTURE_VERSION})`;
|
|
130
|
+
const detail = `runtime reachable (${envelope.runtimeId}, appVersion ${appVersion}; fixture pin ${ORCA_FIXTURE_VERSION}); ${hooks} — ${selection}`;
|
|
73
131
|
return { verdict: appVersion === ORCA_FIXTURE_VERSION ? "pass" : "warn", detail };
|
|
74
132
|
}
|
|
75
133
|
if (reachable === false)
|
|
@@ -459,8 +517,8 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
459
517
|
}
|
|
460
518
|
if (trustNa.length)
|
|
461
519
|
rows.push(` ${dim("=")} ${dim(`n/a (${trustNa.length}): ${trustNa.join(", ")}`)}`);
|
|
462
|
-
//
|
|
463
|
-
//
|
|
520
|
+
// Capability is diagnostic only: auto-selection is decided from terminal identity markers, never
|
|
521
|
+
// from this runtime probe, and a failed probe cannot silently substitute another driver.
|
|
464
522
|
const orca = await probeOrcaCapability(cwd, opts);
|
|
465
523
|
rows.push(legend("execution runtime:"));
|
|
466
524
|
rows.push(alignedStatusRow(orca.verdict, "orca", orca.detail));
|
|
@@ -577,9 +635,11 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
577
635
|
const probed = probedMs !== undefined ? dim(` ${(probedMs / 1000).toFixed(1)}s`) : "";
|
|
578
636
|
const auth = !v
|
|
579
637
|
? dim("unknown")
|
|
580
|
-
: v.
|
|
581
|
-
? `${
|
|
582
|
-
:
|
|
638
|
+
: v.probeError
|
|
639
|
+
? `${fail(`probe error (${v.probeError})`)}${probed}`
|
|
640
|
+
: v.authed
|
|
641
|
+
? `${ok("authed")}${probed}`
|
|
642
|
+
: `${fail("unauthed:")} ${trunc(v.reason ?? "probe failed", 40)} (${dateOf(v.probedAt)})`;
|
|
583
643
|
const d = disallowedBy({ adapter: a.id, model: m }, cfg.routing);
|
|
584
644
|
const denied = d?.by === "deny" ? d.entry : "—";
|
|
585
645
|
const pref = preferRanks({ adapter: a.id, model: m }, cfg).map((p) => `${p.shape}#${p.rank}`).join(",") || "—";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { parseArgs } from "node:util";
|
|
@@ -8,20 +9,20 @@ import { configTemplate, DEFAULT_CONFIG, globalConfigDir, loadConfig } from "../
|
|
|
8
9
|
import { LEGACY_PREFIX, specTemplate } from "../../compile/native.js";
|
|
9
10
|
import { BANNER, kvRow, legend, rule, statusRow, title } from "../../brand.js";
|
|
10
11
|
import { tickmarkrDir } from "../../graph/graph.js";
|
|
12
|
+
import { orcaHostDetected } from "../../drivers/index.js";
|
|
11
13
|
import { Journal } from "../../run/journal.js";
|
|
12
14
|
import { doctor } from "./doctor.js";
|
|
13
15
|
import { assembleFleetEditor } from "./fleet.js";
|
|
14
16
|
const SCAFFOLD_SPEC = "tickmarkr.spec.md";
|
|
15
17
|
// Operator-approved (2026-07-17) environments footer — no npm install for herdr (npm package
|
|
16
|
-
// "herdr" is a reserved 0.0.0 placeholder as of that date).
|
|
17
|
-
//
|
|
18
|
-
// herdr-else-subprocess and never picks it, so the footer names it beside the other two choices.
|
|
18
|
+
// "herdr" is a reserved 0.0.0 placeholder as of that date). Auto resolves herdr → orca →
|
|
19
|
+
// subprocess; the orca leg is environment-only, using the two markers Orca stamps in its terminals.
|
|
19
20
|
const ENVIRONMENTS_FOOTER = [
|
|
20
21
|
"environments:",
|
|
21
|
-
" herdr — the full cockpit — every worker, judge, and consult is a visible pane you can watch and unblock · https://herdr.dev",
|
|
22
|
-
" orca — visible terminals in the Orca app — an
|
|
22
|
+
" herdr — the full cockpit — every worker, judge, and consult is a visible pane you can watch and unblock · https://herdr.dev · auto picks it first when HERDR_ENV=1",
|
|
23
|
+
" orca — visible terminals in the Orca app — auto picks it inside an Orca terminal after herdr and before subprocess; set driver: orca to force it · https://onorca.dev",
|
|
23
24
|
" claude code — tickmarkr init --agent installs the /tkr skills + AGENTS.md so Claude Code (or any agent CLI) drives the loop natively",
|
|
24
|
-
" anywhere — no herdr? same fail-closed gates, headless subprocess driver",
|
|
25
|
+
" anywhere — no herdr or Orca terminal? same fail-closed gates, headless subprocess driver (legacy: no herdr? same fail-closed gates, headless subprocess driver when Orca markers are absent)",
|
|
25
26
|
].join("\n");
|
|
26
27
|
/** Latest journal without a run-end event, if any. */
|
|
27
28
|
function activeRunId(cwd) {
|
|
@@ -140,17 +141,6 @@ you were handed the whole picture — list the repository root, open every guida
|
|
|
140
141
|
direction each moves, what makes a criterion real, and why an absence or a source-text grep is never a
|
|
141
142
|
criterion. Read it before authoring or repairing acceptance items.
|
|
142
143
|
- **Editing \`src/gates/\`, \`src/compile/\` or \`src/graph/\`?** Read \`docs/codebase/ARCHITECTURE.md\` first.
|
|
143
|
-
- **EVERY fix gets a ship/no-ship decision, recorded, at the moment it is made.** Ask one question of each
|
|
144
|
-
one: *does a user hit this defect?* If yes, the fix belongs in \`src/**\` or \`skills/**\` — the only trees
|
|
145
|
-
the package carries (\`files: [dist, schema, skills, fixtures]\`). A script, overlay, config entry or
|
|
146
|
-
operator-side workaround that resolves the symptom **locally is not the fix; it is a decision to leave
|
|
147
|
-
every other user broken**, and it must say so in writing and name the condition that removes it.
|
|
148
|
-
**The default answer is SHIP.** A local remedy is the exception and carries the burden of proof.
|
|
149
|
-
Watch for the three shapes this hides in: a fix applied where you happened to be standing rather than
|
|
150
|
-
where the defect lives; an observation filed with a product fix named in its own text and queued
|
|
151
|
-
nowhere; and a local tool that quietly grows into a product feature nobody shipped. **A defect and its
|
|
152
|
-
fix must be recorded in the same place, or the queue silently becomes a list of things everyone assumed
|
|
153
|
-
someone else had shipped.**
|
|
154
144
|
${DOCS_END}
|
|
155
145
|
`;
|
|
156
146
|
// Every applicable host location gets the skills, each paired with its own repository guidance
|
|
@@ -229,7 +219,28 @@ const describeDrift = (d) => [
|
|
|
229
219
|
// should not be asked again just because a copy drifted. Staleness is a different question with a
|
|
230
220
|
// different answer (`--agent --force`), and it is reported by name on every init path below.
|
|
231
221
|
const skillsInstalled = (cwd) => hostTargets(cwd).every((t) => AGENT_SKILLS.every((s) => existsSync(join(t.skillsDir, s, "SKILL.md"))));
|
|
232
|
-
const wizardDriverDefault = () => process.env.HERDR_ENV === "1" ? "herdr" : "auto";
|
|
222
|
+
const wizardDriverDefault = () => process.env.HERDR_ENV === "1" ? "herdr" : orcaHostDetected() ? "orca" : "auto";
|
|
223
|
+
function packageName(cwd) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
226
|
+
return typeof parsed.name === "string" ? parsed.name : null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function gitTracked(cwd, path) {
|
|
233
|
+
const rel = relative(cwd, path);
|
|
234
|
+
if (rel.startsWith(".."))
|
|
235
|
+
return false;
|
|
236
|
+
try {
|
|
237
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", rel], { cwd, stdio: "ignore" });
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
233
244
|
async function installAgentFiles(cwd, force, docs, notes) {
|
|
234
245
|
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
235
246
|
let prompt;
|
|
@@ -257,13 +268,14 @@ async function installAgentFiles(cwd, force, docs, notes) {
|
|
|
257
268
|
notes.push(`kept current ${dest}`);
|
|
258
269
|
continue;
|
|
259
270
|
}
|
|
271
|
+
const tracked = exists && gitTracked(cwd, dest);
|
|
260
272
|
if (exists && !force && !(await confirm(`Overwrite ${dest} (${describeDrift(stale)})?`))) {
|
|
261
|
-
notes.push(`skipped existing ${dest} — ${describeDrift(stale)}; pass --force to overwrite it`);
|
|
273
|
+
notes.push(`skipped existing ${tracked ? "tracked " : ""}${dest} — ${describeDrift(stale)}; pass --force to overwrite it`);
|
|
262
274
|
continue;
|
|
263
275
|
}
|
|
264
276
|
// whole skill dir, not just SKILL.md — the overseer ships its pane-watcher script
|
|
265
277
|
cpSync(fileURLToPath(new URL(`../../../skills/${skill}`, import.meta.url)), join(skillsDir, skill), { recursive: true });
|
|
266
|
-
notes.push(`${exists ? "overwrote" : "wrote"} ${dest}`);
|
|
278
|
+
notes.push(`${exists ? "overwrote" : "wrote"} ${tracked ? "tracked " : ""}${dest}`);
|
|
267
279
|
}
|
|
268
280
|
const current = existsSync(docPath) ? readFileSync(docPath, "utf8") : "";
|
|
269
281
|
// The block is CANONICAL guidance ("stated once here, restated nowhere"), so a repo that has
|
|
@@ -341,6 +353,9 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
341
353
|
});
|
|
342
354
|
// banner at START on the visual surface — every init path, not just the wizard, and never trailing the probe (operator report 2026-07-17)
|
|
343
355
|
emitBanner();
|
|
356
|
+
if (packageName(cwd) === "tickmarkr") {
|
|
357
|
+
throw new Error("OBS-584: tickmarkr init refuses to run inside the tickmarkr source repository");
|
|
358
|
+
}
|
|
344
359
|
const gdir = values["global-dir"] ?? globalConfigDir();
|
|
345
360
|
mkdirSync(gdir, { recursive: true });
|
|
346
361
|
const notes = [];
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
1
4
|
import { allAdapters, discoverChannels, doctorAgeMs, modelAuthExclusions, probeAll, readDoctor, servableExclusions, servabilityLine } from "../../adapters/registry.js";
|
|
2
5
|
import { formatModelAuthLine, contextWindowLints, modelLints, preferEntryLints, ttyVisual } from "../../adapters/model-lints.js";
|
|
3
6
|
import { GLYPHS, dim, rule, title, warn } from "../../brand.js";
|
|
4
|
-
import { parseArgs } from "node:util";
|
|
5
7
|
import { collateralLints, sourceScopeLints } from "../../compile/collateral.js";
|
|
6
8
|
import { classifyContextPath } from "../../compile/native.js";
|
|
7
9
|
import { DEFAULT_CONFIG, overlayPreferShapes, ROUTING_MODES, TIER_RANK } from "../../config/config.js";
|
|
@@ -106,10 +108,15 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
106
108
|
}
|
|
107
109
|
const g = loadGraph(cwd);
|
|
108
110
|
const oracleRefusals = new Map();
|
|
111
|
+
const oracleAdvisories = new Map();
|
|
109
112
|
if (g.tasks.some((task) => task.acceptance.some((item) => typeof item === "object" && item.oracle === "test"))) {
|
|
110
113
|
const listing = await (opts.listTests ?? listVitestTests)(cwd);
|
|
111
114
|
for (const task of g.tasks) {
|
|
112
115
|
const refusals = [];
|
|
116
|
+
const advisories = [];
|
|
117
|
+
const authoredTest = task.files
|
|
118
|
+
.map((path) => path.replace(/^\.\//, ""))
|
|
119
|
+
.find((path) => /^tests\/.+\.test\.ts$/.test(path) && !/[?*{[]/.test(path) && !existsSync(join(cwd, path)));
|
|
113
120
|
if (listing.status === "failed") {
|
|
114
121
|
if (task.acceptance.some((item) => typeof item === "object" && item.oracle === "test")) {
|
|
115
122
|
refusals.push(`acceptance oracle unresolved — runner listing failed: ${listing.error.split("\n")[0]}`);
|
|
@@ -117,7 +124,10 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
117
124
|
}
|
|
118
125
|
else {
|
|
119
126
|
for (const audit of auditNamedTestOracles(task.acceptance, listing.tests)) {
|
|
120
|
-
if (audit.matches.length === 0) {
|
|
127
|
+
if (audit.matches.length === 0 && authoredTest) {
|
|
128
|
+
advisories.push(`acceptance oracle ${JSON.stringify(audit.criterion)} not yet written (worker authors ${authoredTest})`);
|
|
129
|
+
}
|
|
130
|
+
else if (audit.matches.length === 0) {
|
|
121
131
|
refusals.push(`acceptance oracle ${JSON.stringify(audit.criterion)} matches zero runner-listed test names`);
|
|
122
132
|
}
|
|
123
133
|
else if (audit.matches.length > 1) {
|
|
@@ -127,6 +137,8 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
127
137
|
}
|
|
128
138
|
if (refusals.length)
|
|
129
139
|
oracleRefusals.set(task.id, refusals);
|
|
140
|
+
if (advisories.length)
|
|
141
|
+
oracleAdvisories.set(task.id, advisories);
|
|
130
142
|
}
|
|
131
143
|
}
|
|
132
144
|
const inputFindings = await taskInputFindings(g.tasks, cwd);
|
|
@@ -283,6 +295,8 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
283
295
|
lines.push(d);
|
|
284
296
|
lints.push(`${t.id}: unroutable — ${msg}`);
|
|
285
297
|
}
|
|
298
|
+
for (const advisory of oracleAdvisories.get(t.id) ?? [])
|
|
299
|
+
lines.push(` ! ${advisory}`);
|
|
286
300
|
}
|
|
287
301
|
const inputWarnings = inputFindings.filter((finding) => finding.severity === "warn");
|
|
288
302
|
if (inputWarnings.length) {
|
|
@@ -4,6 +4,7 @@ import { ttyVisual } from "../../adapters/model-lints.js";
|
|
|
4
4
|
import { addUsage } from "../../adapters/types.js";
|
|
5
5
|
import { dim, rule, title } from "../../brand.js";
|
|
6
6
|
import { loadConfig } from "../../config/config.js";
|
|
7
|
+
import { modelProvider } from "../../gates/review.js";
|
|
7
8
|
import { buildProofBundle, gateDeclined } from "../../report/bundle.js";
|
|
8
9
|
import { compareRuns } from "../../report/compare.js";
|
|
9
10
|
import { estimateCosts } from "../../report/cost.js";
|
|
@@ -35,6 +36,22 @@ const channelLabel = (data) => {
|
|
|
35
36
|
const { adapter, model } = a;
|
|
36
37
|
return typeof adapter === "string" && typeof model === "string" ? `${adapter}:${model}` : EM;
|
|
37
38
|
};
|
|
39
|
+
const modelFromChannel = (channel) => channel.slice(channel.indexOf(":") + 1);
|
|
40
|
+
const reviewerIdentity = (data) => {
|
|
41
|
+
const details = typeof data.details === "string" ? data.details : "";
|
|
42
|
+
const fromDetails = /reviewer\s+([^\s;()]+:[^\s;()]+)/i.exec(details)?.[1];
|
|
43
|
+
const reviewer = typeof data.reviewer === "string" ? data.reviewer : fromDetails;
|
|
44
|
+
if (!reviewer || !reviewer.includes(":"))
|
|
45
|
+
return undefined;
|
|
46
|
+
const vendor = typeof data.vendor === "string"
|
|
47
|
+
? data.vendor
|
|
48
|
+
: /vendor:\s*([^;()]+)/i.exec(details)?.[1]?.trim()
|
|
49
|
+
?? /\(([^;:()]+)\):/.exec(details)?.[1]?.trim();
|
|
50
|
+
const provider = typeof data.provider === "string"
|
|
51
|
+
? data.provider
|
|
52
|
+
: modelProvider(modelFromChannel(reviewer), vendor ?? "unknown");
|
|
53
|
+
return { reviewer, provider, ...(vendor ? { vendor } : {}) };
|
|
54
|
+
};
|
|
38
55
|
const rateBasis = (row) => {
|
|
39
56
|
if (!row.rate)
|
|
40
57
|
return EM;
|
|
@@ -245,6 +262,7 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
245
262
|
const dispatches = events.filter((e) => e.taskId === taskId && e.event === "task-dispatch");
|
|
246
263
|
const channels = dispatches.map((e) => channelLabel(e.data));
|
|
247
264
|
const gates = events.filter((e) => e.taskId === taskId && e.event === "gate-result");
|
|
265
|
+
const leg2 = events.filter((e) => e.taskId === taskId && e.event === "review-leg2");
|
|
248
266
|
const consults = events.filter((e) => e.taskId === taskId && e.event === "consult-verdict");
|
|
249
267
|
const deviations = events.filter((e) => e.taskId === taskId && e.event === "route-deviation");
|
|
250
268
|
const merge = [...events].reverse().find((e) => e.taskId === taskId && e.event === "merge");
|
|
@@ -267,10 +285,28 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
267
285
|
if (gates.length) {
|
|
268
286
|
for (const g of gates) {
|
|
269
287
|
// T11: a declined gate never ran — report it as declined, never as a pass.
|
|
270
|
-
|
|
288
|
+
let pass = gateDeclined(g.data) ? "declined" : g.data.pass === true ? "pass" : g.data.pass === false ? "fail" : EM;
|
|
271
289
|
const gate = typeof g.data.gate === "string" ? g.data.gate : EM;
|
|
290
|
+
if (gate === "review" && pass === "pass") {
|
|
291
|
+
const reviewer = reviewerIdentity(g.data);
|
|
292
|
+
const before = events.slice(0, events.indexOf(g) + 1).reverse()
|
|
293
|
+
.find((e) => e.taskId === taskId && e.event === "task-dispatch");
|
|
294
|
+
const author = channelLabel(before?.data ?? {});
|
|
295
|
+
const authorProvider = author === EM ? "unknown" : modelProvider(modelFromChannel(author));
|
|
296
|
+
if (reviewer && authorProvider !== "unknown" && authorProvider === reviewer.provider) {
|
|
297
|
+
pass = "pass (same-provider — independence not established)";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
272
300
|
lines.push(` - ${gate}: ${pass} — ${firstLine(g.data.details)}`);
|
|
273
301
|
}
|
|
302
|
+
for (const row of leg2) {
|
|
303
|
+
const pass = row.data.pass === true ? "pass" : row.data.pass === false ? "fail" : EM;
|
|
304
|
+
const identity = reviewerIdentity(row.data);
|
|
305
|
+
const identityText = identity
|
|
306
|
+
? `reviewer ${identity.reviewer}${identity.vendor ? ` (vendor: ${identity.vendor}; provider: ${identity.provider})` : ` (provider: ${identity.provider})`}`
|
|
307
|
+
: firstLine(row.data.details);
|
|
308
|
+
lines.push(` - review-leg2: ${pass} (supersedes daemon review) — ${identityText}`);
|
|
309
|
+
}
|
|
274
310
|
}
|
|
275
311
|
else
|
|
276
312
|
lines.push(` - ${EM}`);
|
|
@@ -4,6 +4,11 @@ export declare function parseCriteria(text: string): AcceptanceItem[];
|
|
|
4
4
|
export declare const HUMAN_CHANNEL: BillingChannel;
|
|
5
5
|
export declare const HUMAN_AUTHOR: Assignment;
|
|
6
6
|
export declare function verifyStateDir(cwd: string): string;
|
|
7
|
+
/** Linked worktrees share operational state with the repository that owns the common git dir. */
|
|
8
|
+
export declare function verifyStateRoot(cwd: string): Promise<string>;
|
|
9
|
+
/** Cache identity is repository content, never a checkout path. */
|
|
10
|
+
export declare function baselineCachePath(cwd: string, baseSha: string, commands: Record<string, string>): string;
|
|
11
|
+
export declare function excludeAuthorProvider(channels: BillingChannel[], author: BillingChannel): BillingChannel[];
|
|
7
12
|
export declare function verify(argv: string[], cwd?: string): Promise<{
|
|
8
13
|
out: string;
|
|
9
14
|
code: number;
|