tickmarkr 2.8.4 → 2.8.6
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.js +3 -2
- package/dist/adapters/codex-commit-check.d.ts +4 -3
- package/dist/adapters/codex-commit-check.js +44 -29
- package/dist/adapters/codex.d.ts +2 -2
- package/dist/adapters/codex.js +38 -32
- package/dist/adapters/cursor-agent.js +2 -1
- package/dist/adapters/grok.js +2 -1
- package/dist/adapters/kimi.js +11 -9
- package/dist/adapters/model-lints.js +4 -3
- package/dist/adapters/opencode.js +2 -1
- package/dist/adapters/pi.js +3 -2
- package/dist/adapters/qwen.js +2 -1
- package/dist/adapters/registry.js +2 -1
- package/dist/adapters/types.d.ts +10 -1
- package/dist/adapters/types.js +18 -3
- package/dist/cli/commands/approve.js +9 -28
- package/dist/cli/commands/doctor.d.ts +49 -5
- package/dist/cli/commands/doctor.js +956 -94
- package/dist/cli/commands/fleet.js +11 -3
- package/dist/cli/commands/init.js +27 -19
- package/dist/cli/commands/status.js +9 -6
- package/dist/cli/commands/verify.js +12 -3
- package/dist/compile/gsd.js +4 -3
- package/dist/compile/index.js +4 -1
- package/dist/compile/native.js +8 -6
- package/dist/config/config.d.ts +15 -0
- package/dist/config/config.js +67 -5
- package/dist/drivers/host-env.d.ts +3 -0
- package/dist/drivers/host-env.js +47 -0
- package/dist/drivers/orca.js +3 -1
- package/dist/drivers/subprocess.d.ts +1 -1
- package/dist/drivers/subprocess.js +4 -3
- package/dist/gates/baseline.d.ts +1 -1
- package/dist/gates/baseline.js +4 -3
- package/dist/gates/cache.js +2 -4
- package/dist/graph/schema.js +3 -1
- package/dist/run/daemon.js +54 -4
- package/dist/run/git-trust.d.ts +11 -0
- package/dist/run/git-trust.js +1 -1
- package/dist/run/git.d.ts +13 -1
- package/dist/run/git.js +34 -14
- package/dist/run/journal.d.ts +15 -1
- package/dist/run/journal.js +83 -17
- package/dist/run/lock.d.ts +5 -0
- package/dist/run/lock.js +202 -9
- package/dist/run/protocol.d.ts +1 -1
- package/dist/tui/cockpit/live-store.js +12 -2
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +65 -21
- package/skills/tickmarkr-loop/SKILL.md +65 -21
- package/skills/tickmarkr-overseer/SKILL.md +88 -32
|
@@ -7,7 +7,7 @@ import { allAdapters, discoverChannels, doctorAgeMs, initDoctorReuse, modelAuthE
|
|
|
7
7
|
import { catalogModelAdvisory, catalogTierRanking, declaredModelWindow, fleetUnclassifiedModels } from "../../adapters/model-lints.js";
|
|
8
8
|
import { CATALOG_REFRESH_TIMEOUT_MS, formatCatalogRefreshLegs, readCachedCatalog, refreshCatalogCommand } from "../../adapters/catalog-remote.js";
|
|
9
9
|
import { CLAUDE_ALIAS_IDENTITY_STAMPS, readClaudeAliasIdentity } from "../../adapters/claude-code.js";
|
|
10
|
-
import { ConfigError, DEFAULT_CONFIG, fleetEditableFromConfig, fleetEditableEquals, formatFleetPrint, globalConfigDir, DENY_SCOPES, initialDenyPropOf, loadConfigWithMode, lowerLayerModelOverrides, overlayBytesLoadError, readOverlayFile, stagedDenyKeyOf, renderFleetOverlayWrite, repoOverlayPath, repoShadowedUserKeys, ROUTING_MODES, TIER_RANK, unifiedYamlDiff, universeEntryMatches, } from "../../config/config.js";
|
|
10
|
+
import { ConfigError, DEFAULT_CONFIG, fleetEditableFromConfig, fleetEditableEquals, formatFleetPrint, globalConfigDir, DENY_SCOPES, initialDenyPropOf, loadConfigWithMode, lowerLayerModelOverrides, overlayBytesLoadError, ownRepoOverlayPath, readOverlayFile, stagedDenyKeyOf, renderFleetOverlayWrite, repoOverlayPath, repoShadowedUserKeys, ROUTING_MODES, TIER_RANK, unifiedYamlDiff, universeEntryMatches, } from "../../config/config.js";
|
|
11
11
|
import { exclusionReason, projectFleetWhy, renderFleetWhy } from "../../config/fleet-why.js";
|
|
12
12
|
import { overlayHoldingLine, overlayHoldsMap } from "../../config/fleet-overlay.js";
|
|
13
13
|
import { SHAPES, TIERS } from "../../graph/schema.js";
|
|
@@ -355,7 +355,10 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
355
355
|
// them (destinationEdits below) — the repository overlay when it declares the key or masks it
|
|
356
356
|
// with null at it or above, else the user overlay.
|
|
357
357
|
const userPath = join(globalDir, "config.yaml");
|
|
358
|
-
|
|
358
|
+
// Queue row 195 / D-2102: the save destination is the checkout's OWN file even when the reader
|
|
359
|
+
// fallback (repoOverlayPath) has this linked worktree reading its main checkout's overlay — a
|
|
360
|
+
// first save here creates the worktree's own overlay, which then wins wholesale.
|
|
361
|
+
const repoPath = ownRepoOverlayPath(cwd);
|
|
359
362
|
const currentUserText = () => (existsSync(userPath) ? readFileSync(userPath, "utf8") : "");
|
|
360
363
|
const pathOf = (destination) => (destination === "repo" ? repoPath : userPath);
|
|
361
364
|
// the candidate bytes stand in for the destination layer in the one loader path
|
|
@@ -1243,7 +1246,12 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
1243
1246
|
// T10: both overlays are guarded whichever one is written, so a holder that moved after the review
|
|
1244
1247
|
// (the destination itself) refuses instead of retargeting. Null = still current.
|
|
1245
1248
|
const stalePreview = (pending, reviewed, prior) => {
|
|
1246
|
-
|
|
1249
|
+
// Queue row 195 / D-2102: a repo-destination review renders against the EFFECTIVE repository
|
|
1250
|
+
// overlay — in a linked worktree with no overlay of its own that is the main checkout's file,
|
|
1251
|
+
// while the write lands in the worktree's own (ownRepoOverlayPath). The guard therefore
|
|
1252
|
+
// re-reads the effective layer, not the destination's prior bytes; either file moving after
|
|
1253
|
+
// the review moves that read (an own file appearing wins wholesale), so the refusal stands.
|
|
1254
|
+
const destinationNow = pending.destination === "repo" ? currentRepoOverlayText(cwd) : (prior ?? currentUserText());
|
|
1247
1255
|
const repoNow = pending.destination === "repo" ? destinationNow : currentRepoOverlayText(cwd);
|
|
1248
1256
|
const userNow = pending.destination === "user" ? destinationNow : currentUserText();
|
|
1249
1257
|
if (repoNow !== pending.repo) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,7 +10,8 @@ import { LEGACY_PREFIX, specTemplate } from "../../compile/native.js";
|
|
|
11
10
|
import { BANNER, kvRow, legend, rule, statusRow, title } from "../../brand.js";
|
|
12
11
|
import { stateDirName, tickmarkrDir } from "../../graph/graph.js";
|
|
13
12
|
import { orcaHostDetected } from "../../drivers/index.js";
|
|
14
|
-
import { WORKTREES_DIR } from "../../run/git.js";
|
|
13
|
+
import { gitSync, WORKTREES_DIR } from "../../run/git.js";
|
|
14
|
+
import { GitTrustRefusal } from "../../run/git-trust.js";
|
|
15
15
|
import { Journal } from "../../run/journal.js";
|
|
16
16
|
import { runLockOwner, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
17
17
|
import { doctor } from "./doctor.js";
|
|
@@ -24,7 +24,7 @@ const ENVIRONMENTS_FOOTER = [
|
|
|
24
24
|
"environments:",
|
|
25
25
|
" herdr — the full cockpit — every worker, judge, and consult is a visible pane you can watch and unblock · https://herdr.dev · auto picks it first when HERDR_ENV=1",
|
|
26
26
|
" orca — visible terminals in the Orca app — auto picks it inside an Orca terminal after herdr and before subprocess; set driver: orca to force it · https://onorca.dev",
|
|
27
|
-
" claude code — tickmarkr init --agent installs the /tkr skills + AGENTS.md so Claude Code (or any agent CLI) drives the loop natively",
|
|
27
|
+
" claude code — tickmarkr init --agent installs the /tkr skills + AGENTS.md so Claude Code (or any agent CLI) drives the loop natively; in Claude Desktop's Code tab (neither herdr nor Orca) one single-tier session runs the loop on the headless subprocess driver, watched with tickmarkr status --watch --plain",
|
|
28
28
|
" 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)",
|
|
29
29
|
].join("\n");
|
|
30
30
|
/** Current run truth, lock first; falls back to an abandoned journal. */
|
|
@@ -115,7 +115,7 @@ A run is green only when ALL of these hold: the run-end event exists in the jour
|
|
|
115
115
|
|
|
116
116
|
### Verified handoffs
|
|
117
117
|
|
|
118
|
-
When relaying missions between agents, never use bare send-text (\`herdr agent send\` / pane send-text) — it omits Enter. Use \`herdr pane run <pane> "<message>"\` or \`herdr notification show "<message>"\`. Confirm delivery by reading the TARGET composer afterward; if the sent text still sits unsubmitted after ~15 s, retry the Enter only when that composer read shows the staged text and no permission prompt or numbered choice holds focus — an Enter sent onto an active prompt approves it or picks a choice instead of submitting the message (OBS-1119). Under that guard, send-keys enter to that pane once and read again (OBS-975); when a prompt or choice holds focus instead, resolve it first and re-read before retrying. Never report "relayed" without read-back.
|
|
118
|
+
When relaying missions between agents, never use bare send-text (\`herdr agent send\` / pane send-text) — it omits Enter. Use \`herdr pane run <pane> "<message>"\` or \`herdr notification show "<message>"\`. Confirm delivery by reading the TARGET composer afterward; if the sent text still sits unsubmitted after ~15 s, retry the Enter only when that composer read shows the staged text and no permission prompt or numbered choice holds focus — an Enter sent onto an active prompt approves it or picks a choice instead of submitting the message (OBS-1119). Under that guard, send-keys enter to that pane once and read again (OBS-975); when a prompt or choice holds focus instead, resolve it first and re-read before retrying. Never report "relayed" without read-back. On a plain host (neither herdr nor Orca) there is no pane to read back: hand off through the host's cross-session message tool, and count the handoff confirmed only by the peer's reply.
|
|
119
119
|
|
|
120
120
|
### Orient before you act — this block may be the ONLY guidance your host loaded
|
|
121
121
|
|
|
@@ -256,13 +256,8 @@ function gitTracked(cwd, path) {
|
|
|
256
256
|
const rel = relative(cwd, path);
|
|
257
257
|
if (rel.startsWith(".."))
|
|
258
258
|
return false;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return true;
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
259
|
+
// queue row 2 item b: protected git; a trust refusal throws, an ordinary failure still reads as untracked
|
|
260
|
+
return gitSync(cwd, ["ls-files", "--error-unmatch", "--", rel]).status === 0;
|
|
266
261
|
}
|
|
267
262
|
async function installAgentFiles(cwd, force, docs, notes) {
|
|
268
263
|
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -375,19 +370,29 @@ function isPathInside(parentDir, candidatePath) {
|
|
|
375
370
|
}
|
|
376
371
|
/**
|
|
377
372
|
* The ONLY way the --remove path runs git: GIT_DIR, GIT_WORK_TREE and GIT_COMMON_DIR are stripped so the repository acted on
|
|
378
|
-
* is always cwd's own, and LC_ALL=C keeps stderr parseable.
|
|
373
|
+
* is always cwd's own, and LC_ALL=C keeps stderr parseable. Queue row 2 item b: it runs through the protected path (gitSync),
|
|
374
|
+
* whose GitTrustRefusal propagates unchanged; a failed git throws an error carrying its status and stderr.
|
|
379
375
|
*/
|
|
380
376
|
function removeGit(cwd, args) {
|
|
381
377
|
const { GIT_DIR: _dir, GIT_WORK_TREE: _workTree, GIT_COMMON_DIR: _commonDir, ...env } = process.env;
|
|
382
|
-
|
|
378
|
+
const r = gitSync(cwd, args, { env: { ...env, LC_ALL: "C" } });
|
|
379
|
+
if (r.error)
|
|
380
|
+
throw r.error;
|
|
381
|
+
if (r.status !== 0)
|
|
382
|
+
throw Object.assign(new Error(`git ${args.join(" ")} failed (${r.status ?? r.signal})`), { status: r.status, stderr: r.stderr });
|
|
383
|
+
return r.stdout;
|
|
383
384
|
}
|
|
385
|
+
/** A trust refusal is never one of the --remove path's fail-open answers. */
|
|
386
|
+
const rethrowRefusal = (error) => { if (error instanceof GitTrustRefusal)
|
|
387
|
+
throw error; };
|
|
384
388
|
function isLinkedWorktree(cwd) {
|
|
385
389
|
try {
|
|
386
390
|
const gitDir = removeGit(cwd, ["rev-parse", "--git-dir"]).trim();
|
|
387
391
|
const gitCommonDir = removeGit(cwd, ["rev-parse", "--git-common-dir"]).trim();
|
|
388
392
|
return resolve(cwd, gitDir) !== resolve(cwd, gitCommonDir);
|
|
389
393
|
}
|
|
390
|
-
catch {
|
|
394
|
+
catch (error) {
|
|
395
|
+
rethrowRefusal(error);
|
|
391
396
|
return false;
|
|
392
397
|
}
|
|
393
398
|
}
|
|
@@ -405,7 +410,8 @@ function checkLiveLock(cwd) {
|
|
|
405
410
|
return mainOwner;
|
|
406
411
|
}
|
|
407
412
|
}
|
|
408
|
-
catch {
|
|
413
|
+
catch (error) {
|
|
414
|
+
rethrowRefusal(error);
|
|
409
415
|
// not a git repo
|
|
410
416
|
}
|
|
411
417
|
return undefined;
|
|
@@ -420,6 +426,7 @@ function isGitRepo(cwd) {
|
|
|
420
426
|
return true;
|
|
421
427
|
}
|
|
422
428
|
catch (err) {
|
|
429
|
+
rethrowRefusal(err);
|
|
423
430
|
const e = err;
|
|
424
431
|
const stderr = String(e.stderr ?? "").trim();
|
|
425
432
|
if (e.status === 128 && stderr.startsWith("fatal: not a git repository (or any of the parent directories)"))
|
|
@@ -433,6 +440,7 @@ function git(cwd, args, message) {
|
|
|
433
440
|
return removeGit(cwd, args);
|
|
434
441
|
}
|
|
435
442
|
catch (err) {
|
|
443
|
+
rethrowRefusal(err);
|
|
436
444
|
const stderr = String(err.stderr ?? "").trim();
|
|
437
445
|
throw new Error(stderr ? `${message}: ${stderr}` : message);
|
|
438
446
|
}
|
|
@@ -522,16 +530,16 @@ function handleRemove(cwd) {
|
|
|
522
530
|
const out = removeGit(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads/tickmarkr/"]);
|
|
523
531
|
branches = out.split("\n").map((b) => b.trim()).filter(Boolean);
|
|
524
532
|
}
|
|
525
|
-
catch {
|
|
526
|
-
|
|
533
|
+
catch (error) {
|
|
534
|
+
rethrowRefusal(error);
|
|
527
535
|
}
|
|
528
536
|
let preservedRefs = [];
|
|
529
537
|
try {
|
|
530
538
|
const out = removeGit(cwd, ["for-each-ref", "--format=%(refname)", "refs/tickmarkr/preserved/"]);
|
|
531
539
|
preservedRefs = out.split("\n").map((r) => r.trim()).filter(Boolean);
|
|
532
540
|
}
|
|
533
|
-
catch {
|
|
534
|
-
|
|
541
|
+
catch (error) {
|
|
542
|
+
rethrowRefusal(error);
|
|
535
543
|
}
|
|
536
544
|
for (const b of branches) {
|
|
537
545
|
notes.push(`left branch: ${b}`);
|
|
@@ -13,7 +13,7 @@ import { projectOperatorSummary } from "../../run/operator-summary.js";
|
|
|
13
13
|
import { trackJournalRows } from "../../run/protocol.js";
|
|
14
14
|
import { newestPark, permittedDecisionVerbs } from "./approve.js";
|
|
15
15
|
import { currentOwed, endToEndFirstPass, firstPassText, leadLines, leadRows, needsYouText, wallText } from "./report.js";
|
|
16
|
-
import { Journal, bindingToken, effectiveEvents, formatJournalNarration, parseJournalText, physicalLine, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, fatalRunEndCause, recordedTaskFailureKind, runHasEnded,
|
|
16
|
+
import { Journal, bindingToken, effectiveEvents, formatJournalNarration, parseJournalText, physicalLine, engagementComparable, isQualityFailureParkKind, parseRunId, preservedRefsByTask, fatalRunEndCause, recordedTaskFailureKind, runHasEnded, restoresUpheldFeedback, visibleControls, } from "../../run/journal.js";
|
|
17
17
|
import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
18
18
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
19
19
|
import { desiredPanes } from "../../run/reconcile.js";
|
|
@@ -1070,15 +1070,18 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1070
1070
|
}).map(sanitizeTaskText)
|
|
1071
1071
|
: [];
|
|
1072
1072
|
// OBS-738: recovery facts stay on the journal's two established reducers. The prefix fold keeps an
|
|
1073
|
-
// older journal equally readable by asking
|
|
1073
|
+
// older journal equally readable by asking restoresUpheldFeedback what was funded at that exact
|
|
1074
1074
|
// restore boundary; current resume-restore rows additionally carry the same task for narration.
|
|
1075
1075
|
// OBS-1178: both folds read decisions through the one decision fold over the whole journal.
|
|
1076
|
+
// Queue row 225 (D-2111): only each task's NEWEST restore speaks — never a historical set.
|
|
1076
1077
|
const decided = effectiveEvents(events);
|
|
1077
1078
|
const preservedRefs = preservedRefsByTask(events);
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1079
|
+
const newestRestores = new Map();
|
|
1080
|
+
decided.forEach((event, index) => { if (event.event === "resume-restore" && event.taskId)
|
|
1081
|
+
newestRestores.set(event.taskId, index); });
|
|
1082
|
+
const restoredUpheldFeedback = new Set([...newestRestores]
|
|
1083
|
+
.filter(([taskId, index]) => restoresUpheldFeedback(decided.slice(0, index), taskId))
|
|
1084
|
+
.map(([taskId]) => taskId));
|
|
1082
1085
|
// OBS-1178: a failed task's newest task-failed row is the token its recheck binds to. Only an
|
|
1083
1086
|
// effective decision consumes it — a refused or unsound recheck leaves the task failed and the token shown.
|
|
1084
1087
|
const failureTokens = new Map();
|
|
@@ -14,7 +14,7 @@ import { getTask, loadGraph } from "../../graph/graph.js";
|
|
|
14
14
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
15
15
|
import { executionSignal } from "../../run/execution-budget.js";
|
|
16
16
|
import { addDetachedWorktree, linkNodeModules, reflogWitness, removeWorktree, shGit, shGitOk } from "../../run/git.js";
|
|
17
|
-
import { foldOwedChecks, integrationMapped, owedCriteria, Journal, OWED_DISCHARGE_EVENT } from "../../run/journal.js";
|
|
17
|
+
import { foldOwedChecks, integrationMapped, owedCriteria, Journal, OWED_DISCHARGE_EVENT, parseRunId } from "../../run/journal.js";
|
|
18
18
|
import { describeLeaseWait, withRepositoryLease } from "../../run/lease.js";
|
|
19
19
|
import { isPidLive } from "../../run/lock.js";
|
|
20
20
|
import { LAUNCHSERVICES_PORT_STOP, probeLaunchServicesPorts, recordLaunchServices } from "../../run/launchservices-check.js";
|
|
@@ -195,8 +195,17 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
195
195
|
const stateRoot = await verifyStateRoot(cwd);
|
|
196
196
|
const fileRoot = (file) => existsSync(join(cwd, ".tickmarkr", file)) ? cwd : stateRoot;
|
|
197
197
|
if (resolve(stateRoot) !== resolve(cwd)) {
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
// Queue row 195 / D-2102: the fallback is no longer "read-only" — the one line names every
|
|
199
|
+
// state file with the path it was read from and its origin, AND every path this invocation
|
|
200
|
+
// writes: the shared verdict store always, doctor.json when present, the --record journal when given.
|
|
201
|
+
const reads = STATE_FILES.map(file => `${file}: ${join(fileRoot(file), ".tickmarkr", file)} (${fileRoot(file) === cwd ? "local" : "common root"})`).join("; ");
|
|
202
|
+
const doctorPath = join(fileRoot("doctor.json"), ".tickmarkr", "doctor.json");
|
|
203
|
+
const writes = [
|
|
204
|
+
`verdict store ${getVerdictStore(join(stateRoot, ".tickmarkr")).dir}`,
|
|
205
|
+
...(existsSync(doctorPath) ? [`doctor.json ${doctorPath}`] : []),
|
|
206
|
+
...(values.record ? [`record journal ${join(stateRoot, ".tickmarkr", "runs", parseRunId(values.record), "journal.jsonl")}`] : []),
|
|
207
|
+
].join("; ");
|
|
208
|
+
console.error(`verify: linked worktree — state files resolve from ${join(stateRoot, ".tickmarkr")} (reads: ${reads}; writes: ${writes})`);
|
|
200
209
|
}
|
|
201
210
|
const cfg = loadConfig(fileRoot("config.yaml"));
|
|
202
211
|
const head = (await shGitOk("git rev-parse HEAD", cwd)).trim();
|
package/dist/compile/gsd.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
2
|
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
4
3
|
import { parse as parseYaml } from "yaml";
|
|
5
4
|
import { AcceptanceItemSchema, TIERS, validateGraph } from "../graph/schema.js";
|
|
6
5
|
import { CompileError, assertWriteScope, inferShape, sha256 } from "./common.js";
|
|
6
|
+
import { gitSync } from "../run/git.js";
|
|
7
7
|
import { classifyContextPath } from "./native.js";
|
|
8
8
|
// GSD artifact front-end (spec v1.3): one GSD *plan* is one tickmarkr *task* — a plan is
|
|
9
9
|
// worktree-sized; its inner <task> steps stay in the worker prompt via context[0] = the plan file.
|
|
@@ -306,9 +306,10 @@ export function compileGsd(src, root) {
|
|
|
306
306
|
// Match native context reachability against the committed tree. This block only obtains the HEAD
|
|
307
307
|
// snapshot; classifyContextPath remains the single authority for missing, untracked, and glob refs.
|
|
308
308
|
const gitDir = dirname(files[0]);
|
|
309
|
-
|
|
309
|
+
// queue row 2 item b: protected git; a trust refusal throws instead of reading as "no repository"
|
|
310
|
+
const top = gitSync(gitDir, ["rev-parse", "--show-toplevel"]);
|
|
310
311
|
// -z (queue row 71): without it a non-ASCII path is C-quoted and a tracked ref reads as untracked
|
|
311
|
-
const tree =
|
|
312
|
+
const tree = gitSync(gitDir, ["ls-tree", "--full-tree", "-r", "--name-only", "-z", "HEAD"]);
|
|
312
313
|
const repoRoot = typeof top.stdout === "string" ? top.stdout.trim() : "";
|
|
313
314
|
// `root` is the repository whose worktrees will consume these refs. A source nested inside some
|
|
314
315
|
// unrelated repository (as with vendored fixtures) must not be judged against that outer tree.
|
package/dist/compile/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parse } from "yaml";
|
|
4
|
+
import { repoOverlayPath } from "../config/config.js";
|
|
4
5
|
import { validateGraph } from "../graph/schema.js";
|
|
5
6
|
import { taskUnitContractErrors } from "./collateral.js";
|
|
6
7
|
import { blocksCompile, ownershipFindings, renderOwnershipFinding } from "./ownership.js";
|
|
@@ -37,7 +38,9 @@ function detect(src) {
|
|
|
37
38
|
function repoOverlayMode(repoRoot) {
|
|
38
39
|
if (!repoRoot)
|
|
39
40
|
return undefined;
|
|
40
|
-
|
|
41
|
+
// Queue row 195 / D-2102: the ONE repo-layer path rule — a linked worktree without an overlay of
|
|
42
|
+
// its own proves against the main checkout's file, never against defaults.
|
|
43
|
+
const path = repoOverlayPath(repoRoot);
|
|
41
44
|
if (!existsSync(path))
|
|
42
45
|
return undefined;
|
|
43
46
|
try {
|
package/dist/compile/native.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
3
|
import { filesGlob } from "../graph/files-glob.js";
|
|
5
4
|
import picomatch from "picomatch";
|
|
6
5
|
import { GATE_NAMES, GRAPH_ROUTING_MODES, ORACLES, renderAcceptanceItem, SHAPES, TIERS, validateGraph, } from "../graph/schema.js";
|
|
6
|
+
import { gitSync } from "../run/git.js";
|
|
7
7
|
import { criterionReadsOnly } from "./collateral.js";
|
|
8
8
|
import { CompileError, inferShape, sha256 } from "./common.js";
|
|
9
9
|
// OBS-170/OBS-184: `context:` is a promise to the worker, and nothing ever checked it could be kept.
|
|
@@ -18,7 +18,8 @@ import { CompileError, inferShape, sha256 } from "./common.js";
|
|
|
18
18
|
// `ls-tree -r HEAD` enumerates that tree once; per-path `cat-file -e HEAD:<path>` is the same oracle
|
|
19
19
|
// one path at a time, and one spawn beats N.
|
|
20
20
|
function trackedAtHead(dir) {
|
|
21
|
-
|
|
21
|
+
// queue row 2 item b: every compile git runs through the protected path; a trust refusal throws, never fails open
|
|
22
|
+
const git = (...args) => gitSync(dir, args);
|
|
22
23
|
const top = git("rev-parse", "--show-toplevel");
|
|
23
24
|
// --full-tree is load-bearing: without it `ls-tree` scopes to the CWD's subtree and prints paths
|
|
24
25
|
// relative to it, so a spec in specs/ would see only specs/-relative names and misjudge every
|
|
@@ -146,7 +147,7 @@ function symlinkScopeWarnings(file, tasks, base) {
|
|
|
146
147
|
if (!scoped.length || !repositoryRoot(file))
|
|
147
148
|
return [];
|
|
148
149
|
const tree = base ?? "HEAD";
|
|
149
|
-
const git = (...args) =>
|
|
150
|
+
const git = (...args) => gitSync(dirname(file), args);
|
|
150
151
|
const failed = (r) => r.error || r.signal || r.status !== 0 || typeof r.stdout !== "string";
|
|
151
152
|
const why = (r) => r.signal ? `signal ${r.signal}` : r.status === null ? "git did not run" : `git exit ${r.status}`;
|
|
152
153
|
const advisory = (what, r) => `tickmarkr: advisory: files[] symlink inspection could not read ${what} in the selected git tree ${JSON.stringify(tree)} (${why(r)}) — symlink scope is unverified for this compile.`;
|
|
@@ -270,7 +271,7 @@ function testsAtHead(root) {
|
|
|
270
271
|
const cached = headTestCache.get(root);
|
|
271
272
|
if (cached)
|
|
272
273
|
return cached;
|
|
273
|
-
const grep =
|
|
274
|
+
const grep = gitSync(root, ["grep", "-I", "-n", "-e", "", "HEAD", "--", "tests/*.test.ts", "tests/**/*.test.ts"]);
|
|
274
275
|
if (grep.status !== 0 || typeof grep.stdout !== "string") {
|
|
275
276
|
headTestCache.set(root, []);
|
|
276
277
|
return [];
|
|
@@ -389,7 +390,7 @@ function criterionScopeFinding(task, text, criterion, id, tests) {
|
|
|
389
390
|
// 20-task spec compiled in seconds and the committed-spec corpus test timed out. Fail-open like
|
|
390
391
|
// testsAtHead: no git answer means no corpus, and the lint stays quiet rather than wrong.
|
|
391
392
|
function repoFileList(root) {
|
|
392
|
-
const ls =
|
|
393
|
+
const ls = gitSync(root, ["ls-files", "-z", "--cached", "--others", "--exclude-standard"]);
|
|
393
394
|
if (ls.status !== 0 || typeof ls.stdout !== "string")
|
|
394
395
|
return [];
|
|
395
396
|
return ls.stdout.split("\0").filter(Boolean).sort();
|
|
@@ -949,7 +950,8 @@ acceptance is required on every task (a nested list of observable outcomes).
|
|
|
949
950
|
mode: partner-led | risk-based | staff-led — this engagement's routing mode
|
|
950
951
|
(loses only to an explicit \`run --mode\` flag)
|
|
951
952
|
base: commit-or-ref — optional declared source base; the declaration must begin at column zero
|
|
952
|
-
(
|
|
953
|
+
(proven against the run's starting commit at run and resume start; a drifted
|
|
954
|
+
or unresolvable base is refused before anything is written)
|
|
953
955
|
|
|
954
956
|
Fields available per task:
|
|
955
957
|
goal: outcome the task must achieve (defaults to the title if omitted)
|
package/dist/config/config.d.ts
CHANGED
|
@@ -459,6 +459,21 @@ export type FleetEditable = Partial<Record<DenyScopeKey, string[]>> & {
|
|
|
459
459
|
map: Record<string, MapEntry>;
|
|
460
460
|
floors: Record<string, Tier>;
|
|
461
461
|
};
|
|
462
|
+
/** The checkout's OWN repo-layer overlay — every writer's destination, and the reader's answer in
|
|
463
|
+
* every case but the one fallback below. */
|
|
464
|
+
export declare function ownRepoOverlayPath(repoRoot: string): string;
|
|
465
|
+
/**
|
|
466
|
+
* Queue row 195 / D-2102: .tickmarkr/ is gitignored, so a fresh session worktree has no overlay of
|
|
467
|
+
* its own and every command there would silently run on defaults plus the global layer. ONE rule,
|
|
468
|
+
* and every repo-layer reader goes through it: a checkout whose own state-dir config.yaml exists
|
|
469
|
+
* reads it; otherwise a LINKED worktree — a gitfile checkout whose trusted common dir
|
|
470
|
+
* (src/run/git-trust.ts trustedCommonDir) lies outside it — reads
|
|
471
|
+
* dirname(common dir)/<state dir>/config.yaml when that file exists; every other case (a main
|
|
472
|
+
* checkout or its subdirectory, no enclosing checkout, a git trust refusal, an absent main file)
|
|
473
|
+
* keeps the checkout's own path. WRITERS never follow this fallback: they use ownRepoOverlayPath.
|
|
474
|
+
* The trust check includes missing, redirected or symlinked commondir metadata and config.worktree
|
|
475
|
+
* under extensions.worktreeConfig; none can authorize a shared overlay.
|
|
476
|
+
*/
|
|
462
477
|
export declare function repoOverlayPath(repoRoot: string): string;
|
|
463
478
|
export declare function readOverlayFile(path: string): Record<string, unknown>;
|
|
464
479
|
/** A discovered fleet universe row: the served models of one adapter and, per model, the resolved
|
package/dist/config/config.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join, sep } from "node:path";
|
|
4
4
|
import { filesGlob } from "../graph/files-glob.js";
|
|
5
5
|
import { Document, isScalar, parse } from "yaml";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { CITED_MODEL_WINDOWS } from "../adapters/model-windows.js";
|
|
8
8
|
import { stateDirName } from "../graph/graph.js";
|
|
9
|
+
import { assertGitTrust, trustedCommonDir } from "../run/git-trust.js";
|
|
9
10
|
import { EFFORTS, SHAPES, TIERS } from "../graph/schema.js";
|
|
10
11
|
const TierEnum = z.enum(TIERS, {
|
|
11
12
|
error: (iss) => `Invalid option: expected one of "cheap"|"mid"|"frontier" (got ${JSON.stringify(iss.input)})`,
|
|
@@ -744,7 +745,7 @@ export function globalConfigDir() {
|
|
|
744
745
|
}
|
|
745
746
|
export function overlayPreferShapes(repoRoot, opts = {}) {
|
|
746
747
|
const shapes = new Set();
|
|
747
|
-
for (const layer of [readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml")), readYaml(
|
|
748
|
+
for (const layer of [readYaml(join(opts.globalDir ?? globalConfigDir(), "config.yaml")), readYaml(repoOverlayPath(repoRoot))]) {
|
|
748
749
|
const map = layer?.routing?.map;
|
|
749
750
|
if (!map)
|
|
750
751
|
continue;
|
|
@@ -835,7 +836,7 @@ export function loadConfigWithMode(repoRoot, opts = {}) {
|
|
|
835
836
|
// v1.52 T2: repoOverlayText substitutes candidate bytes for the on-disk repo layer — the fleet
|
|
836
837
|
// write guard validates EXACTLY what it is about to write through this one loader path.
|
|
837
838
|
const repoCfg = opts.repoOverlayText === undefined
|
|
838
|
-
? readYaml(
|
|
839
|
+
? readYaml(repoOverlayPath(repoRoot))
|
|
839
840
|
: parse(opts.repoOverlayText);
|
|
840
841
|
const merged = deepMerge(deepMerge(structuredClone(DEFAULT_CONFIG), globalCfg), repoCfg);
|
|
841
842
|
const r = TickmarkrConfigSchema.safeParse(merged);
|
|
@@ -1017,9 +1018,70 @@ export function configTemplate(overlay) {
|
|
|
1017
1018
|
}
|
|
1018
1019
|
// Fleet overlay mutation, legacy serialization, and diff rendering live together in fleet-overlay.ts.
|
|
1019
1020
|
export { FLEET_OVERLAY_KEYS, fleetEditableEquals, fleetRepoOverlayFromDelta, renderFleetOverlayWrite, repoOverlayYaml, serializeFleetOverlay, unifiedYamlDiff, } from "./fleet-overlay.js";
|
|
1020
|
-
|
|
1021
|
+
/** The checkout's OWN repo-layer overlay — every writer's destination, and the reader's answer in
|
|
1022
|
+
* every case but the one fallback below. */
|
|
1023
|
+
export function ownRepoOverlayPath(repoRoot) {
|
|
1021
1024
|
return join(repoRoot, stateDirName(repoRoot), "config.yaml");
|
|
1022
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Queue row 195 / D-2102: .tickmarkr/ is gitignored, so a fresh session worktree has no overlay of
|
|
1028
|
+
* its own and every command there would silently run on defaults plus the global layer. ONE rule,
|
|
1029
|
+
* and every repo-layer reader goes through it: a checkout whose own state-dir config.yaml exists
|
|
1030
|
+
* reads it; otherwise a LINKED worktree — a gitfile checkout whose trusted common dir
|
|
1031
|
+
* (src/run/git-trust.ts trustedCommonDir) lies outside it — reads
|
|
1032
|
+
* dirname(common dir)/<state dir>/config.yaml when that file exists; every other case (a main
|
|
1033
|
+
* checkout or its subdirectory, no enclosing checkout, a git trust refusal, an absent main file)
|
|
1034
|
+
* keeps the checkout's own path. WRITERS never follow this fallback: they use ownRepoOverlayPath.
|
|
1035
|
+
* The trust check includes missing, redirected or symlinked commondir metadata and config.worktree
|
|
1036
|
+
* under extensions.worktreeConfig; none can authorize a shared overlay.
|
|
1037
|
+
*/
|
|
1038
|
+
export function repoOverlayPath(repoRoot) {
|
|
1039
|
+
const own = ownRepoOverlayPath(repoRoot);
|
|
1040
|
+
if (existsSync(own))
|
|
1041
|
+
return own;
|
|
1042
|
+
let dotGit;
|
|
1043
|
+
try {
|
|
1044
|
+
for (let dir = realpathSync(repoRoot);;) {
|
|
1045
|
+
const candidate = join(dir, ".git");
|
|
1046
|
+
try {
|
|
1047
|
+
lstatSync(candidate);
|
|
1048
|
+
dotGit = candidate;
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
catch { /* keep walking */ }
|
|
1052
|
+
const up = dirname(dir);
|
|
1053
|
+
if (up === dir)
|
|
1054
|
+
break;
|
|
1055
|
+
dir = up;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
return own;
|
|
1060
|
+
}
|
|
1061
|
+
// A directory .git is a main checkout (or one of its subdirectories); only a gitfile can be linked.
|
|
1062
|
+
if (!dotGit)
|
|
1063
|
+
return own;
|
|
1064
|
+
try {
|
|
1065
|
+
if (!lstatSync(dotGit).isFile())
|
|
1066
|
+
return own;
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
return own;
|
|
1070
|
+
}
|
|
1071
|
+
let common;
|
|
1072
|
+
try {
|
|
1073
|
+
assertGitTrust(repoRoot);
|
|
1074
|
+
common = trustedCommonDir(repoRoot);
|
|
1075
|
+
}
|
|
1076
|
+
catch {
|
|
1077
|
+
return own;
|
|
1078
|
+
}
|
|
1079
|
+
const checkoutRoot = dirname(dotGit);
|
|
1080
|
+
if (common === checkoutRoot || common.startsWith(`${checkoutRoot}${sep}`))
|
|
1081
|
+
return own; // not linked
|
|
1082
|
+
const shared = ownRepoOverlayPath(dirname(common));
|
|
1083
|
+
return existsSync(shared) ? shared : own;
|
|
1084
|
+
}
|
|
1023
1085
|
export function readOverlayFile(path) {
|
|
1024
1086
|
const raw = readYaml(path);
|
|
1025
1087
|
if (raw === undefined || raw === null)
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const HOST_SESSION_VARS: readonly ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_HOST_SESSION_ID", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_MESSAGING_SOCKET", "CLAUDE_CODE_MESSAGING_TOKEN", "CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH", "CLAUDE_CODE_SESSION_ATTENDED", "CLAUDE_CODE_EXECPATH", "CLAUDE_CODE_DESKTOP_APP_VERSION", "CLAUDE_CODE_TERMINAL_MCP_TOOLS", "CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL", "CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES", "CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING", "CLAUDE_CODE_REPORT_FINDINGS", "CLAUDE_CODE_STARTUP_FAILURE_RESULTS", "CLAUDE_CODE_EAGER_FLUSH", "CLAUDE_CODE_DISABLE_CRON", "CLAUDE_CODE_SIMPLE", "CLAUDE_CODE_DISABLE_TERMINAL_TITLE", "CLAUDE_CODE_ACCOUNT_UUID", "CLAUDE_CODE_ORGANIZATION_UUID", "CLAUDE_CODE_USER_EMAIL", "CLAUDE_CODE_OAUTH_SCOPES", "CLAUDE_AGENT_SDK_VERSION", "CLAUDE_AGENT_SDK_MCP_NO_PREFIX", "CLAUDE_PID", "CLAUDE_EFFORT", "CLAUDE_PREVIEW_CLASSIFIER_FLOOR", "CLAUDE_CODE_BRIDGE_SESSION_ID"];
|
|
2
|
+
/** A copy of `env` without the host-session names; the input (the daemon's own process.env) is never mutated. */
|
|
3
|
+
export declare function withoutHostSession(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Queue row 193 (D-2102 add.1): a daemon launched from an agent session (the Claude Desktop Code tab, or a
|
|
2
|
+
// claude CLI seat on Orca or herdr) carries that session's host variables, and an agent CLI that inherits
|
|
3
|
+
// them gains the host's tools and effort. This CLOSED denylist is stripped at every choke point an agent
|
|
4
|
+
// CLI or gate command passes: the shell seam, the subprocess driver's seal, the pane seeds and the bare
|
|
5
|
+
// probes. Exact names only — never a prefix: an unlisted CLAUDE_CODE variable and every user auth or
|
|
6
|
+
// provider variable reach children byte-equal. Declared residual: a host variable a future host adds
|
|
7
|
+
// reaches children until it is listed here. Leaf module: imports nothing from src/.
|
|
8
|
+
export const HOST_SESSION_VARS = [
|
|
9
|
+
"CLAUDECODE",
|
|
10
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
11
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
12
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
13
|
+
"CLAUDE_CODE_CHILD_SESSION",
|
|
14
|
+
"CLAUDE_CODE_MESSAGING_SOCKET",
|
|
15
|
+
"CLAUDE_CODE_MESSAGING_TOKEN",
|
|
16
|
+
"CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH",
|
|
17
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
18
|
+
"CLAUDE_CODE_EXECPATH",
|
|
19
|
+
"CLAUDE_CODE_DESKTOP_APP_VERSION",
|
|
20
|
+
"CLAUDE_CODE_TERMINAL_MCP_TOOLS",
|
|
21
|
+
"CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL",
|
|
22
|
+
"CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES",
|
|
23
|
+
"CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING",
|
|
24
|
+
"CLAUDE_CODE_REPORT_FINDINGS",
|
|
25
|
+
"CLAUDE_CODE_STARTUP_FAILURE_RESULTS",
|
|
26
|
+
"CLAUDE_CODE_EAGER_FLUSH",
|
|
27
|
+
"CLAUDE_CODE_DISABLE_CRON",
|
|
28
|
+
"CLAUDE_CODE_SIMPLE",
|
|
29
|
+
"CLAUDE_CODE_DISABLE_TERMINAL_TITLE",
|
|
30
|
+
"CLAUDE_CODE_ACCOUNT_UUID",
|
|
31
|
+
"CLAUDE_CODE_ORGANIZATION_UUID",
|
|
32
|
+
"CLAUDE_CODE_USER_EMAIL",
|
|
33
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
34
|
+
"CLAUDE_AGENT_SDK_VERSION",
|
|
35
|
+
"CLAUDE_AGENT_SDK_MCP_NO_PREFIX",
|
|
36
|
+
"CLAUDE_PID",
|
|
37
|
+
"CLAUDE_EFFORT",
|
|
38
|
+
"CLAUDE_PREVIEW_CLASSIFIER_FLOOR",
|
|
39
|
+
"CLAUDE_CODE_BRIDGE_SESSION_ID",
|
|
40
|
+
];
|
|
41
|
+
/** A copy of `env` without the host-session names; the input (the daemon's own process.env) is never mutated. */
|
|
42
|
+
export function withoutHostSession(env = process.env) {
|
|
43
|
+
const out = { ...env };
|
|
44
|
+
for (const k of HOST_SESSION_VARS)
|
|
45
|
+
delete out[k];
|
|
46
|
+
return out;
|
|
47
|
+
}
|
package/dist/drivers/orca.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createWorktree, FORK_CAP_ENV, resolvedForkCap, sh } from "../run/git.js
|
|
|
7
7
|
import { Journal, parseRunId } from "../run/journal.js";
|
|
8
8
|
import { stateDirName } from "../graph/graph.js";
|
|
9
9
|
import { readWatchBoard, requestWatchBoardStop, stopWatchBoard, supervisionPresencePath, WATCH_OWNER_ENV } from "../run/supervision.js";
|
|
10
|
+
import { HOST_SESSION_VARS } from "./host-env.js";
|
|
10
11
|
import { MAX_BUF } from "./subprocess.js";
|
|
11
12
|
import { formatOwnedName, panesToClose, parseOwnedName } from "./types.js";
|
|
12
13
|
// Orca (onorca.dev) as a third execution surface beside herdr and subprocess. tickmarkr keeps
|
|
@@ -966,7 +967,8 @@ export class OrcaDriver {
|
|
|
966
967
|
// UI's active worktree (`active`/`current`) nor the daemon's cwd — and the command itself moves
|
|
967
968
|
// into the checkout, because Orca cannot be asked to place a terminal in a path it does not
|
|
968
969
|
// track (OBS-1004). Shell startup can swallow that prefix; prove it from scrollback below.
|
|
969
|
-
|
|
970
|
+
// Queue row 193: the fork cap stays first; the agent session's host variables never reach the worker.
|
|
971
|
+
const payload = `export ${FORK_CAP_ENV}=${shq(process.env[FORK_CAP_ENV] ?? resolvedForkCap())}; unset ${HOST_SESSION_VARS.join(" ")}; ${cmd}`;
|
|
970
972
|
launchGuards.getStore()?.(); // v2.6.8 T1: eligibility is re-read here, after every awaited lookup above
|
|
971
973
|
const env = await this.call("create", [
|
|
972
974
|
"terminal", "create", "--worktree", `path:${tracked}`, "--title", st.title, "--command", inCheckout(st.cwd, payload),
|
|
@@ -2,7 +2,7 @@ import type { ExecutorDriver, NotifyOpts, Slot, TransportState } from "./types.j
|
|
|
2
2
|
export declare const MAX_BUF: number;
|
|
3
3
|
export declare const HERDR_CONTROL_VARS: readonly ["HERDR_ENV", "HERDR_SOCKET_PATH", "ORCA_TERMINAL_HANDLE", "ORCA_PANE_KEY", "ORCA_TAB_ID"];
|
|
4
4
|
/**
|
|
5
|
-
* Copy of worker env with the fork cap applied and host control-plane vars stripped.
|
|
5
|
+
* Copy of worker env with the fork cap applied and host control-plane and host-session vars stripped.
|
|
6
6
|
* Workers retain the run's concurrency-derived cap (resolvedForkCap), independently of
|
|
7
7
|
* verification's suite-window budget. The operator's own export still wins.
|
|
8
8
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { shq } from "../adapters/types.js";
|
|
3
3
|
import { createWorktree, FORK_CAP_ENV, resolvedForkCap } from "../run/git.js";
|
|
4
|
+
import { HOST_SESSION_VARS, withoutHostSession } from "./host-env.js";
|
|
4
5
|
// HARD-03: cap retained worker output so a chatty worker can't grow tickmarkr's heap unbounded.
|
|
5
6
|
// 2MB is safe against BOTH consumers: the largest read() call site asks for 1000 lines
|
|
6
7
|
// (daemon.ts:217/247), and every marker (TICKMARKR_RESULT, TICKMARKR_EXIT_<nonce>) is emitted at the
|
|
@@ -21,12 +22,12 @@ export const HERDR_CONTROL_VARS = [
|
|
|
21
22
|
"ORCA_TAB_ID",
|
|
22
23
|
];
|
|
23
24
|
/**
|
|
24
|
-
* Copy of worker env with the fork cap applied and host control-plane vars stripped.
|
|
25
|
+
* Copy of worker env with the fork cap applied and host control-plane and host-session vars stripped.
|
|
25
26
|
* Workers retain the run's concurrency-derived cap (resolvedForkCap), independently of
|
|
26
27
|
* verification's suite-window budget. The operator's own export still wins.
|
|
27
28
|
*/
|
|
28
29
|
export function sealHerdrEnv(env = process.env) {
|
|
29
|
-
const out =
|
|
30
|
+
const out = withoutHostSession(env); // queue row 193
|
|
30
31
|
if (!(FORK_CAP_ENV in out))
|
|
31
32
|
out[FORK_CAP_ENV] = resolvedForkCap();
|
|
32
33
|
for (const k of HERDR_CONTROL_VARS)
|
|
@@ -37,7 +38,7 @@ export function sealHerdrEnv(env = process.env) {
|
|
|
37
38
|
export function herdrSealShellPrefix(env = process.env) {
|
|
38
39
|
const forkCap = sealHerdrEnv(env)[FORK_CAP_ENV] ?? resolvedForkCap();
|
|
39
40
|
return `export ${FORK_CAP_ENV}=${shq(forkCap)}; ` +
|
|
40
|
-
HERDR_CONTROL_VARS.map((k) => `unset ${k}`).join("; ") + "
|
|
41
|
+
HERDR_CONTROL_VARS.map((k) => `unset ${k}`).join("; ") + `; unset ${HOST_SESSION_VARS.join(" ")}; `;
|
|
41
42
|
}
|
|
42
43
|
export class SubprocessDriver {
|
|
43
44
|
id = "subprocess";
|
package/dist/gates/baseline.d.ts
CHANGED
|
@@ -60,7 +60,7 @@ export declare function beginGateEvidence(cwd: string, gate: string, command: st
|
|
|
60
60
|
subjectCommit: string | null;
|
|
61
61
|
};
|
|
62
62
|
termination: {
|
|
63
|
-
kind: "unknown" | "
|
|
63
|
+
kind: "unknown" | "signal" | "timeout" | "not-started" | "exit";
|
|
64
64
|
exitCode: number | null;
|
|
65
65
|
signal: string | null;
|
|
66
66
|
timedOut: boolean | null;
|
package/dist/gates/baseline.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
2
|
import { EVICTION_TOMBSTONE_LIMIT, EVICTION_TOMBSTONES_FILE, parseEvictionTombstones } from "../run/receipt-resolver.js";
|
|
4
3
|
import { existsSync, readFileSync, mkdirSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, rmdirSync } from "node:fs";
|
|
5
4
|
import { availableParallelism, loadavg, tmpdir } from "node:os";
|
|
6
5
|
import { basename, join } from "node:path";
|
|
7
6
|
import { DEFAULT_EVIDENCE_QUOTA_BYTES } from "../config/config.js";
|
|
8
|
-
import { dependencyLinkRefusal, DEFAULT_SHELL_TIMEOUT_MS, describeCapacity, sameCapacity, sh } from "../run/git.js";
|
|
7
|
+
import { dependencyLinkRefusal, gitSync, DEFAULT_SHELL_TIMEOUT_MS, describeCapacity, sameCapacity, sh } from "../run/git.js";
|
|
9
8
|
import { executionSignal } from "../run/execution-budget.js";
|
|
10
9
|
import { isVitestTestCommand, manifestChildEnv, manifestFileCount } from "./test-manifest.js";
|
|
11
10
|
export const EVIDENCE_TAIL_BYTES = 16 * 1024;
|
|
@@ -121,7 +120,9 @@ export function beginGateEvidence(cwd, gate, command, opts = {}, nonce) {
|
|
|
121
120
|
let subjectCommit = opts.subjectCommit ?? null;
|
|
122
121
|
if (!subjectCommit) {
|
|
123
122
|
try {
|
|
124
|
-
|
|
123
|
+
const result = gitSync(cwd, ["rev-parse", "HEAD"]);
|
|
124
|
+
if (result.status === 0 && result.stdout.trim())
|
|
125
|
+
subjectCommit = result.stdout.trim();
|
|
125
126
|
}
|
|
126
127
|
catch { /* unknown subject */ }
|
|
127
128
|
}
|