Scaffold CommiTea: yarn workspaces, Electron shell, core label schema, design system

- apps/desktop: electron-vite + React + Tailwind mapped onto design tokens
  (preflight off; tokens/base.css owns the reset); boots to a Reginald
  placeholder proving fonts/tokens/core wiring
- packages/core: pure TS; gitea label schema (est/*, p/*, deadline/hard)
  with pessimistic conflict resolution + 15 unit tests
- docs/design: full design handoff (tokens, 16 component contracts,
  interactive 14-screen prototype, Reginald voice rules)
- docs/PLAN.md: product plan (purity rule, pm-state repo, deterministic
  scheduler + Monte Carlo, directive log)
- Deliberate deviation from novelpad stack: no ElectricSQL/PGlite — local
  store is a rebuildable cache over gitea REST/webhooks (better-sqlite3
  in main process, arriving in P1)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Christian LeDoux
2026-07-07 20:42:46 -04:00
commit 7a5cacc54c
268 changed files with 15766 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
{
"name": "@commitea/core",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "^5.7.3",
"vitest": "^3.0.5"
}
}

View File

@@ -0,0 +1,10 @@
export {
ESTIMATE_LABELS,
PRIORITY_LABELS,
HARD_DEADLINE_LABEL,
extractLabelFacts,
isCommiteaLabel,
parseEstimateLabel,
parsePriorityLabel,
} from './labels/label-schema.js'
export type { EstimateLabel, LabelFacts, PriorityLabel } from './labels/label-schema.js'

View File

@@ -0,0 +1,97 @@
import { describe, expect, it } from 'vitest'
import {
ESTIMATE_LABELS,
extractLabelFacts,
parseEstimateLabel,
parsePriorityLabel,
} from './label-schema.js'
describe('parseEstimateLabel', () => {
it.each([
['est/1d', 1],
['est/2d', 2],
['est/3d', 3],
['est/5d', 5],
['est/8d', 8],
])('parses %s → %i days', (label, days) => {
expect(parseEstimateLabel(label)).toBe(days)
})
it('rejects estimates outside the fixed set', () => {
expect(parseEstimateLabel('est/4d')).toBeNull()
expect(parseEstimateLabel('est/13d')).toBeNull()
expect(parseEstimateLabel('est/3h')).toBeNull()
expect(parseEstimateLabel('EST/3d')).toBeNull()
})
})
describe('parsePriorityLabel', () => {
it('parses p/1 through p/4', () => {
expect(parsePriorityLabel('p/1')).toBe(1)
expect(parsePriorityLabel('p/4')).toBe(4)
})
it('rejects out-of-range and malformed priorities', () => {
expect(parsePriorityLabel('p/0')).toBeNull()
expect(parsePriorityLabel('p/5')).toBeNull()
expect(parsePriorityLabel('P/1')).toBeNull()
expect(parsePriorityLabel('priority/1')).toBeNull()
})
})
describe('extractLabelFacts', () => {
it('extracts all three axes from a full label set', () => {
const facts = extractLabelFacts(['est/3d', 'p/2', 'deadline/hard', 'bug'])
expect(facts).toEqual({
estimateDays: 3,
priority: 2,
hardDeadline: true,
malformed: [],
conflicts: [],
})
})
it('ignores labels outside CommiTea namespaces entirely', () => {
const facts = extractLabelFacts(['bug', 'enhancement', 'wontfix'])
expect(facts.estimateDays).toBeNull()
expect(facts.priority).toBeNull()
expect(facts.malformed).toEqual([])
})
it('reports in-namespace labels that fail to parse as malformed', () => {
const facts = extractLabelFacts(['est/4d', 'p/9', 'deadline/soft'])
expect(facts.malformed).toEqual(['est/4d', 'p/9', 'deadline/soft'])
expect(facts.estimateDays).toBeNull()
expect(facts.priority).toBeNull()
expect(facts.hardDeadline).toBe(false)
})
it('resolves estimate conflicts pessimistically and reports them', () => {
const facts = extractLabelFacts(['est/2d', 'est/8d'])
expect(facts.estimateDays).toBe(8)
expect(facts.conflicts).toEqual(['est/8d'])
})
it('resolves priority conflicts toward urgency and reports them', () => {
const facts = extractLabelFacts(['p/3', 'p/1'])
expect(facts.priority).toBe(1)
expect(facts.conflicts).toEqual(['p/1'])
})
it('handles the empty label list', () => {
expect(extractLabelFacts([])).toEqual({
estimateDays: null,
priority: null,
hardDeadline: false,
malformed: [],
conflicts: [],
})
})
it('keeps the fixed estimate set in sync with its parser', () => {
for (const label of ESTIMATE_LABELS) {
expect(parseEstimateLabel(label)).not.toBeNull()
}
})
})

View File

@@ -0,0 +1,91 @@
/**
* The gitea label schema — CommiTea's only footprint in managed work repos.
* Fixed sets by design (docs/PLAN.md): estimates are coarse on purpose, the
* scheduler's calibration layer refines them; anything outside these sets is
* someone else's label and none of our business.
*/
export const ESTIMATE_LABELS = ['est/1d', 'est/2d', 'est/3d', 'est/5d', 'est/8d'] as const
export const PRIORITY_LABELS = ['p/1', 'p/2', 'p/3', 'p/4'] as const
export const HARD_DEADLINE_LABEL = 'deadline/hard'
export type EstimateLabel = (typeof ESTIMATE_LABELS)[number]
export type PriorityLabel = (typeof PRIORITY_LABELS)[number]
const COMMITEA_NAMESPACES = ['est/', 'p/', 'deadline/'] as const
export interface LabelFacts {
/** Days from an `est/*` label; null when absent. Largest wins on conflict. */
estimateDays: number | null
/** 1 (most urgent) … 4 from a `p/*` label; null when absent. Most urgent wins on conflict. */
priority: number | null
hardDeadline: boolean
/** Labels inside CommiTea namespaces that don't match the fixed sets. */
malformed: string[]
/** Valid CommiTea labels that duplicated an axis (e.g. two `est/*` labels). */
conflicts: string[]
}
export function parseEstimateLabel(label: string): number | null {
if (!(ESTIMATE_LABELS as readonly string[]).includes(label)) return null
return Number(/^est\/(\d+)d$/.exec(label)![1])
}
export function parsePriorityLabel(label: string): number | null {
if (!(PRIORITY_LABELS as readonly string[]).includes(label)) return null
return Number(label.slice(2))
}
export function isCommiteaLabel(label: string): boolean {
return COMMITEA_NAMESPACES.some((ns) => label.startsWith(ns))
}
/**
* Reduce an issue's label list to scheduler inputs. Conflicting labels on one
* axis resolve pessimistically (largest estimate) / urgently (lowest priority
* number) and are reported in `conflicts` so the agent can nag about them.
*/
export function extractLabelFacts(labels: readonly string[]): LabelFacts {
const facts: LabelFacts = {
estimateDays: null,
priority: null,
hardDeadline: false,
malformed: [],
conflicts: [],
}
for (const label of labels) {
if (!isCommiteaLabel(label)) continue
if (label === HARD_DEADLINE_LABEL) {
facts.hardDeadline = true
continue
}
const estimate = parseEstimateLabel(label)
if (estimate !== null) {
if (facts.estimateDays !== null) {
facts.conflicts.push(label)
facts.estimateDays = Math.max(facts.estimateDays, estimate)
} else {
facts.estimateDays = estimate
}
continue
}
const priority = parsePriorityLabel(label)
if (priority !== null) {
if (facts.priority !== null) {
facts.conflicts.push(label)
facts.priority = Math.min(facts.priority, priority)
} else {
facts.priority = priority
}
continue
}
facts.malformed.push(label)
}
return facts
}

View File

@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": ["ES2022"]
},
"include": ["src"]
}