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.
- package/dist/adapters/kimi.d.ts +25 -1
- package/dist/adapters/kimi.js +80 -0
- package/dist/adapters/types.d.ts +6 -0
- package/dist/cli/commands/eval.d.ts +4 -0
- package/dist/cli/commands/eval.js +26 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/eval/canary.d.ts +46 -0
- package/dist/eval/canary.js +113 -0
- package/dist/eval/dispatch.d.ts +31 -0
- package/dist/eval/dispatch.js +207 -0
- package/dist/eval/fixtures.d.ts +22 -0
- package/dist/eval/fixtures.js +85 -0
- package/dist/eval/report.d.ts +35 -0
- package/dist/eval/report.js +82 -0
- package/dist/eval/selfcheck.d.ts +22 -0
- package/dist/eval/selfcheck.js +177 -0
- package/dist/run/daemon.js +125 -102
- package/dist/run/interactive-seed.d.ts +15 -0
- package/dist/run/interactive-seed.js +25 -0
- package/fixtures/eval/canary/solution/a.txt +1 -0
- package/fixtures/eval/canary/spec.md +8 -0
- package/fixtures/eval/canary/start/a.txt +1 -0
- package/fixtures/eval/sample/solution/a.txt +1 -0
- package/fixtures/eval/sample/spec.md +8 -0
- package/fixtures/eval/sample/start/a.txt +1 -0
- package/fixtures/gsd-sample/07-live-check/07-01-PLAN.md +42 -0
- package/fixtures/gsd-sample/07-live-check/07-02-PLAN.md +21 -0
- package/fixtures/gsd-sample/07-live-check/07-03-PLAN.md +18 -0
- package/fixtures/gsd-sample/07-live-check/07-03-SUMMARY.md +1 -0
- package/fixtures/missing-mandatory-gate.native.md +10 -0
- package/fixtures/sample-pin.prd.md +18 -0
- package/fixtures/sample.native.md +35 -0
- package/fixtures/sample.prd.md +22 -0
- package/fixtures/speckit-sample/tasks.md +20 -0
- package/package.json +3 -2
package/dist/run/daemon.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
741
|
-
|
|
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 (
|
|
749
|
-
|
|
750
|
-
output = await driver.read(slot, 1000);
|
|
685
|
+
if (seedResult?.seedFailed) {
|
|
686
|
+
finished = false;
|
|
751
687
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
let
|
|
761
|
-
|
|
762
|
-
while (
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
await
|
|
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
|
-
|
|
769
|
-
|
|
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 (
|
|
772
|
-
|
|
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 @@
|
|
|
1
|
+
canary-start
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
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,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.
|
|
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",
|