tickmarkr 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/catalog-remote.d.ts +12 -4
- package/dist/adapters/catalog-remote.js +97 -45
- package/dist/adapters/catalog.js +5 -3
- package/dist/adapters/claude-code.d.ts +1 -1
- package/dist/adapters/claude-code.js +8 -5
- package/dist/adapters/codex.js +6 -7
- package/dist/adapters/model-lints.d.ts +9 -5
- package/dist/adapters/model-lints.js +56 -15
- package/dist/adapters/model-windows.js +11 -0
- package/dist/adapters/prompt.js +1 -0
- package/dist/adapters/qwen.d.ts +5 -0
- package/dist/adapters/qwen.js +153 -0
- package/dist/adapters/registry.js +13 -1
- package/dist/adapters/types.d.ts +1 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli/commands/compile.d.ts +3 -0
- package/dist/cli/commands/compile.js +91 -34
- package/dist/cli/commands/doctor.d.ts +4 -3
- package/dist/cli/commands/doctor.js +28 -9
- package/dist/cli/commands/fleet.d.ts +4 -0
- package/dist/cli/commands/fleet.js +60 -15
- package/dist/cli/commands/init.js +12 -13
- package/dist/cli/commands/plan.js +75 -11
- package/dist/cli/commands/run.js +20 -1
- package/dist/cli/commands/status.d.ts +1 -0
- package/dist/cli/commands/status.js +59 -16
- package/dist/cli/commands/verify.d.ts +1 -0
- package/dist/cli/commands/verify.js +5 -0
- package/dist/cli/commands/version.js +2 -2
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/compile/collateral.d.ts +14 -12
- package/dist/compile/collateral.js +32 -33
- package/dist/compile/index.d.ts +4 -1
- package/dist/compile/index.js +53 -8
- package/dist/compile/native.d.ts +4 -2
- package/dist/compile/native.js +63 -6
- package/dist/compile/ownership.js +41 -10
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.js +51 -5
- package/dist/drivers/herdr.d.ts +2 -0
- package/dist/drivers/herdr.js +43 -4
- package/dist/drivers/orca.d.ts +18 -1
- package/dist/drivers/orca.js +163 -15
- package/dist/drivers/types.d.ts +10 -0
- package/dist/gates/baseline.d.ts +26 -2
- package/dist/gates/baseline.js +115 -13
- package/dist/gates/review.d.ts +6 -4
- package/dist/gates/review.js +26 -31
- package/dist/gates/run-gates.d.ts +5 -2
- package/dist/gates/run-gates.js +34 -17
- package/dist/graph/graph.d.ts +20 -0
- package/dist/graph/graph.js +66 -1
- package/dist/route/preference.d.ts +6 -0
- package/dist/route/preference.js +40 -0
- package/dist/route/router.js +15 -2
- package/dist/run/consult.d.ts +1 -0
- package/dist/run/consult.js +35 -7
- package/dist/run/daemon.d.ts +9 -0
- package/dist/run/daemon.js +266 -59
- package/dist/run/git.d.ts +4 -0
- package/dist/run/git.js +51 -6
- package/dist/run/journal.d.ts +1 -1
- package/dist/run/journal.js +5 -2
- package/dist/run/lock.d.ts +6 -0
- package/dist/run/lock.js +41 -1
- package/dist/tui/ink/fleet-app.d.ts +4 -0
- package/dist/tui/ink/fleet-app.js +45 -16
- package/package.json +59 -1
- package/skills/tickmarkr-overseer/SKILL.md +39 -4
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +79 -0
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +90 -4
|
@@ -11,6 +11,7 @@ import { BANNER, kvRow, legend, rule, statusRow, title } from "../../brand.js";
|
|
|
11
11
|
import { tickmarkrDir } from "../../graph/graph.js";
|
|
12
12
|
import { orcaHostDetected } from "../../drivers/index.js";
|
|
13
13
|
import { Journal } from "../../run/journal.js";
|
|
14
|
+
import { runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
14
15
|
import { doctor } from "./doctor.js";
|
|
15
16
|
import { assembleFleetEditor } from "./fleet.js";
|
|
16
17
|
const SCAFFOLD_SPEC = "tickmarkr.spec.md";
|
|
@@ -24,20 +25,18 @@ const ENVIRONMENTS_FOOTER = [
|
|
|
24
25
|
" claude code — tickmarkr init --agent installs the /tkr skills + AGENTS.md so Claude Code (or any agent CLI) drives the loop natively",
|
|
25
26
|
" 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)",
|
|
26
27
|
].join("\n");
|
|
27
|
-
/**
|
|
28
|
-
function
|
|
29
|
-
const
|
|
28
|
+
/** Current run truth, lock first; falls back to an abandoned journal. */
|
|
29
|
+
function activeRunLine(cwd) {
|
|
30
|
+
const locked = runLockRunId(cwd);
|
|
31
|
+
const runId = locked ?? Journal.latestRunId(cwd, { withJournal: true });
|
|
30
32
|
if (!runId)
|
|
31
33
|
return null;
|
|
34
|
+
let events = [];
|
|
32
35
|
try {
|
|
33
|
-
|
|
34
|
-
if (events.some((e) => e.event === "run-end"))
|
|
35
|
-
return null;
|
|
36
|
-
return runId;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return null;
|
|
36
|
+
events = Journal.open(cwd, runId).read();
|
|
40
37
|
}
|
|
38
|
+
catch { /* lock may exist before first journal row */ }
|
|
39
|
+
return runStatusLine(cwd, runId, events);
|
|
41
40
|
}
|
|
42
41
|
/** Relative paths of specs/*.spec.md already in the repo. */
|
|
43
42
|
function existingSpecs(cwd) {
|
|
@@ -56,9 +55,9 @@ function existingSpecs(cwd) {
|
|
|
56
55
|
}
|
|
57
56
|
/** Context-aware next-steps line (operator-approved 2026-07-17). */
|
|
58
57
|
function nextSteps(cwd, scaffoldedSpec) {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
58
|
+
const runLine = activeRunLine(cwd);
|
|
59
|
+
if (runLine)
|
|
60
|
+
return `${runLine} — tickmarkr status`;
|
|
62
61
|
const specs = existingSpecs(cwd);
|
|
63
62
|
if (specs.length > 0) {
|
|
64
63
|
const listed = specs.length <= 3 ? specs.join(", ") : `${specs.slice(0, 3).join(", ")}, …`;
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
|
-
import { allAdapters,
|
|
4
|
+
import { allAdapters, doctorAgeMs, modelAuthExclusions, probeAll, readDoctor, rolePools, servableExclusions, servabilityLine } from "../../adapters/registry.js";
|
|
5
5
|
import { formatModelAuthLine, contextWindowLints, modelLints, preferEntryLints, ttyVisual } from "../../adapters/model-lints.js";
|
|
6
6
|
import { GLYPHS, dim, rule, title, warn } from "../../brand.js";
|
|
7
7
|
import { collateralLints, sourceScopeLints } from "../../compile/collateral.js";
|
|
8
8
|
import { classifyContextPath } from "../../compile/native.js";
|
|
9
|
-
import { DEFAULT_CONFIG, overlayPreferShapes, ROUTING_MODES, TIER_RANK } from "../../config/config.js";
|
|
9
|
+
import { DEFAULT_CONFIG, effectiveReviewPolicy, overlayPreferShapes, ROUTING_MODES, TIER_RANK } from "../../config/config.js";
|
|
10
10
|
import { loadGraph } from "../../graph/graph.js";
|
|
11
11
|
import { renderAcceptanceItem } from "../../graph/schema.js";
|
|
12
12
|
import { resolveRunMode } from "../../run/daemon.js";
|
|
13
|
-
import { excludedChannels, exclusionLine } from "../../route/preference.js";
|
|
13
|
+
import { disallowedBy, excludedChannels, exclusionLine, routingEntrySeatLines } from "../../route/preference.js";
|
|
14
14
|
import { staffLedEvidence } from "../../route/profile.js";
|
|
15
15
|
import { route, RoutingError } from "../../route/router.js";
|
|
16
16
|
import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.js";
|
|
17
|
-
import { modelId } from "../../gates/review.js";
|
|
18
|
-
import { loadRoutingProfile } from "../../run/journal.js";
|
|
17
|
+
import { modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
18
|
+
import { Journal, loadRoutingProfile } from "../../run/journal.js";
|
|
19
19
|
import { harnessLine, resolveHarness } from "../harness.js";
|
|
20
|
-
import { shq } from "../../adapters/types.js";
|
|
20
|
+
import { channelKey, shq } from "../../adapters/types.js";
|
|
21
21
|
import { shGit } from "../../run/git.js";
|
|
22
|
+
import { runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
22
23
|
import { driverEvidence, pickDriver } from "../../drivers/index.js";
|
|
23
24
|
// T4 (v1.50): TTY-only brand pass — the title helper frames the routing table, lint/unroutable
|
|
24
25
|
// markers carry the attention glyph, section labels dim to chrome (the doctor/status system).
|
|
@@ -44,6 +45,19 @@ const fleetCanCrossVendorReview = (channels) => {
|
|
|
44
45
|
return false;
|
|
45
46
|
};
|
|
46
47
|
const NAMED_CRITERION_PATH = /(?:^|[\s("'`])((?:src|tests|fixtures|scripts)\/[A-Za-z0-9_@{}*?.,/+-]+\.(?:tsx|ts|jsx|json|js|mjs|cjs|md|txt))(?![A-Za-z0-9])/g;
|
|
48
|
+
const judgeOracle = (task) => task.acceptance.some((item) => typeof item === "string" || (typeof item === "object" && item.oracle === "judge"));
|
|
49
|
+
const latestRunStatusLine = (cwd) => {
|
|
50
|
+
const locked = runLockRunId(cwd);
|
|
51
|
+
const runId = locked ?? Journal.latestRunId(cwd, { withJournal: true });
|
|
52
|
+
if (!runId)
|
|
53
|
+
return null;
|
|
54
|
+
let events = [];
|
|
55
|
+
try {
|
|
56
|
+
events = Journal.open(cwd, runId).read();
|
|
57
|
+
}
|
|
58
|
+
catch { /* lock may exist before first journal row */ }
|
|
59
|
+
return runStatusLine(cwd, runId, events);
|
|
60
|
+
};
|
|
47
61
|
async function taskInputFindings(tasks, cwd) {
|
|
48
62
|
const pathsByTask = tasks.map((task) => {
|
|
49
63
|
const paths = new Set(task.files.map((path) => path.replace(/^\.\//, "")));
|
|
@@ -153,7 +167,8 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
153
167
|
// readDoctor cache path: staleness line only fires here (probeAll fallback is fresh by construction).
|
|
154
168
|
const cached = readDoctor(cwd);
|
|
155
169
|
const health = cached ?? (await probeAll(adapters));
|
|
156
|
-
const
|
|
170
|
+
const pools = rolePools(cfg, adapters, health);
|
|
171
|
+
const channels = pools.worker;
|
|
157
172
|
// VIS-04 trust ramp (VALIDATION 13-01-11): preview:true bypasses the routing.learned:off short-circuit so
|
|
158
173
|
// the static-vs-learned comparison renders even when the daemon's learned routing is off. Cold ⇒ undefined ⇒
|
|
159
174
|
// output byte-identical to today.
|
|
@@ -168,13 +183,22 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
168
183
|
// source, and the explore posture; each task row carries a floor-derivation line below.
|
|
169
184
|
// v1.89 T4: the harness names itself ABOVE the routing table — the table is only as trustworthy as the
|
|
170
185
|
// binary that produced it, and version equality cannot tell an installed package from a checkout.
|
|
186
|
+
const runLine = latestRunStatusLine(cwd);
|
|
171
187
|
const lines = [
|
|
172
188
|
`tickmarkr plan — dry run (${channels.length} channels available)`,
|
|
173
189
|
`mode: ${mode.mode} (${source}) · explore ${cfg.routing.explore?.mode ?? "on"}`,
|
|
174
190
|
`driver: ${driverEvidence(cfg, selectedDriver, values.driver)}`,
|
|
175
191
|
harnessLine(resolveHarness(harnessFrom)),
|
|
192
|
+
...(runLine ? [runLine] : []),
|
|
176
193
|
"",
|
|
177
194
|
];
|
|
195
|
+
const judgeDeny = disallowedBy(cfg.judge, cfg.routing, "judge");
|
|
196
|
+
if (judgeDeny?.by === "deny") {
|
|
197
|
+
lines.push(`REFUSAL OBS-576: judge seat ${cfg.judge.adapter}:${cfg.judge.model} is removed by flat routing.deny (${judgeDeny.entry}) — move worker-only policy to routing.deny.workers`, "");
|
|
198
|
+
}
|
|
199
|
+
const entrySeats = routingEntrySeatLines(cfg);
|
|
200
|
+
if (entrySeats.length)
|
|
201
|
+
lines.push(...entrySeats, "");
|
|
178
202
|
const derivation = (shape) => {
|
|
179
203
|
const floor = cfg.routing.floors[shape];
|
|
180
204
|
return floor ? ` floor ${floor} ← ${mode.provenance[shape] ?? "config floors"}` : null;
|
|
@@ -235,6 +259,22 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
235
259
|
lints.push(...modelLints(cfg, health, adapters, { tty: ttyVisual() })); // health may be pre-v1.5/probeAll-fallback — no-detection branch covers both
|
|
236
260
|
// v1.54 T3: dead-steering sweep — advisory only, renders with the routing lints, never alters routing.
|
|
237
261
|
lints.push(...preferEntryLints(cfg, health, overlayPreferShapes(cwd)));
|
|
262
|
+
const preferMatches = (entry, pool) => pool.some((c) => entry === c.adapter || entry === `${c.adapter}:${c.model}`);
|
|
263
|
+
const lintInertPrefer = (surface, entries, poolName, pool) => {
|
|
264
|
+
for (const entry of entries ?? []) {
|
|
265
|
+
if (!preferMatches(entry, pool))
|
|
266
|
+
lints.push(`${surface} '${entry}' resolves to no live channel of ${poolName} pool`);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
for (const [shape, entry] of Object.entries(cfg.routing.map)) {
|
|
270
|
+
lintInertPrefer(`routing.map.${shape}.prefer`, entry.prefer, "worker", pools.worker);
|
|
271
|
+
}
|
|
272
|
+
lintInertPrefer("review.prefer", cfg.review.prefer, "review", pools.review);
|
|
273
|
+
lintInertPrefer("consult.prefer", cfg.consult.prefer, "consult", pools.consult);
|
|
274
|
+
const reviewProviders = new Set(pools.review.map((c) => modelProvider(c.model, c.vendor)));
|
|
275
|
+
if (pools.review.length > 0 && reviewProviders.size < 2) {
|
|
276
|
+
lints.push(`review pool concentration: ${pools.review.length} channel(s), ${reviewProviders.size} provider(s)`);
|
|
277
|
+
}
|
|
238
278
|
// The guard here was `channels.length && !fleetCanCrossVendorReview(channels)`, so the review lint went
|
|
239
279
|
// SILENT at zero channels — the first-run state, where review.required is set and nothing can route at all.
|
|
240
280
|
// Deleting that clause outright is worse than the silence: it fires "no cross-vendor reviewer pair in fleet"
|
|
@@ -245,10 +285,32 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
245
285
|
if (cfg.review.required && channels.length === 0) {
|
|
246
286
|
lints.push("review: review.required is set but no channel can route — the fleet is empty; install or authenticate an adapter");
|
|
247
287
|
}
|
|
248
|
-
else if (cfg.review.required && !fleetCanCrossVendorReview(
|
|
288
|
+
else if (cfg.review.required && !fleetCanCrossVendorReview(pools.review)) {
|
|
249
289
|
lints.push("review: no cross-vendor reviewer pair in fleet — set review.required: false to waive");
|
|
250
290
|
}
|
|
251
291
|
let cost = 0;
|
|
292
|
+
const seatCost = (seat) => seat?.channel === "api" ? cfg.pricing[seat.tier] : 0;
|
|
293
|
+
const judgeSeat = (task) => {
|
|
294
|
+
const shapeGates = cfg.gates.byShape?.[task.shape];
|
|
295
|
+
if (!task.gates.includes("acceptance") || shapeGates?.acceptance === false)
|
|
296
|
+
return { line: "none — acceptance gate disabled", cost: 0 };
|
|
297
|
+
if (!judgeOracle(task))
|
|
298
|
+
return { line: "none — no judge oracle", cost: 0 };
|
|
299
|
+
const seat = pools.judge.find((c) => c.adapter === cfg.judge.adapter && c.model === cfg.judge.model);
|
|
300
|
+
return { line: `${cfg.judge.adapter}:${cfg.judge.model}`, cost: seatCost(seat) };
|
|
301
|
+
};
|
|
302
|
+
const reviewSeat = (task, author) => {
|
|
303
|
+
const shapeGates = cfg.gates.byShape?.[task.shape];
|
|
304
|
+
if (!task.gates.includes("review") || shapeGates?.review === false)
|
|
305
|
+
return { line: "none — review gate disabled", cost: 0 };
|
|
306
|
+
const policy = effectiveReviewPolicy(task.files, cfg.review);
|
|
307
|
+
if (policy === "judge-only")
|
|
308
|
+
return { line: "none — reviewPolicy judge-only: declared leaf paths", cost: 0 };
|
|
309
|
+
const reviewer = pickReviewer(author, pools.review, [], cfg.review.prefer ?? [], task.routingHints?.floor);
|
|
310
|
+
if (!reviewer)
|
|
311
|
+
return { line: "none — no cross-vendor reviewer available", cost: 0 };
|
|
312
|
+
return { line: `${channelKey(reviewer)} — reviewPolicy full`, cost: seatCost(reviewer) };
|
|
313
|
+
};
|
|
252
314
|
const routed = [];
|
|
253
315
|
for (const t of g.tasks) {
|
|
254
316
|
const refusals = [...(oracleRefusals.get(t.id) ?? []), ...(inputRefusals.get(t.id) ?? [])];
|
|
@@ -264,8 +326,10 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
264
326
|
lints.push(suf ? `${l}${suf}` : l);
|
|
265
327
|
}
|
|
266
328
|
const est = r.assignment.channel === "sub" ? 0 : cfg.pricing[r.assignment.tier];
|
|
267
|
-
|
|
268
|
-
|
|
329
|
+
const judge = judgeSeat(t);
|
|
330
|
+
const review = reviewSeat(t, r.assignment);
|
|
331
|
+
cost += est + judge.cost + review.cost;
|
|
332
|
+
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} c${String(t.complexity).padEnd(3)}→ ${r.assignment.adapter}:${r.assignment.model} [${r.assignment.channel}/${r.assignment.tier}]${t.timeoutMinutes !== undefined ? ` (timeout ${t.timeoutMinutes}m)` : ""}${t.humanGate ? " (human gate)" : ""}${est ? ` ~$${est.toFixed(2)}` : ""} — ${r.provenance}`, ` judge: ${judge.line}${judge.cost ? ` ~$${judge.cost.toFixed(2)}` : ""}`, ` review: ${review.line}${review.cost ? ` ~$${review.cost.toFixed(2)}` : ""}`);
|
|
269
333
|
const d = derivation(t.shape);
|
|
270
334
|
if (d)
|
|
271
335
|
lines.push(d);
|
|
@@ -302,7 +366,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
302
366
|
if (inputWarnings.length) {
|
|
303
367
|
lines.push("", "input warnings:", ...inputWarnings.map((finding) => ` ! ${finding.taskId}: ${finding.detail}`));
|
|
304
368
|
}
|
|
305
|
-
lines.push("", `est. cost (API
|
|
369
|
+
lines.push("", `est. cost (API LLM seats, rough): ~$${cost.toFixed(2)} + consult calls`);
|
|
306
370
|
// VIS-04: summary only when a profile is active AND something deviates. Labeled by the switch — off = preview.
|
|
307
371
|
if (profile && deviations) {
|
|
308
372
|
lines.push(cfg.routing.learned === "off"
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -2,7 +2,7 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { allAdapters, discoverChannels, probeAll, readDoctor } from "../../adapters/registry.js";
|
|
3
3
|
import { ROUTING_MODES } from "../../config/config.js";
|
|
4
4
|
import { parseDriverOverride, pickDriver } from "../../drivers/index.js";
|
|
5
|
-
import { loadGraph } from "../../graph/graph.js";
|
|
5
|
+
import { loadGraph, onDiskSpecHash, readCompileRefusal } from "../../graph/graph.js";
|
|
6
6
|
import { formatSummary, resolveRunMode, runDaemon } from "../../run/daemon.js";
|
|
7
7
|
import { isRunLockLive } from "../../run/lock.js";
|
|
8
8
|
import { route, NO_EXPLORE_ENV } from "../../route/router.js";
|
|
@@ -424,7 +424,26 @@ export async function run(argv, cwd = process.cwd()) {
|
|
|
424
424
|
if (values.quality)
|
|
425
425
|
console.warn(QUALITY_ALIAS_NOTICE);
|
|
426
426
|
const flagMode = values.mode ?? (values.quality ? "partner-led" : undefined);
|
|
427
|
+
const refusal = (() => {
|
|
428
|
+
try {
|
|
429
|
+
return readCompileRefusal(cwd);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
throw new Error(`refusing to run: ${error instanceof Error ? error.message : String(error)}. `
|
|
433
|
+
+ "Recompile successfully with `tickmarkr compile <src>` before running.");
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
436
|
+
if (refusal) {
|
|
437
|
+
throw new Error(`refusing to run the prior graph: the last compile of ${refusal.source} was refused at ${refusal.refusedAt}:\n`
|
|
438
|
+
+ `${refusal.error}\n`
|
|
439
|
+
+ "Recompile successfully with `tickmarkr compile <src>` to clear the refusal record before running.");
|
|
440
|
+
}
|
|
427
441
|
const graph = loadGraph(cwd);
|
|
442
|
+
const diskSpec = onDiskSpecHash(cwd, graph);
|
|
443
|
+
if (diskSpec && diskSpec.hash !== graph.spec.hash) {
|
|
444
|
+
throw new Error(`refusing to run a stale graph: ${diskSpec.path} content hash ${diskSpec.hash} differs from `
|
|
445
|
+
+ `the graph's recorded spec hash ${graph.spec.hash}. Recompile with \`tickmarkr compile <src>\` before running.`);
|
|
446
|
+
}
|
|
428
447
|
const { cfg, conflict } = resolveRunMode(cwd, { flag: flagMode, spec: graph.mode });
|
|
429
448
|
if (conflict) {
|
|
430
449
|
// Loud, never silent: live intent (the flag) may override compiled intent (the spec) — strict refuses.
|
|
@@ -21,6 +21,7 @@ export declare const GATE_KEYS: {
|
|
|
21
21
|
readonly acceptance: "A";
|
|
22
22
|
readonly review: "R";
|
|
23
23
|
};
|
|
24
|
+
export declare const STATUS_HELP = "usage: tickmarkr status [<runId>] [--watch [--events]] [--oneline]\nWith --watch --events, stdout is one JSON document per decision event and stderr carries keepalive lines.\nDo not merge stdout and stderr (for example with 2>&1): doing so corrupts the JSON document stream.";
|
|
24
25
|
export declare const decisionEventsFromJournal: (events: JournalEvent[], runId: string, stateDir?: string) => DecisionEvent[];
|
|
25
26
|
export declare const taskBox: (status: TaskStatus) => string;
|
|
26
27
|
export declare const gateBox: (state: "open" | "pass" | "fail" | "skip", unicode: boolean) => string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { GLYPHS, LIVE } from "../../brand.js";
|
|
4
5
|
import { DEFAULT_CONFIG, loadConfig } from "../../config/config.js";
|
|
@@ -8,7 +9,7 @@ import { blockedTasks, graphDefinitionHash, loadGraph, stateDirName } from "../.
|
|
|
8
9
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
9
10
|
import { foldActivity } from "../../run/activity.js";
|
|
10
11
|
import { Journal, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, recordedTaskFailureKind, runHasEnded, upheldFeedbackByTask, } from "../../run/journal.js";
|
|
11
|
-
import { isPidLive,
|
|
12
|
+
import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
12
13
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
13
14
|
import { desiredPanes } from "../../run/reconcile.js";
|
|
14
15
|
import { normalizeStallSnapshot } from "../../run/stall.js";
|
|
@@ -108,6 +109,11 @@ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇",
|
|
|
108
109
|
const ASCII_SPINNER = ["|", "/", "-", "\\"];
|
|
109
110
|
const SAVE_TERMINAL_TITLE = "\x1b[22;0t";
|
|
110
111
|
const RESTORE_TERMINAL_TITLE = "\x1b[23;0t";
|
|
112
|
+
const ENTER_ALTERNATE_SCREEN = "\x1b[?1049h";
|
|
113
|
+
const LEAVE_ALTERNATE_SCREEN = "\x1b[?1049l";
|
|
114
|
+
export const STATUS_HELP = `usage: tickmarkr status [<runId>] [--watch [--events]] [--oneline]
|
|
115
|
+
With --watch --events, stdout is one JSON document per decision event and stderr carries keepalive lines.
|
|
116
|
+
Do not merge stdout and stderr (for example with 2>&1): doing so corrupts the JSON document stream.`;
|
|
111
117
|
const decisionEvidence = (stateDir, runId, sequence) => `${stateDir}/runs/${runId}/journal.jsonl#L${sequence}`;
|
|
112
118
|
const DECISION_GATE_VERDICTS = {
|
|
113
119
|
passed: "passed",
|
|
@@ -854,17 +860,6 @@ const parseJournalSnapshot = (raw) => raw.split("\n").flatMap((line) => {
|
|
|
854
860
|
// .tickmarkr metadata; an engine-written lock necessarily passed through that initializer already, so
|
|
855
861
|
// status avoids invoking it in stripped reader-purity fixtures where no engine lock can exist.
|
|
856
862
|
const canReadRunLockOwner = (cwd) => existsSync(join(cwd, stateDirName(cwd), ".gitignore"));
|
|
857
|
-
const reportableLockRunId = (owner) => {
|
|
858
|
-
if (!owner?.live || owner.runId === undefined)
|
|
859
|
-
return undefined;
|
|
860
|
-
try {
|
|
861
|
-
return parseRunId(owner.runId);
|
|
862
|
-
}
|
|
863
|
-
catch {
|
|
864
|
-
// Repository-wide locks can be held for non-run work such as compile; those are not a status run.
|
|
865
|
-
return undefined;
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
863
|
const readRunJournalRaw = (cwd, runId, allowMissingJournal) => {
|
|
869
864
|
const dir = join(cwd, stateDirName(cwd), "runs", runId);
|
|
870
865
|
try {
|
|
@@ -894,7 +889,7 @@ const readRunJournalRaw = (cwd, runId, allowMissingJournal) => {
|
|
|
894
889
|
const readRunRecord = (cwd, graph, namedRunId) => {
|
|
895
890
|
const explicitRunId = namedRunId === undefined ? undefined : parseRunId(namedRunId);
|
|
896
891
|
const lockedRunId = explicitRunId === undefined && canReadRunLockOwner(cwd)
|
|
897
|
-
?
|
|
892
|
+
? runLockRunId(cwd)
|
|
898
893
|
: undefined;
|
|
899
894
|
const runId = explicitRunId ?? lockedRunId ?? Journal.latestRunId(cwd, { withJournal: true });
|
|
900
895
|
if (!runId)
|
|
@@ -941,6 +936,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
941
936
|
const record = readRunRecord(cwd, g, namedRunId);
|
|
942
937
|
const runId = record?.runId;
|
|
943
938
|
const events = record?.events ?? [];
|
|
939
|
+
const runLine = runId && record ? runStatusLine(cwd, runId, events) : null;
|
|
944
940
|
// The header and every live-phase consumer share this exact process reading. Proved death retires
|
|
945
941
|
// stale phase markers; unknown identity deliberately does not, preserving pre-identity journals.
|
|
946
942
|
const daemon = daemonLiveness(events);
|
|
@@ -1085,7 +1081,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1085
1081
|
});
|
|
1086
1082
|
const zone = journalRowsOnly ? `${divider}zone ${localZoneLabel(zoneReference)}` : "";
|
|
1087
1083
|
const header = runId
|
|
1088
|
-
? `tickmarkr status${divider}run ${runId}${zone}${supersededBy ? `${divider}superseded by ${supersededBy}` : ""}${!comparable ? `${divider}${NOT_COMPARABLE_NOTICE}` : ""}${divider}${liveness(events, daemon, now).replaceAll(" · ", divider)}${verify ? `${divider}${verify}` : ""}${divider}${tipPhase ? `${done}/${total} tasks done${divider}run not verified` : `${done}/${total} done`}`
|
|
1084
|
+
? `tickmarkr status${divider}${runLine ?? `run ${runId}`}${zone}${supersededBy ? `${divider}superseded by ${supersededBy}` : ""}${!comparable ? `${divider}${NOT_COMPARABLE_NOTICE}` : ""}${divider}${liveness(events, daemon, now).replaceAll(" · ", divider)}${verify ? `${divider}${verify}` : ""}${divider}${tipPhase ? `${done}/${total} tasks done${divider}run not verified` : `${done}/${total} done`}`
|
|
1089
1085
|
: `tickmarkr status${zone}${divider}no runs yet${divider}${done}/${total} done`;
|
|
1090
1086
|
const legendLine = ` gates: ${GATE_NAMES.map((gate) => `${GATE_KEYS[gate]} ${gate}`).join(divider)}`;
|
|
1091
1087
|
return {
|
|
@@ -1109,7 +1105,8 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1109
1105
|
const tally = tipPhase
|
|
1110
1106
|
? `${progressTone(`${progressGlyph} ${done}/${total} tasks done`)}${dot}${progressTone("run not verified")}`
|
|
1111
1107
|
: done === total && total > 0 ? ok(`${done}/${total} done`) : information(`${done}/${total} done`);
|
|
1112
|
-
const
|
|
1108
|
+
const load1 = os.loadavg()[0] ?? 0;
|
|
1109
|
+
const live = `${liveness(events, daemon, now)} · load1 ${load1.toFixed(2)}`
|
|
1113
1110
|
.replace(/\bdead\b/u, fail("dead"))
|
|
1114
1111
|
.replace(/\bfinished\b/u, dim("finished"))
|
|
1115
1112
|
.replace(/\balive\b/u, running("alive"))
|
|
@@ -1127,6 +1124,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1127
1124
|
? ok(verify)
|
|
1128
1125
|
: tipFailed ? fail(`${GLYPHS.fail} ${verify}`) : warn(`${GLYPHS.attention} ${verify}`)]),
|
|
1129
1126
|
...(runId ? [live] : []),
|
|
1127
|
+
...(runLine && runLine !== `run ${runId}` ? [runLine] : []),
|
|
1130
1128
|
...(journalRowsOnly ? [`zone ${localZoneLabel(zoneReference)}`] : []),
|
|
1131
1129
|
].join(separator);
|
|
1132
1130
|
// The brand lockup is two rows and stays two rows at every width: the muted chip, and the running
|
|
@@ -1324,6 +1322,8 @@ const oneLine = (cwd, namedRunId) => {
|
|
|
1324
1322
|
return sanitizeTaskText([record.runId, ...claims].join(" · "));
|
|
1325
1323
|
};
|
|
1326
1324
|
export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
1325
|
+
if (argv.some((arg) => arg === "--help" || arg === "-h"))
|
|
1326
|
+
return STATUS_HELP;
|
|
1327
1327
|
const namedRunId = positionalRunId(argv);
|
|
1328
1328
|
if (argv.includes("--oneline"))
|
|
1329
1329
|
return oneLine(cwd, namedRunId);
|
|
@@ -1419,7 +1419,7 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1419
1419
|
// newer runs start. The no-argument form tracks the live lock first, then latest journal.
|
|
1420
1420
|
const explicitRunId = namedRunId === undefined ? undefined : parseRunId(namedRunId);
|
|
1421
1421
|
const lockedRunId = explicitRunId === undefined && canReadRunLockOwner(cwd)
|
|
1422
|
-
?
|
|
1422
|
+
? runLockRunId(cwd)
|
|
1423
1423
|
: undefined;
|
|
1424
1424
|
const runId = explicitRunId ?? lockedRunId ?? Journal.latestRunId(cwd, { withJournal: true });
|
|
1425
1425
|
if (!runId)
|
|
@@ -1456,6 +1456,13 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1456
1456
|
// a dead board keeps writing and reads ARMED. That is the over-claiming direction, the one an operator
|
|
1457
1457
|
// acts on, and the one this instrument exists to close.
|
|
1458
1458
|
const armed = bounded ? undefined : armWatchSupervision(cwd, opts.supervisionBeatMs);
|
|
1459
|
+
let alternateScreen = false;
|
|
1460
|
+
const leaveAlternateScreen = () => {
|
|
1461
|
+
if (!alternateScreen)
|
|
1462
|
+
return;
|
|
1463
|
+
alternateScreen = false;
|
|
1464
|
+
process.stdout.write(LEAVE_ALTERNATE_SCREEN);
|
|
1465
|
+
};
|
|
1459
1466
|
let titleSaved = false;
|
|
1460
1467
|
const restoreTitle = () => {
|
|
1461
1468
|
if (!titleSaved)
|
|
@@ -1478,6 +1485,34 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1478
1485
|
}
|
|
1479
1486
|
process.stdout.write(`\x1b]0;⏳ ${hotPhase.taskId} ${phaseLabel(hotPhase)} ${fmtElapsed(nowMs - hotPhase.startedAt)}\x07`);
|
|
1480
1487
|
};
|
|
1488
|
+
const signalHandlers = [];
|
|
1489
|
+
let exitingFromSignal = false;
|
|
1490
|
+
const exitFromSignal = (code) => () => {
|
|
1491
|
+
if (exitingFromSignal)
|
|
1492
|
+
return;
|
|
1493
|
+
exitingFromSignal = true;
|
|
1494
|
+
if (titleSaved) {
|
|
1495
|
+
process.removeListener("exit", restoreTitle);
|
|
1496
|
+
restoreTitle();
|
|
1497
|
+
}
|
|
1498
|
+
if (!alternateScreen) {
|
|
1499
|
+
process.exit(code);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
process.removeListener("exit", leaveAlternateScreen);
|
|
1503
|
+
alternateScreen = false;
|
|
1504
|
+
process.stdout.write(LEAVE_ALTERNATE_SCREEN, () => process.exit(code));
|
|
1505
|
+
};
|
|
1506
|
+
if (!bounded && !eventStream && tty) {
|
|
1507
|
+
process.stdout.write(ENTER_ALTERNATE_SCREEN);
|
|
1508
|
+
alternateScreen = true;
|
|
1509
|
+
process.once("exit", leaveAlternateScreen);
|
|
1510
|
+
for (const [signal, code] of [["SIGINT", 130], ["SIGTERM", 143]]) {
|
|
1511
|
+
const handler = exitFromSignal(code);
|
|
1512
|
+
signalHandlers.push([signal, handler]);
|
|
1513
|
+
process.once(signal, handler);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1481
1516
|
try {
|
|
1482
1517
|
for (let i = 0; i < iterations; i++) {
|
|
1483
1518
|
const nowMs = now();
|
|
@@ -1490,6 +1525,8 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1490
1525
|
if (bounded)
|
|
1491
1526
|
eventLines.push(line);
|
|
1492
1527
|
}
|
|
1528
|
+
if (decisionEvents.length === 0)
|
|
1529
|
+
process.stderr.write("status: keepalive\n");
|
|
1493
1530
|
}
|
|
1494
1531
|
else {
|
|
1495
1532
|
frame = renderFrame(cwd, binaryVersion, nowMs, i, workerLiveness, true, namedRunId);
|
|
@@ -1525,6 +1562,12 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1525
1562
|
process.removeListener("exit", restoreTitle);
|
|
1526
1563
|
restoreTitle();
|
|
1527
1564
|
}
|
|
1565
|
+
for (const [signal, handler] of signalHandlers)
|
|
1566
|
+
process.removeListener(signal, handler);
|
|
1567
|
+
if (alternateScreen) {
|
|
1568
|
+
process.removeListener("exit", leaveAlternateScreen);
|
|
1569
|
+
leaveAlternateScreen();
|
|
1570
|
+
}
|
|
1528
1571
|
}
|
|
1529
1572
|
return bounded ? eventStream ? eventLines.join("\n") : frames.join(sep) : "";
|
|
1530
1573
|
}
|
|
@@ -9,6 +9,7 @@ export declare function verifyStateRoot(cwd: string): Promise<string>;
|
|
|
9
9
|
/** Cache identity is repository content, never a checkout path. */
|
|
10
10
|
export declare function baselineCachePath(cwd: string, baseSha: string, commands: Record<string, string>): string;
|
|
11
11
|
export declare function excludeAuthorProvider(channels: BillingChannel[], author: BillingChannel): BillingChannel[];
|
|
12
|
+
export declare const VERIFY_HELP = "usage: tickmarkr verify [--base <ref>] [--criteria <file> | --task <id>] [--json]\nThe final verdict and JSON result are written to stdout; progress and diagnostics are written to stderr.\nDo not merge stdout and stderr (for example with 2>&1): doing so corrupts the verdict stream.";
|
|
12
13
|
export declare function verify(argv: string[], cwd?: string): Promise<{
|
|
13
14
|
out: string;
|
|
14
15
|
code: number;
|
|
@@ -125,7 +125,12 @@ async function warnWideTaskRange(cwd, stateRoot, taskId, mergeBase) {
|
|
|
125
125
|
console.error(`verify: WARNING --task ${taskId} range also carries merge commit(s) for ${others.map((row) => `${row.taskId} ${row.commit.slice(0, 12)}`).join(", ")}; ${taskId}'s own merge ${own.commit} is the intended HEAD`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
+
export const VERIFY_HELP = `usage: tickmarkr verify [--base <ref>] [--criteria <file> | --task <id>] [--json]
|
|
129
|
+
The final verdict and JSON result are written to stdout; progress and diagnostics are written to stderr.
|
|
130
|
+
Do not merge stdout and stderr (for example with 2>&1): doing so corrupts the verdict stream.`;
|
|
128
131
|
export async function verify(argv, cwd = process.cwd()) {
|
|
132
|
+
if (argv.some((arg) => arg === "--help" || arg === "-h"))
|
|
133
|
+
return { out: VERIFY_HELP, code: 0 };
|
|
129
134
|
const { values } = parseArgs({
|
|
130
135
|
args: argv,
|
|
131
136
|
options: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
-
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "../../../package.json");
|
|
6
6
|
const distDir = join(dirname(pkgPath), "dist");
|
|
@@ -25,5 +25,5 @@ export function distFingerprint(dir = distDir) {
|
|
|
25
25
|
}
|
|
26
26
|
export async function version(argv = [], dir = distDir) {
|
|
27
27
|
const { version: v } = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
28
|
-
return argv.includes("--dist") ? `${v} dist:${distFingerprint(dir)}` : v;
|
|
28
|
+
return argv.includes("--dist") ? `${v} dist:${resolve(dir)} fingerprint:${distFingerprint(dir)}` : v;
|
|
29
29
|
}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type CommandResult = string | {
|
|
|
5
5
|
};
|
|
6
6
|
export type CommandMap = Record<string, (argv: string[]) => Promise<CommandResult>>;
|
|
7
7
|
export declare const COMMANDS: CommandMap;
|
|
8
|
-
export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix (--fix writes the test-runner ignore when a safe edit exists)\n fleet interactive fleet editor (fleet --print for CI drift checks)\n compile <src> spec \u2192 .tickmarkr/graph.json (fails without acceptance criteria)\n scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n eval run checked-in fixtures against every channel in isolated temp repos\n run execute the graph (--concurrency N --driver auto|herdr|subprocess|orca --route-strict; orca runs only when named)\n status live run state\n stats all-run channel delivery, red, rescue, author and reviewer statistics\n verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD \u2014
|
|
8
|
+
export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix (--fix writes the test-runner ignore when a safe edit exists)\n fleet interactive fleet editor (fleet --print for CI drift checks)\n compile <src> spec \u2192 .tickmarkr/graph.json (fails without acceptance criteria)\n scope <intent> draft a compiled native spec beside an answered intent (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n eval run checked-in fixtures against every channel in isolated temp repos\n run execute the graph (--concurrency N --driver auto|herdr|subprocess|orca --route-strict; orca runs only when named)\n status live run state (--watch --events: JSON documents on stdout, keepalives on stderr; 2>&1 corrupts the stream)\n stats all-run channel delivery, red, rescue, author and reviewer statistics\n verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD \u2014 verdict/JSON on stdout, progress on stderr; 2>&1 corrupts the verdict stream (--base main --criteria <file> | --task <id> [--files <glob>] [--author adapter:model] [--no-review] [--json])\n resume <id> continue a run from its journal\n report <id> cost/quality report (--md for committable execution record)\n profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)\n ui open the Fleet Studio TUI (full-screen tabbed cockpit)\n unlock remove a stale/garbage run lock (refuses if the holder is alive)\n beat <tier> record one supervision beat for orchestrator|orchestrator-context|overseer|overseer-context|watch, --seat <identity> required (--stand-down to hand off); a supervising seat's own watcher loop calls it, and status reads the tier STALE once the beats stop\n approve <id> <task> release a park (--uphold sides with the reviewer and funds a fixed attempt; --by <name> --reason <text>); takes effect on resume";
|
|
9
9
|
export declare function dispatch(cmd: string | undefined, argv: string[], commands?: CommandMap): Promise<{
|
|
10
10
|
out: string;
|
|
11
11
|
code: number;
|
package/dist/cli/index.js
CHANGED
|
@@ -37,9 +37,9 @@ usage: tickmarkr <command>
|
|
|
37
37
|
plan dry-run routing table + cost estimate + floor lints
|
|
38
38
|
eval run checked-in fixtures against every channel in isolated temp repos
|
|
39
39
|
run execute the graph (--concurrency N --driver auto|herdr|subprocess|orca --route-strict; orca runs only when named)
|
|
40
|
-
status live run state
|
|
40
|
+
status live run state (--watch --events: JSON documents on stdout, keepalives on stderr; 2>&1 corrupts the stream)
|
|
41
41
|
stats all-run channel delivery, red, rescue, author and reviewer statistics
|
|
42
|
-
verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD —
|
|
42
|
+
verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD — verdict/JSON on stdout, progress on stderr; 2>&1 corrupts the verdict stream (--base main --criteria <file> | --task <id> [--files <glob>] [--author adapter:model] [--no-review] [--json])
|
|
43
43
|
resume <id> continue a run from its journal
|
|
44
44
|
report <id> cost/quality report (--md for committable execution record)
|
|
45
45
|
profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)
|
|
@@ -47,12 +47,21 @@ export declare function collateralLints(tasks: ReadonlyArray<Pick<Task, "id" | "
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function criterionReadsOnly(text: string, target: string): boolean;
|
|
49
49
|
export declare function newDirectoryLints(tasks: ReadonlyArray<Pick<Task, "id" | "files">>, repoRoot: string): string[];
|
|
50
|
+
export interface SourceScopeFinding {
|
|
51
|
+
taskId: string;
|
|
52
|
+
/** Complete sorted hit set. Rendering may cap it; compile-time enforcement never does. */
|
|
53
|
+
paths: string[];
|
|
54
|
+
}
|
|
55
|
+
/** Structured OBS-76 findings shared by human rendering and native compile enforcement. */
|
|
56
|
+
export declare function sourceScopeFindings(tasks: ReadonlyArray<Pick<Task, "id" | "files" | "acceptance">>, repoRoot: string): SourceScopeFinding[];
|
|
57
|
+
/** Human display is derived from the structured finding; no enforcement path parses this text. */
|
|
58
|
+
export declare function renderSourceScopeFinding(finding: SourceScopeFinding): string;
|
|
50
59
|
/**
|
|
51
60
|
* OBS-76 class: sweep src/ for out-of-scope source files that reference a symbol the acceptance
|
|
52
61
|
* criteria name — the v1.52 router.ts omission, named at plan time instead of one judge round in.
|
|
53
62
|
* Advisory plan output only, same contract as collateralLints.
|
|
54
63
|
*/
|
|
55
|
-
export declare function sourceScopeLints(tasks: ReadonlyArray<Pick<Task, "id" | "files" | "acceptance">>, repoRoot: string): string[];
|
|
64
|
+
export declare function sourceScopeLints(tasks: ReadonlyArray<Pick<Task, "id" | "files" | "acceptance">>, repoRoot: string, findings?: readonly SourceScopeFinding[]): string[];
|
|
56
65
|
/** Max acceptance items per task. Consult-converged (copus/csol/cfable R2). */
|
|
57
66
|
export declare const MAX_ACCEPTANCE_ITEMS = 6;
|
|
58
67
|
/** Max files[] patterns per task. */
|
|
@@ -141,22 +150,15 @@ export type ReviewParticipation = TickmarkrConfig["review"] & {
|
|
|
141
150
|
export declare function reviewParticipationErrors(tasks: ReadonlyArray<Pick<Task, "id" | "files" | "shape">>, review: ReviewParticipation): string[];
|
|
142
151
|
/**
|
|
143
152
|
* Every Task Unit Contract violation in one pass, ready to throw. `repoRoot` backs the
|
|
144
|
-
* symbol-ownership lint and
|
|
145
|
-
*
|
|
153
|
+
* symbol-ownership lint and participation overlay, and defaults to the invocation directory for
|
|
154
|
+
* programmatic compiles. The CLI passes its target repository explicitly.
|
|
146
155
|
*
|
|
147
156
|
* The read declaration (`context:`) rides on this parameter, on the task objects themselves — the
|
|
148
157
|
* aggregator never infers it from the repo, the config, or a sibling task. It is OPTIONAL: a caller
|
|
149
158
|
* whose task objects carry no `context` gets byte-identical verdicts to the ones it got before the
|
|
150
159
|
* field existed, because an absent declaration grants no authority at all.
|
|
151
160
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* PROGRAMMATIC compile whose target repo differs from process.cwd() resolves the wrong root and can
|
|
155
|
-
* miss that repo's own `review.criticalPaths`. Threading it is one line in src/compile/index.ts,
|
|
156
|
-
* outside this task's file scope. Two things bound the exposure meanwhile, both live: the critical
|
|
157
|
-
* set here is the UNION with the shipped defaults, so a wrong root can never lower enforcement below
|
|
158
|
-
* the shipped floor; and the review GATE — which is handed the run's real config — refuses to skip a
|
|
159
|
-
* critical path itself (src/gates/review.ts), so a lint this seam misses costs a late verdict rather
|
|
160
|
-
* than an unreviewed one. The compile lint is the early warning; the gate is the fail-closed backstop.
|
|
161
|
+
* The compile seam in src/compile/index.ts threads `compileSource`'s repo root here when supplied;
|
|
162
|
+
* otherwise this function deliberately checks process.cwd().
|
|
161
163
|
*/
|
|
162
164
|
export declare function taskUnitContractErrors(tasks: ReadonlyArray<Pick<Task, "id" | "goal" | "files" | "deps" | "acceptance" | "shape"> & Partial<Pick<Task, "context">>>, repoRoot?: string, review?: ReviewParticipation): string[];
|