tickmarkr 1.89.0 → 1.90.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli/commands/doctor.d.ts +16 -0
  2. package/dist/cli/commands/doctor.js +52 -0
  3. package/dist/cli/commands/init.js +1 -0
  4. package/dist/cli/commands/verify.d.ts +9 -0
  5. package/dist/cli/commands/verify.js +177 -0
  6. package/dist/cli/index.d.ts +1 -1
  7. package/dist/cli/index.js +3 -1
  8. package/dist/compile/collateral.js +7 -7
  9. package/dist/compile/common.js +2 -2
  10. package/dist/compile/native.d.ts +15 -1
  11. package/dist/compile/native.js +209 -3
  12. package/dist/config/config.js +2 -2
  13. package/dist/gates/baseline.d.ts +39 -5
  14. package/dist/gates/baseline.js +67 -8
  15. package/dist/gates/scope.js +3 -3
  16. package/dist/graph/files-glob.d.ts +18 -0
  17. package/dist/graph/files-glob.js +22 -0
  18. package/dist/run/daemon.d.ts +2 -0
  19. package/dist/run/daemon.js +14 -7
  20. package/dist/run/git.d.ts +3 -0
  21. package/dist/run/git.js +6 -3
  22. package/dist/run/merge.d.ts +4 -1
  23. package/dist/run/merge.js +26 -7
  24. package/fixtures/authoring-lints/01-awk-range-self-pass.spec.md +12 -0
  25. package/fixtures/authoring-lints/02-judge-text-key-miss.spec.md +7 -0
  26. package/fixtures/authoring-lints/03-c1-t41-rendered-observable.spec.md +8 -0
  27. package/fixtures/authoring-lints/04-c1-t24-named-file.spec.md +8 -0
  28. package/fixtures/authoring-lints/05-c2-t24-t28-dep-inversion.spec.md +7 -0
  29. package/fixtures/authoring-lints/06-c2-denumbered-coupling.spec.md +7 -0
  30. package/fixtures/authoring-lints/07-c3a-t41-line-count-proxy.spec.md +7 -0
  31. package/fixtures/authoring-lints/08-c3b-t41-governance-referent.spec.md +7 -0
  32. package/fixtures/authoring-lints/09-c4-universals-without-pointer.spec.md +7 -0
  33. package/fixtures/authoring-lints/10-c5-t34-conjunct-flood.spec.md +7 -0
  34. package/fixtures/authoring-lints/11-c6-t34-q3-q9-q20-bundle.spec.md +7 -0
  35. package/fixtures/authoring-lints/12-c7-t24-prose-seam.spec.md +8 -0
  36. package/package.json +1 -1
  37. package/skills/tickmarkr-overseer/SKILL.md +5 -0
  38. package/skills/tickmarkr-overseer/scripts/watch-contamination.sh +4 -1
  39. package/skills/tickmarkr-overseer/scripts/watch-pending-input.sh +18 -0
@@ -1,4 +1,5 @@
1
1
  import type { TickmarkrConfig } from "../config/config.js";
2
+ import { type Baseline } from "../gates/baseline.js";
2
3
  export interface TipVerifyResult {
3
4
  gate: string;
4
5
  cmd: string;
@@ -7,6 +8,8 @@ export interface TipVerifyResult {
7
8
  fingerprints: string[];
8
9
  details: string;
9
10
  artifact?: string;
11
+ /** Q121s: nonzero exit whose failures are ALL baseline-recorded — forgiven exactly as the battery forgives. */
12
+ forgiven?: boolean;
10
13
  }
11
14
  export declare function integrationBranch(cfg: TickmarkrConfig, runId: string): string;
12
15
  export declare function ensureIntegration(repo: string, branch: string, baseRef: string): Promise<string>;
@@ -19,4 +22,4 @@ export declare function mergeTask(intWt: string, taskBranch: string, message: st
19
22
  branchTip: string;
20
23
  };
21
24
  }>;
22
- export declare function verifyIntegrationTip(intWt: string, commands: Record<string, string>, runDir: string): Promise<TipVerifyResult[]>;
25
+ export declare function verifyIntegrationTip(intWt: string, commands: Record<string, string>, runDir: string, baseline?: Baseline): Promise<TipVerifyResult[]>;
package/dist/run/merge.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { shq } from "../adapters/types.js";
4
- import { fingerprint } from "../gates/baseline.js";
4
+ import { classifyFailureOutput, fingerprint, freshFailures } from "../gates/baseline.js";
5
5
  import { tickmarkrDir } from "../graph/graph.js";
