Resolve the five design-session open items from PLAN.md: - decisions.md: soft write-path, poll-only sync (NAT), lognormal cold-start priors, purity test binds the SQLite cache - pm-state.md: sidecar layout + directive/capacity/calibration schemas + lifecycle inference table - agent-tools.md: query_project read tool + three write tools Also gitignore .env.* (protect the gitea PAT) and record the P0 actual: 10 labels, 5 milestones, 34 tracer-bullet issues + 51 dependencies filed on christian/commitea as the first managed project. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.7 KiB
Settled design decisions
Resolves the "Open items for design session" in PLAN.md. Dated 2026-07-08. Companion docs: pm-state.md (sidecar formats), agent-tools.md (tool schemas).
D1 — Write path is soft, split by semantics
Chat (Reginald) is the write path for PM-semantic mutations; gitea-native content stays directly editable.
- Through the agent — estimates (
est/*), priority (p/*),deadline/hard, milestone assignment, dependency edits, and directives. Additive ops act directly; destructive ops go propose-approve (Dialog or the consequence diff on the Directives screen). - Direct, reconciled — issue title/description, comments, assignees. Edit them in the CommiTea UI ("composer writes to gitea, as you") or in the gitea web UI; reconcile absorbs out-of-band edits because gitea is the source of truth for intent. The agent observes changes on the next reconcile and may object in standup, but never blocks them.
Rejected: strict (every write through chat). Hostile to quick edits and fights the reconcile-from-gitea model — an edit made in gitea's own web UI would be un-representable.
D2 — Sync is poll + reconcile, no live webhooks in v1
gitea.stephenmann.io is remote and the desktop app sits behind NAT, so the
server cannot POST to a localhost webhook. v1:
- Reconcile on launch — full read of the work repo(s) +
pm-staterepo into the local SQLite cache. - Light poll while running —
since/conditional-request poll of issues and the issue timeline (target 30–60 s cadence; visible < 2 s is a webhook-era goal, relaxed to the poll interval for v1). - The change-source is an interface (
ChangeSource) with a polling implementation; a webhook implementation can plug in later for a LAN / self-hosted / tunnelled instance without touching the reconcile core.
Rejected now: reachability-detection hybrid (moving parts, cleanup-on-quit), outbound tunnel (runtime dependency + public ingress). Both remain future options behind the same interface.
D3 — Cold-start forecasts use lognormal per-bucket priors
Before the team has n ≥ 20 closed issues with estimates, Monte Carlo samples a
lognormal actual-duration distribution per estimate bucket
(1d/2d/3d/5d/8d), with a pessimism-skewed median (actuals run long). The
priors live in code (@commitea/core), not in a data file. At n ≥ 20 the
scheduler switches to the team's own empirical fit (see calibration model in
pm-state.md); byLabel / byPerson bias terms layer on once
their own sample sizes clear a floor.
Rejected: uniform pessimism multiplier (actual = est × U[1.3, 2.0]) — cruder,
dishonest tails, no path to per-bucket calibration.
D4 — Purity test applies to the SQLite cache, not the pm-state repo
The plan's invariant — delete the sidecar → resync → no truth lost — is about the local SQLite cache, which is a rebuildable index over two durable sources:
- Work repo(s) in gitea — human-authored intent (issues, milestones + due dates, dependencies, assignees, labels, comments).
pm-staterepo in gitea — the sidecar's own durable truth that is not regenerable from the work repo: directive log, capacity config, charter, and the (cached-but-committed) calibration model.
Delete SQLite → rebuild from both repos → nothing lost. The pm-state repo is
never the thing you delete; it is versioned and backed up in gitea like any
other repo. Regenerable state (issue mirror, inferred lifecycle timestamps,
Monte Carlo forecasts, focus snapshot) lives in SQLite only and is recomputed on
rebuild. See pm-state.md for the file/table split.