tickmarkr 1.83.0 → 1.85.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/claude-code.d.ts +1 -0
- package/dist/adapters/claude-code.js +57 -1
- package/dist/adapters/fake.js +9 -0
- package/dist/adapters/types.d.ts +3 -0
- package/dist/adapters/types.js +21 -0
- package/dist/cli/commands/status.js +160 -30
- package/dist/compile/collateral.d.ts +86 -2
- package/dist/compile/collateral.js +294 -3
- package/dist/config/config.d.ts +62 -0
- package/dist/config/config.js +157 -2
- package/dist/drivers/herdr.d.ts +20 -3
- package/dist/drivers/herdr.js +288 -105
- package/dist/gates/baseline.d.ts +1 -0
- package/dist/gates/baseline.js +91 -13
- package/dist/gates/review.d.ts +7 -0
- package/dist/gates/review.js +99 -6
- package/dist/gates/run-gates.d.ts +9 -0
- package/dist/gates/run-gates.js +285 -41
- package/dist/run/daemon.d.ts +48 -2
- package/dist/run/daemon.js +1417 -315
- package/dist/run/journal.d.ts +56 -3
- package/dist/run/journal.js +275 -1
- package/dist/run/stall.d.ts +35 -1
- package/dist/run/stall.js +118 -8
- package/dist/tui/cockpit/components.d.ts +30 -1
- package/dist/tui/cockpit/components.js +19 -3
- package/dist/tui/cockpit/derive.d.ts +29 -2
- package/dist/tui/cockpit/derive.js +219 -23
- package/dist/tui/cockpit/layout.d.ts +75 -6
- package/dist/tui/cockpit/layout.js +97 -19
- package/dist/tui/cockpit/live.d.ts +14 -1
- package/dist/tui/cockpit/live.js +223 -29
- package/dist/tui/cockpit/pointer.d.ts +261 -0
- package/dist/tui/cockpit/pointer.js +610 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +36 -5
- package/dist/tui/cockpit/run-cockpit.js +270 -51
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { extname, join, relative } from "node:path";
|
|
3
3
|
import picomatch from "picomatch";
|
|
4
|
+
import { criticalPathHits, DEFAULT_CONFIG, DEFAULT_REVIEW_CRITICAL_PATHS, effectiveReviewPolicy, loadConfig, } from "../config/config.js";
|
|
4
5
|
import { renderAcceptanceItem } from "../graph/schema.js";
|
|
5
6
|
// Advisory plan-time scan only (OBS-12/13/14/21, OBS-76). NEVER expands files[], fails compile,
|
|
6
7
|
// or feeds the scope gate — a warning the author acts on. Plain-text (no AST), capped + sorted.
|
|
@@ -320,7 +321,297 @@ export function taskBudgetErrors(tasks) {
|
|
|
320
321
|
}
|
|
321
322
|
return errors;
|
|
322
323
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
324
|
+
// ── R2 velocity lints (OVERSEER-RULING-20260731-velocity) ───────────────────────────────────
|
|
325
|
+
// v1.84's T1 cost nine review rounds enforcing law the compiler could have refused at authoring
|
|
326
|
+
// time. These three bounds are that refusal: a bounded task surface, goal prose forced down into
|
|
327
|
+
// judge/test criteria, and criteria that may not name symbols owned outside the task's files[].
|
|
328
|
+
/**
|
|
329
|
+
* Max acceptance × files[] surface per task (OVERSEER-RULING-20260731-velocity, R2:
|
|
330
|
+
* `surface = acceptance.length × files.length > 24` fails compile). EVERY criterion counts —
|
|
331
|
+
* a judge item is still a thing one worker must satisfy and one reviewer must verify in the
|
|
332
|
+
* same pass, and counting only test oracles let judge-heavy tasks bypass the bound (the first
|
|
333
|
+
* T6 attempt was rejected for exactly that narrowing). The calibration corpus pins the bound:
|
|
334
|
+
* v1.84's original T1 measures 6×5=30 and fails; v1.79's tasks pass — its widest, T5 at
|
|
335
|
+
* 4×7=28, rides the recorded exception below rather than redefining the metric.
|
|
336
|
+
*/
|
|
337
|
+
export const MAX_TASK_SURFACE = 24;
|
|
338
|
+
/**
|
|
339
|
+
* Max goal words per acceptance criterion (v1.84's T1 measured 75). Gameable only by adding
|
|
340
|
+
* criteria — which is the point: every law migrated from goal prose to a criterion moves its
|
|
341
|
+
* enforcement from the 40-minute review round to the 2-minute judge.
|
|
342
|
+
*/
|
|
343
|
+
export const MAX_GOAL_WORDS_PER_CRITERION = 60;
|
|
344
|
+
export const SURFACE_CONTRACT_EXCEPTIONS = [
|
|
345
|
+
{
|
|
346
|
+
id: "T5",
|
|
347
|
+
acceptance: 4,
|
|
348
|
+
files: [
|
|
349
|
+
"src/adapters/prompt.ts", "src/gates/acceptance.ts", "src/gates/llm.ts", "src/gates/review.ts",
|
|
350
|
+
"tests/gates/acceptance.test.ts", "tests/gates/judge-retry.test.ts", "tests/gates/review.test.ts",
|
|
351
|
+
],
|
|
352
|
+
reason: "v1.79 T5 (4×7=28) — the velocity ruling's own fast baseline (7/7 in 89 min), shipped green "
|
|
353
|
+
+ "before R2 existed; the bound was calibrated FROM this corpus, not retroactively enforced on it.",
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "T4",
|
|
357
|
+
acceptance: 5,
|
|
358
|
+
files: [
|
|
359
|
+
"src/gates/run-gates.ts", "src/run/daemon.ts",
|
|
360
|
+
"tests/gates/pipeline.test.ts", "tests/gates/review-retry.test.ts", "tests/gates/run-gates.test.ts",
|
|
361
|
+
],
|
|
362
|
+
reason: "v1.85 T4 (5×5=25) — recorded in specs/v1.85-speed-truth.spec.md's Not-in-scope block: the two "
|
|
363
|
+
+ "retry-seam test pins are non-negotiable collateral and splitting the pipeline task would "
|
|
364
|
+
+ "re-serialize it.",
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
function matchesSurfaceException(t, exceptions) {
|
|
368
|
+
const files = (t.files ?? []).map((f) => f.replace(/^\.\//, "")).sort();
|
|
369
|
+
const items = t.acceptance?.length ?? 0;
|
|
370
|
+
for (const ex of exceptions) {
|
|
371
|
+
if (ex.id !== t.id || ex.acceptance !== items)
|
|
372
|
+
continue;
|
|
373
|
+
const exFiles = [...ex.files].sort();
|
|
374
|
+
if (exFiles.length === files.length && exFiles.every((f, i) => f === files[i]))
|
|
375
|
+
return ex;
|
|
376
|
+
}
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* R2 surface bound: acceptance criteria × files[] patterns, one error per offending task.
|
|
381
|
+
* `exceptions` defaults to the recorded contract amendments; tests pass [] to prove an
|
|
382
|
+
* exception is load-bearing.
|
|
383
|
+
*/
|
|
384
|
+
export function surfaceErrors(tasks, exceptions = SURFACE_CONTRACT_EXCEPTIONS) {
|
|
385
|
+
const errors = [];
|
|
386
|
+
for (const t of tasks) {
|
|
387
|
+
const items = t.acceptance?.length ?? 0;
|
|
388
|
+
const files = t.files?.length ?? 0;
|
|
389
|
+
const surface = items * files;
|
|
390
|
+
if (surface <= MAX_TASK_SURFACE)
|
|
391
|
+
continue;
|
|
392
|
+
if (matchesSurfaceException(t, exceptions))
|
|
393
|
+
continue; // recorded contract amendment
|
|
394
|
+
errors.push(`${t.id} has an acceptance×files surface of ${surface} (${items} criteria × ${files} files[] `
|
|
395
|
+
+ `patterns), above the ${MAX_TASK_SURFACE} bound — split the task so one worker builds and one `
|
|
396
|
+
+ `reviewer verifies a smaller unit in a single pass (R2).`);
|
|
397
|
+
}
|
|
398
|
+
return errors;
|
|
399
|
+
}
|
|
400
|
+
/** Words carrying at least one letter or digit in ANY script — bare dashes and bullets are not words. */
|
|
401
|
+
function goalWordCount(goal) {
|
|
402
|
+
return goal.split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
|
|
403
|
+
}
|
|
404
|
+
/** R2 goal-density bound: goal words per acceptance criterion, one error per offending task. */
|
|
405
|
+
export function goalDensityErrors(tasks) {
|
|
406
|
+
const errors = [];
|
|
407
|
+
for (const t of tasks) {
|
|
408
|
+
const items = t.acceptance?.length ?? 0;
|
|
409
|
+
if (!items || !t.goal)
|
|
410
|
+
continue;
|
|
411
|
+
const words = goalWordCount(t.goal);
|
|
412
|
+
const ratio = Math.round((words / items) * 10) / 10;
|
|
413
|
+
if (ratio > MAX_GOAL_WORDS_PER_CRITERION) {
|
|
414
|
+
errors.push(`${t.id} carries a goal of ${ratio} words per acceptance criterion (${words} words ÷ ${items} `
|
|
415
|
+
+ `criteria), above the ${MAX_GOAL_WORDS_PER_CRITERION} bound — move goal prose into judge/test `
|
|
416
|
+
+ `criteria; law that lives only in prose is enforced by a 40-minute review round instead of the `
|
|
417
|
+
+ `2-minute judge (R2).`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return errors;
|
|
421
|
+
}
|
|
422
|
+
// planFrame class (OBS-248): `export function NAME`, `export const NAME =`, `class NAME` etc.
|
|
423
|
+
// Token heuristic like criteriaSymbols — definition keywords only, no AST, no method/property forms.
|
|
424
|
+
function definitionRe(symbol) {
|
|
425
|
+
return new RegExp(`(?:^|\\n)[ \\t]*(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?`
|
|
426
|
+
+ `(?:function\\*?\\s+|const\\s+|let\\s+|var\\s+|class\\s+|interface\\s+|type\\s+|enum\\s+)${symbol}\\b`);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The blocking ownership lint gets its own index, NOT walkCode: walkCode is a capped, fail-open
|
|
430
|
+
* ADVISORY scan (400 files, src/ only, skips giant and unreadable files) — the right shape for
|
|
431
|
+
* plan-time warnings, wrong for a compile error. A unique definition hiding in the 401st file,
|
|
432
|
+
* another code root (tests/, scripts/, a root-level config), or a skipped file would silently
|
|
433
|
+
* pass. This walk is complete: every code file under the repo root, no count cap, no size cap,
|
|
434
|
+
* and anything unreadable is reported so the lint can fail closed instead of trusting a
|
|
435
|
+
* partial scan. Generated mirrors are not definition sites: dist/ is tsc's copy of src/
|
|
436
|
+
* (rebuilt by the test gate's pretest), so indexing it would double every definition and read
|
|
437
|
+
* every unique site as ambiguous — the ownership rule indexes SOURCE only.
|
|
438
|
+
*/
|
|
439
|
+
function walkAllCode(repoRoot) {
|
|
440
|
+
const files = [];
|
|
441
|
+
const unreadable = [];
|
|
442
|
+
const rel = (full) => relative(repoRoot, full).split("\\").join("/") || ".";
|
|
443
|
+
const walk = (dir) => {
|
|
444
|
+
let entries;
|
|
445
|
+
try {
|
|
446
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
unreadable.push(rel(dir));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
453
|
+
for (const e of entries) {
|
|
454
|
+
if (e.name === "node_modules" || e.name === ".git" || e.name === "dist" || e.name.startsWith("."))
|
|
455
|
+
continue;
|
|
456
|
+
const full = join(dir, e.name);
|
|
457
|
+
if (e.isDirectory()) {
|
|
458
|
+
walk(full);
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (!e.isFile() || !CODE_EXT.test(e.name))
|
|
462
|
+
continue;
|
|
463
|
+
files.push(rel(full));
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
try {
|
|
467
|
+
if (!statSync(repoRoot).isDirectory())
|
|
468
|
+
return { files, unreadable }; // no root: empty index
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
return { files, unreadable };
|
|
472
|
+
}
|
|
473
|
+
walk(repoRoot);
|
|
474
|
+
return { files, unreadable };
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* R2 symbol ownership: a code identifier named in a judge or test criterion that resolves uniquely
|
|
478
|
+
* to a definition OUTSIDE the task's files[] is unsatisfiable by construction — the v1.84 T1
|
|
479
|
+
* planFrame defect, which burned two review rounds because the enabling symbol lived in another
|
|
480
|
+
* task's file. Zero definition sites (plain-language false positive, external symbol, or a file
|
|
481
|
+
* the task itself creates) and ambiguous tokens (multiple definition sites) stay silent. The index
|
|
482
|
+
* must be COMPLETE: any code path that cannot be read is its own error — the rule fails closed
|
|
483
|
+
* rather than trust a partial scan.
|
|
484
|
+
*/
|
|
485
|
+
export function symbolOwnershipErrors(tasks, repoRoot) {
|
|
486
|
+
const perTask = tasks
|
|
487
|
+
.map((t) => ({ t, symbols: criteriaSymbols(t.acceptance ?? []) }))
|
|
488
|
+
.filter((x) => x.symbols.length);
|
|
489
|
+
if (!perTask.length)
|
|
490
|
+
return [];
|
|
491
|
+
const { files: codeFiles, unreadable } = walkAllCode(repoRoot);
|
|
492
|
+
const errors = [];
|
|
493
|
+
const incomplete = (path) => `cannot prove the symbol-ownership rule for ${perTask.map((x) => x.t.id).join(", ")}: ${path} `
|
|
494
|
+
+ `is unreadable, so the code index is incomplete — restore read access (or remove the path) and `
|
|
495
|
+
+ `recompile; the ownership lint fails closed rather than trust a partial scan (OBS-248).`;
|
|
496
|
+
for (const u of unreadable)
|
|
497
|
+
errors.push(incomplete(u));
|
|
498
|
+
// resolve each symbol to its definition site(s) once, shared across tasks
|
|
499
|
+
const allSymbols = [...new Set(perTask.flatMap((x) => x.symbols))];
|
|
500
|
+
const res = new Map(allSymbols.map((s) => [s, definitionRe(s)]));
|
|
501
|
+
const sites = new Map(allSymbols.map((s) => [s, []]));
|
|
502
|
+
for (const cf of codeFiles) {
|
|
503
|
+
let text;
|
|
504
|
+
try {
|
|
505
|
+
text = readFileSync(join(repoRoot, cf), "utf8"); // no size cap — a skipped file is a blind spot
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
errors.push(incomplete(cf));
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
for (const sym of allSymbols) {
|
|
512
|
+
if (text.includes(sym) && res.get(sym).test(text))
|
|
513
|
+
sites.get(sym).push(cf);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
for (const { t, symbols } of perTask) {
|
|
517
|
+
// OBS-22: scopeGate accepts picomatch globs; ownership must agree.
|
|
518
|
+
const scoped = picomatch(t.files.map((f) => f.replace(/^\.\//, "")), { dot: true });
|
|
519
|
+
for (const sym of symbols) {
|
|
520
|
+
const defs = sites.get(sym) ?? [];
|
|
521
|
+
if (defs.length !== 1)
|
|
522
|
+
continue; // unknown or ambiguous — silent by ruling
|
|
523
|
+
const site = defs[0];
|
|
524
|
+
if (scoped(site))
|
|
525
|
+
continue; // defined inside the task's own write surface
|
|
526
|
+
errors.push(`${t.id}: criterion identifier "${sym}" is defined only in ${site}, which is not in files[] — `
|
|
527
|
+
+ `add ${site} to files[] or reword the criterion; a worker scoped to files[] cannot satisfy a `
|
|
528
|
+
+ `criterion whose enabling symbol lives outside it (OBS-248).`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return errors;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* R3 review participation (OVERSEER-RULING-20260731-velocity; OBS-186). The compiler assigns
|
|
535
|
+
* `reviewPolicy` from the DECLARED files[] — `judge-only` only when every declared path is provably
|
|
536
|
+
* docs/CHANGELOG/RELEASING/version-mirror leaf work — and the operator's floor may raise it. A task
|
|
537
|
+
* that would SKIP review while touching `review.criticalPaths` is the exact shape OBS-186 caught in
|
|
538
|
+
* the field: the riskiest task in the bundle silently declining the one gate that exists for it. That
|
|
539
|
+
* cannot be a warning, because nothing downstream would stop it — the gate would honour the policy.
|
|
540
|
+
*/
|
|
541
|
+
export function reviewParticipationErrors(tasks, review) {
|
|
542
|
+
// The shipped critical paths are a FLOOR, not a default that a config replaces: this lint resolves
|
|
543
|
+
// its config from a repo root the compile seam cannot always name (see taskUnitContractErrors), and
|
|
544
|
+
// the one direction that must never happen is a wrong root lowering enforcement below what tickmarkr
|
|
545
|
+
// ships. Union is monotone — a repo-local list can only add.
|
|
546
|
+
const critical = [...new Set([...DEFAULT_REVIEW_CRITICAL_PATHS, ...(review.criticalPaths ?? [])])];
|
|
547
|
+
const errors = [];
|
|
548
|
+
for (const t of tasks) {
|
|
549
|
+
const files = t.files ?? [];
|
|
550
|
+
const policy = effectiveReviewPolicy(files, review);
|
|
551
|
+
// The non-monotone switch, caught at the only seam that can still refuse the graph. A shape
|
|
552
|
+
// override cannot demote a task the paths assigned `full` — that is precisely the demotion
|
|
553
|
+
// review.policy is structurally incapable of, arriving through a second door.
|
|
554
|
+
const shapeOff = t.shape !== undefined && review.byShape?.[t.shape]?.review === false;
|
|
555
|
+
if (policy === "full" && shapeOff) {
|
|
556
|
+
errors.push(`${t.id} declares full-review work (reviewPolicy full — at least one declared path is not `
|
|
557
|
+
+ `leaf-class) while gates.byShape.${t.shape}.review: false would omit the review gate — `
|
|
558
|
+
+ `remove that override, or narrow files[] to docs/CHANGELOG/RELEASING/version-mirror leaf `
|
|
559
|
+
+ `work; participation is decided by paths, and config may raise a policy, never lower one (R3).`);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (policy !== "judge-only" && !shapeOff)
|
|
563
|
+
continue; // full review actually runs — nothing skips
|
|
564
|
+
const hits = criticalPathHits(files, critical);
|
|
565
|
+
if (!hits.length)
|
|
566
|
+
continue;
|
|
567
|
+
const why = policy === "judge-only"
|
|
568
|
+
? "reviewPolicy judge-only — every declared path is leaf-class work"
|
|
569
|
+
: `gates.byShape.${t.shape}.review: false`;
|
|
570
|
+
errors.push(`${t.id} would skip cross-vendor review (${why}) `
|
|
571
|
+
+ `while touching review.criticalPaths ${hits.join(", ")} — narrow files[] out `
|
|
572
|
+
+ `of the critical path, or set review.policy: full so the task is reviewed; participation is `
|
|
573
|
+
+ `decided by paths, and a critical path may never be reviewed by the judge alone (R3).`);
|
|
574
|
+
}
|
|
575
|
+
return errors;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* The ACTIVE participation config for a compile rooted at `repoRoot`. Same default-argument contract
|
|
579
|
+
* as `repoRoot` itself: the CLI and daemon compile from inside the target repo, so the repo's own
|
|
580
|
+
* config (plus the global overlay) is the config the run will gate under. A config the loader cannot
|
|
581
|
+
* read degrades to the shipped defaults rather than crashing the compile — every command that reaches
|
|
582
|
+
* this seam loads the same config itself and reports a malformed one loudly.
|
|
583
|
+
*/
|
|
584
|
+
function activeReviewParticipation(repoRoot) {
|
|
585
|
+
try {
|
|
586
|
+
const cfg = loadConfig(repoRoot);
|
|
587
|
+
return { ...cfg.review, byShape: cfg.gates.byShape };
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return { ...DEFAULT_CONFIG.review, byShape: DEFAULT_CONFIG.gates.byShape };
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Every Task Unit Contract violation in one pass, ready to throw. `repoRoot` backs the
|
|
595
|
+
* symbol-ownership lint and the participation config, and defaults to the invocation directory —
|
|
596
|
+
* correct for the CLI/daemon, which compile from inside the target repo.
|
|
597
|
+
*
|
|
598
|
+
* KNOWN GAP (R2 review, T6/T7 scope): the compile seam in src/compile/index.ts
|
|
599
|
+
* (`enforceTaskUnitContract`) does not thread `compileSource`'s `root` argument into this call, so a
|
|
600
|
+
* PROGRAMMATIC compile whose target repo differs from process.cwd() resolves the wrong root and can
|
|
601
|
+
* miss that repo's own `review.criticalPaths`. Threading it is one line in src/compile/index.ts,
|
|
602
|
+
* outside this task's file scope. Two things bound the exposure meanwhile, both live: the critical
|
|
603
|
+
* set here is the UNION with the shipped defaults, so a wrong root can never lower enforcement below
|
|
604
|
+
* the shipped floor; and the review GATE — which is handed the run's real config — refuses to skip a
|
|
605
|
+
* critical path itself (src/gates/review.ts), so a lint this seam misses costs a late verdict rather
|
|
606
|
+
* than an unreviewed one. The compile lint is the early warning; the gate is the fail-closed backstop.
|
|
607
|
+
*/
|
|
608
|
+
export function taskUnitContractErrors(tasks, repoRoot = process.cwd(), review = activeReviewParticipation(repoRoot)) {
|
|
609
|
+
return [
|
|
610
|
+
...separabilityErrors(tasks),
|
|
611
|
+
...taskBudgetErrors(tasks),
|
|
612
|
+
...surfaceErrors(tasks),
|
|
613
|
+
...goalDensityErrors(tasks),
|
|
614
|
+
...symbolOwnershipErrors(tasks, repoRoot),
|
|
615
|
+
...reviewParticipationErrors(tasks, review),
|
|
616
|
+
];
|
|
326
617
|
}
|
package/dist/config/config.d.ts
CHANGED
|
@@ -52,6 +52,63 @@ export declare const SubPricingSchema: z.ZodObject<{
|
|
|
52
52
|
windowsPerMonthHigh: z.ZodNumber;
|
|
53
53
|
}, z.core.$strip>;
|
|
54
54
|
export type SubPricing = z.infer<typeof SubPricingSchema>;
|
|
55
|
+
export declare const REVIEW_POLICIES: readonly ["full", "judge-only"];
|
|
56
|
+
export type ReviewPolicy = (typeof REVIEW_POLICIES)[number];
|
|
57
|
+
/**
|
|
58
|
+
* The leaf class: work a judge alone can carry because there is no behaviour to review. It is CLOSED
|
|
59
|
+
* and enumerated here — documentation pages, the changelog, the release runbook, and the version
|
|
60
|
+
* mirrors a release bump rewrites. Everything else, including tests and scripts, is full-review work.
|
|
61
|
+
*/
|
|
62
|
+
export declare const REVIEW_LEAF_GLOBS: readonly ["docs/**", "CHANGELOG.md", "RELEASING.md", "package.json", "package-lock.json"];
|
|
63
|
+
/**
|
|
64
|
+
* The version mirrors a release bump rewrites — and ONLY at the repo root: `docs/package.json` is a
|
|
65
|
+
* real manifest for something, not a mirror. Membership here is still a claim about paths, and a
|
|
66
|
+
* manifest diff that moves more than the version field leaves the class; the gate re-tests that
|
|
67
|
+
* against the actual diff content (src/gates/review.ts) because a path cannot say it by itself.
|
|
68
|
+
*/
|
|
69
|
+
export declare const REVIEW_VERSION_MIRRORS: ReadonlySet<string>;
|
|
70
|
+
/**
|
|
71
|
+
* Paths whose review may never be skipped (R3): the gate/run/driver/adapter surface — lifecycle,
|
|
72
|
+
* demux, and everything that reaches a shell. Shipped as the default so the compile lint that
|
|
73
|
+
* enforces them is live in a repo that never configures anything.
|
|
74
|
+
*/
|
|
75
|
+
export declare const DEFAULT_REVIEW_CRITICAL_PATHS: readonly ["src/gates/**", "src/run/**", "src/drivers/**", "src/adapters/**"];
|
|
76
|
+
/**
|
|
77
|
+
* True when `path` — an ACTUAL path a diff touched — is provably leaf-class work. Enumerated, closed,
|
|
78
|
+
* and positive: the two named root files, the two root version mirrors, and documentation formats
|
|
79
|
+
* under `docs/`. Everything else, including a `.vue`/`.svelte`/`Makefile`/`package.json` parked under
|
|
80
|
+
* `docs/`, is behaviour and draws a full review.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isReviewLeafPath(path: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* True when a DECLARED pattern may claim leaf work. A declaration is a claim about a directory, not
|
|
85
|
+
* a file: `docs/**` is how a documentation task declares itself and has to stay claimable, so a glob
|
|
86
|
+
* rooted at `docs/` reads leaf here while `docs/tool.ts` does not read leaf above. The claim is never
|
|
87
|
+
* the last word — reviewGate re-tests it against what the diff actually touched and promotes when the
|
|
88
|
+
* diff leaves the class. A glob rooted anywhere else can claim nothing.
|
|
89
|
+
*/
|
|
90
|
+
export declare function isReviewLeafDeclaration(pattern: string): boolean;
|
|
91
|
+
/** full beats judge-only. The join is monotone by construction: nothing here can LOWER a policy. */
|
|
92
|
+
export declare function raiseReviewPolicy(...policies: ReadonlyArray<ReviewPolicy | undefined>): ReviewPolicy;
|
|
93
|
+
/**
|
|
94
|
+
* The compiler's assignment, from the task's DECLARED files[]. judge-only only when EVERY declared
|
|
95
|
+
* path is provably leaf work — one non-leaf pattern (or an empty files[]) makes it full.
|
|
96
|
+
*/
|
|
97
|
+
export declare function declaredReviewPolicy(files: ReadonlyArray<string>): ReviewPolicy;
|
|
98
|
+
/** The declared assignment after the operator's floor is applied — config may raise it, never lower it. */
|
|
99
|
+
export declare function effectiveReviewPolicy(files: ReadonlyArray<string>, review: {
|
|
100
|
+
policy?: ReviewPolicy;
|
|
101
|
+
}): ReviewPolicy;
|
|
102
|
+
/**
|
|
103
|
+
* Declared patterns that intersect a critical path. Directional containment is the certain half — a
|
|
104
|
+
* critical `src/gates/**` contains a declared `src/gates/review.ts`, and a declared `docs/**` contains
|
|
105
|
+
* a critical `docs/security/**`. It is NOT intersection: two globs can overlap without either matching
|
|
106
|
+
* the other as a literal string, which is how a judge-only task declaring `docs/**\/README.md` slipped
|
|
107
|
+
* past a critical `docs/security/*.md`. When BOTH sides are globs the conservative overlap test above
|
|
108
|
+
* decides, and it decides toward flagging — an over-flagged task is narrowed by its author, an
|
|
109
|
+
* under-flagged one skips the only gate that exists for it.
|
|
110
|
+
*/
|
|
111
|
+
export declare function criticalPathHits(files: ReadonlyArray<string>, criticalPaths?: ReadonlyArray<string>): string[];
|
|
55
112
|
export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
56
113
|
concurrency: z.ZodNumber;
|
|
57
114
|
driver: z.ZodEnum<{
|
|
@@ -173,6 +230,11 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
|
173
230
|
complexityThreshold: z.ZodNumber;
|
|
174
231
|
required: z.ZodBoolean;
|
|
175
232
|
prefer: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
233
|
+
policy: z.ZodOptional<z.ZodEnum<{
|
|
234
|
+
full: "full";
|
|
235
|
+
"judge-only": "judge-only";
|
|
236
|
+
}>>;
|
|
237
|
+
criticalPaths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
176
238
|
}, z.core.$strip>;
|
|
177
239
|
consult: z.ZodObject<{
|
|
178
240
|
adapter: z.ZodString;
|
package/dist/config/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import picomatch from "picomatch";
|
|
4
5
|
import { parse, stringify } from "yaml";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
import { stateDirName } from "../graph/graph.js";
|
|
@@ -73,6 +74,143 @@ export const SubPricingSchema = z
|
|
|
73
74
|
windowsPerMonthHigh: z.number().int().positive(),
|
|
74
75
|
})
|
|
75
76
|
.refine((s) => s.windowsPerMonthLow <= s.windowsPerMonthHigh, "windowsPerMonthLow must be ≤ windowsPerMonthHigh");
|
|
77
|
+
// ── Review participation (R3 of OVERSEER-RULING-20260731-velocity; OBS-186) ────────────────────
|
|
78
|
+
// Participation is keyed on PATHS — what a task DECLARES at compile time and what its diff ACTUALLY
|
|
79
|
+
// touches at gate time — never on complexity. `complexityThreshold` capped participation at a number
|
|
80
|
+
// the authoring law forbade tasks from reaching, so cross-vendor review was unreachable by
|
|
81
|
+
// construction while `required: true` claimed the opposite. Paths cannot drift out from under the
|
|
82
|
+
// gate that way: the same predicate decides both ends.
|
|
83
|
+
export const REVIEW_POLICIES = ["full", "judge-only"];
|
|
84
|
+
/**
|
|
85
|
+
* The leaf class: work a judge alone can carry because there is no behaviour to review. It is CLOSED
|
|
86
|
+
* and enumerated here — documentation pages, the changelog, the release runbook, and the version
|
|
87
|
+
* mirrors a release bump rewrites. Everything else, including tests and scripts, is full-review work.
|
|
88
|
+
*/
|
|
89
|
+
export const REVIEW_LEAF_GLOBS = [
|
|
90
|
+
"docs/**", "CHANGELOG.md", "RELEASING.md", "package.json", "package-lock.json",
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Membership is a POSITIVE rule, not a blacklist. A blacklist of source extensions is a list of the
|
|
94
|
+
* formats someone thought of, and everything nobody thought of — `docs/widget.vue`, `docs/site.svelte`,
|
|
95
|
+
* `docs/Makefile`, `docs/package.json`, an extensionless script — falls into the leaf class and skips
|
|
96
|
+
* its review. That direction of failure is the whole defect OBS-186 names, so the rule is inverted:
|
|
97
|
+
* under `docs/` a path is leaf work only when its extension is a DOCUMENTATION format. An unknown
|
|
98
|
+
* format is behaviour until proven otherwise, which is the fail-closed direction.
|
|
99
|
+
*/
|
|
100
|
+
const REVIEW_DOC_EXT_RE = /\.(?:md|mdx|markdown|txt|rst|adoc|asciidoc)$/i;
|
|
101
|
+
/**
|
|
102
|
+
* The version mirrors a release bump rewrites — and ONLY at the repo root: `docs/package.json` is a
|
|
103
|
+
* real manifest for something, not a mirror. Membership here is still a claim about paths, and a
|
|
104
|
+
* manifest diff that moves more than the version field leaves the class; the gate re-tests that
|
|
105
|
+
* against the actual diff content (src/gates/review.ts) because a path cannot say it by itself.
|
|
106
|
+
*/
|
|
107
|
+
export const REVIEW_VERSION_MIRRORS = new Set(["package.json", "package-lock.json"]);
|
|
108
|
+
/** The changelog and the release runbook, at the repo root, by exact name. */
|
|
109
|
+
const REVIEW_LEAF_FILES = new Set(["CHANGELOG.md", "RELEASING.md"]);
|
|
110
|
+
/**
|
|
111
|
+
* Paths whose review may never be skipped (R3): the gate/run/driver/adapter surface — lifecycle,
|
|
112
|
+
* demux, and everything that reaches a shell. Shipped as the default so the compile lint that
|
|
113
|
+
* enforces them is live in a repo that never configures anything.
|
|
114
|
+
*/
|
|
115
|
+
export const DEFAULT_REVIEW_CRITICAL_PATHS = [
|
|
116
|
+
"src/gates/**", "src/run/**", "src/drivers/**", "src/adapters/**",
|
|
117
|
+
];
|
|
118
|
+
const GLOB_META_RE = /[*?[\]{}]/;
|
|
119
|
+
const normPath = (p) => p.replace(/^\.\//, "").trim();
|
|
120
|
+
/**
|
|
121
|
+
* True when `path` — an ACTUAL path a diff touched — is provably leaf-class work. Enumerated, closed,
|
|
122
|
+
* and positive: the two named root files, the two root version mirrors, and documentation formats
|
|
123
|
+
* under `docs/`. Everything else, including a `.vue`/`.svelte`/`Makefile`/`package.json` parked under
|
|
124
|
+
* `docs/`, is behaviour and draws a full review.
|
|
125
|
+
*/
|
|
126
|
+
export function isReviewLeafPath(path) {
|
|
127
|
+
const p = normPath(path);
|
|
128
|
+
if (!p.length)
|
|
129
|
+
return false;
|
|
130
|
+
if (REVIEW_LEAF_FILES.has(p) || REVIEW_VERSION_MIRRORS.has(p))
|
|
131
|
+
return true;
|
|
132
|
+
return p.startsWith("docs/") && REVIEW_DOC_EXT_RE.test(p);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* True when a DECLARED pattern may claim leaf work. A declaration is a claim about a directory, not
|
|
136
|
+
* a file: `docs/**` is how a documentation task declares itself and has to stay claimable, so a glob
|
|
137
|
+
* rooted at `docs/` reads leaf here while `docs/tool.ts` does not read leaf above. The claim is never
|
|
138
|
+
* the last word — reviewGate re-tests it against what the diff actually touched and promotes when the
|
|
139
|
+
* diff leaves the class. A glob rooted anywhere else can claim nothing.
|
|
140
|
+
*/
|
|
141
|
+
export function isReviewLeafDeclaration(pattern) {
|
|
142
|
+
const p = normPath(pattern);
|
|
143
|
+
if (!p.length)
|
|
144
|
+
return false;
|
|
145
|
+
return isReviewLeafPath(p) || (GLOB_META_RE.test(p) && p.startsWith("docs/"));
|
|
146
|
+
}
|
|
147
|
+
/** full beats judge-only. The join is monotone by construction: nothing here can LOWER a policy. */
|
|
148
|
+
export function raiseReviewPolicy(...policies) {
|
|
149
|
+
return policies.includes("full") ? "full" : "judge-only";
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The compiler's assignment, from the task's DECLARED files[]. judge-only only when EVERY declared
|
|
153
|
+
* path is provably leaf work — one non-leaf pattern (or an empty files[]) makes it full.
|
|
154
|
+
*/
|
|
155
|
+
export function declaredReviewPolicy(files) {
|
|
156
|
+
const declared = files.map(normPath).filter((f) => f.length > 0);
|
|
157
|
+
return declared.length > 0 && declared.every(isReviewLeafDeclaration) ? "judge-only" : "full";
|
|
158
|
+
}
|
|
159
|
+
/** The declared assignment after the operator's floor is applied — config may raise it, never lower it. */
|
|
160
|
+
export function effectiveReviewPolicy(files, review) {
|
|
161
|
+
return raiseReviewPolicy(declaredReviewPolicy(files), review.policy);
|
|
162
|
+
}
|
|
163
|
+
/** The literal head of a pattern — everything before its first glob metacharacter. */
|
|
164
|
+
const literalPrefix = (p) => {
|
|
165
|
+
const i = p.search(GLOB_META_RE);
|
|
166
|
+
return i < 0 ? p : p.slice(0, i);
|
|
167
|
+
};
|
|
168
|
+
/** The literal tail of a pattern — everything after its last glob metacharacter. */
|
|
169
|
+
const literalSuffix = (p) => p.replace(/^.*[*?[\]{}]/s, "");
|
|
170
|
+
const eitherWay = (a, b, rel) => rel(a, b) || rel(b, a);
|
|
171
|
+
/**
|
|
172
|
+
* Do two GLOBS have a path in common? Exact glob-vs-glob intersection is undecidable in general, so
|
|
173
|
+
* this fails CLOSED: the pair is treated as intersecting unless their literal heads or their literal
|
|
174
|
+
* tails prove they cannot meet. `docs/**\/README.md` vs `docs/security/*.md` share the head `docs/`
|
|
175
|
+
* and the tail `.md` is a tail of `/README.md`, so `docs/security/README.md` is correctly flagged;
|
|
176
|
+
* `src/**\/*.ts` vs `src/**\/*.md` share a head but neither tail contains the other, so they are not.
|
|
177
|
+
*/
|
|
178
|
+
const globsMayIntersect = (a, b) => eitherWay(literalPrefix(a), literalPrefix(b), (x, y) => x.startsWith(y))
|
|
179
|
+
&& eitherWay(literalSuffix(a), literalSuffix(b), (x, y) => x.endsWith(y));
|
|
180
|
+
/**
|
|
181
|
+
* Declared patterns that intersect a critical path. Directional containment is the certain half — a
|
|
182
|
+
* critical `src/gates/**` contains a declared `src/gates/review.ts`, and a declared `docs/**` contains
|
|
183
|
+
* a critical `docs/security/**`. It is NOT intersection: two globs can overlap without either matching
|
|
184
|
+
* the other as a literal string, which is how a judge-only task declaring `docs/**\/README.md` slipped
|
|
185
|
+
* past a critical `docs/security/*.md`. When BOTH sides are globs the conservative overlap test above
|
|
186
|
+
* decides, and it decides toward flagging — an over-flagged task is narrowed by its author, an
|
|
187
|
+
* under-flagged one skips the only gate that exists for it.
|
|
188
|
+
*/
|
|
189
|
+
export function criticalPathHits(files, criticalPaths = []) {
|
|
190
|
+
const hits = new Set();
|
|
191
|
+
for (const raw of files) {
|
|
192
|
+
const f = normPath(raw);
|
|
193
|
+
if (!f)
|
|
194
|
+
continue;
|
|
195
|
+
for (const rawCritical of criticalPaths) {
|
|
196
|
+
const critical = normPath(rawCritical);
|
|
197
|
+
if (!critical)
|
|
198
|
+
continue;
|
|
199
|
+
if (f === critical || picomatch(critical, { dot: true })(f) || picomatch(f, { dot: true })(critical)
|
|
200
|
+
|| (GLOB_META_RE.test(f) && GLOB_META_RE.test(critical) && globsMayIntersect(f, critical))) {
|
|
201
|
+
hits.add(f);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return [...hits].sort();
|
|
206
|
+
}
|
|
207
|
+
// R3 (OBS-186): `review: false` here is a SECOND participation switch, and unlike review.policy it is
|
|
208
|
+
// not monotone — it can omit the review gate from a task the path rules assigned `full`. Participation
|
|
209
|
+
// is decided by paths, so the two are JOINED at the one seam that can still refuse the graph:
|
|
210
|
+
// reviewParticipationErrors (src/compile/collateral.ts) fails compile when a shape's `review: false`
|
|
211
|
+
// would demote a full-review task or silence a critical path. The key itself stays parseable (a run's
|
|
212
|
+
// own config, the setup cockpit and the shipped template all round-trip it); what it can no longer do
|
|
213
|
+
// is take effect behind the policy's back.
|
|
76
214
|
const ShapeGateParticipationSchema = z
|
|
77
215
|
.object({ acceptance: z.boolean().optional(), review: z.boolean().optional() })
|
|
78
216
|
.passthrough()
|
|
@@ -157,7 +295,22 @@ export const TickmarkrConfigSchema = z.object({
|
|
|
157
295
|
judge: z.object({ adapter: z.string(), model: z.string() }),
|
|
158
296
|
// v1.53 T2: prefer — ordered reviewer preference (entry grammar: adapter | adapter:model, same as
|
|
159
297
|
// routing.map.prefer). Reorders diversity-eligible channels only; never widens or narrows eligibility.
|
|
160
|
-
review: z.object({
|
|
298
|
+
review: z.object({
|
|
299
|
+
// RETIRED as a participation switch (R3 / OBS-186, v1.85): review participation is decided by the
|
|
300
|
+
// paths a task declares and the paths its diff touches — never by complexity. The key survives
|
|
301
|
+
// because routing hints and the setup cockpit still read it as a complexity landmark; NOTHING in
|
|
302
|
+
// src/gates/review.ts reads it any more, and a value here can no longer disable a review.
|
|
303
|
+
complexityThreshold: z.number(),
|
|
304
|
+
required: z.boolean(),
|
|
305
|
+
prefer: z.array(z.string()).optional(),
|
|
306
|
+
// R3: the operator's participation floor. "full" forces a cross-vendor review on every task;
|
|
307
|
+
// "judge-only" is the neutral floor (it leaves the compiler's per-task assignment standing).
|
|
308
|
+
// Config may RAISE a task's policy and can never lower one — see raiseReviewPolicy.
|
|
309
|
+
policy: z.enum(REVIEW_POLICIES).optional(),
|
|
310
|
+
// R3: globs no task may skip review on (input/demux/lifecycle, gates/run/drivers/adapters, anything
|
|
311
|
+
// reaching a shell). A judge-only task intersecting one of these fails COMPILE, never silently skips.
|
|
312
|
+
criticalPaths: z.array(z.string()).optional(),
|
|
313
|
+
}),
|
|
161
314
|
// v1.54 T1: prefer — ranked consult seat failover. Entries MUST be adapter:model (unlike
|
|
162
315
|
// review.prefer's adapter|adapter:model grammar): a consult seat has no channel to inherit a
|
|
163
316
|
// model from, so a bare adapter is meaningless and fails config load fail-closed. The pinned
|
|
@@ -303,7 +456,9 @@ export const DEFAULT_CONFIG = {
|
|
|
303
456
|
pricing: { cheap: 0.1, mid: 0.5, frontier: 2.5 },
|
|
304
457
|
gates: { diffCap: DEFAULT_DIFF_CAP },
|
|
305
458
|
judge: { adapter: "claude-code", model: "fable" },
|
|
306
|
-
|
|
459
|
+
// R3: no `policy` floor — the neutral floor leaves the compiler's per-task assignment standing, so
|
|
460
|
+
// the path-keyed rule is reachable out of the box rather than raised to full by construction.
|
|
461
|
+
review: { complexityThreshold: 7, required: true, criticalPaths: [...DEFAULT_REVIEW_CRITICAL_PATHS] },
|
|
307
462
|
consult: { adapter: "claude-code", model: "fable", stallMinutes: 15 },
|
|
308
463
|
// v1.4: gate LLM calls (judge/review/consult) run headless by default; pane opts back into visible agents.
|
|
309
464
|
// v1.2: workers are the real agent TUI in the pane; "print" restores the -p-rendered-in-pane path.
|
package/dist/drivers/herdr.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ExecutorDriver, type NotifyOpts, type Slot, type SlotOpts } from "
|
|
|
2
2
|
export declare const TRAILER_SAFE_FLOOR_COLS = 108;
|
|
3
3
|
export declare const TRAILER_WIDTH_MARGIN = 2;
|
|
4
4
|
export declare const DELIVERY_ATTEMPTS = 3;
|
|
5
|
+
export declare const DISPATCH_START_PREFIX = "TICKMARKR_START_";
|
|
5
6
|
export interface HerdrTimeSource {
|
|
6
7
|
now: () => number;
|
|
7
8
|
sleep: (ms: number) => Promise<void>;
|
|
@@ -12,12 +13,20 @@ export declare class DeliveryReadinessError extends Error {
|
|
|
12
13
|
readonly phase = "READINESS";
|
|
13
14
|
constructor(waitedMs: number, transcript: string);
|
|
14
15
|
}
|
|
16
|
+
export declare class DeliveryCorruptedError extends Error {
|
|
17
|
+
readonly pane: string;
|
|
18
|
+
readonly transcript: string;
|
|
19
|
+
readonly phase = "DISPATCH";
|
|
20
|
+
constructor(pane: string, reason: string, transcript: string);
|
|
21
|
+
}
|
|
22
|
+
export type DriverJournal = (event: string, slotName: string, data: Record<string, unknown>) => void;
|
|
15
23
|
/** First-generation join direction from measured trailer-safe floor (43-MEASUREMENT.md). */
|
|
16
24
|
export declare function workerSplitDirection(paneCols: number | null, safeFloor?: number, margin?: number): "right" | "down";
|
|
17
25
|
export declare class HerdrDriver implements ExecutorDriver {
|
|
18
26
|
private bin;
|
|
19
27
|
private workersPerTab;
|
|
20
28
|
private time;
|
|
29
|
+
private journal?;
|
|
21
30
|
id: string;
|
|
22
31
|
interactive: boolean;
|
|
23
32
|
private groups;
|
|
@@ -26,16 +35,18 @@ export declare class HerdrDriver implements ExecutorDriver {
|
|
|
26
35
|
private dispatchLeases;
|
|
27
36
|
private deliveredPanes;
|
|
28
37
|
private inputBoxes;
|
|
38
|
+
private bootstraps;
|
|
39
|
+
private journalRoots;
|
|
29
40
|
private ws;
|
|
30
41
|
private callerPane;
|
|
31
42
|
private watches;
|
|
32
|
-
constructor(bin?: string, workersPerTab?: number, time?: HerdrTimeSource);
|
|
43
|
+
constructor(bin?: string, workersPerTab?: number, time?: HerdrTimeSource, journal?: DriverJournal | undefined);
|
|
44
|
+
private appendDispatchRetry;
|
|
33
45
|
private serial;
|
|
34
46
|
private deliveryQueue;
|
|
35
47
|
private reserveDispatch;
|
|
36
48
|
private verifyPaneIdentityBinding;
|
|
37
49
|
private deliveryMatches;
|
|
38
|
-
private shellExecutionEchoed;
|
|
39
50
|
private submissionRegistered;
|
|
40
51
|
static available(): boolean;
|
|
41
52
|
private herdr;
|
|
@@ -52,7 +63,13 @@ export declare class HerdrDriver implements ExecutorDriver {
|
|
|
52
63
|
private glyphFor;
|
|
53
64
|
private joinGroup;
|
|
54
65
|
run(slot: Slot, cmd: string): Promise<void>;
|
|
55
|
-
private
|
|
66
|
+
private dispatchWithRetry;
|
|
67
|
+
private isTuiTurn;
|
|
68
|
+
private dispatch;
|
|
69
|
+
private deliverAtomic;
|
|
70
|
+
private awaitDispatchAck;
|
|
71
|
+
private freshPane;
|
|
72
|
+
private deliverTyped;
|
|
56
73
|
nudge(slot: Slot, message: string): Promise<boolean>;
|
|
57
74
|
private deliverPersistentShellCommand;
|
|
58
75
|
private submitVerifiedDelivery;
|