Skip to content

KKNFZA: off-board local ticketing — tracker identity + join (Model B)#548

Open
TheMostlyGreat wants to merge 44 commits into
mainfrom
claude/ticketing-migration-safeword-m73r9p
Open

KKNFZA: off-board local ticketing — tracker identity + join (Model B)#548
TheMostlyGreat wants to merge 44 commits into
mainfrom
claude/ticketing-migration-safeword-m73r9p

Conversation

@TheMostlyGreat

Copy link
Copy Markdown
Collaborator

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/phase stay in the tracked ticket.md so 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.
  • New src/ticket-create/ module (routing, identity, creation-mode) — kept out of commands/ to satisfy the cli-no-cross-command-imports layer rule.
  • createIssueFirstTicket mints identity before the folder and turns an EEXIST into a friendly error; createTicket (provider:none path) is untouched.
  • Reverse join: resolveFolderByTrackerKey maps a tracker key back to its local folder via the tracker-map.json sidecar, tolerating stale entries.
  • Adopt is idempotent (Decision C): no auto-reconcile of a partial-create crash, a successful create records its ref, and the rare orphan is surfaced by follow-up 01EAKC.

Docs (child ⑤)

  • New Tracker Integration reference page: connect → one-way sync → GitHub Actions CI recipe → egress/secrets.
  • ticketBridge block documented in Configuration; sidebar entry added. Website builds clean.

What must not break

  • Default provider: none projects do nothing — no network, no behavior change.
  • Gates continue to read status/phase from local ticket.md; the tracker never drives local state.

Testing

Not in this PR (optional, deferred)

  • SM1.AC2 (upstream status heads-up), check staleness nudge, children ②/③ (churn cleanups), SAFEWORD.md/SKILL.md prose rewrite, 01EAKC (orphan surfacing).

🤖 Generated with Claude Code


Generated by Claude Code

claude added 30 commits June 27, 2026 21:38
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
claude added 7 commits June 28, 2026 22:01
…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
…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
claude added 7 commits June 29, 2026 00:19
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants