docs(todos): standardize all TODOs with running numbers and delegatable instructions

Replace ad-hoc AGENTIC-N comments with standardized TODO-NNN format across
the entire codebase. Each TODO has:
  - Running number (TODO-001 through TODO-011)
  - Priority level (high/medium/low)
  - Phase reference (0, A.1, A.4, B, cleanup)
  - Clear step-by-step instructions an AI agent can follow

TODO index:
  TODO-001: Kill switch maintenance banner (providers.tsx)
  TODO-002: Feedback button in settings page
  TODO-003: Accessibility focus trap for modals
  TODO-004: Clone routine template instead of mutating in-place
  TODO-005: Wire real LLM enrichment for context messages
  TODO-006: Centralize backend URL configuration
  TODO-007: MCP tool integration tests (common-plat)
  TODO-008: Wire trackEvent() calls into routes + components
  TODO-009: Unit tests for AI context generation
  TODO-010: Import PRODUCT_ID from product-config (5 route files)
  TODO-011: Wire error boundary to telemetry

Added consolidated TODO Index table at top of AGENTIC_AI_ROADMAP.md
for agent scanning. 219 backend tests pass, no code changes.
This commit is contained in:
saravanakumardb1 2026-04-01 01:05:25 -07:00
parent f94c2d8424
commit 9897d2cd09
12 changed files with 101 additions and 19 deletions

View File

