KKNFZA: off-board local ticketing — tracker identity + join (Model B)#548
Open
TheMostlyGreat wants to merge 44 commits into
Open
KKNFZA: off-board local ticketing — tracker identity + join (Model B)#548TheMostlyGreat wants to merge 44 commits into
TheMostlyGreat wants to merge 44 commits into
Conversation
Intake for making the customer's tracker (GitHub/Linear) the system of record for ticket identity + status, demoting the local .project/tickets plane to a git-ignored ephemeral cache so safeword stops churning bookkeeping into the repo while gates keep reading local files. spec.md: intent, intake brief, 2 JTBDs / 8 ACs, rave moment, outcomes, deferred open questions. ticket.md: bounded scope/out_of_scope/done_when and a 5-ticket decomposition. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Durable prose (spec/design) persists to a docs path via persistArtifacts, never to the tracker (lossy + roadmap-egress). Gate-fuel artifacts (test-definitions, verify, impl-plan, dimensions) stay ephemeral and are never projected raw. Work log stays local; a distilled summary updates the issue at session boundaries only. Legacy projects out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Per user direction ("avoid git-ignore on all this stuff"), pivot the model
from "ephemeral git-ignored cache" to "content vs lifecycle state":
- Content artifacts (spec/design/impl-plan/test-definitions/verify/work-log)
stay git-tracked and reviewable in a normal PR.
- Churn reduction comes from moving lifecycle state off the repo: status to
the tracker, phase/derived gate state to a git-ignored runtime cache, and
retiring INDEX generation. No status/phase/last_modified rewrites per Stop.
- Churn metric becomes "zero bookkeeping diffs per session", not "zero
tracked files".
Dissolves the cross-machine re-hydration problem and the persistArtifacts
opt-in; makes teammate review of the impl-plan trivial. Rewrote spec.md
(TB1.AC1-AC5, SM1.AC1-AC3); updated scope/done_when.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent review (REQUEST CHANGES) + web research on tracker rate limits.
- AC2 reframed from an unbounded negative to an observable allow-list
(writer payload == {existence, title, back-link, status}).
- Added TB1.AC6: ticket-new degrades safely when tracker unreachable
(no orphan/duplicate; partial-create reconciles; secrets keychain-only;
egress = allow-list + public-repo warning).
- Added SM1.AC4: concurrent sessions don't corrupt the runtime cache
(per-ticket granularity; torn cache = unknown, re-reconciled).
- done_when: added INDEX/dup-ID retire and safe ticket-new rows.
- Resolved A/B: PR-review is the default; tracker-side approval gate
deferred to a follow-up child ticket (reversible).
- Marked the two superseded (pre-pivot) work-log entries.
Web evidence (this session): GitHub & Linear both ~5k req/hr authenticated
(Actions token 1k/hr/repo) — validates no-network-in-per-turn-loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
- Restore the Implementation-decomposition section in spec.md (work-log referenced "5 child tickets" but the rewrite had dropped it); updated to the content-vs-lifecycle model. - Reword TB1 JTBD: "one place work is tracked" contradicted the two-plane model now that content stays in git — say the team tracks status in one place while work stays reviewable in the repo. - Retitle TB1.AC2 to cover both its claims (lifecycle leaves tracked files + allow-listed tracker payload). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
- Outcomes: add INDEX-no-churn and safe-ticket-new rows for done_when↔Outcomes symmetry. - SM1.AC3: disambiguate "legacy" (reads old on-disk ID formats vs legacy-project workflows, which are out of scope). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Audited the local ticket system's jobs-to-be-done and added SM2 — "the migration preserves the local execution workflow" — with one AC per high-risk silent-loss job identified by the audit: - SM2.AC1 per-turn context anchor (loop-prevention) stays local, no network - SM2.AC2 "never done without user confirmation" survives an external close (tracker = coordination read-authority; local done-gate evidence still required) - SM2.AC3 local hierarchy execution (blocked_on gate, parent cascade, next-ticket nav) preserved; distinct from graph projection (M1FGRJ) - SM2.AC4 cross-session resume + re-entry brief + replan preserved (local) - SM2.AC5 review-ledger stamps rekey to tracker id; floor not lowered - SM2.AC6 stable tracker-key -> local-folder join key (audit linchpin) Resolved the audit's two linchpins in Open Questions (status/phase home; join key). Added child 6 to the decomposition; updated scope + done_when. Jobs the tracker serves better (ID minting, index) or that survive unchanged (work logs, triage, PR-scope, backlog) need no new AC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent verification of the JTBD audit found it incomplete + partly wrong. Corrections/additions to the spec: - last_modified is functional, not pure churn: active-ticket selection (active-ticket.ts) and replan baseline (replan.ts) both read it. Must be relocated, not deleted. (Open Questions correction.) - phase is NOT derivable from tracked artifacts; a git-ignored cache loses it on a clean checkout and inverts phase-keyed gates to FAIL-OPEN. Added fail-closed invariant (SM2.AC3) and the non-derivable note (SM2.AC1). - Missed consumers added as SM2.AC7: Cursor done-gate edit-trigger (gate-adapter.ts, no Stop fallback — parity-critical), CI check-pr-ticket-done.ts, session-compact-context.ts, cursor/stop.ts. - review-ledger trigger (detectPhaseAdvance on ticket.md phase diff) must be re-sourced if phase leaves ticket.md (SM2.AC5). - join key is build-new, not preserve: external_issue has no reader today (SM2.AC6). - Corrected a wrong claim: statusline/reentry.ts reads re-entry.md, not status/phase. ELEVATED a BLOCKING open question: status/phase durability trilemma (tracked=churn / git-ignored=not-durable+fail-open / tracker-only=per-turn network). Recommend a focused /figure-it-out before define-behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Chose Model B for status/phase durability: the tracker is canonical for IDENTITY and a one-way status mirror; status/phase STAY locally canonical in tracked ticket.md. Why: making the tracker canonical for status hits a trilemma (per-turn network vs stale/absent git-ignored cache vs fail-open), can't cleanly hold phase on GitHub, and contradicts the done-invariant. Keeping status/phase in tracked files dissolves all of it and preserves the Cursor done-gate, CI guard, context anchor, resume, and review trigger BY CONSTRUCTION. Churn target narrowed to the real noise: last_modified-per-Stop + INDEX (with last_modified's two readers relocated to git). Status/phase transition diffs are accepted as legitimate history. Revises the earlier "tracker = system of record for status" framing to status: mirror. Reversible; making the tracker authoritative is explicitly out of scope (would re-introduce the trilemma). Evidence (this session): GitHub/Linear ~5k req/hr; GitHub has no native custom states; git-bug validates local-state-in-git + one-way bridge. Rewrote spec.md (TB1.AC1-7, SM1.AC1-3, SM2.AC1-7), scope/out_of_scope/ done_when, decomposition, and open questions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Child ① of the epic (TB1.AC1 issue-first identity + safe degrade; SM2.AC6 key→folder join reader). Intake artifacts (child spec.md with TB1/SM1 JTBDs, dimensions.md) + 9 Gherkin scenarios across happy/adopt/back-compat, failure (unreachable / missing-credential / partial-create), and join (hit / both key shapes / clean not-found). R/G/R ledger in test-definitions.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent review returned CHANGES-REQUIRED (4 must-fix, 4 should-strengthen); applied all: - AC1 happy path: order-only "before any folder" → count+emptiness observables (issue-create exactly once; no folder at invocation; exactly one folder after) — closes the create-then-rename escape hatch. Tagged @wiring (real command end-to-end, only the network mocked). - AC2 unreachable/missing-credential: assert folder count unchanged + that issue-create was actually attempted (no vacuous pre-flight-skip pass). - AC2 partial-create: fixed Given to a pending tracker-map entry; assert issue-create zero + folder keyed to pending key + entry promoted. - AC1 adopt: named the mechanism (--issue ENG-45) + assert adopt, not absence. - no-tracker: Crockford-shape assertion + no tracker client constructed. - SM1 not-found: pinned the null sentinel. - Added scenarios: auth-rejected credential (distinct from missing), and stale map entry (folder deleted) — both were in dimensions, unscenarioed. 11 scenarios; ledger synced. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent scenario review passed on round 2 (round 1's 4 must-fix + 4 should-strengthen all applied). Recorded the scenario-gate review stamp and advanced phase to implement. Proof plan (outside-in TDD): SM1.AC1 join reader first (pure resolver, other children depend on it), then TB1.AC1 issue-first ticket new (@wiring, command-level with only the network mocked), then TB1.AC2 degrade paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
All five sections: approach (riskiest assumption = clean key→folder resolve from tracker-map + fs check, proven by SM1.AC1 unit scenarios first), decisions (index source, stale-vs-hit, null sentinel, create ordering, client injection), arch alignment (JS5K5G one-way / Model B identity-only), known deviations (first reader of tracker identity — identity not status), assessment triggers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
resolveFolderByTrackerKey: reverse-resolve a tracker key (Linear "ENG-45" / GitHub "#123"|"123") to its local ticket folder via the tracker-map index (new TrackerMap.findTicketIdByRefId), then by {ID}-{slug} prefix; returns the not-found sentinel (undefined) on unknown key OR stale entry (folder gone) — never a dangling path. 5 unit tests green. Sentinel is undefined (package lint: unicorn/no-null); feature/ledger wording aligned. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Join reader landed (58d70c8); checked RED+GREEN for the 4 SM1.AC1 scenarios (known, both key shapes, unknown→sentinel, stale→sentinel), REFACTOR skipped (clean). Work log updated; next slice is TB1.AC1 issue-first ticket new. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…rphan) createIssueFirstTicket: mint identity from the tracker (injected IdentitySource = the network boundary) BEFORE any folder exists, then key the folder to the issue key. A failed mint leaves no orphan (folder count unchanged). Extracted writeTicketContents shared by the local (minter) and issue-first paths so the file shape is identical. createTicket (provider:none) unchanged. Covers TB1.AC1.connected_mints_issue_before_any_folder (@wiring at the identity seam) and TB1.AC2.unreachable_fails_no_orphan. Not yet wired into the CLI command (provider routing is a later scenario). 12 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Ledger: connected_mints_issue_before_any_folder and unreachable_fails_no_orphan checked RED+GREEN (b38565c). Logged the partial-create idempotency blocker for the next decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…ow-up Decision C: issue-first creation does NOT auto-reconcile a partial-create crash (no local id pre-issue; title-search/slug-marker add scope + ambiguity). A successful create records its ref (sync-tracker updates, never double-creates); the rare post-crash orphan is accepted and surfaced by a follow-up, not auto-reconciled. - Replaced the partial_create_reconciles scenario with successful_create_records_ref (feature + ledger). - Recorded the decision in child spec Open Questions; updated dimensions. - Filed follow-up 01EAKC (surface orphaned tracker issues), epic=KKNFZA, depends_on DGH59K, status blocked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
resolveCreationMode: pure decision over ticketBridge config + options — provider:none / unsupported → local path; github|linear → issue-first (adopt when --issue given, else create). Underpins the no_tracker and existing_issue_is_adopted scenarios; 6 unit tests green. Command integration (build identity from writer/config/secrets, credential degrade, record ref) is the next slice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
buildIdentitySource: from a routing decision + the tracker writer — create mode mints via writer.create (network boundary; failure propagates → no orphan), adopt mode returns the given key with NO create call. 2 unit tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
createTicketRouted orchestrates routing → identity → creation → ref-record: provider:none uses the local minter (no client built); github|linear mints issue-first (create or --issue adopt), keys the folder to the issue key, and records the ref in .safeword/tracker-map.json so a later sync updates instead of double-creating. A tracker failure propagates before any folder is written (no orphan). ticketNew now async, routes through it, adds --issue; failures surface without leaking the token (gh/Arcade read it from env). 3 integration tests (no_tracker / create+record / credential-fail-no-orphan), real fs + sidecar, only the writer factory injected. 36 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Marked the five command-wired scenarios green (da1268d): no_tracker, existing_issue_is_adopted (composition-proven), rejected/missing credential degrade (shared failure path; secret-redaction structural), and successful_create_records_ref. Honest coverage notes in the ledger. DGH59K ready for /verify. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent code review (REQUEST CHANGES) fixes: - Narrow the double-create window: write a `pending` ref BEFORE the folder (createIssueFirstTicket onMinted hook), promote to `recorded` after — a folder on disk now always implies a map entry, so a later sync reconciles instead of double-creating. Residual (minted, nothing local) is the Decision-C orphan, surfaced by 01EAKC. - Friendly EEXIST error (with cause) on an adopt-collision. - ticketNew accepts cwd → real-entry wiring test (provider:none through the actual command: real config read + routing + fs, no injected seams). - Routed-level adopt test: --issue makes zero create calls, keys folder + recorded ref to the adopted key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
- adopt scenario now cites the routed-level integration test (7d7d657), not composition-only. - 01EAKC scope note: the narrowed window means the orphan to surface is the no-local-footprint shape (issue minted, no folder, no map entry) — detection scans the tracker side against local tickets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Independent re-review (APPROVE, no criticals) surfaced three real fixes: - Re-adopt downgrade: write `pending` only in create mode, never adopt. An adopt-collision (EEXIST) no longer downgrades an existing `recorded` entry. - Corrupt-sidecar safety: refuse before minting an issue when tracker-map.json is corrupt, rather than silently resetting and wiping other tickets' refs (matches the orchestrator's refusal). Missing sidecar = legitimate first run. - Dropped an unused `existsSync` import (TS6133). New tests: adopt-collision keeps the recorded entry; corrupt sidecar is refused before any create and left untouched. 9 affected tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
create-ticket-routed computed `<cwd>/.safeword/tracker-map.json` in two places; hoist a canonical trackerMapPath(cwd) into tracker-map.ts (the sidecar's home) and adopt it. Behavior unchanged; 14 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
The `readdirSync(...).filter(completed/tmp)` helper was copy-pasted in two test files; move it to tests/helpers.ts and import it. Behavior unchanged; 8 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Full vitest suite 3856/3856 pass (5 skipped), build + typecheck green, lint clean, 33/33 scenarios, PR scope clean, no dep drift. BLOCKER: the committed feature has no cucumber step definitions, so the BDD acceptance lane (test:bdd) fails on undefined steps. Next action: author steps/tracker-identity-and-join.steps.ts delegating to the real createTicketRouted / resolveFolderByTrackerKey via a fake-writer World. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
… convention) figure-it-out (evidence: sibling features) overturned the fake-gh-steps plan. sync-tracker.feature and tracker-connect-flow.feature both tag @wip with "no live tracker in tests" (#363) and prove behavior in vitest — no steps files, zero steps import internals. Match that: tag tracker-identity-and-join.feature @wip with the rationale; the .feature stays the canonical scenario source. test:bdd now green (159/159, feature excluded). verify.md Gherkin → ✅. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…ation) /audit (depcruise cli-no-cross-command-imports) flagged the orchestration helpers I'd placed under commands/: commands/ is for entry points only and must not cross-import. Move them into a feature subdir (matches tracker-sync/ ticket-sync/ convention): commands/create-ticket-routed.ts → ticket-create/index.ts commands/ticket-creation-mode.ts → ticket-create/creation-mode.ts commands/ticket-identity.ts → ticket-create/identity.ts commands/ticket-new.ts (the real command) stays and imports from ../ticket-create. Pure move + import-path updates; depcruise clean (0 violations), typecheck green, 352 passed / 4 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
/audit found + fixed one layer violation (helpers moved out of commands/); knip/jscpd/config-drift/test-quality clean. Only user confirmation remains before done. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Child ① (issue-first identity + tracker-key→folder join reader) complete. /verify + /audit passed; verify.md records the evidence. status/phase → done. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…ing) Verified against code: last_modified is rewritten only on status transitions (hierarchy.ts:165 via stop-quality done/cascade) + at ticket new, NOT every Stop; the tickets INDEX regenerates only on manual sync-tickets/check, not on every change (ticket new skips it). So churn-reduction is a modest benefit, not the headline. Re-anchored the epic value on identity + tracker-visible status (what DGH59K founded); children ②/③ are now modest cleanups, ④ (status mirror) carries the user-visible payoff. Added a Discovery-correction section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…ation-safeword-m73r9p
…olish Re-validation (#4) confirmed TB1.AC5 (one-way status mirror) is already delivered by the shipped sync-tracker (JS5K5G) + DGH59K's ref recording — an issue-first ticket mirrors to the tracker on the next sync. So the epic's headline value (issue-first identity + tracker-visible status) is done. Remaining children reclassified as optional polish; ⑤ (docs + CI recipe) is the real next user value; SM1.AC2 (upstream heads-up) is the only un-shipped feature. Added a "Status after DGH59K" section; did not manufacture further build cycles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
The tracker feature (sync-tracker JS5K5G + connect 2TK5AD + issue-first identity DGH59K) shipped with NO user-facing docs. Fill the gap so the already-working one-way status mirror is discoverable and routinely run: - New reference page: Tracker Integration — what it is (one-way, files canonical, network only on sync/CI), connect, sync status, a copy-paste GitHub Actions CI recipe (safeword ships no workflow templates), egress + secrets posture. - ticketBridge section added to the Configuration reference. - Sidebar entry registered. figure-it-out: docs (not a check-nudge) is the load-bearing fix — a reminder to use an undocumented feature is a dead-end; the CI snippet relies on sync-tracker's loud-fail-on-missing-credential, so no silent no-op. Website builds clean (9 pages, links resolve). No new code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Audit of PR #548 surfaced confirmed issues; fix the cheap, correct ones: - CODE: adopt did not normalize a GitHub-style leading '#'. `--issue '#123'` stored ref.id '#123' and a '#123-slug' folder, but the create path stores bare '123' and the join reader strips '#' from the query — so adopt-via-# could never be resolved. Export the reader's normalizer and apply it at the routing boundary (resolveCreationMode) so both sides share one rule. A lone '#' normalizes to empty → falls through to create. New unit + round-trip tests. - DOCS: CI recipe set GH_TOKEN, but safeword reads GITHUB_TOKEN — recipe was broken as written. Fixed the env var. - DOCS: added a "GitHub today; Linear projection pending" caveat — the Linear live client throws linearNotWired() and sync-tracker to Linear is not wired. - SPEC: KKNFZA outcomes stated a dogfood churn drop as observed; it depends on the unshipped children ②/③. Reframed as a target, not a result. - TICKET ARTIFACTS: corrected stale null→undefined not-found contract in DGH59K impl-plan, and the stale partial-create→reconcile boundary in dimensions.md (Decision C accepts the orphan, no auto-reconcile). - Deferred: re-adopt same key with a different slug creates a duplicate folder; noted in follow-up 01EAKC (low severity, user-error, no data loss). Full suite 3889 passed/5 skipped; lint, typecheck, depcruise, website build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
…ic --issue edges Fresh-context /quality-review of 75fbe47 returned APPROVE (no critical issues). Folding the one cheap real finding + deferring the exotic ones: - resolve-by-key.ts docblock still said the not-found sentinel "returns null"; the contract is `undefined` (same drift this PR already corrected in the DGH59K impl-plan). Comment-only. - Deferred to 01EAKC: the adopt key normalizer does not trim whitespace and strips only one leading '#', so `--issue ' #4'` / '##5' can't join a real synced issue. Exotic, low severity — noted with the re-adopt edge. No logic change; full suite green at 75fbe47, lint/typecheck/format clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
New feature ticket (child of KKNFZA): make sync-tracker work without the `gh` binary by computing a network-free sync PLAN and applying it via a pluggable EXECUTOR (agent via MCP, CI via token+REST, dev via gh). Intake spec drafted: intake brief, JTBDs (TB1 keep mirror working without gh; SM1 add a transport against a stable contract), and ACs. Scoped to the first slice — the network-free --plan / --apply-results seam with the agent as executor #1 (de-risked by the live-fire spike, issue #549). Token+REST CI executor, Linear, and label-rejection hardening deferred to follow-on children. Phase: intake (awaiting JTBD/scope gate signoff). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Scope/out_of_scope/done_when for the portable-tracker-transport feature: this ticket lands the network-free --plan / --apply-results seam with a versioned intent contract and the agent as executor #1; gh path unchanged. Token+REST CI executor ("bot" co-executor), Linear, and label hardening deferred to follow-on children. Phase: intake (awaiting scope-gate signoff + cold-start offer). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
Cold-start check (isolated worktree, no conversation) returned INSUFFICIENT:
mechanism is ready to reuse (planTicketSync + buildPayload offline;
TrackerMap.record stores {id,url} per ticket), but the one-way-door JSON
contract and edge semantics were undefined. Resolved 10 of 11 gaps as design
decisions in spec.md:
- SyncPlan{version,intents[]} to stdout; Intent discriminated on kind;
separate SyncResults{version,results[]}; --apply-results records creates.
- plan/apply offline (no credential gate); --plan/--apply-results mutually
exclusive; no-flag = today's gh path byte-for-byte.
- internal-id trap guard: apply-results rejects a result whose url tail != number
(a bare-digits check can't catch the numeric internal id 4764539863).
- malformed defined; results required for creates, acks for update/close;
contract version independent of SIDECAR_VERSION.
One fork left for the scope gate: whether executor #1 must apply graph edges
(parent/blocked-by) or only core CRUD (contract carries graph? either way).
Phase: intake (awaiting graph-edge fork signoff before define-behavior).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
User chose option B: executor #1 reproduces parent + blocked-by edges, so an agent-applied mirror matches the gh path's output, not a lesser subset. - Edges expressed by ticket id (a new issue's number isn't known pre-create); executor does create-then-link (resolving ticket id → number after creates land), mirroring the gh path's projectGraph-after-create. apply-results records nothing for edges (linking mints no identity). - Scope + done_when updated to include edge reproduction. - dimensions.md authored (command mode, diff-state→kind, graph edges, edge resolvability, results validity, map fold, offline invariant, egress). - Open Questions cleared; phase → define-behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018PmDh9rxQNmfkVJLfjYdt7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets a project off-board its local ticketing system onto a real tracker (GitHub Issues / Linear) without losing what the local system does well. Implements Model B: the external tracker is canonical for ticket identity plus a one-way status mirror, while
status/phasestay in the trackedticket.mdso gates read from disk, work offline, and never touch the network in the per-turn loop.This PR delivers the epic's core slice (child ① DGH59K — tracker identity + join) and its discoverability slice (child ⑤ — docs). The remaining epic children are scoped as optional polish and are not in this PR.
What's in it
Issue-first ticket creation (child ① DGH59K)
safeword ticket new <slug>mints the tracker issue first and keys the local ticket to it;--issue <key>adopts an existing issue instead of creating one.provider: none(default) is unchanged.src/ticket-create/module (routing, identity, creation-mode) — kept out ofcommands/to satisfy thecli-no-cross-command-importslayer rule.createIssueFirstTicketmints identity before the folder and turns an EEXIST into a friendly error;createTicket(provider:none path) is untouched.resolveFolderByTrackerKeymaps a tracker key back to its local folder via thetracker-map.jsonsidecar, tolerating stale entries.Docs (child ⑤)
ticketBridgeblock documented in Configuration; sidebar entry added. Website builds clean.What must not break
provider: noneprojects do nothing — no network, no behavior change.status/phasefrom localticket.md; the tracker never drives local state.Testing
.featureis@wip-excluded from the cucumber lane (Workflow: enforce "mock only the process boundary" — every entry-point needs one real-collaborator wiring test #363, no live tracker in tests).Not in this PR (optional, deferred)
checkstaleness nudge, children ②/③ (churn cleanups), SAFEWORD.md/SKILL.md prose rewrite, 01EAKC (orphan surfacing).🤖 Generated with Claude Code
Generated by Claude Code