tickmarkr 1.87.0 → 1.90.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/adapters/catalog.d.ts +18 -1
- package/dist/adapters/catalog.js +44 -1
- package/dist/adapters/fake.d.ts +2 -1
- package/dist/adapters/fake.js +7 -0
- package/dist/adapters/grok.js +11 -0
- package/dist/adapters/kimi.d.ts +2 -1
- package/dist/adapters/kimi.js +36 -0
- package/dist/adapters/opencode.js +17 -0
- package/dist/adapters/pi.js +11 -0
- package/dist/adapters/prompt.js +8 -1
- package/dist/adapters/registry.js +76 -57
- package/dist/adapters/types.d.ts +34 -3
- package/dist/adapters/types.js +99 -1
- package/dist/cli/commands/approve.d.ts +2 -0
- package/dist/cli/commands/approve.js +104 -84
- package/dist/cli/commands/compile.d.ts +1 -1
- package/dist/cli/commands/compile.js +29 -12
- package/dist/cli/commands/doctor.d.ts +16 -0
- package/dist/cli/commands/doctor.js +52 -0
- package/dist/cli/commands/init.js +2 -1
- package/dist/cli/commands/plan.d.ts +1 -1
- package/dist/cli/commands/plan.js +10 -1
- package/dist/cli/commands/report.js +49 -0
- package/dist/cli/commands/status.js +298 -96
- package/dist/cli/commands/verify.d.ts +9 -0
- package/dist/cli/commands/verify.js +177 -0
- package/dist/cli/harness.d.ts +13 -0
- package/dist/cli/harness.js +50 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/compile/collateral.js +11 -11
- package/dist/compile/common.js +2 -2
- package/dist/compile/index.d.ts +14 -3
- package/dist/compile/index.js +36 -10
- package/dist/compile/native.d.ts +15 -1
- package/dist/compile/native.js +310 -28
- package/dist/config/config.js +2 -2
- package/dist/drivers/subprocess.d.ts +6 -1
- package/dist/drivers/subprocess.js +9 -4
- package/dist/gates/acceptance.d.ts +21 -1
- package/dist/gates/acceptance.js +67 -22
- package/dist/gates/artifact-manifest.d.ts +119 -0
- package/dist/gates/artifact-manifest.js +357 -0
- package/dist/gates/baseline.d.ts +45 -5
- package/dist/gates/baseline.js +119 -15
- package/dist/gates/llm.js +37 -26
- package/dist/gates/review.d.ts +16 -11
- package/dist/gates/review.js +44 -150
- package/dist/gates/run-gates.js +124 -7
- package/dist/gates/scope.js +3 -3
- package/dist/graph/files-glob.d.ts +18 -0
- package/dist/graph/files-glob.js +22 -0
- package/dist/graph/schema.d.ts +3 -1
- package/dist/graph/schema.js +4 -1
- package/dist/run/daemon.d.ts +44 -0
- package/dist/run/daemon.js +2334 -1973
- package/dist/run/git.d.ts +53 -0
- package/dist/run/git.js +119 -5
- package/dist/run/interactive-seed.d.ts +6 -2
- package/dist/run/interactive-seed.js +72 -5
- package/dist/run/journal.d.ts +9 -1
- package/dist/run/journal.js +99 -9
- package/dist/run/lock.d.ts +11 -0
- package/dist/run/lock.js +97 -6
- package/dist/run/merge.d.ts +4 -1
- package/dist/run/merge.js +26 -7
- package/dist/run/outcome.d.ts +50 -0
- package/dist/run/outcome.js +152 -0
- package/dist/run/protocol.d.ts +460 -0
- package/dist/run/protocol.js +433 -0
- package/dist/run/supervision.d.ts +29 -0
- package/dist/run/supervision.js +189 -0
- package/fixtures/authoring-lints/01-awk-range-self-pass.spec.md +12 -0
- package/fixtures/authoring-lints/02-judge-text-key-miss.spec.md +7 -0
- package/fixtures/authoring-lints/03-c1-t41-rendered-observable.spec.md +8 -0
- package/fixtures/authoring-lints/04-c1-t24-named-file.spec.md +8 -0
- package/fixtures/authoring-lints/05-c2-t24-t28-dep-inversion.spec.md +7 -0
- package/fixtures/authoring-lints/06-c2-denumbered-coupling.spec.md +7 -0
- package/fixtures/authoring-lints/07-c3a-t41-line-count-proxy.spec.md +7 -0
- package/fixtures/authoring-lints/08-c3b-t41-governance-referent.spec.md +7 -0
- package/fixtures/authoring-lints/09-c4-universals-without-pointer.spec.md +7 -0
- package/fixtures/authoring-lints/10-c5-t34-conjunct-flood.spec.md +7 -0
- package/fixtures/authoring-lints/11-c6-t34-q3-q9-q20-bundle.spec.md +7 -0
- package/fixtures/authoring-lints/12-c7-t24-prose-seam.spec.md +8 -0
- package/fixtures/wrapped-acceptance.native.md +29 -0
- package/package.json +1 -1
- package/schema/rungraph.schema.json +21 -2
- package/skills/tickmarkr-overseer/SKILL.md +262 -5
- package/skills/tickmarkr-overseer/scripts/watch-artifacts.sh +79 -8
- package/skills/tickmarkr-overseer/scripts/watch-contamination.sh +80 -0
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +86 -0
- package/skills/tickmarkr-overseer/scripts/watch-parks.sh +96 -0
- package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +201 -0
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { BANNER, GLYPHS, dim, fail, legend, ok, rule, statusRow, title, warn } from "../../brand.js";
|
|
4
|
+
import { DEFAULT_CONFIG, loadConfig } from "../../config/config.js";
|
|
4
5
|
import { HerdrDriver } from "../../drivers/herdr.js";
|
|
5
|
-
import { formatOwnedName, } from "../../drivers/types.js";
|
|
6
|
+
import { formatOwnedName, parseOwnedName, } from "../../drivers/types.js";
|
|
6
7
|
import { blockedTasks, graphDefinitionHash, loadGraph, stateDirName } from "../../graph/graph.js";
|
|
7
8
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
8
9
|
import { foldActivity } from "../../run/activity.js";
|
|
9
10
|
import { Journal, engagementComparable, isQualityFailureParkKind, recordedTaskFailureKind, runHasEnded, } from "../../run/journal.js";
|
|
11
|
+
import { desiredPanes } from "../../run/reconcile.js";
|
|
10
12
|
import { normalizeStallSnapshot } from "../../run/stall.js";
|
|
13
|
+
import { readSupervision, supervisionText } from "../../run/supervision.js";
|
|
11
14
|
import { deriveRunCockpitData, } from "../../tui/cockpit/derive.js";
|
|
12
|
-
import {
|
|
15
|
+
import { COCKPIT_COLUMN_FLOOR, LAYOUT_PRIORITY } from "../../tui/cockpit/layout.js";
|
|
16
|
+
import { cellWidth, fitCells, wrapCells } from "../../tui/cockpit/width.js";
|
|
13
17
|
// ponytail: fixed 2s refresh; promote to config.visibility.* only when an operator asks.
|
|
14
18
|
const REFRESH_MS = 2000;
|
|
15
|
-
|
|
19
|
+
// The claim leads, the guidance follows: an incomparable journal is named in three words a wrapped
|
|
20
|
+
// board keeps on one row, and the remedy trails it. Both surfaces state the claim; nothing derived
|
|
21
|
+
// from such a journal — task states, gate outcomes, tip verification — may be presented as this
|
|
22
|
+
// run's, so the verify claim beside it reads `unavailable` rather than the recorded verdict.
|
|
23
|
+
const NOT_COMPARABLE_CLAIM = "graph not comparable";
|
|
24
|
+
const NOT_COMPARABLE_NOTICE = `${NOT_COMPARABLE_CLAIM} — recompiled since this run; resume with \`--graph-changed\` to audit this recompile`;
|
|
16
25
|
const PRIOR_GRAPH_MARKER = "prior graph";
|
|
17
26
|
// One compact host-zone label per screen. Each event below is still converted from its own
|
|
18
27
|
// complete UTC instant, so the zone's historical rules — including a transition within one frame
|
|
@@ -160,6 +169,11 @@ const postWebhookFireAndForget = (post, url, event) => {
|
|
|
160
169
|
// Webhooks are observational. A synchronous bridge failure is as inert as a rejected request.
|
|
161
170
|
}
|
|
162
171
|
};
|
|
172
|
+
// `phase` is a coarse word — the daemon writes "judge" for the acceptance gate and, for a review
|
|
173
|
+
// round, whatever phaseForGate() maps that gate to. The gate a row must NAME is the event's own
|
|
174
|
+
// `gate` field; a value that is not one of the seven declared gates is not a gate name and is
|
|
175
|
+
// ignored rather than printed.
|
|
176
|
+
const recordedGate = (value) => typeof value === "string" && GATE_NAMES.includes(value) ? value : undefined;
|
|
163
177
|
const taskPhase = (value) => {
|
|
164
178
|
if (value === "worker" || value === "gates" || value === "judge" || value === "review" || value === "merge")
|
|
165
179
|
return value;
|
|
@@ -191,8 +205,10 @@ const livePhases = (events) => {
|
|
|
191
205
|
const attempt = typeof event.data.attempt === "number" && Number.isInteger(event.data.attempt) && event.data.attempt >= 0
|
|
192
206
|
? event.data.attempt
|
|
193
207
|
: 0;
|
|
194
|
-
|
|
195
|
-
|
|
208
|
+
const gate = recordedGate(event.data.gate);
|
|
209
|
+
if (phase && Number.isFinite(startedAt)) {
|
|
210
|
+
live.set(event.taskId, { taskId: event.taskId, phase, startedAt, order, attempt, ...(gate ? { gate } : {}) });
|
|
211
|
+
}
|
|
196
212
|
continue;
|
|
197
213
|
}
|
|
198
214
|
if (!event.taskId)
|
|
@@ -200,7 +216,7 @@ const livePhases = (events) => {
|
|
|
200
216
|
const active = live.get(event.taskId);
|
|
201
217
|
if (!active)
|
|
202
218
|
continue;
|
|
203
|
-
const gate = phaseGate(active.phase);
|
|
219
|
+
const gate = active.gate ?? phaseGate(active.phase);
|
|
204
220
|
const matched = (active.phase === "worker" && event.event === "worker-result")
|
|
205
221
|
|| (active.phase === "gates" && event.event === "gate-result")
|
|
206
222
|
|| (gate !== undefined && event.event === "gate-result" && event.data.gate === gate)
|
|
@@ -215,7 +231,14 @@ const livePhases = (events) => {
|
|
|
215
231
|
}
|
|
216
232
|
return live;
|
|
217
233
|
};
|
|
218
|
-
|
|
234
|
+
// A gate the journal named is rendered by that name. Only a phase the journal left ungated falls
|
|
235
|
+
// back to the phase word — the word alone cannot tell an acceptance round from a review one, since
|
|
236
|
+
// the daemon records both under a phase of its own choosing (phaseForGate).
|
|
237
|
+
const phaseLabel = (phase) => {
|
|
238
|
+
if (typeof phase !== "string")
|
|
239
|
+
return phase.gate ? `gate ${phase.gate}` : phaseLabel(phase.phase);
|
|
240
|
+
return phase.startsWith("gate:") ? `gate ${phase.slice("gate:".length)}` : phase;
|
|
241
|
+
};
|
|
219
242
|
const fmtElapsed = (ms) => {
|
|
220
243
|
const seconds = Math.max(0, Math.floor(ms / 1000));
|
|
221
244
|
if (seconds < 60)
|
|
@@ -230,7 +253,7 @@ const workerOutputAge = (phase, now, worker) => now - (worker?.phaseStartedAt ==
|
|
|
230
253
|
const phaseDetail = (phase, now, worker) => {
|
|
231
254
|
const elapsed = fmtElapsed(now - phase.startedAt);
|
|
232
255
|
if (phase.phase !== "worker")
|
|
233
|
-
return `${phaseLabel(phase
|
|
256
|
+
return `${phaseLabel(phase)} · ${elapsed} elapsed`;
|
|
234
257
|
const age = fmtElapsed(workerOutputAge(phase, now, worker));
|
|
235
258
|
const output = worker?.phaseStartedAt === phase.startedAt && worker.hasOutput
|
|
236
259
|
? `last output ${age} ago`
|
|
@@ -335,6 +358,26 @@ export const shortGoal = (goal, max) => {
|
|
|
335
358
|
return fitCells(clause, cells).trimEnd();
|
|
336
359
|
return `${fitCells(clause, cells - 3).trimEnd()}...`;
|
|
337
360
|
};
|
|
361
|
+
const detailText = (segments, budget) => {
|
|
362
|
+
const shedOrder = LAYOUT_PRIORITY.slice(LAYOUT_PRIORITY.indexOf("journal") + 1).reverse();
|
|
363
|
+
const shed = new Set();
|
|
364
|
+
const compose = () => segments
|
|
365
|
+
.filter((segment) => segment.text.length > 0 && !shed.has(segment.element))
|
|
366
|
+
.map((segment) => segment.text)
|
|
367
|
+
.join(" · ");
|
|
368
|
+
for (const element of shedOrder) {
|
|
369
|
+
if (cellWidth(compose()) <= budget)
|
|
370
|
+
break;
|
|
371
|
+
shed.add(element);
|
|
372
|
+
}
|
|
373
|
+
return compose();
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Every board line leaves through the cockpit's own wrap, measured in display cells: no drawn line
|
|
377
|
+
* exceeds the board's columns, and nothing is shortened into a count to get there. Blank separators
|
|
378
|
+
* stay blank — `wrapCells` always answers at least one row.
|
|
379
|
+
*/
|
|
380
|
+
const boardRows = (lines, columns) => lines.flatMap((line) => wrapCells(line, columns, { continuationPrefix: " " }));
|
|
338
381
|
// VIS-11 (v1.13): a liveness header for renderFrame — last journal event age + whether the recorded
|
|
339
382
|
// daemon pid is still alive. Honest about unknowns: a pre-v1.13 journal with no pid renders "unknown",
|
|
340
383
|
// never fabricated (garbage pid data fails toward unknown too). kill(pid,0) is a signal probe, not a
|
|
@@ -446,15 +489,93 @@ const tipVerifyPhase = (events) => {
|
|
|
446
489
|
};
|
|
447
490
|
const tipVerifyText = (phase) => {
|
|
448
491
|
if (phase.state === "pending")
|
|
449
|
-
return "
|
|
492
|
+
return "verify pending";
|
|
450
493
|
const gates = phase.gates.length ? phase.gates.join(", ") : "gate unknown";
|
|
451
|
-
const failed = `
|
|
494
|
+
const failed = `verify FAILED (${gates})`;
|
|
452
495
|
const reverify = phase.state === "re-verifying" ? ` → re-verifying (run attempt ${phase.attempt})` : "";
|
|
453
496
|
const fingerprints = phase.fingerprints === undefined
|
|
454
497
|
? ""
|
|
455
498
|
: ` · ${phase.fingerprints} fingerprint${phase.fingerprints === 1 ? "" : "s"}`;
|
|
456
499
|
return `${failed}${reverify}${fingerprints}`;
|
|
457
500
|
};
|
|
501
|
+
// The only tip-verification verdicts a run-end can RECORD. Anything else in that field — absent, a
|
|
502
|
+
// value carrying a newline or terminal controls, a word from some other vocabulary — records no
|
|
503
|
+
// verdict, and the surfaces say so instead of echoing it or counting `tip-verify` events to guess
|
|
504
|
+
// (an operator-local reimplementation counted them and scored two failed runs as verified).
|
|
505
|
+
const RECORDED_VERIFY = new Set(["passed", "failed"]);
|
|
506
|
+
const recordedVerify = (events) => {
|
|
507
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
508
|
+
const event = events[i];
|
|
509
|
+
if (event.event !== "run-end")
|
|
510
|
+
continue;
|
|
511
|
+
const recorded = event.data.tipVerify;
|
|
512
|
+
return typeof recorded === "string" && RECORDED_VERIFY.has(recorded)
|
|
513
|
+
? recorded
|
|
514
|
+
: "unrecorded";
|
|
515
|
+
}
|
|
516
|
+
return "unrecorded"; // no run-end: this run's record names no verdict yet
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* What the board may CLAIM about tip verification, in one place for both surfaces.
|
|
520
|
+
*
|
|
521
|
+
* An incomparable journal yields no claim about this graph at all, so it reads `unavailable` rather
|
|
522
|
+
* than the recorded verdict. A live phase (failed, re-verifying, pending) keeps its own richer
|
|
523
|
+
* phrase. Otherwise the run-end field is READ: `passed` is stated positively — silence used to be
|
|
524
|
+
* the only sign of a passed tip, which reads identically to a surface that simply hides it — and a
|
|
525
|
+
* record that ends without naming a verdict is `unrecorded`. A run still in flight claims nothing.
|
|
526
|
+
*/
|
|
527
|
+
const verifyClaim = (events, comparable, tipPhase) => {
|
|
528
|
+
if (!comparable)
|
|
529
|
+
return "verify unavailable";
|
|
530
|
+
if (tipPhase)
|
|
531
|
+
return tipVerifyText(tipPhase);
|
|
532
|
+
if (!runHasEnded(events))
|
|
533
|
+
return undefined;
|
|
534
|
+
return `verify ${recordedVerify(events)}`;
|
|
535
|
+
};
|
|
536
|
+
/**
|
|
537
|
+
* Where an operator can still look, per task — folded from the daemon's OWN pane authority
|
|
538
|
+
* (`desiredPanes`, the reconcile fold that decides which owned panes should exist right now) and
|
|
539
|
+
* the visibility setting that governs when a worker pane closes. A locator built from dispatch
|
|
540
|
+
* history alone survives the events that retire a pane — `task-done` closes a merged task's worker
|
|
541
|
+
* pane, `run-end` retires every pane of the run — and sends the operator to a pane that is gone.
|
|
542
|
+
*
|
|
543
|
+
* Under `keepPanes: "attempt"` the daemon closes a worker pane as soon as it harvests that attempt,
|
|
544
|
+
* before the gates run and before `worker-result` is journaled (daemon.ts), so a harvested attempt
|
|
545
|
+
* has no pane left however desirable the fold finds it.
|
|
546
|
+
*/
|
|
547
|
+
const workerPaneLocators = (events, runId, keepPanes) => {
|
|
548
|
+
const locators = new Map();
|
|
549
|
+
for (const name of desiredPanes(events, runId)) {
|
|
550
|
+
const owned = parseOwnedName(name);
|
|
551
|
+
if (owned?.role === "worker")
|
|
552
|
+
locators.set(owned.taskId, name);
|
|
553
|
+
}
|
|
554
|
+
if (keepPanes !== "attempt")
|
|
555
|
+
return locators;
|
|
556
|
+
const harvested = new Set();
|
|
557
|
+
for (const event of events) {
|
|
558
|
+
if (!event.taskId)
|
|
559
|
+
continue;
|
|
560
|
+
if (event.event === "task-dispatch")
|
|
561
|
+
harvested.delete(event.taskId);
|
|
562
|
+
else if (event.event === "worker-result")
|
|
563
|
+
harvested.add(event.taskId);
|
|
564
|
+
}
|
|
565
|
+
for (const taskId of harvested)
|
|
566
|
+
locators.delete(taskId);
|
|
567
|
+
return locators;
|
|
568
|
+
};
|
|
569
|
+
// status is a reader of the repo it reports on: the same resolved config every other command loads.
|
|
570
|
+
// A config that will not load is not a reason to fail a frame — the shipped default keeps the fold.
|
|
571
|
+
const keepPanesSetting = (cwd) => {
|
|
572
|
+
try {
|
|
573
|
+
return loadConfig(cwd).visibility.keepPanes;
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
return DEFAULT_CONFIG.visibility.keepPanes;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
458
579
|
const liveness = (events, now = Date.now()) => {
|
|
459
580
|
const last = events.at(-1);
|
|
460
581
|
if (!last)
|
|
@@ -507,58 +628,82 @@ const parseJournalSnapshot = (raw) => raw.split("\n").flatMap((line) => {
|
|
|
507
628
|
return [];
|
|
508
629
|
}
|
|
509
630
|
});
|
|
631
|
+
/**
|
|
632
|
+
* ONE journal read per answer, and every claim about the run folded from THOSE bytes — the board's
|
|
633
|
+
* task rows, activity, phases, gates, liveness and tip verification, and the compact one-line form
|
|
634
|
+
* alike. A second read is what lets two snapshots be presented as one state: a line the daemon
|
|
635
|
+
* appends between them pairs a task count from one instant with a verdict from another.
|
|
636
|
+
*
|
|
637
|
+
* An explicit <runId> is a resolution, not a hint: `Journal.open` refuses an id without a readable
|
|
638
|
+
* journal, so status fails loudly naming that id instead of rendering any other run.
|
|
639
|
+
*/
|
|
640
|
+
const readRunRecord = (cwd, graph, namedRunId) => {
|
|
641
|
+
const runId = namedRunId ?? Journal.latestRunId(cwd, { withJournal: true });
|
|
642
|
+
if (!runId)
|
|
643
|
+
return undefined;
|
|
644
|
+
const raw = readFileSync(join(Journal.open(cwd, runId).dir, "journal.jsonl"), "utf8");
|
|
645
|
+
const events = parseJournalSnapshot(raw);
|
|
646
|
+
// The resume comparator is the fail-closed baseline; a matching graph-rehash is the daemon's
|
|
647
|
+
// append-only audit that authorizes this status replay after stop-amend-resume.
|
|
648
|
+
const engagement = statusEngagement(events, graphDefinitionHash(graph));
|
|
649
|
+
return {
|
|
650
|
+
runId,
|
|
651
|
+
raw,
|
|
652
|
+
events,
|
|
653
|
+
comparable: engagement.comparable,
|
|
654
|
+
...(engagement.rehashAt === undefined ? {} : { rehashAt: engagement.rehashAt }),
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
/**
|
|
658
|
+
* The cockpit fold over those same bytes — the journal-backed task authority both forms read.
|
|
659
|
+
*
|
|
660
|
+
* The daemon creates journal.jsonl with lock-reclaimed before run-start, and a reader may also
|
|
661
|
+
* catch an empty or torn first write. Those snapshots own no task fold yet; once run-start exists,
|
|
662
|
+
* however, a fold failure is unexpected and must surface instead of becoming a false empty run.
|
|
663
|
+
*/
|
|
664
|
+
const recordTaskRows = (record, graph) => record.comparable && record.events.some((event) => event.event === "run-start")
|
|
665
|
+
? new Map(deriveRunCockpitData({ fileName: `${record.runId}.journal.jsonl`, raw: record.raw }, "status", { graph }).taskRows.map((row) => [row.taskId, row]))
|
|
666
|
+
: new Map();
|
|
667
|
+
/** The graph tasks this run's record actually speaks about — a row with no recorded fact is silence. */
|
|
668
|
+
const recordedTasks = (graph, rows) => graph.tasks.filter((task) => {
|
|
669
|
+
const row = rows.get(task.id);
|
|
670
|
+
return row !== undefined && (row.state !== undefined
|
|
671
|
+
|| row.lastEventTime !== undefined
|
|
672
|
+
|| row.attempts !== undefined
|
|
673
|
+
|| row.actor !== undefined
|
|
674
|
+
|| row.merged === true
|
|
675
|
+
|| row.parkKind !== undefined);
|
|
676
|
+
});
|
|
677
|
+
const recordedDone = (tasks, rows) => tasks.filter((task) => graphTaskStatus(rows.get(task.id)?.state, task.status) === "done").length;
|
|
510
678
|
const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness = new Map(), journalRowsOnly = false, namedRunId) => {
|
|
511
679
|
const g = loadGraph(cwd);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const
|
|
680
|
+
const record = readRunRecord(cwd, g, namedRunId);
|
|
681
|
+
const runId = record?.runId;
|
|
682
|
+
const events = record?.events ?? [];
|
|
683
|
+
const comparable = record?.comparable ?? false;
|
|
684
|
+
const rehashAt = record?.rehashAt;
|
|
515
685
|
const assignments = new Map();
|
|
516
686
|
let replayed = null;
|
|
517
|
-
let cockpit;
|
|
518
|
-
let events = [];
|
|
519
687
|
const contexts = new Map();
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
events = parseJournalSnapshot(raw);
|
|
532
|
-
// The daemon creates journal.jsonl with lock-reclaimed before run-start and a reader may also
|
|
533
|
-
// catch an empty/torn first write. Those snapshots own no task fold yet; once run-start exists,
|
|
534
|
-
// however, a fold failure is unexpected and must surface instead of becoming a false empty run.
|
|
535
|
-
if (journalRowsOnly && events.some((event) => event.event === "run-start")) {
|
|
536
|
-
cockpit = deriveRunCockpitData({ fileName: `${runId}.journal.jsonl`, raw }, "status", { graph: g });
|
|
537
|
-
}
|
|
538
|
-
const sup = [...events].reverse().find((e) => e.event === "superseded" && typeof e.data.by === "string");
|
|
539
|
-
supersededBy = sup?.data.by;
|
|
540
|
-
// The resume comparator is the fail-closed baseline; a matching graph-rehash is the daemon's
|
|
541
|
-
// append-only audit that authorizes this status replay after stop-amend-resume.
|
|
542
|
-
const engagement = statusEngagement(events, graphDefinitionHash(g));
|
|
543
|
-
comparable = engagement.comparable;
|
|
544
|
-
rehashAt = engagement.rehashAt;
|
|
545
|
-
if (comparable) {
|
|
546
|
-
if (!journalRowsOnly)
|
|
547
|
-
replayed = j.replayStatuses();
|
|
548
|
-
for (const e of events) {
|
|
549
|
-
if (!journalRowsOnly && e.event === "task-dispatch" && e.taskId) {
|
|
550
|
-
const a = e.data.assignment;
|
|
551
|
-
if (typeof a.adapter === "string" && typeof a.model === "string") {
|
|
552
|
-
assignments.set(e.taskId, `${a.adapter}:${a.model}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (e.event === "context-sample" && e.taskId && typeof e.data.tokens === "number" && Number.isFinite(e.data.tokens)) {
|
|
556
|
-
contexts.set(e.taskId, e.data.tokens); // last write wins
|
|
688
|
+
// v1.53 T5: this run is dead — a newer run replaced it
|
|
689
|
+
const supersededBy = [...events].reverse()
|
|
690
|
+
.find((e) => e.event === "superseded" && typeof e.data.by === "string")?.data.by;
|
|
691
|
+
if (record && comparable) {
|
|
692
|
+
if (!journalRowsOnly)
|
|
693
|
+
replayed = Journal.open(cwd, record.runId).replayStatuses();
|
|
694
|
+
for (const e of events) {
|
|
695
|
+
if (!journalRowsOnly && e.event === "task-dispatch" && e.taskId) {
|
|
696
|
+
const a = e.data.assignment;
|
|
697
|
+
if (typeof a.adapter === "string" && typeof a.model === "string") {
|
|
698
|
+
assignments.set(e.taskId, `${a.adapter}:${a.model}`);
|
|
557
699
|
}
|
|
558
700
|
}
|
|
701
|
+
if (e.event === "context-sample" && e.taskId && typeof e.data.tokens === "number" && Number.isFinite(e.data.tokens)) {
|
|
702
|
+
contexts.set(e.taskId, e.data.tokens); // last write wins
|
|
703
|
+
}
|
|
559
704
|
}
|
|
560
705
|
}
|
|
561
|
-
const taskRows =
|
|
706
|
+
const taskRows = record && journalRowsOnly ? recordTaskRows(record, g) : new Map();
|
|
562
707
|
const clockFallback = new Date(now);
|
|
563
708
|
const recordedClock = new Date(events.at(-1)?.ts ?? now);
|
|
564
709
|
const zoneReference = Number.isFinite(recordedClock.getTime()) ? recordedClock : clockFallback;
|
|
@@ -573,19 +718,7 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
573
718
|
: task.status,
|
|
574
719
|
})),
|
|
575
720
|
};
|
|
576
|
-
const renderedTasks = journalRowsOnly
|
|
577
|
-
? cockpit !== undefined && comparable
|
|
578
|
-
? g.tasks.filter((task) => {
|
|
579
|
-
const row = taskRows.get(task.id);
|
|
580
|
-
return row !== undefined && (row.state !== undefined
|
|
581
|
-
|| row.lastEventTime !== undefined
|
|
582
|
-
|| row.attempts !== undefined
|
|
583
|
-
|| row.actor !== undefined
|
|
584
|
-
|| row.merged === true
|
|
585
|
-
|| row.parkKind !== undefined);
|
|
586
|
-
})
|
|
587
|
-
: []
|
|
588
|
-
: g.tasks;
|
|
721
|
+
const renderedTasks = journalRowsOnly ? recordedTasks(g, taskRows) : g.tasks;
|
|
589
722
|
const starved = new Set(blockedTasks(effective).map((t) => t.id));
|
|
590
723
|
// OBS-104: ONE activity fold feeds both surfaces — never re-derived here. Comparable events only
|
|
591
724
|
// (a recompiled graph's journal must not animate the wrong tasks); with no or stale journal the
|
|
@@ -593,13 +726,21 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
593
726
|
const activity = foldActivity(comparable ? events : [], effective.tasks);
|
|
594
727
|
const unicode = visual();
|
|
595
728
|
const divider = unicode ? " · " : " / ";
|
|
729
|
+
// SUP-01: derived by stat() alone, from THIS frame's clock. status stays a reader — nothing here
|
|
730
|
+
// creates, touches or reaps a beat file, so an absent supervision dir stays absent.
|
|
731
|
+
const supervision = readSupervision(cwd, now);
|
|
596
732
|
const width = process.stdout.columns ?? 120;
|
|
597
733
|
const done = journalRowsOnly
|
|
598
|
-
? renderedTasks
|
|
734
|
+
? recordedDone(renderedTasks, taskRows)
|
|
599
735
|
: effective.tasks.filter((task) => task.status === "done").length;
|
|
600
736
|
const total = g.tasks.length;
|
|
601
737
|
const ended = comparable && runHasEnded(events);
|
|
602
738
|
const tipPhase = comparable ? tipVerifyPhase(events) : undefined;
|
|
739
|
+
const verify = runId ? verifyClaim(events, comparable, tipPhase) : undefined;
|
|
740
|
+
// Where an operator can still look: only panes this run's record leaves open (see workerPaneLocators).
|
|
741
|
+
const panes = runId && comparable
|
|
742
|
+
? workerPaneLocators(events, runId, keepPanesSetting(cwd))
|
|
743
|
+
: new Map();
|
|
603
744
|
const taskIds = new Set(g.tasks.map((task) => task.id));
|
|
604
745
|
const phases = comparable ? livePhases(events) : new Map();
|
|
605
746
|
for (const taskId of phases.keys())
|
|
@@ -632,24 +773,25 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
632
773
|
const gates = comparable
|
|
633
774
|
? gateSnapshot(t, events, rehashAt)
|
|
634
775
|
: { states: defaultGateStates(t), priorGraph: false };
|
|
635
|
-
|
|
776
|
+
const pane = panes.get(t.id);
|
|
777
|
+
return { t, st, merged, failureKind, redTier, label, assignCol, isStarved, phrase, channel, ctx, livePhase, pane, ...gates };
|
|
636
778
|
});
|
|
637
779
|
if (!unicode) {
|
|
638
780
|
// machine/CI surface — journals without phase-start stay byte-identical; new phase-aware frames
|
|
639
781
|
// use an ASCII spinner so pipes never receive terminal-only braille/ANSI.
|
|
640
|
-
const rows = cells.map(({ t, st, merged, label, assignCol, livePhase, states, priorGraph }) => {
|
|
782
|
+
const rows = cells.map(({ t, st, merged, label, assignCol, livePhase, states, priorGraph, pane }) => {
|
|
641
783
|
const chain = gateChain(states, false);
|
|
642
784
|
const prefix = livePhase ? ` ${ASCII_SPINNER[animationFrame % ASCII_SPINNER.length]} ${t.id} ` : ` ${surfaceTaskBox(st, merged)} ${t.id} `;
|
|
643
|
-
const suffix = ` ${chain}${priorGraph ? ` ${PRIOR_GRAPH_MARKER}` : ""} ${livePhase ? "running" : surfaceStatusWord(st)}${label} ${assignCol}`;
|
|
785
|
+
const suffix = ` ${chain}${priorGraph ? ` ${PRIOR_GRAPH_MARKER}` : ""} ${livePhase ? "running" : surfaceStatusWord(st)}${label} ${assignCol}${pane ? ` pane ${pane}` : ""}`;
|
|
644
786
|
return `${prefix}${shortGoal(t.title, Math.max(0, width - prefix.length - suffix.length))}${suffix}`;
|
|
645
787
|
});
|
|
646
788
|
const zone = journalRowsOnly ? `${divider}zone ${localZoneLabel(zoneReference)}` : "";
|
|
647
789
|
const header = runId
|
|
648
|
-
? `tickmarkr status${divider}run ${runId}${zone}${supersededBy ? `${divider}superseded by ${supersededBy}` : ""}${!comparable ? `${divider}${NOT_COMPARABLE_NOTICE}` : ""}${divider}${liveness(events, now).replaceAll(" · ", divider)}${
|
|
790
|
+
? `tickmarkr status${divider}run ${runId}${zone}${supersededBy ? `${divider}superseded by ${supersededBy}` : ""}${!comparable ? `${divider}${NOT_COMPARABLE_NOTICE}` : ""}${divider}${liveness(events, now).replaceAll(" · ", divider)}${verify ? `${divider}${verify}` : ""}${divider}${tipPhase ? `${done}/${total} tasks done${divider}run not verified` : `${done}/${total} done`}`
|
|
649
791
|
: `tickmarkr status${zone}${divider}no runs yet${divider}${done}/${total} done`;
|
|
650
792
|
const legendLine = ` gates: ${GATE_NAMES.map((gate) => `${GATE_KEYS[gate]} ${gate}`).join(divider)}`;
|
|
651
793
|
return {
|
|
652
|
-
content: [header, legendLine, ...rows].join("\n"),
|
|
794
|
+
content: [header, legendLine, ` ${supervisionText(supervision, divider)}`, ...rows].join("\n"),
|
|
653
795
|
...(hotPhase ? { hotPhase } : {}),
|
|
654
796
|
...(runId ? { runId } : {}),
|
|
655
797
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
@@ -659,6 +801,10 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
659
801
|
// dim chrome, semantic color only on verdicts, completion gauge, and column-aligned status
|
|
660
802
|
// rows (pad plain text FIRST, colorize after: ANSI has zero display width and would corrupt
|
|
661
803
|
// padEnd math)
|
|
804
|
+
// The cockpit owns this board's column contract: `COCKPIT_COLUMN_FLOOR` is the narrowest board it
|
|
805
|
+
// plans, so below it the terminal — not a second floor stated here — decides what happens to the
|
|
806
|
+
// drawn rows. Every column figure below is derived from this one.
|
|
807
|
+
const boardColumns = Math.max(COCKPIT_COLUMN_FLOOR, width);
|
|
662
808
|
const dot = dim(" · ");
|
|
663
809
|
const anyFailed = cells.some((c) => c.redTier);
|
|
664
810
|
const gaugeCells = 10;
|
|
@@ -674,38 +820,51 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
674
820
|
const tally = tipPhase
|
|
675
821
|
? `${progressTone(`${done}/${total} tasks done`)}${dot}${progressTone("run not verified")}`
|
|
676
822
|
: `${done}/${total} done`;
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
(runId
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
823
|
+
// The prototype's header order, ported: identity, then what the run is worth (verdict and
|
|
824
|
+
// tally), then the instruments that decorate it. A board that must wrap keeps the decisive
|
|
825
|
+
// segments on the row an operator reads first; nothing is dropped to get there.
|
|
826
|
+
const header = ` ${[
|
|
827
|
+
title(runId ? `run ${runId}` : "tickmarkr"),
|
|
828
|
+
...(runId ? [] : ["no runs yet"]),
|
|
829
|
+
...(runId && supersededBy ? [warn(`superseded by ${supersededBy}`)] : []),
|
|
830
|
+
...(runId && !comparable ? [warn(NOT_COMPARABLE_NOTICE)] : []),
|
|
831
|
+
...(verify === undefined
|
|
832
|
+
? []
|
|
833
|
+
: [verify === "verify passed" ? ok(verify) : tipFailed ? fail(verify) : warn(verify)]),
|
|
834
|
+
`${gauge} ${tipPhase ? tally : done === total && total > 0 ? ok(tally) : tally}`,
|
|
835
|
+
...(runId ? [live] : []),
|
|
836
|
+
...(journalRowsOnly ? [`zone ${localZoneLabel(zoneReference)}`] : []),
|
|
837
|
+
].join(dot)}`;
|
|
838
|
+
const hr = rule(Math.min(boardColumns, 100));
|
|
687
839
|
// OBS-104 run-level now line: names the most recent journal event. TTY frame only — the non-TTY
|
|
688
840
|
// machine surface is byte-pinned (status-brand golden) and must not drift. Rendered BELOW the task
|
|
689
841
|
// rows: the line names task ids, and rows must stay the first id-bearing lines for grep consumers.
|
|
690
842
|
const nowLine = activity.now ? [legend(` now: ${activity.now}`)] : [];
|
|
691
843
|
const gatesLegend = legend(` gates: ${GATE_NAMES.join(" · ")}`);
|
|
844
|
+
// Every known tier, beaten or not: a tier missing from this line would read as a healthy one.
|
|
845
|
+
const supervisionLegend = legend(` ${supervisionText(supervision)}`);
|
|
692
846
|
// Two-line card per task (operator request, v1.67): line 1 carries identity + verdict — glyph,
|
|
693
847
|
// id, title at full width, and only SHORT status words. Line 2 carries the machinery, dim and
|
|
694
848
|
// aligned under the title: gate chain, live activity phrase (or channel), ctx. Long channel names
|
|
695
849
|
// and activity phrases live on line 2 only, so they can never squeeze the title or wrap line 1.
|
|
696
850
|
const taskVerdict = (c) => c.merged ? "pass" : c.redTier ? "fail" : c.st === "failed" || c.st === "human" ? "warn" : "neutral";
|
|
697
851
|
const statusWord = (c) => c.livePhase ? "running" : c.redTier ? "failed" : c.st === "failed" ? "warn" : surfaceStatusWord(c.st);
|
|
698
|
-
|
|
852
|
+
// Every column figure is display cells through the cockpit's width authority — never String
|
|
853
|
+
// length, which over-charges a wide cluster and bills a combining mark that draws nothing.
|
|
854
|
+
const idW = Math.max(...cells.map((c) => cellWidth(c.t.id)), 2);
|
|
699
855
|
// plain-text status suffixes for width math only — starved / failed gate names / approval hint
|
|
700
856
|
const suffixPlain = (c) => (c.isStarved ? " · starved" : "") + failedSuffix(c.states)
|
|
701
857
|
+ humanGateSuffix(c.t, graphTaskStatus(c.st, c.t.status), c.states);
|
|
702
|
-
const stW = Math.max(...cells.map((c) => statusWord(c)
|
|
703
|
-
const avail = Math.max(8,
|
|
858
|
+
const stW = Math.max(...cells.map((c) => cellWidth(statusWord(c) + suffixPlain(c))));
|
|
859
|
+
const avail = Math.max(8, boardColumns - (5 + idW) - 2 - stW);
|
|
704
860
|
const titles = cells.map((c) => shortGoal(c.t.title, avail));
|
|
705
861
|
const titleW = Math.max(8, ...titles.map(cellWidth));
|
|
706
|
-
|
|
862
|
+
// Line 2 starts under the title column — but never past what the board can pay for: an id near
|
|
863
|
+
// the schema's 64-character maximum would otherwise indent the machinery clean off a narrow row.
|
|
864
|
+
const chainCells = cellWidth(gateChain(GATE_NAMES.map(() => "open"), true));
|
|
865
|
+
const indent = " ".repeat(Math.max(0, Math.min(idW + 5, boardColumns - chainCells - 2)));
|
|
707
866
|
const rows = cells.map((c, i) => {
|
|
708
|
-
const { t, st, merged, failureKind, redTier, states, priorGraph, isStarved, phrase, channel, ctx, livePhase } = c;
|
|
867
|
+
const { t, st, merged, failureKind, redTier, states, priorGraph, isStarved, phrase, channel, ctx, livePhase, pane } = c;
|
|
709
868
|
const word = statusWord(c);
|
|
710
869
|
const staleWorker = livePhase?.phase === "worker"
|
|
711
870
|
&& workerOutputAge(livePhase, now, workerLiveness.get(t.id)) >= 60_000;
|
|
@@ -723,25 +882,68 @@ const renderFrame = (cwd, now = Date.now(), animationFrame = 0, workerLiveness =
|
|
|
723
882
|
? ` ${(staleWorker ? warn : dim)(SPINNER[animationFrame % SPINNER.length])} ${taskLabel}`
|
|
724
883
|
: ` ${statusRow(taskVerdict(c), taskLabel)}`;
|
|
725
884
|
// activity already names its channel for in-flight attempts — never repeat it
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
...(
|
|
729
|
-
...(
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
885
|
+
const chain = gateChain(states, true);
|
|
886
|
+
const segments = [
|
|
887
|
+
...(priorGraph ? [{ element: "progressCaption", text: PRIOR_GRAPH_MARKER }] : []),
|
|
888
|
+
...(phrase
|
|
889
|
+
? [
|
|
890
|
+
{ element: "journal", text: phrase },
|
|
891
|
+
...(phrase.includes(channel) || channel === "-"
|
|
892
|
+
? []
|
|
893
|
+
: [{ element: "secondaryHeader", text: channel }]),
|
|
894
|
+
]
|
|
895
|
+
: [{ element: "secondaryHeader", text: channel }]),
|
|
896
|
+
...(failureKind && !phrase?.includes(failureKind)
|
|
897
|
+
? [{ element: "progressCaption", text: failureKind }]
|
|
898
|
+
: []),
|
|
899
|
+
// A pane an operator can still open is structure, not decoration: it wraps like the blockers
|
|
900
|
+
// above it rather than being shed, because half an address is no address.
|
|
901
|
+
...(pane ? [{ element: "journal", text: `pane ${pane}` }] : []),
|
|
902
|
+
...(ctx !== undefined ? [{ element: "statTiles", text: `ctx ${ctx}` }] : []),
|
|
903
|
+
];
|
|
904
|
+
const detail = detailText(segments, boardColumns - cellWidth(indent) - cellWidth(chain) - 2);
|
|
905
|
+
// A board narrow enough to surrender every detail segment ends the row at the gate chain: the
|
|
906
|
+
// separator is part of the segment it separates, never chrome left standing on its own.
|
|
907
|
+
const line2 = detail ? `${indent}${chain} ${dim(detail)}` : `${indent}${chain}`;
|
|
908
|
+
return [line1, line2, ""];
|
|
733
909
|
}).flat();
|
|
734
910
|
if (!nowLine.length)
|
|
735
911
|
rows.pop(); // cards end blank-separated; drop the dangling one
|
|
736
912
|
return {
|
|
737
|
-
content: [header, hr, gatesLegend, "", ...rows, ...nowLine].join("\n"),
|
|
913
|
+
content: boardRows([header, hr, gatesLegend, supervisionLegend, "", ...rows, ...nowLine], boardColumns).join("\n"),
|
|
738
914
|
...(hotPhase ? { hotPhase } : {}),
|
|
739
915
|
...(runId ? { runId } : {}),
|
|
740
916
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
741
917
|
};
|
|
742
918
|
};
|
|
919
|
+
/**
|
|
920
|
+
* The compact form a statusline calls, so journal interpretation happens ONCE, inside the product.
|
|
921
|
+
* (An operator-local bash reimplementation counted `tip-verify` events instead of reading the
|
|
922
|
+
* run-end field and scored two runs whose tip verification had FAILED as verified.)
|
|
923
|
+
*
|
|
924
|
+
* One line, one journal read, and every word from the run's own record: the tally and the verdict
|
|
925
|
+
* are folded from the same bytes, so a completion appended between two reads can never be paired
|
|
926
|
+
* with a verdict the other read never saw. Plain text through the task-text sanitizer — a statusline
|
|
927
|
+
* embeds this, so no recorded value may carry a control byte or a row break into its host.
|
|
928
|
+
*/
|
|
929
|
+
const oneLine = (cwd, namedRunId) => {
|
|
930
|
+
const graph = loadGraph(cwd);
|
|
931
|
+
const record = readRunRecord(cwd, graph, namedRunId);
|
|
932
|
+
if (!record)
|
|
933
|
+
return sanitizeTaskText(`tickmarkr · no runs yet · 0/${graph.tasks.length} done`);
|
|
934
|
+
const rows = recordTaskRows(record, graph);
|
|
935
|
+
const claims = record.comparable
|
|
936
|
+
? [
|
|
937
|
+
`${recordedDone(recordedTasks(graph, rows), rows)}/${graph.tasks.length} done`,
|
|
938
|
+
`verify ${recordedVerify(record.events)}`,
|
|
939
|
+
]
|
|
940
|
+
: [NOT_COMPARABLE_CLAIM, "verify unavailable"];
|
|
941
|
+
return sanitizeTaskText([record.runId, ...claims].join(" · "));
|
|
942
|
+
};
|
|
743
943
|
export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
744
944
|
const namedRunId = positionalRunId(argv);
|
|
945
|
+
if (argv.includes("--oneline"))
|
|
946
|
+
return oneLine(cwd, namedRunId);
|
|
745
947
|
// cockpit surface: banner + frame on a TTY (doctor's pattern); pipes get the bare frame
|
|
746
948
|
if (!argv.includes("--watch")) {
|
|
747
949
|
const { content } = renderFrame(cwd, opts.now?.() ?? Date.now(), 0, undefined, false, namedRunId);
|
|
@@ -849,7 +1051,7 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
849
1051
|
titleSaved = true;
|
|
850
1052
|
process.once("exit", restoreTitle);
|
|
851
1053
|
}
|
|
852
|
-
process.stdout.write(`\x1b]0;⏳ ${hotPhase.taskId} ${phaseLabel(hotPhase
|
|
1054
|
+
process.stdout.write(`\x1b]0;⏳ ${hotPhase.taskId} ${phaseLabel(hotPhase)} ${fmtElapsed(nowMs - hotPhase.startedAt)}\x07`);
|
|
853
1055
|
};
|
|
854
1056
|
try {
|
|
855
1057
|
for (let i = 0; i < iterations; i++) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Assignment, type BillingChannel } from "../../adapters/types.js";
|
|
2
|
+
import { type AcceptanceItem } from "../../graph/schema.js";
|
|
3
|
+
export declare function parseCriteria(text: string): AcceptanceItem[];
|
|
4
|
+
export declare const HUMAN_CHANNEL: BillingChannel;
|
|
5
|
+
export declare const HUMAN_AUTHOR: Assignment;
|
|
6
|
+
export declare function verify(argv: string[], cwd?: string): Promise<{
|
|
7
|
+
out: string;
|
|
8
|
+
code: number;
|
|
9
|
+
}>;
|