tickmarkr 1.68.0 → 1.69.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 (36) hide show
  1. package/dist/adapters/kimi.d.ts +25 -1
  2. package/dist/adapters/kimi.js +80 -0
  3. package/dist/adapters/types.d.ts +6 -0
  4. package/dist/cli/commands/eval.d.ts +4 -0
  5. package/dist/cli/commands/eval.js +26 -0
  6. package/dist/cli/index.d.ts +1 -1
  7. package/dist/cli/index.js +3 -1
  8. package/dist/eval/canary.d.ts +46 -0
  9. package/dist/eval/canary.js +113 -0
  10. package/dist/eval/dispatch.d.ts +31 -0
  11. package/dist/eval/dispatch.js +207 -0
  12. package/dist/eval/fixtures.d.ts +22 -0
  13. package/dist/eval/fixtures.js +85 -0
  14. package/dist/eval/report.d.ts +35 -0
  15. package/dist/eval/report.js +82 -0
  16. package/dist/eval/selfcheck.d.ts +22 -0
  17. package/dist/eval/selfcheck.js +177 -0
  18. package/dist/run/daemon.js +125 -102
  19. package/dist/run/interactive-seed.d.ts +15 -0
  20. package/dist/run/interactive-seed.js +25 -0
  21. package/fixtures/eval/canary/solution/a.txt +1 -0
  22. package/fixtures/eval/canary/spec.md +8 -0
  23. package/fixtures/eval/canary/start/a.txt +1 -0
  24. package/fixtures/eval/sample/solution/a.txt +1 -0
  25. package/fixtures/eval/sample/spec.md +8 -0
  26. package/fixtures/eval/sample/start/a.txt +1 -0
  27. package/fixtures/gsd-sample/07-live-check/07-01-PLAN.md +42 -0
  28. package/fixtures/gsd-sample/07-live-check/07-02-PLAN.md +21 -0
  29. package/fixtures/gsd-sample/07-live-check/07-03-PLAN.md +18 -0
  30. package/fixtures/gsd-sample/07-live-check/07-03-SUMMARY.md +1 -0
  31. package/fixtures/missing-mandatory-gate.native.md +10 -0
  32. package/fixtures/sample-pin.prd.md +18 -0
  33. package/fixtures/sample.native.md +35 -0
  34. package/fixtures/sample.prd.md +22 -0
  35. package/fixtures/speckit-sample/tasks.md +20 -0
  36. package/package.json +3 -2
@@ -16,6 +16,7 @@ import { runGates } from "../gates/run-gates.js";
16
16
  import { addEvidence, attributeBlocked, blockedTasks, getTask, graphDefinitionHash, loadGraph, pendingTasks, readyTasks, saveGraph, setStatus } from "../graph/graph.js";
17
17
  import { augmentRetryBrief, consult, renderRetryGuidance } from "./consult.js";
18
18
  import { cleanupRunWorktrees, gitHead, linkNodeModules, sh, shGit, WORKTREE_LAYOUT_CONTRACT, worktreePath } from "./git.js";
19
+ import { runInteractiveSeed } from "./interactive-seed.js";
19
20
  import { classifyWorkerResultCause, engagementComparable, Journal, loadRoutingProfile, newRunId } from "./journal.js";
20
21
  import { acquireRunLock, releaseRunLock } from "./lock.js";
21
22
  import { ensureIntegration, integrationBranch, integrationHead, mergeTask, verifyIntegrationTip } from "./merge.js";