@ -58,9 +58,16 @@ export interface ContextMessageResult {
// ── LLM enrichment (extraction-service or ollama-client) ── // ── LLM enrichment (extraction-service or ollama-client) ──
// TODO(AGENTIC-6): Replace this stub with a real LLM call via // TODO-005: Wire real LLM enrichment for context messages
// @bytelyst/extraction client (timer-context task) or @bytelyst/ollama-client. // Priority: high | Phase: A.4
// For now we build the prompt and return a simulated enriched message. // Replace the stub below with a real LLM call. Two options:
// Option A: @bytelyst/extraction client — POST to extraction-service /api/extract
// with task='timer-context'. Requires creating a new extraction task type in
// learning_ai_common_plat/services/extraction-service/src/modules/extract/.
// Option B: @bytelyst/ollama-client — call Ollama directly with buildPrompt().
// Simpler, no extraction-service dependency, but no task abstraction.
// The prompt is already built by buildPrompt() below. Just replace the fetch stub
// with a real client call. Keep the 5s timeout and null-return-on-error pattern.
async function llmEnrich(input: ContextMessageInput): Promise<string | null> { async function llmEnrich(input: ContextMessageInput): Promise<string | null> {
// Only attempt if extraction service URL is configured // Only attempt if extraction service URL is configured
const extractionUrl = config.EXTRACTION_SERVICE_URL; const extractionUrl = config.EXTRACTION_SERVICE_URL;

View File

@ -3,6 +3,15 @@
* *
* Centralised here so backend routes and web components can reference * Centralised here so backend routes and web components can reference
* a single source of truth for event names (Phase 0.5). * a single source of truth for event names (Phase 0.5).
*
* TODO-008: Wire trackEvent() calls into backend routes
* Priority: medium | Phase: B
* These constants are defined but not yet imported anywhere. Wire them in:
* - agent-actions/routes.ts: AGENT_INBOX_ACTION_APPROVED on approve,
* AGENT_INBOX_ACTION_REJECTED on reject, AGENT_INBOX_BATCH_APPROVED on batch
* - server.ts context-message route: AI_CONTEXT_ENRICHED when source='llm',
* AI_CONTEXT_FALLBACK_USED when source='keyword' or 'generic'
* - Use: import { bufferEvent } from './telemetry.js' then bufferEvent(EVENT_NAME, { ...metadata })
*/ */
// ── MCP ─────────────────────────────────────────────────────── // ── MCP ───────────────────────────────────────────────────────

View File

@ -30,6 +30,9 @@ import {
type HouseholdInvite, type HouseholdInvite,
} from './types.js'; } from './types.js';
// TODO-010: Import PRODUCT_ID from product-config instead of hardcoding
// Priority: low | Phase: cleanup
// Replace this line with: import { PRODUCT_ID } from '../../lib/product-config.js';
const PRODUCT_ID = 'chronomind'; const PRODUCT_ID = 'chronomind';
function isAdmin(household: HouseholdDoc, userId: string): boolean { function isAdmin(household: HouseholdDoc, userId: string): boolean {

View File

@ -24,6 +24,9 @@ import {
type RoutineDoc, type RoutineDoc,
} from './types.js'; } from './types.js';
// TODO-010: Import PRODUCT_ID from product-config instead of hardcoding
// Priority: low | Phase: cleanup
// Replace this line with: import { PRODUCT_ID } from '../../lib/product-config.js';
const PRODUCT_ID = 'chronomind'; const PRODUCT_ID = 'chronomind';
export async function routineRoutes(app: FastifyInstance) { export async function routineRoutes(app: FastifyInstance) {
@ -154,8 +157,15 @@ export async function routineRoutes(app: FastifyInstance) {
} }
const now = new Date().toISOString(); const now = new Date().toISOString();
// TODO(AGENTIC-5): When starting from a template, consider cloning instead of // TODO-004: Clone template instead of mutating in-place
// mutating the template directly. For now we transition in-place. // Priority: medium | Phase: A.1
// When routine.isTemplate is true:
// 1. Create a NEW RoutineDoc (crypto.randomUUID() for id) with isTemplate=false
// 2. Copy all fields from the template into the clone
// 3. Set the clone's status to 'active', startedAt to now, first step to 'active'
// 4. Leave the original template unchanged (status stays 'template')
// 5. Return the new clone, not the template
// This lets users reuse templates multiple times without losing the original.
const result = await repo.updateRoutine( const result = await repo.updateRoutine(
id, id,
auth.sub, auth.sub,

View File

@ -25,6 +25,9 @@ import {
type SharedTimerDoc, type SharedTimerDoc,
} from './types.js'; } from './types.js';
// TODO-010: Import PRODUCT_ID from product-config instead of hardcoding
// Priority: low | Phase: cleanup
// Replace this line with: import { PRODUCT_ID } from '../../lib/product-config.js';
const PRODUCT_ID = 'chronomind'; const PRODUCT_ID = 'chronomind';
async function requireMembership(householdId: string, userId: string) { async function requireMembership(householdId: string, userId: string) {

View File

@ -27,6 +27,10 @@ import {
type FreeSlot, type FreeSlot,
} from './types.js'; } from './types.js';
// TODO-010: Import PRODUCT_ID from product-config instead of hardcoding
// Priority: low | Phase: cleanup
// Replace this line with: import { PRODUCT_ID } from '../../lib/product-config.js';
// Same fix needed in: routines/routes.ts, households/routes.ts, webhooks/routes.ts, shared-timers/routes.ts
const PRODUCT_ID = 'chronomind'; const PRODUCT_ID = 'chronomind';
export async function timerRoutes(app: FastifyInstance) { export async function timerRoutes(app: FastifyInstance) {

View File

@ -10,6 +10,9 @@ import { dispatchEvent } from './dispatcher.js';
import { extractAuth } from '../../lib/auth.js'; import { extractAuth } from '../../lib/auth.js';
import { BadRequestError } from '@bytelyst/errors'; import { BadRequestError } from '@bytelyst/errors';
// TODO-010: Import PRODUCT_ID from product-config instead of hardcoding
// Priority: low | Phase: cleanup
// Replace this line with: import { PRODUCT_ID } from '../../lib/product-config.js';
const PRODUCT_ID = 'chronomind'; const PRODUCT_ID = 'chronomind';
export async function webhookRoutes(app: FastifyInstance) { export async function webhookRoutes(app: FastifyInstance) {

View File

@ -8,6 +8,28 @@
--- ---
## Open TODO Index
> **For AI agents:** Scan this table, pick the next unresolved TODO by priority, and address it.
> Search the codebase for `TODO-NNN` to find the in-code comment with full instructions.
> After completing a TODO, remove it from this table and mark the corresponding roadmap checkbox.
| TODO | Priority | Phase | File(s) | Summary |
|------|----------|-------|---------|---------|
| **TODO-001** | medium | 0 | `web/src/app/providers.tsx` | Kill switch maintenance banner — create `<MaintenanceBanner />`, set React state when `checkKillSwitch()` returns `disabled=true`, disable timer creation buttons |
| **TODO-002** | medium | 0 | `web/src/app/(app)/settings/page.tsx` (create) | Wire feedback button into settings page (or floating FAB) using `feedbackClient` from `web/src/lib/feedback.ts` |
| **TODO-003** | medium | 0 | `web/src/components/CreateTimerModal.tsx`, `web/src/components/AlarmOverlay.tsx` | Apply `@bytelyst/accessibility` focus trap + screen reader announcements. Ensure `--cm-*` color tokens meet WCAG AA contrast |
| **TODO-004** | medium | A.1 | `backend/src/modules/routines/routes.ts` | Clone template into new RoutineDoc instead of mutating in-place. Create new doc with `crypto.randomUUID()`, leave original template unchanged |
| **TODO-005** | high | A.4 | `backend/src/lib/ai-context.ts` | Replace extraction-service stub with real LLM call. Option A: `@bytelyst/extraction` client with `timer-context` task. Option B: `@bytelyst/ollama-client` directly |
| **TODO-006** | low | A.4 | `web/src/lib/context-messages.ts` | Centralize backend URL — create `getBackendUrl()` in `product-config.ts` instead of raw `NEXT_PUBLIC_CHRONOMIND_BACKEND_URL` env var |
| **TODO-007** | medium | A.3 | `learning_ai_common_plat/services/mcp-server/` | Add integration tests for 5 new ChronoMind MCP tools (reschedule, availability, routine start, agent-actions list, agent-actions approve) |
| **TODO-008** | medium | B | `backend/src/lib/telemetry-events.ts`, `web/src/lib/telemetry-events.ts` | Wire `trackEvent()` / `bufferEvent()` calls into routes and components using the defined telemetry constants. See in-code comments for specific call sites |
| **TODO-009** | medium | A.4 | `backend/src/lib/ai-context.ts`, `web/src/lib/context-messages.ts` | Add unit tests for `generateContextMessage()` (backend) and `fetchEnrichedMessage()` (web). Test keyword fallback, LLM enrichment, generic fallback, and error paths |
| **TODO-010** | low | cleanup | `backend/src/modules/timers/routes.ts`, `routines/routes.ts`, `households/routes.ts`, `webhooks/routes.ts`, `shared-timers/routes.ts` | Replace `const PRODUCT_ID = 'chronomind'` with `import { PRODUCT_ID } from '../../lib/product-config.js'` in all 5 files |
| **TODO-011** | low | cleanup | `web/src/app/error.tsx` | Wire error boundary to telemetry — `trackEvent('app.error_boundary', { message, digest })` and optionally `@bytelyst/diagnostics-client` |
---
## Current State Summary ## Current State Summary
| Layer | Status | Key Files | | Layer | Status | Key Files |
@ -103,13 +125,13 @@ These proxy to `chronomind-backend` (port 4011) via `chronomind-client.ts`.
- [x] `web/src/lib/feedback.ts` — integrate `@bytelyst/feedback-client` (commit: f3e14e2) - [x] `web/src/lib/feedback.ts` — integrate `@bytelyst/feedback-client` (commit: f3e14e2)
- `createFeedbackClient({ baseUrl, productId, getAccessToken })` - `createFeedbackClient({ baseUrl, productId, getAccessToken })`
- [ ] Add feedback button to settings page (or floating FAB) (commit: ) - [ ] Add feedback button to settings page (or floating FAB) (commit: )
- TODO(AGENTIC-3): Wire feedback button into settings page UI - TODO-002: Wire feedback button into settings page using `feedbackClient` from `web/src/lib/feedback.ts`
### 0.3 — Accessibility Package (Web) ### 0.3 — Accessibility Package (Web)
- [x] Add `@bytelyst/accessibility` to web/package.json (commit: f3e14e2) - [x] Add `@bytelyst/accessibility` to web/package.json (commit: f3e14e2)
- [ ] Integrate helpers into existing modals (commit: ) - [ ] Integrate helpers into existing modals (commit: )
- TODO(AGENTIC-4): Apply focus trap + screen reader announcements to CreateTimerModal, AlarmOverlay - TODO-003: Apply `@bytelyst/accessibility` focus trap + screen reader to CreateTimerModal, AlarmOverlay
- Ensure all `--cm-*` color tokens meet WCAG AA contrast - Ensure all `--cm-*` color tokens meet WCAG AA contrast
### 0.4 — Feature Flags for New Features (Backend) ### 0.4 — Feature Flags for New Features (Backend)
@ -141,8 +163,8 @@ These proxy to `chronomind-backend` (port 4011) via `chronomind-client.ts`.
### Phase 0 Exit Criteria ### Phase 0 Exit Criteria
- [x] Kill switch client initialized on web app startup (f3e14e2) - [x] Kill switch client initialized on web app startup (f3e14e2)
- [x] Feedback client created (f3e14e2) — UI entry point deferred to TODO(AGENTIC-3) - [x] Feedback client created (f3e14e2) — UI entry point deferred to TODO-002
- [x] Accessibility package added (f3e14e2) — modal integration deferred to TODO(AGENTIC-4) - [x] Accessibility package added (f3e14e2) — modal integration deferred to TODO-003
- [x] 7 new feature flags registered (all default `false`) (f3e14e2) - [x] 7 new feature flags registered (all default `false`) (f3e14e2)
- [x] Telemetry event names defined (16 events, backend + web) (f3e14e2) - [x] Telemetry event names defined (16 events, backend + web) (f3e14e2)
- [x] All existing tests still pass (182 backend + 394 web) - [x] All existing tests still pass (182 backend + 394 web)
@ -205,7 +227,7 @@ Extend `learning_ai_common_plat/services/mcp-server/src/modules/chronomind/`:
- `chronomind.agentActions.approve` — approve a proposed action - `chronomind.agentActions.approve` — approve a proposed action
- Write tools (reschedule, routines.start) record agent action for audit trail (fail-open) - Write tools (reschedule, routines.start) record agent action for audit trail (fail-open)
- [ ] MCP tool tests (commit: ) - [ ] MCP tool tests (commit: )
- TODO(AGENTIC-8): Add integration tests for the 5 new MCP tools - TODO-007: Add integration tests for the 5 new ChronoMind MCP tools in common-plat
### A.4 — Context-Aware AI Messages (LLM upgrade) ### A.4 — Context-Aware AI Messages (LLM upgrade)
@ -220,9 +242,9 @@ Upgrade `web/src/lib/context-messages.ts` with optional LLM enrichment:
- [x] Backend route: `POST /api/context-message` (commit: c80c1e4) - [x] Backend route: `POST /api/context-message` (commit: c80c1e4)
- [x] `web/src/lib/context-messages.ts` — add `fetchEnrichedMessage()` (commit: c80c1e4) - [x] `web/src/lib/context-messages.ts` — add `fetchEnrichedMessage()` (commit: c80c1e4)
- Graceful degradation: keyword rules remain primary, LLM messages shown as "AI suggestion" - Graceful degradation: keyword rules remain primary, LLM messages shown as "AI suggestion"
- TODO(AGENTIC-9): Add trackEvent() calls for ai_context.enriched / ai_context.fallback_used - TODO-008: Wire trackEvent() calls for ai_context.enriched / ai_context.fallback_used
- [ ] Tests for AI context generation (commit: ) - [ ] Tests for AI context generation (commit: )
- TODO(AGENTIC-10): Add unit tests for generateContextMessage() and fetchEnrichedMessage() - TODO-009: Add unit tests for generateContextMessage() and fetchEnrichedMessage()
### Phase A Exit Criteria ### Phase A Exit Criteria

View File

@ -11,7 +11,11 @@ export default function GlobalError({
reset: () => void; reset: () => void;
}) { }) {
useEffect(() => { useEffect(() => {
// TODO: send to telemetry once wired // TODO-011: Wire error boundary to telemetry
// Priority: low | Phase: cleanup
// Import { trackEvent } from '../lib/telemetry' and send error details:
// trackEvent('app.error_boundary', { message: error.message, digest: error.digest })
// Also consider sending to @bytelyst/diagnostics-client if available.
}, [error]); }, [error]);
return ( return (

View File

@ -17,12 +17,17 @@ export function Providers({ children }: { children: ReactNode }) {
initTelemetry(); initTelemetry();
initFeatureFlags(); initFeatureFlags();
initDiagnostics(); initDiagnostics();
// TODO(AGENTIC-1): When kill switch returns disabled=true, show a maintenance // TODO-001: Kill switch maintenance banner
// banner and disable timer creation. For now we just log and check. // Priority: medium | Phase: 0
// When checkKillSwitch() returns disabled=true:
// 1. Set a React state flag (e.g. `isMaintenanceMode`)
// 2. Render a <MaintenanceBanner /> component at the top of the app
// 3. Disable timer creation buttons (pass flag via context or prop)
// 4. Use --cm-warning-* design tokens for the banner styling
// File to create: web/src/components/MaintenanceBanner.tsx
checkKillSwitch().then((disabled) => { checkKillSwitch().then((disabled) => {
if (disabled) { if (disabled) {
// TODO(AGENTIC-2): Render a visible maintenance banner component here. // TODO-001: Surface this in the UI (see instructions above)
// For now, the kill switch result is available but not surfaced in UI.
} }
}); });
}, []); }, []);

View File

@ -192,8 +192,13 @@ export async function fetchEnrichedMessage(params: {
timeOfDay?: string; timeOfDay?: string;
recentTimerLabels?: string[]; recentTimerLabels?: string[];
}): Promise<EnrichedMessageResult> { }): Promise<EnrichedMessageResult> {
// TODO(AGENTIC-7): Use the backend URL from product-config or auth-api // TODO-006: Centralize backend URL configuration
// instead of relative path, once backend is always available. // Priority: low | Phase: A.4
// Replace the raw env var with a centralized config helper. Options:
// 1. Create getBackendUrl() in web/src/lib/product-config.ts that reads
// NEXT_PUBLIC_CHRONOMIND_BACKEND_URL with a sensible default
// 2. Or use the same pattern as auth-api.ts getBaseUrl() but for the product backend
// This avoids scattering env var references across multiple files.
try { try {
const backendUrl = process.env.NEXT_PUBLIC_CHRONOMIND_BACKEND_URL ?? 'http://localhost:4011'; const backendUrl = process.env.NEXT_PUBLIC_CHRONOMIND_BACKEND_URL ?? 'http://localhost:4011';
const res = await fetch(`${backendUrl}/api/context-message`, { const res = await fetch(`${backendUrl}/api/context-message`, {

View File

@ -3,6 +3,13 @@
* *
* Mirrors backend/src/lib/telemetry-events.ts single source of truth * Mirrors backend/src/lib/telemetry-events.ts single source of truth
* for event names used in React components and API clients (Phase 0.5). * for event names used in React components and API clients (Phase 0.5).
*
* TODO-008: Wire trackEvent() calls into web components
* Priority: medium | Phase: B
* These constants are defined but not yet imported anywhere. Wire them in:
* - Dashboard.tsx or agent inbox component: AGENT_INBOX_ACTION_APPROVED / REJECTED
* - Context message display component: AI_CONTEXT_ENRICHED / AI_CONTEXT_FALLBACK_USED
* - Use: import { trackEvent } from './telemetry' then trackEvent(EVENT_NAME, { ...metadata })
*/ */
// ── MCP ─────────────────────────────────────────────────────── // ── MCP ───────────────────────────────────────────────────────