6
6
  import { gitHead, linkNodeModules, resolveIntegrationBranch, sh, shGit, shGitOk, WORKTREES_DIR } from "./git.js";
7
7
  export function integrationBranch(cfg, runId) {
@@ -44,22 +44,41 @@ export async function mergeTask(intWt, taskBranch, message, gatedCommit) {
44
44
  await shGit("git merge --abort", intWt);
45
45
  return { ok: false, conflict };
46
46
  }
47
- // OBS-34: strict exit-code verify on the integration tip — no baseline forgiveness.
48
- export async function verifyIntegrationTip(intWt, commands, runDir) {
47
+ // OBS-34 ruled strict exit-code verify; Q121s (TRIAL T-OBS-1) narrows it: a red whose failure
48
+ // fingerprints are ALL recorded in the run's baseline is forgiven with the battery's own math
49
+ // (freshFailures) — on any repo whose main carries a pre-existing red, strict verify made a green
50
+ // terminus unreachable by construction and misattributed the red to the last merged task.
51
+ // Fail-closed edges kept: no baseline → strict; baseline green for that gate → strict; any fresh
52
+ // fingerprint, or output with no recognizable failure shape, → failed.
53
+ export async function verifyIntegrationTip(intWt, commands, runDir, baseline) {
49
54
  const results = [];
50
55
  for (const [gate, cmd] of Object.entries(commands)) {
51
56
  const r = await sh(cmd, intWt);
52
57
  const raw = r.stdout + "\n" + r.stderr;
53
- const artifact = r.code !== 0 ? join(runDir, `tip-verify-${gate}.log`) : undefined;
58
+ const stripped = raw.split(intWt).join("");
59
+ const entry = baseline?.commands[gate];
60
+ const { failing, unreadable } = freshFailures(entry, stripped);
61
+ // `?? 1` is the battery's own default (baseline.ts compareToBaseline): an exitCode-less legacy
62
+ // entry reads as red-at-baseline there, so it must read the same here or old baselines silently
63
+ // lose forgiveness. Battery parity on the infra rule too (T9): infrastructure-only output means
64
+ // the runner never completed a suite — nothing was verified, so nothing is forgivable, however
65
+ // familiar its fingerprints. Stricter-than-battery edge kept: unreadable output never forgives.
66
+ const forgiven = r.code !== 0 && entry !== undefined && (entry.exitCode ?? 1) !== 0
67
+ && failing.length === 0 && !unreadable && classifyFailureOutput(stripped) !== "infra";
68
+ const pass = r.code === 0 || forgiven;
69
+ const artifact = pass ? undefined : join(runDir, `tip-verify-${gate}.log`);
54
70
  if (artifact)
55
71
  writeFileSync(artifact, raw);
56
72
  results.push({
57
73
  gate,
58
74
  cmd,
59
- pass: r.code === 0,
75
+ pass,
60
76
  exitCode: r.code,
61
- fingerprints: r.code !== 0 ? fingerprint(raw.split(intWt).join("")) : [],
62
- details: r.code === 0 ? "exit 0" : `exit ${r.code}`,
77
+ fingerprints: r.code !== 0 ? fingerprint(stripped) : [],
78
+ details: r.code === 0 ? "exit 0"
79
+ : forgiven ? `exit ${r.code} but only baseline-recorded failures (forgiven vs baseline)`
80
+ : `exit ${r.code}`,
81
+ ...(forgiven ? { forgiven: true } : {}),
63
82
  ...(artifact ? { artifact } : {}),
64
83
  });
65
84
  }
@@ -0,0 +1,12 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 Finding 0 awk-range self-pass certified only the task heading -->
3
+
4
+ ## T1: Clean leading task
5
+ - goal: Keep the first extracted task deliberately clean
6
+ - acceptance:
7
+ - judge: the first task remains ordinary canary data
8
+
9
+ ## T2: Finding after the range start
10
+ - goal: Consume T28 classification without declaring the dependency
11
+ - acceptance:
12
+ - judge: classification controls the conclusion
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 judge-text extractor read judge instead of the compiled text key -->
3
+
4
+ ## T1: Typed judge finding
5
+ - goal: Put the finding only in a typed judge item
6
+ - acceptance:
7
+ - judge: cite the audit before accepting the task
@@ -0,0 +1,8 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T41 amendment 10422761 repaired the stale 52/64 asserting-test catch-22 -->
3
+
4
+ ## T41: Render the normalized numerator
5
+ - goal: Change the stale demo gate numerator to its normalized value
6
+ - files: src/tui/cockpit/derive.ts, tests/compile/authoring-lints.test.ts
7
+ - acceptance:
8
+ - judge: the tracked demo renders `52/64` after skipped outcomes leave the pass numerator
@@ -0,0 +1,8 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T24 amendment 46e744bf repaired the vacuous-grace asserting-test catch-22 -->
3
+
4
+ ## T24: Replace the vacuous grace oracle
5
+ - goal: Replace the helper-only grace proof with the production daemon oracle
6
+ - files: src/run/daemon.ts, tests/compile/authoring-lints.test.ts
7
+ - acceptance:
8
+ - judge: `tests/run/worktree-evidence.test.ts` carries the production runDaemon differential
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T24-T28 inversion required T24 to build its undeclared successor machinery -->
3
+
4
+ ## T24: Inverted classification consumer
5
+ - goal: Use T28 flat CPU classification before that task can run
6
+ - acceptance:
7
+ - judge: the classification permits the liveness conclusion
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T26 de-numbered T28 coupling hid the same dependency inversion -->
3
+
4
+ ## T26: Coupling without a task token
5
+ - goal: Conclude only when whose independent flat CPU classification permits the result
6
+ - acceptance:
7
+ - judge: the later classification changes the conclusion
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T41 physical-line proxy pressured derive wiring into code golf -->
3
+
4
+ ## T41: Bound structure by a proxy
5
+ - goal: Wire the normalized outcome through the projection
6
+ - acceptance:
7
+ - judge: the physical line count is not greater than the base count
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T41 the-audit referent left asserting-test ownership outside the spec -->
3
+
4
+ ## T41: Delegate scope to governance prose
5
+ - goal: Own the projection surface
6
+ - acceptance:
7
+ - judge: include every asserting test exposed by the audit
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T26-T28 open universals omitted the closed enumeration pointer -->
3
+
4
+ ## T28: Open prohibition surface
5
+ - goal: Keep liveness evidence fail-open
6
+ - acceptance:
7
+ - judge: no pane may supply a liveness conclusion, and no capture can become authored evidence
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T34 criterion 3 flooded one title with independently failing conjuncts -->
3
+
4
+ ## T34: Many behaviors under one title
5
+ - goal: Make criterion failure attributable
6
+ - acceptance:
7
+ - judge: the driver records contact and preserves provenance while worktree evidence stays authoritative, then failed delivery remains distinct plus journal output stays stable with the same task status
@@ -0,0 +1,7 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T34 bundled Q3-Q9-Q20 and reran all three when its weakest concern failed -->
3
+
4
+ ## T34: Bundle three queue concerns
5
+ - goal: Subsume Q3, Q9 and Q20 in one implementation task
6
+ - acceptance:
7
+ - judge: the combined implementation satisfies its bundled concerns
@@ -0,0 +1,8 @@
1
+ <!-- tickmarkr:spec -->
2
+ <!-- provenance: v1.89 T24 production-driver prose seam named no existing exported boundary -->
3
+
4
+ ## T24: Govern the daemon through prose
5
+ - goal: No pane text may supply liveness; enforce the rule through the production driver seam
6
+ - files: src/run/daemon.ts
7
+ - acceptance:
8
+ - judge: the daemon conclusion follows only independent evidence
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.89.0",
3
+ "version": "1.90.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -228,6 +228,11 @@ number — an unmeasured budget is not a small budget.
228
228
  sent` reports a delivery that never happened. Never chain `wait && act` or trust a send's exit status —
229
229
  confirm the pane exists and read it back. An orchestrator's pane can vanish mid-mission without any event
230
230
  reaching you; the first symptom is a dispatch into nothing.
231
+ - **Renaming a live agent kills every watcher keyed on the old NAME.** herdr resolves names live,
232
+ so after `herdr agent rename` the old name stops existing and any `agent wait <old-name>` or
233
+ name-keyed poll script exits with `agent_not_running` — an exit shaped exactly like a real wake.
234
+ Re-arm name-keyed watchers in the same act as the rename; file-keyed artifact watchers are
235
+ unaffected (one more reason to prefer them).
231
236
  - Stale typed input is unclearable via CLI — supersede it:
232
237
  `pane run "<-- disregard everything before this arrow (stale draft). ACTUAL: <message>"`.
233
238
 
@@ -29,7 +29,10 @@ STATE="$(dirname "$J")/.contamination.seen"
29
29
  INFRA_RE='vitest-worker|Timeout calling|JS heap out of memory|ENOMEM|EAGAIN|spawn ENOENT|Killed|SIGKILL'
30
30
 
31
31
  load1() { uptime | sed 's/.*load averages*: *//' | awk '{print $1}' | tr -d ','; }
32
- vitest_n() { pgrep -f vitest 2>/dev/null | wc -l | tr -d ' '; }
32
+ # Count real vitest runners only. `pgrep -f vitest` over-counts by an order: it matches the WORD
33
+ # in any argv — worker prompts, VITEST_MAX_FORKS in shell strings (measured 7 vs 2 real, 2026-08-10).
34
+ # [v] keeps the pattern from matching its own grep. Forked workers retitle to "node (vitest N)" (comm).
35
+ vitest_n() { ps -axo comm,command 2>/dev/null | grep -Ec 'node_modules/(\.bin/)?[v]itest|[v]itest/dist/|\([v]itest [0-9]+\)'; }
33
36
 
34
37
  # Seed the seen-set so we wake on what happens NEXT, not on history already ruled on.
35
38
  if [ -f "$J" ]; then
@@ -29,6 +29,16 @@ status_of() {
29
29
  # The rendered prompt line is the condition itself. `agent_status` is the harness's OPINION about the
30
30
  # seat and fails in both directions (a wedged worker has reported `idle`, a working one `done`), so the
31
31
  # text in the box is the stronger signal — it is what will not run.
32
+ # GHOST DISCRIMINATOR (OBS-482 addendum 34): claude-code AUTOSUGGEST renders its suggestion on the
33
+ # prompt line wrapped in SGR dim — sampled live 2026-08-11: `❯ \e[0m\e[2mcheck T76 progress\e[0m`.
34
+ # Typed text renders default. A dim-wrapped pending line is a GHOST (rendering, not state): skip it —
35
+ # no wake, no streak. Negative scope: sampled on one theme/version; a theme rendering typed input dim
36
+ # would blind this check TOWARD silence, so a wake is still authoritative but its absence is not proof.
37
+ is_ghost() {
38
+ herdr agent read "$TARGET" --source visible --lines 14 --format ansi 2>/dev/null \
39
+ | grep -F -- "❯" | grep -F -- "$1" | head -1 | grep -q "$(printf '\033')\[2m"
40
+ }
41
+
32
42
  pending_text() {
33
43
  herdr agent read "$TARGET" --source visible --lines 14 2>/dev/null \
34
44
  | sed -n 's/^[[:space:]]*❯[[:space:]]*//p' | head -1 \
@@ -126,6 +136,7 @@ while [ "$elapsed" -lt "$CAP" ]; do
126
136
  case "$S" in
127
137
  idle|done)
128
138
  T=$(pending_text)
139
+ if [ -n "$T" ] && is_ghost "$T"; then T=""; fi
129
140
  if [ -n "$T" ]; then
130
141
  streak=$((streak + 1))
131
142
  if [ "$streak" -ge "$CONFIRM" ]; then
@@ -159,6 +170,13 @@ while [ "$elapsed" -lt "$CAP" ]; do
159
170
  fi
160
171
  echo "PENDING_INPUT $TARGET status=$S sustained=$((streak * POLL))s"
161
172
  echo " unsubmitted: $T"
173
+ # GHOST WARNING (OBS-482 addendum 33): claude-code AUTOSUGGEST renders context-plausible
174
+ # ghost text on this same prompt line, and in text-format reads it is BYTE-IDENTICAL to a
175
+ # typed draft. Ghost text renders dim/grey; typed text renders default. Emit the styled
176
+ # bytes so the reader can discriminate before treating this as a draft.
177
+ echo " styling evidence (dim/grey SGR around the text = autosuggest ghost, NOT a draft):"
178
+ herdr agent read "$TARGET" --source visible --lines 14 --format ansi 2>/dev/null \
179
+ | grep -F -- "$T" | head -2 | cat -v | sed 's/^/ /'
162
180
  echo " the seat is idle and holding live work in its prompt — supersede it, do not re-send:"
163
181
  echo " herdr agent prompt $TARGET \" <-- disregard everything before this arrow (stale draft). ACTUAL: …\""
164
182
  # Name the ACTUAL reason we fell through. Saying "budget exhausted" when the cause was the