@@ -601,17 +602,22 @@ export async function runDaemon(repoRoot, opts = {}) {
601
602
  : cfg.visibility.worker === "interactive" && driver.interactive
602
603
  ? adapter.interactiveCommand(promptFile, assignment.model)
603
604
  : null;
604
- if (cfg.visibility.worker === "interactive" && icmd === null && !modeFallbackNoted) {
605
+ // v1.69 T6: adapters that declare interactiveSeed launch the real TUI and inject the prompt as a
606
+ // user turn; they do NOT need the argv-seeding surface that interactiveCommand represents.
607
+ const hasSeed = retryMode !== "resume" && cfg.visibility.worker === "interactive" && driver.interactive && !!adapter.interactiveSeed;
608
+ if (cfg.visibility.worker === "interactive" && icmd === null && !hasSeed && !modeFallbackNoted) {
605
609
  modeFallbackNoted = true;
606
610
  journal.append("worker-mode-fallback", t.id, { reason: driver.interactive ? "adapter" : "driver" });
607
611
  }
608
- const interactive = icmd !== null;
612
+ const interactive = icmd !== null || hasSeed;
609
613
  // OBS-85 (v1.62 T1): both dispatch branches deliver ONE short script invocation — banner,
610
614
  // adapter command, and nonce exit marker live in a per-attempt script beside the prompt
611
615
  // artifact (the same paneDispatchCommand pattern judge/review/consult dispatches use). The
612
616
  // delivered pane line carries no command substitution and no trailing shell text, so paste
613
617
  // timing can never interleave a `$(…)` with what follows it (the codex corruption class).
614
- const workerCmd = interactive ? icmd : adapter.invoke(t, wt, assignment, { promptFile }).command;
618
+ const workerCmd = interactive
619
+ ? (hasSeed ? ":" : icmd)
620
+ : adapter.invoke(t, wt, assignment, { promptFile }).command;
615
621
  const dispatchScript = promptFile.replace(/\.md$/, ".sh");
616
622
  writeFileSync(dispatchScript, [
617
623
  "export BASH_SILENCE_DEPRECATION_WARNING=1",
@@ -659,117 +665,134 @@ export async function runDaemon(repoRoot, opts = {}) {
659
665
  let exitCode;
660
666
  let timedOut = false;
661
667
  let settleParsed;
668
+ let seedResult;
662
669
  if (interactive) {
663
670
  // v1.2 interactive: the TUI doesn't exit on completion — the trailer is the finish line.
664
671
  // The exit wrapper still fires if the TUI dies (crash/quit): fast-fail instead of burning the timeout.
665
- await driver.run(slot, paneDispatchCommand(dispatchScript));
666
- let paged = false;
667
- // v1.22 T5 / OBS-19: auto-answer a fingerprint-matched trust dialog exactly once per slot.
668
- // Any other blocked/idle dialog still pages the operator (paged latch below).
669
- let trustAnswered = false;
670
672
  finished = false;
671
673
  exitCode = null;
672
- output = await driver.read(slot, 1000);
673
- // OBS-54: reaping keys on new pane output, not dispatch wall clock. Poll at least twice per
674
- // stall window (and at the existing 30s cadence for normal windows) so an active worker resets it.
675
- const stallWindowMs = taskTimeoutMinutes * 60_000;
676
- // OBS-82: the stall clock compares NORMALIZED snapshots so a spinner glyph/elapsed-time
677
- // repaint is silence, not activity. ONLY this inactivity compare sees normalized text —
678
- // trailer detection, harvest, paging, and quota checks all read the raw pane.
679
- let lastStallSnapshot = normalizeStallSnapshot(output);
680
- let lastOutputAt = Date.now();
681
- while (Date.now() - lastOutputAt < stallWindowMs) {
682
- const sliceStart = Date.now();
683
- const remaining = stallWindowMs - (sliceStart - lastOutputAt);
684
- const slice = Math.min(BLOCKED_POLL_MS, Math.max(100, Math.min(stallWindowMs / 2, remaining)));
685
- if (await driver.waitOutput(slot, `(${trailerPattern(nonce)})|TICKMARKR_EXIT_${nonce}:\\d`, slice, { regex: true })) {
686
- // verify before accepting: a worker that merely DISPLAYS a marker (e.g. editing tickmarkr's
687
- // own source, where "TICKMARKR_EXIT:" is a string literal) must not end the wait. Only a
688
- // parseable trailer or a digit-suffixed exit marker in the harvest is completion.
689
- output = await driver.read(slot, 1000); // TUI transcripts carry chrome — read deeper than print's 500
690
- finished = new RegExp(trailerPattern(nonce)).test(output);
691
- const exit = exitRe.exec(output);
692
- if (finished || exit) {
693
- exitCode = exit ? Number(exit[1]) : null; // null ⇔ the TUI is still alive
694
- await sampleContext(); // final poll-seam sample before leaving the wait
695
- break;
696
- }
697
- }
698
- const currentStallSnapshot = normalizeStallSnapshot(await driver.read(slot, 1000));
699
- if (currentStallSnapshot !== lastStallSnapshot) {
700
- lastStallSnapshot = currentStallSnapshot;
701
- lastOutputAt = Date.now();
702
- }
703
- // v1.23 T2: piggyback on this poll slice — same cadence as blocked/idle checks, no new timer.
704
- await sampleContext();
705
- // page on "idle" too: herdr's blocked-scrape is strict and proved flaky for TUI dialogs
706
- // (live check: cursor's trust dialog scraped as idle). "unknown" never pages — that's just
707
- // a pane the scraper can't read (subprocess, dead pane); the task timeout covers those.
708
- const st = paged ? "" : await driver.status(slot);
709
- if (!paged && (st === "blocked" || st === "idle")) {
710
- // T5: once-per-slot auto-answer when the adapter declares a trust dialog and the pane
711
- // text matches. tickmarkr created the worktree from the operator's own repo — safe by construction.
712
- if (!trustAnswered && adapter.trustDialog && driver.sendKey) {
713
- try {
714
- const paneText = await driver.read(slot, 80);
715
- if (matchesTrustDialog(paneText, adapter.trustDialog)) {
716
- trustAnswered = true;
717
- // v1.25 T1: audit trail for live runs — prove the dialog appeared and was answered.
718
- // Latch + sendKey + no-page continue stay byte-identical; this append is additive only.
719
- journal.append("trust-auto-answer", t.id, { slot: slot.name, adapter: adapter.id });
720
- await driver.sendKey(slot, adapter.trustDialog.key);
721
- const spent = Date.now() - sliceStart;
722
- if (spent < slice)
723
- await new Promise((r) => setTimeout(r, Math.min(slice - spent, 1_000)));
724
- continue; // do not page — keep waiting for the trailer
725
- }
726
- }
727
- catch {
728
- /* read/send failed — fall through to page the operator */
729
- }
730
- }
731
- paged = true; // page once — the visible pane is the operator's to unblock; task timeout is the backstop
732
- const why = st === "blocked" ? "is blocked on a prompt — approve in its pane" : "looks idle without finishing — check its pane";
733
- await driver.notify(`tickmarkr ${runId}: ${slot.name} ${why}`, { tier: "attention" });
734
- }
735
- // a dead pane or a false-positive marker display returns fast — sleep the unspent slice, never hot-spin
736
- const spent = Date.now() - sliceStart;
737
- if (spent < slice)
738
- await new Promise((r) => setTimeout(r, Math.min(slice - spent, 1_000)));
674
+ if (adapter.interactiveSeed) {
675
+ // v1.69 T6: launch the real TUI without a prompt, wait for readiness, inject one seed turn,
676
+ // then fall through to the normal trailer harvest. A failed seed is recorded as a finished
677
+ // failure rather than allowed to race the trailer wait.
678
+ seedResult = await runInteractiveSeed({ driver, slot, adapter, assignment, promptFile, taskTimeoutMinutes });
679
+ output = seedResult.output;
739
680
  }
740
- if (!finished && exitCode === null) {
741
- // timed out (or only ever saw false positives): harvest whatever the pane holds now
742
- timedOut = Date.now() - lastOutputAt >= stallWindowMs;
681
+ else {
682
+ await driver.run(slot, paneDispatchCommand(dispatchScript));
743
683
  output = await driver.read(slot, 1000);
744
- finished = new RegExp(trailerPattern(nonce)).test(output);
745
- const exit = exitRe.exec(output);
746
- exitCode = exit ? Number(exit[1]) : null;
747
684
  }
748
- if (finished) {
749
- await driver.waitAgentStatus(slot, "idle", 5_000); // settle, then re-harvest the final render
750
- output = await driver.read(slot, 1000);
685
+ if (seedResult?.seedFailed) {
686
+ finished = false;
751
687
  }
752
- // T5 / OBS-111: an interactive harvest can race the TUI's final paint. When the pane
753
- // contains the nonce token but the JSON hasn't balanced yet, settle and re-read through
754
- // the existing pane-read seam once or twice before recording a malformed-trailer cause.
755
- if (interactive) {
688
+ else {
689
+ let paged = false;
690
+ // v1.22 T5 / OBS-19: auto-answer a fingerprint-matched trust dialog exactly once per slot.
691
+ // Any other blocked/idle dialog still pages the operator (paged latch below).
692
+ let trustAnswered = false;
693
+ finished = false;
694
+ exitCode = null;
695
+ // OBS-54: reaping keys on new pane output, not dispatch wall clock. Poll at least twice per
696
+ // stall window (and at the existing 30s cadence for normal windows) so an active worker resets it.
756
697
  const stallWindowMs = taskTimeoutMinutes * 60_000;
757
- const settleDeadline = attemptStart + stallWindowMs;
758
- const settleDelayMs = 1_000;
759
- const maxSettleRetries = 2;
760
- let settleTries = 0;
761
- settleParsed = adapter.parse(output, nonce);
762
- while (settleParsed.summary === UNPARSEABLE_TRAILER_SUMMARY && settleTries < maxSettleRetries) {
763
- const remaining = settleDeadline - Date.now();
764
- if (remaining <= 0)
765
- break;
766
- await new Promise((r) => setTimeout(r, Math.min(settleDelayMs, remaining)));
698
+ // OBS-82: the stall clock compares NORMALIZED snapshots so a spinner glyph/elapsed-time
699
+ // repaint is silence, not activity. ONLY this inactivity compare sees normalized text —
700
+ // trailer detection, harvest, paging, and quota checks all read the raw pane.
701
+ let lastStallSnapshot = normalizeStallSnapshot(output);
702
+ let lastOutputAt = Date.now();
703
+ while (Date.now() - lastOutputAt < stallWindowMs) {
704
+ const sliceStart = Date.now();
705
+ const remaining = stallWindowMs - (sliceStart - lastOutputAt);
706
+ const slice = Math.min(BLOCKED_POLL_MS, Math.max(100, Math.min(stallWindowMs / 2, remaining)));
707
+ if (await driver.waitOutput(slot, `(${trailerPattern(nonce)})|TICKMARKR_EXIT_${nonce}:\\d`, slice, { regex: true })) {
708
+ // verify before accepting: a worker that merely DISPLAYS a marker (e.g. editing tickmarkr's
709
+ // own source, where "TICKMARKR_EXIT:" is a string literal) must not end the wait. Only a
710
+ // parseable trailer or a digit-suffixed exit marker in the harvest is completion.
711
+ output = await driver.read(slot, 1000); // TUI transcripts carry chrome — read deeper than print's 500
712
+ finished = new RegExp(trailerPattern(nonce)).test(output);
713
+ const exit = exitRe.exec(output);
714
+ if (finished || exit) {
715
+ exitCode = exit ? Number(exit[1]) : null; // null ⇔ the TUI is still alive
716
+ await sampleContext(); // final poll-seam sample before leaving the wait
717
+ break;
718
+ }
719
+ }
720
+ const currentStallSnapshot = normalizeStallSnapshot(await driver.read(slot, 1000));
721
+ if (currentStallSnapshot !== lastStallSnapshot) {
722
+ lastStallSnapshot = currentStallSnapshot;
723
+ lastOutputAt = Date.now();
724
+ }
725
+ // v1.23 T2: piggyback on this poll slice — same cadence as blocked/idle checks, no new timer.
726
+ await sampleContext();
727
+ // page on "idle" too: herdr's blocked-scrape is strict and proved flaky for TUI dialogs
728
+ // (live check: cursor's trust dialog scraped as idle). "unknown" never pages — that's just
729
+ // a pane the scraper can't read (subprocess, dead pane); the task timeout covers those.
730
+ const st = paged ? "" : await driver.status(slot);
731
+ if (!paged && (st === "blocked" || st === "idle")) {
732
+ // T5: once-per-slot auto-answer when the adapter declares a trust dialog and the pane
733
+ // text matches. tickmarkr created the worktree from the operator's own repo — safe by construction.
734
+ if (!trustAnswered && adapter.trustDialog && driver.sendKey) {
735
+ try {
736
+ const paneText = await driver.read(slot, 80);
737
+ if (matchesTrustDialog(paneText, adapter.trustDialog)) {
738
+ trustAnswered = true;
739
+ // v1.25 T1: audit trail for live runs — prove the dialog appeared and was answered.
740
+ // Latch + sendKey + no-page continue stay byte-identical; this append is additive only.
741
+ journal.append("trust-auto-answer", t.id, { slot: slot.name, adapter: adapter.id });
742
+ await driver.sendKey(slot, adapter.trustDialog.key);
743
+ const spent = Date.now() - sliceStart;
744
+ if (spent < slice)
745
+ await new Promise((r) => setTimeout(r, Math.min(slice - spent, 1_000)));
746
+ continue; // do not page — keep waiting for the trailer
747
+ }
748
+ }
749
+ catch {
750
+ /* read/send failed — fall through to page the operator */
751
+ }
752
+ }
753
+ paged = true; // page once — the visible pane is the operator's to unblock; task timeout is the backstop
754
+ const why = st === "blocked" ? "is blocked on a prompt — approve in its pane" : "looks idle without finishing — check its pane";
755
+ await driver.notify(`tickmarkr ${runId}: ${slot.name} ${why}`, { tier: "attention" });
756
+ }
757
+ // a dead pane or a false-positive marker display returns fast — sleep the unspent slice, never hot-spin
758
+ const spent = Date.now() - sliceStart;
759
+ if (spent < slice)
760
+ await new Promise((r) => setTimeout(r, Math.min(slice - spent, 1_000)));
761
+ }
762
+ if (!finished && exitCode === null) {
763
+ // timed out (or only ever saw false positives): harvest whatever the pane holds now
764
+ timedOut = Date.now() - lastOutputAt >= stallWindowMs;
767
765
  output = await driver.read(slot, 1000);
768
- settleParsed = adapter.parse(output, nonce);
769
- settleTries++;
766
+ finished = new RegExp(trailerPattern(nonce)).test(output);
767
+ const exit = exitRe.exec(output);
768
+ exitCode = exit ? Number(exit[1]) : null;
770
769
  }
771
- if (settleParsed.summary !== UNPARSEABLE_TRAILER_SUMMARY) {
772
- finished = settleParsed.summary !== NO_TRAILER_SUMMARY;
770
+ if (finished) {
771
+ await driver.waitAgentStatus(slot, "idle", 5_000); // settle, then re-harvest the final render
772
+ output = await driver.read(slot, 1000);
773
+ }
774
+ // T5 / OBS-111: an interactive harvest can race the TUI's final paint. When the pane
775
+ // contains the nonce token but the JSON hasn't balanced yet, settle and re-read through
776
+ // the existing pane-read seam once or twice before recording a malformed-trailer cause.
777
+ if (interactive) {
778
+ const stallWindowMs = taskTimeoutMinutes * 60_000;
779
+ const settleDeadline = attemptStart + stallWindowMs;
780
+ const settleDelayMs = 1_000;
781
+ const maxSettleRetries = 2;
782
+ let settleTries = 0;
783
+ settleParsed = adapter.parse(output, nonce);
784
+ while (settleParsed.summary === UNPARSEABLE_TRAILER_SUMMARY && settleTries < maxSettleRetries) {
785
+ const remaining = settleDeadline - Date.now();
786
+ if (remaining <= 0)
787
+ break;
788
+ await new Promise((r) => setTimeout(r, Math.min(settleDelayMs, remaining)));
789
+ output = await driver.read(slot, 1000);
790
+ settleParsed = adapter.parse(output, nonce);
791
+ settleTries++;
792
+ }
793
+ if (settleParsed.summary !== UNPARSEABLE_TRAILER_SUMMARY) {
794
+ finished = settleParsed.summary !== NO_TRAILER_SUMMARY;
795
+ }
773
796
  }
774
797
  }
775
798
  }
@@ -0,0 +1,15 @@
1
+ import type { Assignment, WorkerAdapter } from "../adapters/types.js";
2
+ import type { ExecutorDriver, Slot } from "../drivers/types.js";
3
+ export interface InteractiveSeedResult {
4
+ output: string;
5
+ seedFailed: boolean;
6
+ seedError?: string;
7
+ }
8
+ export declare function runInteractiveSeed(opts: {
9
+ driver: Pick<ExecutorDriver, "run" | "waitOutput" | "read">;
10
+ slot: Slot;
11
+ adapter: WorkerAdapter;
12
+ assignment: Assignment;
13
+ promptFile: string;
14
+ taskTimeoutMinutes: number;
15
+ }): Promise<InteractiveSeedResult>;
@@ -0,0 +1,25 @@
1
+ // v1.69 T6: launch-then-seed handoff for adapters whose real TUI cannot be argv-seeded.
2
+ // Both the launch command and the seed line are delivered through the driver's existing `run`
3
+ // primitive (pane-run on herdr). After the seed line is injected we read the pane back and
4
+ // treat a seed that is still sitting in the input box as a hard failure (OBS-105 discipline).
5
+ export async function runInteractiveSeed(opts) {
6
+ const seed = opts.adapter.interactiveSeed;
7
+ await opts.driver.run(opts.slot, seed.launch(opts.assignment.model));
8
+ const ready = await opts.driver.waitOutput(opts.slot, seed.readinessMatch, opts.taskTimeoutMinutes * 60_000);
9
+ if (!ready) {
10
+ const output = await opts.driver.read(opts.slot, 1000);
11
+ return { output, seedFailed: true, seedError: `readiness pattern not seen: ${seed.readinessMatch}` };
12
+ }
13
+ const seedText = seed.seedLine(opts.promptFile);
14
+ await opts.driver.run(opts.slot, seedText);
15
+ let output = "";
16
+ for (let attempt = 0; attempt < 5; attempt++) {
17
+ await new Promise((r) => setTimeout(r, 200));
18
+ output = await opts.driver.read(opts.slot, 1000);
19
+ const bottom = output.trimEnd().split("\n").pop() ?? "";
20
+ if (!bottom.includes(seedText)) {
21
+ return { output, seedFailed: false };
22
+ }
23
+ }
24
+ return { output, seedFailed: true, seedError: "seed line never left the input box" };
25
+ }
@@ -0,0 +1 @@
1
+ canary-verified
@@ -0,0 +1,8 @@
1
+ <!-- tickmarkr:spec -->
2
+ # Judge canary
3
+
4
+ ## T1: Canary verification marker
5
+ - goal: The repository contains a verified canary marker
6
+ - shape: implement
7
+ - acceptance:
8
+ - judge: The file a.txt contains the exact text "canary-verified".
@@ -0,0 +1 @@
1
+ canary-start
@@ -0,0 +1 @@
1
+ hello from solution
@@ -0,0 +1,8 @@
1
+ <!-- tickmarkr:spec -->
2
+ # Eval fixture sample
3
+
4
+ ## T1: Solution text is present
5
+ - goal: The fixture reference content is produced
6
+ - shape: implement
7
+ - acceptance:
8
+ - command: [ "$(cat a.txt)" = "hello from solution" ]
@@ -0,0 +1 @@
1
+ hello from start
@@ -0,0 +1,42 @@
1
+ ---
2
+ phase: 07-live-check
3
+ plan: "01"
4
+ type: execute
5
+ wave: 1
6
+ depends_on: []
7
+ files_modified:
8
+ - src/**
9
+ autonomous: true
10
+ must_haves:
11
+ truths:
12
+ - "truth A holds in the shipped artifact"
13
+ artifacts:
14
+ - path: "src/out.js"
15
+ provides: "the thing"
16
+ ---
17
+
18
+ <objective>
19
+ Implement the first objective sentence. Additional prose that is not the title.
20
+ </objective>
21
+
22
+ <context>
23
+ @.planning/PROJECT.md
24
+ @$HOME/.claude/get-shit-done/workflows/execute-plan.md
25
+ @~/somewhere/outside.md
26
+ </context>
27
+
28
+ <tasks>
29
+
30
+ <task type="auto">
31
+ <name>Task 1: Build the widget</name>
32
+ <action>do the thing</action>
33
+ <verify>look at it</verify>
34
+ <done>widget builds green</done>
35
+ </task>
36
+
37
+ <task type="auto">
38
+ <name>Task 2: Wire the widget</name>
39
+ <done>widget is wired and demoed</done>
40
+ </task>
41
+
42
+ </tasks>
@@ -0,0 +1,21 @@
1
+ ---
2
+ phase: 07-live-check
3
+ plan: "02"
4
+ type: execute
5
+ wave: 2
6
+ depends_on: ["07-01"]
7
+ files_modified:
8
+ - docs/**
9
+ autonomous: true
10
+ ---
11
+
12
+ <objective>
13
+ Document the widget. Trailing sentence.
14
+ </objective>
15
+
16
+ <tasks>
17
+ <task type="checkpoint:human-action" gate="blocking">
18
+ <name>Task 1: Operator reviews the docs</name>
19
+ <done>operator approved the docs page</done>
20
+ </task>
21
+ </tasks>
@@ -0,0 +1,18 @@
1
+ ---
2
+ phase: 07-live-check
3
+ plan: "03"
4
+ type: execute
5
+ depends_on: ["07-02"]
6
+ autonomous: true
7
+ ---
8
+
9
+ <objective>
10
+ Already-finished work. Done earlier.
11
+ </objective>
12
+
13
+ <tasks>
14
+ <task type="auto">
15
+ <name>Task 1: old work</name>
16
+ <done>previously completed</done>
17
+ </task>
18
+ </tasks>
@@ -0,0 +1 @@
1
+ # done earlier
@@ -0,0 +1,10 @@
1
+ <!-- tickmarkr:spec -->
2
+
3
+ ## T1: Missing mandatory build gate
4
+ - gates:
5
+ - test
6
+ - lint
7
+ - evidence
8
+ - scope
9
+ - acceptance:
10
+ - compilation fails loudly naming build
@@ -0,0 +1,18 @@
1
+ # Sample PRD: pinned delivery
2
+
3
+ ## T1: Implement pinned compiler work
4
+ - shape: implement
5
+ - complexity: 7
6
+ - files: src/compiler.ts, src/types.ts
7
+ - pin: claude-code sonnet
8
+ - acceptance:
9
+ - compiler returns a graph
10
+
11
+ ## T2: Verify pinned compiler work
12
+ - shape: tests
13
+ - deps: T1
14
+ - files: tests/compiler.test.ts
15
+ - humanGate: true
16
+ - acceptance:
17
+ - tests reject malformed input
18
+ - reviewer approves the output
@@ -0,0 +1,35 @@
1
+ <!-- drovr:spec -->
2
+ # Native spec: compiler delivery
3
+
4
+ ## T1: Build the native compiler
5
+ - goal: Compile the complete native task surface
6
+ - shape: implement
7
+ - deps: none
8
+ - files: src/compile/native.ts, src/compile/index.ts
9
+ - context: docs/native.md, src/graph/schema.ts
10
+ - complexity: 8
11
+ - humanGate: true
12
+ - pin: claude-code opus
13
+ - floor: frontier
14
+ - gates:
15
+ - build
16
+ - test
17
+ - lint
18
+ - evidence
19
+ - scope
20
+ - acceptance
21
+ - acceptance:
22
+ - every native field reaches the graph
23
+ - malformed fields fail loudly
24
+
25
+ ## T2: Test native detection
26
+ - goal: Keep native and generic markdown routing distinct
27
+ - shape: tests
28
+ - deps: T1
29
+ - files: tests/compile/native.test.ts
30
+ - context: fixtures/sample.native.md
31
+ - complexity: 3
32
+ - humanGate: false
33
+ - acceptance:
34
+ - marked markdown selects native
35
+ - marker-less markdown stays PRD
@@ -0,0 +1,22 @@
1
+ # Sample PRD: tiny greeter
2
+
3
+ ## T1: Implement greet(name) in src/greet.js
4
+ - shape: implement
5
+ - complexity: 3
6
+ - files: src/**
7
+ - acceptance:
8
+ - greet("drover") returns a string containing "drover"
9
+ - module exports a single function
10
+
11
+ ## T2: Cover greet with tests
12
+ - deps: T1
13
+ - files: test/**
14
+ - acceptance:
15
+ - tests fail when greet drops the name
16
+ - npm test exits 0
17
+
18
+ ## T3: Document usage in README
19
+ - deps: T1
20
+ - humanGate: true
21
+ - acceptance:
22
+ - README shows an executable example
@@ -0,0 +1,20 @@
1
+ # Tasks: Sample Feature
2
+
3
+ ## Phase 1: Setup
4
+ - [ ] T001 Create project structure in src/
5
+ - acceptance: src/ and tests/ directories exist with entry points
6
+ - files: src/**, tests/**
7
+ - shape: chore
8
+
9
+ ## Phase 2: Core
10
+ - [ ] T002 [P] Implement token refresh in src/auth/refresh.ts
11
+ - acceptance: expired token triggers exactly one refresh attempt
12
+ - acceptance: refresh failure surfaces a typed error
13
+ - files: src/auth/**
14
+ - complexity: 7
15
+ - [ ] T003 [P] Implement session store in src/auth/store.ts
16
+ - acceptance: sessions persist across restarts
17
+ - files: src/auth/**
18
+ - [ ] T004 Write integration tests for auth flow
19
+ - acceptance: happy path and refresh-failure path covered
20
+ - files: tests/**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "1.68.0",
3
+ "version": "1.69.0",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,8 @@
26
26
  "files": [
27
27
  "dist",
28
28
  "schema",
29
- "skills"
29
+ "skills",
30
+ "fixtures"
30
31
  ],
31
32
  "scripts": {
32
33
  "build": "tsc -p tsconfig.json",