chore: bootstrap SDLC state machine for tidalDB

- Initialize .sdlc/ with config, guidance, and state machine
- Register M0-M8 as released milestones (full engine track history)
- Seed M9 (Community Sync & Revocation) and M10 (Governance & Agent Rights) with features
- Seed product milestones P0-P4 and PG1 gate with features from existing planning docs
- Add Team section to AGENTS.md (tidal-engineer, tidal-visionary, tidal-researcher, tidal-storyteller)
- Add knowledge-librarian agent for .sdlc/knowledge/ curation
- Gitignore .sdlc/telemetry.redb (volatile binary state)
- Add .ai/ scaffold (project knowledge index)
This commit is contained in:
jordan 2026-03-03 00:41:41 -07:00
parent 3cc798fc15
commit 5ceef74f3b
67 changed files with 4675 additions and 0 deletions

35
.ai/index.md Normal file
View File

@ -0,0 +1,35 @@
# .ai Index
Project knowledge base. Entries are organized by category.
## Categories
- **patterns/** — How we do things (coding patterns, architectural conventions)
- **decisions/** — Why we chose X over Y (ADRs, trade-off notes)
- **gotchas/** — Non-obvious pitfalls and workarounds
- **architecture/** — How the system works (data flow, component relationships)
- **conventions/** — Naming, style, standards
## Usage
Entries are harvested automatically after each SDLC artifact is approved.
Each entry follows the format:
```
---
category: patterns
title: How we handle X
learned: YYYY-MM-DD
source: spec|design|review|human
confidence: high|medium|low
---
## Summary
...
## Key Facts
- ...
## File Pointer
`path/to/file.go:line-range`
```

View File

@ -0,0 +1,56 @@
---
model: claude-sonnet-4-6
description: Knowledge librarian for tidalDB — classifies, cross-references, and maintains the project knowledge base
tools: Bash, Read, Write, Edit, Glob, Grep
---
# Knowledge Librarian: tidalDB
You are the knowledge librarian for **tidalDB**. You curate the project knowledge base at `.sdlc/knowledge/` — classifying entries, filling summaries, cross-referencing related work, and publishing entries that are complete.
## Current Catalog
```yaml
classes:
- code: '100'
name: Core Thesis
- code: '200'
name: Domain Model
- code: '300'
name: Module Structure
- code: '400'
name: Storage Architecture
- code: '500'
name: Signal System
- code: '600'
name: Vector Index
- code: '700'
name: Text Search
updated_at: 2026-03-03T06:23:11.759071Z
```
## Core Commands
```bash
sdlc knowledge status # overview
sdlc knowledge list # all entries
sdlc knowledge list --code-prefix 100 # filter by class
sdlc knowledge show <slug> # read an entry
sdlc knowledge update <slug> --code 100.20 # reclassify
sdlc knowledge update <slug> --status published # publish
sdlc knowledge search "<query>" # full-text search
```
## Your Protocol
When asked to maintain the knowledge base:
1. `sdlc knowledge list` — identify entries with `code: uncategorized`
2. Classify each based on title, summary, and tags using the catalog above
3. Fill missing summaries (1-2 sentences, key insight only)
4. Find cross-references: entries with overlapping topics → add to `related[]`
5. Publish entries that are complete and accurate
When adding new knowledge from a workspace:
- Set `origin: harvested`, `harvested_from: "investigation/<slug>"` or `"ponder/<slug>"`
- Write durable insights only — decisions, conclusions, patterns. Not raw dialogue.
- Start with `status: draft`; publish when the content is solid

2
.gitignore vendored
View File

@ -34,3 +34,5 @@ Thumbs.db
# Ephemeral / scratch
tmp/
.claude/worktrees/
.sdlc/tools/*/index/
.sdlc/telemetry.redb

33
.sdlc/config.yaml Normal file
View File

@ -0,0 +1,33 @@
version: 1
project:
name: tidalDB
description: "Single-node embeddable Rust database for the personalized content ranking problem — replaces the 6-system stack (Elasticsearch + Redis + Kafka + feature store + vector DB + ranking service) with one process, one query interface, one operational model."
phases:
enabled:
- draft
- specified
- planned
- ready
- implementation
- review
- audit
- qa
- merge
- released
required_artifacts:
merge:
- qa_results
audit:
- review
specified:
- spec
qa:
- audit
review:
- review
planned:
- spec
- design
- tasks
- qa_plan
sdlc_version: 0.1.0

View File

@ -0,0 +1,74 @@
slug: m10-agent-capability-boundaries
title: Agent Capability Boundaries
description: Per-agent permission scopes controlling read/write access to signal types, user attributes, and ranking profile overrides — enforced at the session layer
phase: draft
created_at: 2026-03-03T06:29:45.070060Z
updated_at: 2026-03-03T06:29:45.070060Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m10-agent-capability-boundaries/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:45.070060Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m10-community-policy-engine
title: Community Policy Engine
description: Declarative schema-level rules governing which signal types community members can read/write; policy is versioned alongside data
phase: draft
created_at: 2026-03-03T06:29:45.064231Z
updated_at: 2026-03-03T06:29:45.064231Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m10-community-policy-engine/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m10-community-policy-engine/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m10-community-policy-engine/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m10-community-policy-engine/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m10-community-policy-engine/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m10-community-policy-engine/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m10-community-policy-engine/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:45.064231Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m10-signal-revocation-controls
title: Signal Revocation Controls
description: User-facing controls to selectively revoke signal contributions from ranking — scoped by signal type, time range, or agent identity
phase: draft
created_at: 2026-03-03T06:29:45.075886Z
updated_at: 2026-03-03T06:29:45.075886Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m10-signal-revocation-controls/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m10-signal-revocation-controls/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m10-signal-revocation-controls/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m10-signal-revocation-controls/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m10-signal-revocation-controls/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m10-signal-revocation-controls/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m10-signal-revocation-controls/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:45.075886Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m9-community-profile-sync
title: Community Profile Sync
description: Opt-in sharing from local embedded profiles to community personalization layers — events flow to shared aggregates while local WAL remains primary
phase: draft
created_at: 2026-03-03T06:29:37.975394Z
updated_at: 2026-03-03T06:29:37.975394Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m9-community-profile-sync/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m9-community-profile-sync/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m9-community-profile-sync/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m9-community-profile-sync/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m9-community-profile-sync/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m9-community-profile-sync/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m9-community-profile-sync/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:37.975394Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m9-leave-revocation
title: Leave & Stop-Forward
description: 'User leaves community layer: stop forwarding new signals, snapshot current contribution boundary, allow opt-back-in path'
phase: draft
created_at: 2026-03-03T06:29:37.984740Z
updated_at: 2026-03-03T06:29:37.984740Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m9-leave-revocation/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m9-leave-revocation/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m9-leave-revocation/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m9-leave-revocation/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m9-leave-revocation/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m9-leave-revocation/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m9-leave-revocation/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:37.984740Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m9-purge-rematerialization
title: Re-materialization after Purge
description: 'Background re-materialization engine: replay community WAL minus purged contributions, verify aggregate convergence, emit audit log'
phase: draft
created_at: 2026-03-03T06:29:37.999341Z
updated_at: 2026-03-03T06:29:37.999341Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m9-purge-rematerialization/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m9-purge-rematerialization/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m9-purge-rematerialization/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m9-purge-rematerialization/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m9-purge-rematerialization/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m9-purge-rematerialization/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m9-purge-rematerialization/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:37.999341Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: m9-retroactive-purge
title: Retroactive Signal Purge
description: 'On explicit purge request: remove user''s contributed signals from community aggregates with deterministic re-materialization, preserving correctness'
phase: draft
created_at: 2026-03-03T06:29:37.992175Z
updated_at: 2026-03-03T06:29:37.992175Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/m9-retroactive-purge/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/m9-retroactive-purge/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/m9-retroactive-purge/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/m9-retroactive-purge/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/m9-retroactive-purge/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/m9-retroactive-purge/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/m9-retroactive-purge/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:37.992175Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p0-concierge-pilot-loop
title: Concierge Pilot Loop
description: Daily briefing workflow with manual QA process and interview cadence — run for 2 weeks with pilot cohort
phase: draft
created_at: 2026-03-03T06:29:53.125784Z
updated_at: 2026-03-03T06:29:53.125784Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p0-concierge-pilot-loop/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:53.125784Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p0-target-segment-recruitment
title: Target Segment & Recruitment
description: Define persona, write recruitment script, build candidate pool of 20-50 target users for concierge pilot
phase: draft
created_at: 2026-03-03T06:29:53.119870Z
updated_at: 2026-03-03T06:29:53.119870Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p0-target-segment-recruitment/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p0-target-segment-recruitment/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p0-target-segment-recruitment/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p0-target-segment-recruitment/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p0-target-segment-recruitment/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p0-target-segment-recruitment/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p0-target-segment-recruitment/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:53.119870Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p0-validation-readout
title: Validation Readout
description: Analyze retention metrics and qualitative interviews; produce go/no-go decision for P1 Concierge Alpha build
phase: draft
created_at: 2026-03-03T06:29:53.132476Z
updated_at: 2026-03-03T06:29:53.132476Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p0-validation-readout/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p0-validation-readout/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p0-validation-readout/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p0-validation-readout/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p0-validation-readout/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p0-validation-readout/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p0-validation-readout/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:29:53.132476Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p1-briefing-ux-reason-labels
title: Briefing UX & Reason Labels
description: Card UI spec with reasons taxonomy and source exposure rules — users see why each item was surfaced
phase: draft
created_at: 2026-03-03T06:30:00.900430Z
updated_at: 2026-03-03T06:30:00.900430Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p1-briefing-ux-reason-labels/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:00.900430Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p1-feedback-loop-ux
title: Feedback Loop UX
description: Mute/hide/like controls with immediate next-refresh reflection; negative feedback visible in ranking within one refresh cycle
phase: draft
created_at: 2026-03-03T06:30:00.907261Z
updated_at: 2026-03-03T06:30:00.907261Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p1-feedback-loop-ux/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p1-feedback-loop-ux/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p1-feedback-loop-ux/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p1-feedback-loop-ux/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p1-feedback-loop-ux/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p1-feedback-loop-ux/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p1-feedback-loop-ux/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:00.907261Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p1-quality-diversity-baseline
title: Quality & Diversity Baseline
description: Quality gates (min completion rate, score threshold) and diversity constraints active in top results; no creator dominates the brief
phase: draft
created_at: 2026-03-03T06:30:00.913882Z
updated_at: 2026-03-03T06:30:00.913882Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p1-quality-diversity-baseline/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:00.913882Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p2-cohort-context-views
title: Cohort & Context Views
description: Cohort-trending surface ('what's trending for people like you') and session context mode visible in the product
phase: draft
created_at: 2026-03-03T06:30:32.880500Z
updated_at: 2026-03-03T06:30:32.880500Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p2-cohort-context-views/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p2-cohort-context-views/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p2-cohort-context-views/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p2-cohort-context-views/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p2-cohort-context-views/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p2-cohort-context-views/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p2-cohort-context-views/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:32.880500Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p2-self-serve-onboarding
title: Self-Serve Onboarding
description: Onboarding flow with defaults and profile bootstrap; new user goes from signup to first ranked brief in under 3 minutes
phase: draft
created_at: 2026-03-03T06:30:32.874164Z
updated_at: 2026-03-03T06:30:32.874164Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p2-self-serve-onboarding/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p2-self-serve-onboarding/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p2-self-serve-onboarding/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p2-self-serve-onboarding/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p2-self-serve-onboarding/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p2-self-serve-onboarding/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p2-self-serve-onboarding/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:32.874164Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p2-trust-controls
title: Trust Controls & Transparency
description: Mute/hide persistence across sessions, quality signals visible to user, explanation UX for why items were surfaced
phase: draft
created_at: 2026-03-03T06:30:32.887225Z
updated_at: 2026-03-03T06:30:32.887225Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p2-trust-controls/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p2-trust-controls/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p2-trust-controls/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p2-trust-controls/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p2-trust-controls/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p2-trust-controls/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p2-trust-controls/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:32.887225Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p3-launch-support-playbook
title: Launch & Support Playbook
description: Launch checklist, incident roles, communication templates — team can respond to production issues within defined SLA
phase: draft
created_at: 2026-03-03T06:31:12.118394Z
updated_at: 2026-03-03T06:31:12.118394Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p3-launch-support-playbook/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p3-launch-support-playbook/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p3-launch-support-playbook/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p3-launch-support-playbook/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p3-launch-support-playbook/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p3-launch-support-playbook/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p3-launch-support-playbook/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.118394Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p3-quality-operations
title: Quality Operations
description: Quality floor checks, regression dashboards, and alerting — no launch-day quality regressions ship undetected
phase: draft
created_at: 2026-03-03T06:31:12.111740Z
updated_at: 2026-03-03T06:31:12.111740Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p3-quality-operations/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p3-quality-operations/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p3-quality-operations/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p3-quality-operations/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p3-quality-operations/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p3-quality-operations/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p3-quality-operations/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.111740Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p3-reliability-slos
title: Reliability & SLOs
description: Launch SLOs defined, monitoring active, error budgets tracked — briefing generation latency and availability within targets
phase: draft
created_at: 2026-03-03T06:31:12.104755Z
updated_at: 2026-03-03T06:31:12.104755Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p3-reliability-slos/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p3-reliability-slos/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p3-reliability-slos/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p3-reliability-slos/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p3-reliability-slos/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p3-reliability-slos/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p3-reliability-slos/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.104755Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p4-monetization-experiments
title: Monetization Experiments
description: Pricing tests, conversion funnel instrumentation — measure which monetization model converts without degrading quality metrics
phase: draft
created_at: 2026-03-03T06:31:12.151616Z
updated_at: 2026-03-03T06:31:12.151616Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p4-monetization-experiments/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p4-monetization-experiments/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p4-monetization-experiments/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p4-monetization-experiments/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p4-monetization-experiments/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p4-monetization-experiments/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p4-monetization-experiments/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.151616Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p4-quality-safe-growth
title: Quality-Safe Growth
description: Guardrails that prevent revenue-over-quality regressions as user volume scales — D7 retention and useful-item rate monitored against growth rate
phase: draft
created_at: 2026-03-03T06:31:12.156872Z
updated_at: 2026-03-03T06:31:12.156872Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p4-quality-safe-growth/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p4-quality-safe-growth/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p4-quality-safe-growth/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p4-quality-safe-growth/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p4-quality-safe-growth/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p4-quality-safe-growth/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p4-quality-safe-growth/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.156872Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: p4-segment-expansion-plan
title: Segment Expansion Plan
description: Data-backed expansion strategy and milestone proposal for next target segment or market
phase: draft
created_at: 2026-03-03T06:31:12.162327Z
updated_at: 2026-03-03T06:31:12.162327Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/p4-segment-expansion-plan/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/p4-segment-expansion-plan/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/p4-segment-expansion-plan/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/p4-segment-expansion-plan/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/p4-segment-expansion-plan/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/p4-segment-expansion-plan/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/p4-segment-expansion-plan/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:31:12.162327Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: pg1-baseline-comparison
title: Baseline Comparison Study
description: A/B comparison against a non-personalized baseline feed — measure click-through, completion, and return rate lift
phase: draft
created_at: 2026-03-03T06:30:08.984440Z
updated_at: 2026-03-03T06:30:08.984440Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/pg1-baseline-comparison/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/pg1-baseline-comparison/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/pg1-baseline-comparison/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/pg1-baseline-comparison/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/pg1-baseline-comparison/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/pg1-baseline-comparison/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/pg1-baseline-comparison/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:08.984440Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: pg1-instrumented-metrics
title: Instrumented Metrics Pipeline
description: Signal write rates, ranking latency p50/p99, personalization staleness, and feedback-loop latency all observable in production
phase: draft
created_at: 2026-03-03T06:30:08.990467Z
updated_at: 2026-03-03T06:30:08.990467Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/pg1-instrumented-metrics/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/pg1-instrumented-metrics/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/pg1-instrumented-metrics/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/pg1-instrumented-metrics/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/pg1-instrumented-metrics/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/pg1-instrumented-metrics/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/pg1-instrumented-metrics/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:08.990467Z
exited: null
dependencies: []
archived: false
schema_version: 2

View File

@ -0,0 +1,74 @@
slug: pg1-personalization-correctness
title: Personalization Correctness Verification
description: Verify the personalization loop (signal write → decay → ranking) is mathematically correct and updates within 100ms of signal write
phase: draft
created_at: 2026-03-03T06:30:08.977848Z
updated_at: 2026-03-03T06:30:08.977848Z
artifacts:
- artifact_type: spec
status: missing
path: .sdlc/features/pg1-personalization-correctness/spec.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: design
status: missing
path: .sdlc/features/pg1-personalization-correctness/design.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: tasks
status: missing
path: .sdlc/features/pg1-personalization-correctness/tasks.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_plan
status: missing
path: .sdlc/features/pg1-personalization-correctness/qa-plan.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: review
status: missing
path: .sdlc/features/pg1-personalization-correctness/review.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: audit
status: missing
path: .sdlc/features/pg1-personalization-correctness/audit.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
- artifact_type: qa_results
status: missing
path: .sdlc/features/pg1-personalization-correctness/qa-results.md
created_at: null
approved_at: null
rejected_at: null
rejection_reason: null
approved_by: null
tasks: []
comments: []
next_comment_seq: 0
blockers: []
phase_history:
- phase: draft
entered: 2026-03-03T06:30:08.977848Z
exited: null
dependencies: []
archived: false
schema_version: 2

204
.sdlc/guidance.md Normal file
View File

@ -0,0 +1,204 @@
# Engineering Guidance
Read this before any implementation, bug fix, or test action.
## North Star: Vision & Architecture
Before writing a single line of code, read:
- **`VISION.md`** — *what* we are building and *why*. Every feature, every tradeoff, every design decision must serve this vision. If a proposed change works against it, surface it before proceeding.
- **`ARCHITECTURE.md`** — *how* the system works. Components, interfaces, data flows, and sequence diagrams showing how everything fits together. Code must conform to the architecture — never silently deviate.
These are the guiding light. When in doubt about any decision, return to them first.
## 1. Build It Right
Do it the proper way — not the quick way. The correct solution is one that
will still be correct in six months. Favor proven patterns, clear
abstractions, and designs that are easy to understand and extend. Never
trade long-term correctness for short-term convenience.
## 2. Understand Bugs Before Fixing Them
Before touching a bug, trace its root cause holistically — read surrounding
code, follow the data flow, understand why it broke. Fix the cause, not the
symptom. A patch that introduces a new bug in three months is worse than
no fix.
## 3. Enterprise Quality Bar
We build enterprise-grade software. The bar is Steve Jobs: relentless
attention to detail, nothing ships that embarrasses us, correctness and
reliability are non-negotiable. If something isn't right, make it right.
## 4. Philosophy of Software Design
Follow John Ousterhout's principles: deep modules, minimal exposed
complexity, interfaces that hide implementation detail, and code readable
in isolation. Complexity is the enemy — fight it at every level.
## 5. Meaningful, Reliable, Fast Tests
Tests must earn their place. When a test breaks, choose deliberately:
- **Remove** — if it adds little value or tests implementation detail
- **Rewrite** — if it was poorly structured for the scenario
- **Refactor** — if the interface it tests changed legitimately
- **Quick-fix** — only if the fix is obvious and the test is clearly valuable
Never keep a flaky or low-value test just to preserve coverage numbers.
## 6. Using sdlc
All state lives in `.sdlc/` YAML files. **Never edit them directly** — use the CLI.
Direct edits cause deserialization failures and corrupt state.
| Action | Command |
|---|---|
| Create feature | `sdlc feature create <slug> --title "…"` |
| Get next action | `sdlc next --for <slug> --json` |
| Write artifact | Write Markdown to `output_path` from the directive |
| Submit draft | `sdlc artifact draft <slug> <type>` |
| Approve artifact | `sdlc artifact approve <slug> <type>` |
| Reject artifact | `sdlc artifact reject <slug> <type>` |
| Merge (release feature) | `sdlc merge <slug>` |
| Add task | `sdlc task add <slug> "title"` |
| Start task | `sdlc task start <slug> <task-id>` |
| Complete task | `sdlc task complete <slug> <task-id>` |
| Block task | `sdlc task block <slug> <task-id> "reason"` |
| Add comment | `sdlc comment create <slug> "body"` |
| Show feature | `sdlc feature show <slug> --json` |
| List tasks | `sdlc task list <slug>` |
| Project state | `sdlc state` |
| Survey milestone waves | `sdlc project prepare [--milestone <slug>]` |
| Mark milestone prepared | `sdlc milestone mark-prepared <slug>` |
| Project phase | `sdlc project status` |
| Escalate to human | `sdlc escalate create --kind <kind> --title "…" --context "…" [--feature <slug>]` |
| List escalations | `sdlc escalate list` |
| Resolve escalation | `sdlc escalate resolve <id> "resolution note"` |
| Knowledge base status | `sdlc knowledge status` |
| List knowledge entries | `sdlc knowledge list [--code-prefix <code>]` |
| Search knowledge base | `sdlc knowledge search <query>` |
| Show knowledge entry | `sdlc knowledge show <slug>` |
| Add knowledge entry | `sdlc knowledge add --title "..." --code <code> --content "..."` |
| Show catalog taxonomy | `sdlc knowledge catalog show` |
| Seed from workspaces | `sdlc knowledge librarian init` |
Phases advance automatically from artifact approvals — never call `sdlc feature transition`.
The only files you write directly are Markdown artifacts to `output_path`.
## 7. SDLC Tool Suite
Project-scoped TypeScript tools in `.sdlc/tools/` — callable by agents and humans during any lifecycle phase.
Read `.sdlc/tools/tools.md` for the full list, or each tool's `README.md` for detailed docs.
| Tool | Command | Purpose |
|---|---|---|
| ama | `sdlc tool run ama --setup` then `sdlc tool run ama --question "..."` | Search codebase for relevant file excerpts |
Build a custom tool: `sdlc tool scaffold <name> "<description>"`
Update the manifest after adding/changing tools: `sdlc tool sync`
## 8. Project Secrets
Encrypted secrets live in `.sdlc/secrets/`. The encrypted files (`.age`) and key
name sidecars (`.meta.yaml`) are **safe to commit**. Plain `.env.*` files must never
be committed — they are gitignored automatically.
| Action | Command |
|---|---|
| List environments | `sdlc secrets env list` |
| List key names (no decrypt) | `sdlc secrets env names <env>` |
| Load secrets into shell | `eval $(sdlc secrets env export <env>)` |
| Set a secret | `sdlc secrets env set <env> KEY=value` |
| List authorized keys | `sdlc secrets keys list` |
| Add a key | `sdlc secrets keys add --name <n> --key "$(cat ~/.ssh/id_ed25519.pub)"` |
| Rekey after key change | `sdlc secrets keys rekey` |
**For agents:** Check `sdlc secrets env names <env>` to see which variables are
available. Load the matching env before any task or build step that needs credentials:
- Feature/local work → `eval $(sdlc secrets env export development)`
- Deploy tasks → `eval $(sdlc secrets env export production)`
Never log or hardcode secret values. Reference by env var name only (e.g. `$ANTHROPIC_API_KEY`).
**In builds:** The vault is for local and agent use only. CI/CD platforms (GitHub Actions,
etc.) manage their own secrets separately — agents cannot inject into platform CI secrets.
If a build needs a credential that must live in CI, use `secret_request` escalation (§9).
## 9. Escalating to the Human
Escalations are for **actions only a human can take**. They are rare and deliberate — not a
general-purpose communication channel. Before escalating, ask: "Can I resolve this myself?"
If yes, do it. If not, escalate.
| Kind | When to escalate | Example |
|---|---|---|
| `secret_request` | Need a credential or env var that doesn't exist | "Add STRIPE_API_KEY to production env in Secrets page" |
| `question` | Strategic decision with no clear right answer | "Should checkout support crypto payments?" |
| `vision` | Product direction is undefined or contradictory | "No vision defined — what is the milestone goal?" |
| `manual_test` | Testing requires physical interaction | "Verify Google OAuth login in production browser" |
**Do NOT escalate:** code review findings, spec ambiguity you can resolve, implementation
decisions, anything an agent can handle autonomously.
**How to escalate:**
```bash
sdlc escalate create \
--kind secret_request \
--title "Need OPENAI_API_KEY in .env.production" \
--context "AI summary feature calls OpenAI in prod. Dev works with a mock. Need the real key to test end-to-end." \
--feature my-ai-feature # omit if not feature-specific
```
**After creating:** stop the current run immediately. If `--feature` was specified, the feature
is now gated by an auto-added Blocker comment. The escalation appears in the Dashboard under
**"Needs Your Attention"**. The human must act before the feature can proceed.
**The difference from `comment --flag blocker`:**
- `comment --flag blocker` — an implementation concern the next agent cycle might fix
- `sdlc escalate create` — an action only a human can perform; stop until resolved
## 10. Frontend API Calls
Never hardcode `http://localhost:PORT` in frontend code — CORS blocks cross-origin
requests in development and the address is wrong in production.
**Pattern:**
- Use a relative base URL (`/api`) in all fetch/client code
- Configure the dev server proxy (Vite `server.proxy`, Next.js `rewrites`,
webpack `devServer.proxy`) to forward `/api``http://localhost:<API_PORT>`
- In production, frontend and API share the same origin — relative paths resolve correctly
When fixing a CORS error or adding a new API client, apply this pattern instead of
adding CORS headers or introducing environment-specific URLs.
## 11. Production Safety
This is a live system with real users. Every change must leave the codebase healthier — not just correct, but cleaner.
**Migrations:** Add defensive deserialization before removing old formats. Never the reverse. Test that both old and new formats load cleanly before shipping.
**Stability hazards to avoid:**
- Infinite loops: any polling, retry, or SSE reconnect loop must have a termination condition and backoff
- Connection exhaustion: SSE subscriptions, DB connections, and broadcast channels must be bounded and cleaned up on drop
- Complex failure modes: prefer simple, flat control flow over deeply nested async chains — when it breaks at 3am, you must be able to read the trace
**Quality bar:** if a change makes the code harder to reason about, makes logs less useful, or adds a failure mode with no clear recovery path — stop and reconsider. Simpler is always better.
## 12. Project Guidelines
Before writing implementation code, check if `.sdlc/guidelines/index.yaml` exists.
If it does, read it and load any guidelines whose `scope` overlaps with the work at hand.
```bash
# Check
ls .sdlc/guidelines/index.yaml 2>/dev/null && cat .sdlc/guidelines/index.yaml
```
Guidelines contain `⚑ Rule:` statements with `✓ Good:` and `✗ Bad:` code examples derived
from this codebase. They are authoritative — if your implementation would violate a rule,
fix the approach before proceeding, not after review catches it.
If no index exists, no guidelines have been published yet. Proceed normally.

View File

@ -0,0 +1,16 @@
classes:
- code: '100'
name: Core Thesis
- code: '200'
name: Domain Model
- code: '300'
name: Module Structure
- code: '400'
name: Storage Architecture
- code: '500'
name: Signal System
- code: '600'
name: Vector Index
- code: '700'
name: Text Search
updated_at: 2026-03-03T06:23:11.759071Z

View File

@ -0,0 +1,4 @@
actions:
- timestamp: 2026-03-03T06:23:11.759775Z
action_type: harvest
detail: librarian init

View File

@ -0,0 +1,8 @@
slug: m0
title: Embeddable Runtime
vision: A developer can cargo add tidalDB, run it in-process with zero config, and get structured diagnostics — proving the embeddable runtime baseline works.
features: []
created_at: 2026-03-03T06:29:10.012810Z
updated_at: 2026-03-03T06:29:28.060717Z
released_at: 2026-03-03T06:29:28.060716Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m1
title: Signal Engine
vision: A developer can write engagement signals and see O(1) decay scores and windowed aggregates — proving signals are a database primitive, not application math.
features: []
created_at: 2026-03-03T06:29:10.025474Z
updated_at: 2026-03-03T06:29:28.068009Z
released_at: 2026-03-03T06:29:28.068009Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: m10
title: Governance & Agent Rights
vision: Community rules and agent-scoped permissions control what signals influence ranking — users and communities can control exactly which signals affect their experience and revoke them safely.
features:
- m10-community-policy-engine
- m10-agent-capability-boundaries
- m10-signal-revocation-controls
created_at: 2026-03-03T06:29:45.049713Z
updated_at: 2026-03-03T06:29:45.091585Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m2
title: Ranked Retrieval
vision: A single RETRIEVE query retrieves, scores, and ranks content using live signals — proving one query replaces what 6 systems currently produce.
features: []
created_at: 2026-03-03T06:29:10.032247Z
updated_at: 2026-03-03T06:29:28.074556Z
released_at: 2026-03-03T06:29:28.074555Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m3
title: Personalized Ranking
vision: User context shapes retrieval and ranking — the 'For You' query works correctly, reflecting user preferences, relationships, and session context.
features: []
created_at: 2026-03-03T06:29:10.038502Z
updated_at: 2026-03-03T06:29:28.080975Z
released_at: 2026-03-03T06:29:28.080974Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m4
title: Agent Memory
vision: Agents can create sessions, write signals with policy constraints, and enforce rate limits inside tidalDB — enabling RLHF loops and conversational memory.
features: []
created_at: 2026-03-03T06:29:10.045226Z
updated_at: 2026-03-03T06:29:28.086574Z
released_at: 2026-03-03T06:29:28.086574Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m5
title: Hybrid Search
vision: Text + semantic + signal-ranked search in one query — BM25, ANN, and personalization fused via RRF, enabling UC-02, UC-10, UC-11.
features: []
created_at: 2026-03-03T06:29:10.052823Z
updated_at: 2026-03-03T06:29:28.091698Z
released_at: 2026-03-03T06:29:28.091698Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m6
title: Full Surface Coverage
vision: Every use case, every sort mode, every filter, every feedback loop is operational — UC-01 through UC-14 complete in a single database.
features: []
created_at: 2026-03-03T06:29:10.059993Z
updated_at: 2026-03-03T06:29:28.097611Z
released_at: 2026-03-03T06:29:28.097611Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m7
title: Production Hardening
vision: Crash safety, graceful degradation, rate limiting, and operational readiness — all UCs at production quality with diagnostics and telemetry.
features: []
created_at: 2026-03-03T06:29:10.067328Z
updated_at: 2026-03-03T06:29:28.103643Z
released_at: 2026-03-03T06:29:28.103643Z
schema_version: 1

View File

@ -0,0 +1,8 @@
slug: m8
title: Distributed Fabric
vision: Multi-region, multi-tenant WAL replication with CRDT reconciliation keeps agent-memory semantics intact across distributed deployments.
features: []
created_at: 2026-03-03T06:29:10.075155Z
updated_at: 2026-03-03T06:29:28.109493Z
released_at: 2026-03-03T06:29:28.109493Z
schema_version: 1

View File

@ -0,0 +1,11 @@
slug: m9
title: Community Sync & Revocation
vision: A user can opt their local profile into community personalization, and later leave — removing their contributions from future ranking without destroying local history.
features:
- m9-community-profile-sync
- m9-leave-revocation
- m9-retroactive-purge
- m9-purge-rematerialization
created_at: 2026-03-03T06:29:37.962436Z
updated_at: 2026-03-03T06:29:38.030047Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: p0
title: Beachhead Validation
vision: 20-50 target users complete a 2-week concierge pilot with D2/D7 retention measured — proving the personal briefing concept is valuable enough for repeated use.
features:
- p0-target-segment-recruitment
- p0-concierge-pilot-loop
- p0-validation-readout
created_at: 2026-03-03T06:29:53.105359Z
updated_at: 2026-03-03T06:29:53.150315Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: p1
title: Concierge Alpha
vision: Daily ranked brief is live for pilot cohort with reason labels, immediate feedback adaptation, and time-budget mode — proving 'Today Brief' usefulness and repeat behavior.
features:
- p1-briefing-ux-reason-labels
- p1-feedback-loop-ux
- p1-quality-diversity-baseline
created_at: 2026-03-03T06:30:00.885199Z
updated_at: 2026-03-03T06:30:00.932156Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: p2
title: Productized Beta
vision: Self-serve onboarding under 3 minutes, cohort-scoped trending surfaces, and trust controls active — D7 retention and useful-item rate exceed baseline comparison feed.
features:
- p2-self-serve-onboarding
- p2-cohort-context-views
- p2-trust-controls
created_at: 2026-03-03T06:30:32.861070Z
updated_at: 2026-03-03T06:30:32.903751Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: p3
title: Public Launch
vision: Briefing generation reliability SLOs met, quality floor enforced, support playbook active — product is ready for public users at real volume.
features:
- p3-reliability-slos
- p3-quality-operations
- p3-launch-support-playbook
created_at: 2026-03-03T06:31:12.087940Z
updated_at: 2026-03-03T06:31:12.135803Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: p4
title: Scale & Revenue Fit
vision: Monetization model validated, quality-safe growth guardrails active, next expansion segment chosen with evidence — sustainable unit economics proven at scale.
features:
- p4-monetization-experiments
- p4-quality-safe-growth
- p4-segment-expansion-plan
created_at: 2026-03-03T06:31:12.140928Z
updated_at: 2026-03-03T06:31:12.178883Z
schema_version: 1

View File

@ -0,0 +1,10 @@
slug: pg1
title: Personalization Core Done Gate
vision: Core personalization loop is correct, immediate, and measurably better than baseline — gate that must pass before P2 Productized Beta proceeds.
features:
- pg1-personalization-correctness
- pg1-baseline-comparison
- pg1-instrumented-metrics
created_at: 2026-03-03T06:30:08.963318Z
updated_at: 2026-03-03T06:30:09.007191Z
schema_version: 1

51
.sdlc/state.yaml Normal file
View File

@ -0,0 +1,51 @@
version: 1
project: tidalDB
active_features:
- m9-community-profile-sync
- m9-leave-revocation
- m9-retroactive-purge
- m9-purge-rematerialization
- m10-community-policy-engine
- m10-agent-capability-boundaries
- m10-signal-revocation-controls
- p0-target-segment-recruitment
- p0-concierge-pilot-loop
- p0-validation-readout
- p1-briefing-ux-reason-labels
- p1-feedback-loop-ux
- p1-quality-diversity-baseline
- pg1-personalization-correctness
- pg1-baseline-comparison
- pg1-instrumented-metrics
- p2-self-serve-onboarding
- p2-cohort-context-views
- p2-trust-controls
- p3-reliability-slos
- p3-quality-operations
- p3-launch-support-playbook
- p4-monetization-experiments
- p4-quality-safe-growth
- p4-segment-expansion-plan
active_directives: []
history: []
blocked: []
milestones:
- m0
- m1
- m2
- m3
- m4
- m5
- m6
- m7
- m8
- m9
- m10
- p0
- p1
- pg1
- p2
- p3
- p4
active_ponders: []
last_updated: 2026-03-03T06:31:12.162637Z

View File

@ -0,0 +1,44 @@
/**
* SDLC Tool Config Loader
*
* Reads .sdlc/tools/<name>/config.yaml. If the file is missing or unparseable,
* returns the provided defaults tools should never hard-fail on missing config.
*
* Supports flat key: value YAML only. Arrays and nested objects are intentionally
* not supported keep tool configs simple scalars.
*/
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
export function loadToolConfig<T extends Record<string, unknown>>(
root: string,
toolName: string,
defaults: T,
): T {
const configPath = join(root, '.sdlc', 'tools', toolName, 'config.yaml')
try {
const raw = readFileSync(configPath, 'utf8')
const parsed = parseSimpleYaml(raw)
return { ...defaults, ...parsed } as T
} catch {
return defaults
}
}
/** Parse a flat key: value YAML file. Skips blank lines, comments, and array items. */
function parseSimpleYaml(content: string): Record<string, unknown> {
const result: Record<string, unknown> = {}
for (const line of content.split('\n')) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue
const colonIdx = trimmed.indexOf(':')
if (colonIdx === -1) continue
const key = trimmed.slice(0, colonIdx).trim()
const rawValue = trimmed.slice(colonIdx + 1).trim()
if (!key || !rawValue) continue
const value = rawValue.replace(/^["'](.*)["']$/, '$1')
const num = Number(value)
result[key] = Number.isNaN(num) ? value : num
}
return result
}

View File

@ -0,0 +1,25 @@
/**
* Standard SDLC Tool Logger
*
* Writes structured log lines to STDERR (never stdout stdout is reserved
* for JSON output). Use this in every tool to produce consistent, parseable logs.
*
* Format: [sdlc-tool:<name>] LEVEL: message
* Example: [sdlc-tool:ama] INFO: Indexed 312 files in 842ms
*
* Set SDLC_TOOL_DEBUG=1 to enable debug-level output.
*/
export function makeLogger(toolName: string) {
const prefix = `[sdlc-tool:${toolName}]`
return {
info: (msg: string) => console.error(`${prefix} INFO: ${msg}`),
warn: (msg: string) => console.error(`${prefix} WARN: ${msg}`),
error: (msg: string) => console.error(`${prefix} ERROR: ${msg}`),
debug: (msg: string) => {
if (process.env.SDLC_TOOL_DEBUG) console.error(`${prefix} DEBUG: ${msg}`)
},
}
}
export type Logger = ReturnType<typeof makeLogger>

View File

@ -0,0 +1,68 @@
/**
* Cross-runtime helpers for Bun, Deno, and Node.
*
* Normalizes: argv access, stdin reading, env access, and process exit
* across the three supported runtimes.
*
* Detection: checks for globalThis.Deno to identify Deno; falls back
* to process (Node.js / Bun).
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
/** Returns command-line arguments after the script name (process.argv[2+]). */
export function getArgs(): string[] {
if (typeof (globalThis as any).Deno !== 'undefined') {
return [...(globalThis as any).Deno.args]
}
return process.argv.slice(2)
}
/** Read all of stdin as a UTF-8 string. Returns empty string if stdin is a TTY or closed. */
export async function readStdin(): Promise<string> {
if (typeof (globalThis as any).Deno !== 'undefined') {
const chunks: Uint8Array[] = []
const reader = (globalThis as any).Deno.stdin.readable.getReader()
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
}
} finally {
reader.releaseLock()
}
const total = chunks.reduce((sum: number, c: Uint8Array) => sum + c.length, 0)
const merged = new Uint8Array(total)
let offset = 0
for (const chunk of chunks) {
merged.set(chunk, offset)
offset += chunk.length
}
return new TextDecoder().decode(merged)
}
// Node.js / Bun
if ((process.stdin as any).isTTY) return ''
const chunks: Buffer[] = []
for await (const chunk of process.stdin) {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
}
return Buffer.concat(chunks).toString('utf8')
}
/** Get a process environment variable. Works across Bun, Deno, and Node. */
export function getEnv(key: string): string | undefined {
if (typeof (globalThis as any).Deno !== 'undefined') {
return (globalThis as any).Deno.env.get(key)
}
return process.env[key]
}
/** Exit the process with the given code. */
export function exit(code: number): never {
if (typeof (globalThis as any).Deno !== 'undefined') {
;(globalThis as any).Deno.exit(code)
}
process.exit(code)
throw new Error('unreachable')
}

View File

@ -0,0 +1,47 @@
/**
* SDLC Tool Shared Interface
*
* Every SDLC tool imports from this file. It defines the full type contract
* that tools must satisfy. Do not change the shape of these types without
* updating all core tools and regenerating tools.md.
*
* Tool protocol (stdin/stdout):
* - --meta No stdin. Writes ToolMeta JSON to stdout.
* - --run Reads JSON from stdin. Writes ToolResult JSON to stdout. Exit 0 ok, 1 error.
* - --setup No stdin. Writes ToolResult JSON to stdout. Exit 0 ok, 1 error.
*
* All log output goes to STDERR. STDOUT is reserved for JSON only.
*/
/** Metadata describing a tool — returned by --meta mode. */
export interface ToolMeta {
/** Matches the directory name exactly (e.g. "ama", "quality-check") */
name: string
/** Human-readable title shown in the tools list */
display_name: string
/** One sentence, present tense, no trailing period */
description: string
/** Semver, mirrors sdlc binary version at install time */
version: string
/** JSON Schema describing valid input for --run */
input_schema: JsonSchema
/** JSON Schema describing the data field in ToolResult */
output_schema: JsonSchema
/** True if --setup must run before first --run */
requires_setup: boolean
/** One sentence describing what setup does (required if requires_setup = true) */
setup_description?: string
}
/** The result envelope returned by --run and --setup modes. */
export interface ToolResult<T = unknown> {
ok: boolean
data?: T
/** Present only when ok = false */
error?: string
/** Wall-clock milliseconds for the operation */
duration_ms?: number
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type JsonSchema = Record<string, any>

36
.sdlc/tools/ama/README.md Normal file
View File

@ -0,0 +1,36 @@
# AMA — Ask Me Anything
Answers questions about the codebase by searching a pre-built keyword index.
## Setup (run once)
```bash
sdlc tool run ama --setup
```
## Usage
```bash
sdlc tool run ama --question "where is JWT validation?"
sdlc tool run ama --question "how does feature transition work?"
```
## How it works
1. `--setup` walks source files, chunks them into 40-line windows, extracts keyword tokens,
and writes `.sdlc/tools/ama/index/chunks.json`
2. `--run` scores chunks by keyword overlap with your question, returns top file excerpts
3. Your AI assistant reads the excerpts and synthesizes an answer
## Configuration
Edit `.sdlc/tools/ama/config.yaml` to change which file extensions are indexed
or to adjust chunk size, overlap, and result count.
## Index location
`.sdlc/tools/ama/index/chunks.json` — gitignored, regenerate with `--setup`
## Re-index when needed
Re-run `--setup` after significant file changes. It's fast and safe to run any time.

View File

@ -0,0 +1,18 @@
name: ama
version: 0.1.0
description: Answers questions about the codebase using a pre-built keyword index
# File extensions to include in the index (comma-separated)
extensions: .ts,.js,.tsx,.jsx,.rs,.go,.py,.rb,.java,.md,.txt,.yaml,.yml,.toml
# Number of lines per chunk
chunk_lines: 40
# Lines of overlap between consecutive chunks (reduces missed context at boundaries)
chunk_overlap: 5
# Maximum results to return per query
max_results: 5
# Skip files larger than this size (kilobytes)
max_file_kb: 500

506
.sdlc/tools/ama/tool.ts Normal file
View File

@ -0,0 +1,506 @@
/**
* AMA Ask Me Anything
* =====================
* Answers questions about the codebase by searching a pre-built keyword index.
*
* WHAT IT DOES
* ------------
* --setup: Walks all source files matching configured extensions. On first run,
* indexes every file. On subsequent runs, skips unchanged files (mtime
* check), re-indexes changed/new files, and prunes deleted files.
* Writes chunks.json (TF-IDF index) and last_indexed.json (mtime map).
* Re-running --setup is always safe (incremental or full).
*
* --run: Reads JSON from stdin: { "question": "string" }
* Loads the TF-IDF index, scores chunks by IDF-weighted keyword overlap,
* returns top results as source excerpts with relevance scores.
* Sources from files changed since last indexing are flagged stale.
*
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
*
* WHAT IT READS
* -------------
* - .sdlc/tools/ama/config.yaml (extensions, chunk settings)
* - .sdlc/tools/ama/index/chunks.json (built by --setup)
* - .sdlc/tools/ama/index/last_indexed.json (mtime map; built by --setup)
* - Source files matching config.extensions (during --setup only)
*
* WHAT IT WRITES
* --------------
* - .sdlc/tools/ama/index/chunks.json (during --setup; TF-IDF index)
* - .sdlc/tools/ama/index/last_indexed.json (during --setup; mtime map for incremental re-runs)
* - STDERR: structured log lines via _shared/log.ts
* - STDOUT: JSON only (ToolResult shape from _shared/types.ts)
*
* EXTENDING
* ---------
* Replace scoreChunks() with embedding-based cosine similarity to improve answer
* quality. The rest of the pipeline (chunking, index format, protocol) stays the same.
*
* For LLM synthesis: call the Claude API in run() with the top excerpts as context.
* Add "synthesis_model" to config.yaml to control which model is used.
*/
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
import { makeLogger } from '../_shared/log.ts'
import { loadToolConfig } from '../_shared/config.ts'
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
import {
readdirSync, readFileSync, writeFileSync, mkdirSync, statSync, existsSync,
} from 'node:fs'
import { join, extname, relative } from 'node:path'
const log = makeLogger('ama')
// ---------------------------------------------------------------------------
// Config
// ---------------------------------------------------------------------------
interface AmaConfig {
chunk_lines: number
chunk_overlap: number
max_results: number
max_file_kb: number
extensions: string
}
const DEFAULT_CONFIG: AmaConfig = {
chunk_lines: 40,
chunk_overlap: 5,
max_results: 5,
max_file_kb: 500,
extensions: '.ts,.js,.tsx,.jsx,.rs,.go,.py,.rb,.java,.md,.txt,.yaml,.yml,.toml',
}
// ---------------------------------------------------------------------------
// Tool metadata
// ---------------------------------------------------------------------------
export const meta: ToolMeta = {
name: 'ama',
display_name: 'AMA — Ask Me Anything',
description: 'Answers questions about the codebase using a pre-built TF-IDF keyword index',
version: '0.2.1',
requires_setup: true,
setup_description: 'Indexes source files for keyword search (first run is full index; subsequent runs are incremental)',
input_schema: {
type: 'object',
required: ['question'],
properties: {
question: { type: 'string', description: 'The question to answer about the codebase' },
},
},
output_schema: {
type: 'object',
properties: {
sources: {
type: 'array',
items: {
type: 'object',
properties: {
path: { type: 'string' },
lines: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },
excerpt: { type: 'string' },
score: { type: 'number', description: 'TF-IDF relevance score (0.01.0)' },
stale: { type: 'boolean', description: 'True if the source file changed since last index run' },
},
},
},
},
},
}
// ---------------------------------------------------------------------------
// Index types
// ---------------------------------------------------------------------------
interface Chunk {
path: string
start: number
end: number
tokens: string[]
}
interface Index {
version: number
generated: string
chunks: Chunk[]
idf: Record<string, number>
}
interface MtimeMap {
version: number
indexed_at: string
files: Record<string, number>
}
interface AmaSource {
path: string
lines: [number, number]
excerpt: string
score: number
stale?: boolean
}
interface AmaOutput {
sources: AmaSource[]
}
// ---------------------------------------------------------------------------
// Setup — build the keyword index
// ---------------------------------------------------------------------------
export async function setup(root: string): Promise<ToolResult<{
files_indexed: number
files_skipped: number
files_pruned: number
chunks_written: number
total_chunks: number
duration_ms: number
index_size_kb: number
}>> {
const start = Date.now()
const config = loadToolConfig(root, 'ama', DEFAULT_CONFIG)
const extensions = new Set(
String(config.extensions).split(',').map(e => e.trim()).filter(Boolean),
)
const indexDir = join(root, '.sdlc', 'tools', 'ama', 'index')
mkdirSync(indexDir, { recursive: true })
const chunksPath = join(indexDir, 'chunks.json')
const mtimePath = join(indexDir, 'last_indexed.json')
// Load previous index and mtime map for incremental re-indexing
let prevChunks: Chunk[] = []
let prevMtimes: Record<string, number> = {}
const isIncremental = existsSync(chunksPath) && existsSync(mtimePath)
if (isIncremental) {
try {
const prevIndex = JSON.parse(readFileSync(chunksPath, 'utf8')) as Index
prevChunks = prevIndex.chunks ?? []
const mtimeData = JSON.parse(readFileSync(mtimePath, 'utf8')) as MtimeMap
prevMtimes = mtimeData.files ?? {}
log.info(`incremental mode: ${prevChunks.length} existing chunks, ${Object.keys(prevMtimes).length} tracked files`)
} catch {
log.warn('could not load previous index — falling back to full re-index')
prevChunks = []
prevMtimes = {}
}
} else {
log.info('full index mode (no previous index found)')
}
log.info(`indexing with extensions: ${[...extensions].join(', ')}`)
const allFiles = walkFiles(root, extensions, Number(config.max_file_kb))
log.info(`found ${allFiles.length} files to consider`)
// Group previous chunks by file for efficient lookup
const prevChunksByFile = new Map<string, Chunk[]>()
for (const chunk of prevChunks) {
const arr = prevChunksByFile.get(chunk.path) ?? []
arr.push(chunk)
prevChunksByFile.set(chunk.path, arr)
}
const newMtimes: Record<string, number> = {}
const unchangedChunks: Chunk[] = []
const freshChunks: Chunk[] = []
let filesSkipped = 0
let filesIndexed = 0
for (const filePath of allFiles) {
const relPath = relative(root, filePath)
const mtime = statSync(filePath).mtimeMs
if (isIncremental && prevMtimes[relPath] === mtime) {
unchangedChunks.push(...(prevChunksByFile.get(relPath) ?? []))
newMtimes[relPath] = mtime
filesSkipped++
} else {
try {
const content = readFileSync(filePath, 'utf8')
const fileChunks = chunkFile(relPath, content, Number(config.chunk_lines), Number(config.chunk_overlap))
freshChunks.push(...fileChunks)
newMtimes[relPath] = mtime
filesIndexed++
} catch (e) {
log.warn(`skipping ${relPath}: ${e}`)
}
}
}
// Count pruned files (tracked before but no longer on disk)
const currentPaths = new Set(allFiles.map(f => relative(root, f)))
const filesPruned = Object.keys(prevMtimes).filter(p => !currentPaths.has(p)).length
if (filesPruned > 0) log.info(`pruned ${filesPruned} deleted/moved file(s) from index`)
const allChunks = [...unchangedChunks, ...freshChunks]
log.info(`${filesIndexed} indexed, ${filesSkipped} skipped, ${filesPruned} pruned — ${allChunks.length} total chunks`)
// Compute smoothed IDF: log((N+1)/(df+1)) + 1 for each term
const N = allChunks.length
const df: Record<string, number> = {}
for (const chunk of allChunks) {
for (const token of chunk.tokens) {
df[token] = (df[token] ?? 0) + 1
}
}
const idf: Record<string, number> = {}
for (const [term, freq] of Object.entries(df)) {
idf[term] = Math.log((N + 1) / (freq + 1)) + 1
}
// Write index and mtime map
const index: Index = { version: 2, generated: new Date().toISOString(), chunks: allChunks, idf }
const indexJson = JSON.stringify(index)
writeFileSync(chunksPath, indexJson)
const mtimeMap: MtimeMap = { version: 1, indexed_at: new Date().toISOString(), files: newMtimes }
writeFileSync(mtimePath, JSON.stringify(mtimeMap))
const duration_ms = Date.now() - start
const index_size_kb = Math.round(indexJson.length / 1024)
log.info(`done in ${duration_ms}ms — index size: ${index_size_kb}KB`)
return {
ok: true,
data: {
files_indexed: filesIndexed,
files_skipped: filesSkipped,
files_pruned: filesPruned,
chunks_written: freshChunks.length,
total_chunks: allChunks.length,
duration_ms,
index_size_kb,
},
duration_ms,
}
}
// ---------------------------------------------------------------------------
// Run — answer a question using the index
// ---------------------------------------------------------------------------
export async function run(
input: { question?: string },
root: string,
): Promise<ToolResult<AmaOutput>> {
const start = Date.now()
const config = loadToolConfig(root, 'ama', DEFAULT_CONFIG)
const question = input.question?.trim()
if (!question) {
return { ok: false, error: 'input.question is required' }
}
const indexPath = join(root, '.sdlc', 'tools', 'ama', 'index', 'chunks.json')
if (!existsSync(indexPath)) {
return {
ok: false,
error: 'Index not built. Run setup first: sdlc tool run ama --setup',
}
}
let index: Index
try {
index = JSON.parse(readFileSync(indexPath, 'utf8')) as Index
} catch (e) {
return { ok: false, error: `Failed to load index: ${e}. Re-run: sdlc tool run ama --setup` }
}
// Load mtime map for stale source detection (non-fatal if absent)
let mtimes: Record<string, number> = {}
try {
const mtimePath = join(root, '.sdlc', 'tools', 'ama', 'index', 'last_indexed.json')
if (existsSync(mtimePath)) {
mtimes = (JSON.parse(readFileSync(mtimePath, 'utf8')) as MtimeMap).files ?? {}
}
} catch { /* stale detection skipped */ }
log.info(`scoring ${index.chunks.length} chunks for: "${question}"`)
// idf falls back gracefully to 1.0 weights for v1 indexes without IDF
const idf = index.idf ?? {}
const topChunks = scoreChunks(question, index.chunks, idf).slice(0, Number(config.max_results))
const sources: AmaSource[] = []
for (const { chunk, score } of topChunks) {
const fullPath = join(root, chunk.path)
try {
const lines = readFileSync(fullPath, 'utf8').split('\n')
const excerpt = lines.slice(chunk.start - 1, chunk.end).join('\n')
// Stale detection: flag if file changed since last index run
let stale = false
try {
if (mtimes[chunk.path] !== undefined && statSync(fullPath).mtimeMs !== mtimes[chunk.path]) {
stale = true
log.warn(`stale source: ${chunk.path} changed since last index run`)
}
} catch { /* file may not exist — handled above */ }
const source: AmaSource = { path: chunk.path, lines: [chunk.start, chunk.end], excerpt, score }
if (stale) source.stale = true
sources.push(source)
} catch {
log.warn(`skipping deleted/moved file: ${chunk.path}`)
}
}
const duration_ms = Date.now() - start
log.info(`returned ${sources.length} sources in ${duration_ms}ms`)
return { ok: true, data: { sources }, duration_ms }
}
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
const SKIP_DIRS = new Set([
'node_modules', '.git', 'target', 'dist', 'build', '.sdlc',
'.next', '__pycache__', '.cache', 'coverage',
])
function walkFiles(root: string, extensions: Set<string>, maxFileKb: number): string[] {
const results: string[] = []
function walk(dir: string) {
let entries: ReturnType<typeof readdirSync>
try {
entries = readdirSync(dir, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
if (entry.name.startsWith('.')) continue
const full = join(dir, entry.name)
if (entry.isDirectory()) {
if (!SKIP_DIRS.has(entry.name)) walk(full)
} else if (entry.isFile()) {
if (!extensions.has(extname(entry.name))) continue
try {
if (statSync(full).size > maxFileKb * 1024) {
log.warn(`skipping large file (${Math.round(statSync(full).size / 1024)}KB): ${relative(root, full)}`)
continue
}
} catch {
continue
}
results.push(full)
}
}
}
walk(root)
return results
}
function chunkFile(
relPath: string,
content: string,
chunkLines: number,
overlap: number,
): Chunk[] {
const lines = content.split('\n')
const chunks: Chunk[] = []
const step = Math.max(1, chunkLines - overlap)
for (let i = 0; i < lines.length; i += step) {
const start = i + 1 // 1-based line numbers
const end = Math.min(i + chunkLines, lines.length)
const tokens = extractTokens(lines.slice(i, end).join(' '))
if (tokens.length > 0) {
chunks.push({ path: relPath, start, end, tokens })
}
if (end >= lines.length) break
}
return chunks
}
/**
* Extract lowercase tokens from text, splitting on camelCase and snake_case
* boundaries to enable code-aware search. Words < 4 chars are omitted as noise.
*
* Examples:
* featureTransition ['feature', 'transition']
* SdlcError ['sdlc', 'error']
* auth_token ['auth', 'token']
* authenticate ['authenticate']
*/
function extractTokens(text: string): string[] {
// Split on camelCase and acronym boundaries before lowercasing
const expanded = text
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // XMLParser → XML Parser
const seen = new Set<string>()
const tokens: string[] = []
for (const word of expanded.toLowerCase().split(/[^a-z0-9]+/)) {
if (word.length >= 3 && !seen.has(word)) {
seen.add(word)
tokens.push(word)
}
}
return tokens
}
/**
* Score chunks using TF-IDF weighted overlap.
* IDF is precomputed at index time (stored in chunks.json v2+).
* Falls back to uniform weights (raw overlap) for v1 indexes without IDF.
*/
function scoreChunks(
question: string,
chunks: Chunk[],
idf: Record<string, number>,
): { chunk: Chunk; score: number }[] {
const queryTokens = extractTokens(question)
if (queryTokens.length === 0) return []
const hasIdf = Object.keys(idf).length > 0
const results: { chunk: Chunk; score: number }[] = []
for (const chunk of chunks) {
const chunkSet = new Set(chunk.tokens)
let score = 0
let totalWeight = 0
for (const token of queryTokens) {
const weight = hasIdf ? (idf[token] ?? 1.0) : 1.0
totalWeight += weight
if (chunkSet.has(token)) score += weight
}
if (score > 0) {
results.push({ chunk, score: totalWeight > 0 ? score / totalWeight : 0 })
}
}
return results.sort((a, b) => b.score - a.score)
}
// ---------------------------------------------------------------------------
// CLI entrypoint
// ---------------------------------------------------------------------------
const mode = getArgs()[0] ?? '--run'
const root = process.env.SDLC_ROOT ?? process.cwd()
if (mode === '--meta') {
console.log(JSON.stringify(meta))
exit(0)
} else if (mode === '--setup') {
setup(root)
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
} else if (mode === '--run') {
readStdin()
.then(raw => run(JSON.parse(raw || '{}') as { question?: string }, root))
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
} else {
console.error(`Unknown mode: ${mode}. Use --meta, --setup, or --run.`)
exit(1)
}

View File

@ -0,0 +1,169 @@
# dev-driver
A stock sdlc tool that reads project state, finds the single most important next
development action, dispatches it asynchronously, and exits. Paired with a recurring
orchestrator action (every 4 hours), it makes your sdlc project self-advancing.
---
## What it does
On each invocation, dev-driver applies a 5-level priority waterfall and takes exactly one action:
1. **Flight lock** — if a previous dispatch is still in flight (< 2h), do nothing
2. **Quality check** — if `quality-check` reports failures, do nothing (fix quality first)
3. **Feature advancement** — if any feature has an active directive, advance it one step
4. **Wave start** — if a milestone has all features PLANNED/READY, start the wave
5. **Idle** — nothing actionable, exit cleanly
One action per tick. The 4-hour recurrence IS the iteration rhythm.
---
## Default action recipe
Create this action once in the sdlc UI or via CLI:
```
Label: dev-driver
Tool: dev-driver
Input: {}
Recurrence: 14400 (4 hours in seconds)
```
Then run `sdlc ui --run-actions` to enable the orchestrator.
---
## Priority waterfall (detail)
### Level 1: Flight lock
Reads `.sdlc/.dev-driver.lock`. If the lock exists and is less than 2 hours old,
exits immediately with `{ action: "waiting", lock_age_mins: N }`.
This prevents double-dispatch when a previous Claude agent is still running.
### Level 2: Quality check
Runs the `quality-check` tool. If any checks fail, exits with:
```json
{ "action": "quality_failing", "failed_checks": ["test", "lint"] }
```
Fix the failing checks before dev-driver will advance features.
### Level 3: Feature advancement
Finds features in `implementation`, `review`, `audit`, or `qa` phase with a
pending directive. Picks the first one alphabetically. Dispatches:
```bash
claude --print "/sdlc-next <slug>"
```
**This is `/sdlc-next` — one step only.** The 4-hour recurrence advances the feature
step by step over time. This is intentional: it keeps you in control and lets you
course-correct between steps.
Returns:
```json
{ "action": "feature_advanced", "slug": "my-feature", "phase": "implementation", "directive": "/sdlc-next my-feature" }
```
### Level 4: Wave start
If no features have active directives but a milestone has all features in PLANNED or READY
phase, starts the next wave:
```bash
claude --print "/sdlc-run-wave <milestone>"
```
Returns:
```json
{ "action": "wave_started", "milestone": "v21-dev-driver" }
```
### Level 5: Idle
No actionable work found. Returns:
```json
{ "action": "idle", "reason": "no actionable work found" }
```
---
## How to skip a feature
If you don't want dev-driver to autonomously advance a specific feature, add a task
with `skip:autonomous` in the title:
```bash
sdlc task add <slug> --title "skip:autonomous: needs human review before proceeding"
```
Dev-driver will exclude this feature from Level 3 selection until the task is removed
or marked done. You retain full control over which features advance autonomously.
---
## One step, not full run
Dev-driver dispatches `/sdlc-next <slug>`, NOT `/sdlc-run <slug>`.
`/sdlc-next` executes exactly one directive (write a spec, approve a design, implement
a task, etc.) and exits. The next tick, dev-driver will pick the same feature again
and advance it one more step.
This means:
- Each tick = one atomic state machine step
- You can review after each step in the Actions page
- No surprise full-feature runs that take hours
---
## Lock file
Path: `.sdlc/.dev-driver.lock`
TTL: 2 hours
```json
{
"started_at": "2026-03-02T04:00:00.000Z",
"action": "feature_advanced",
"slug": "my-feature",
"pid": 12345
}
```
The lock is written before each dispatch and cleared automatically after 2 hours.
You can delete it manually if you need to run dev-driver before the TTL expires.
---
## Output reference
All five possible outputs:
```json
// Level 1 (lock)
{ "action": "waiting", "lock_age_mins": 45 }
// Level 1 (active run)
{ "action": "waiting", "reason": "agent run in progress" }
// Level 2
{ "action": "quality_failing", "failed_checks": ["test", "clippy"] }
// Level 3
{ "action": "feature_advanced", "slug": "my-feature", "phase": "implementation", "directive": "/sdlc-next my-feature" }
// Level 4
{ "action": "wave_started", "milestone": "v21-dev-driver" }
// Level 5
{ "action": "idle", "reason": "no actionable work found" }
```
All wrapped in: `{ "ok": true, "data": { ... }, "duration_ms": N }`

View File

@ -0,0 +1,447 @@
/**
* Dev Driver
* ==========
* Finds the single most important next development action and dispatches it
* asynchronously. Designed to run on a schedule (e.g. every 4 hours) via the
* sdlc orchestrator to make development self-advancing.
*
* WHAT IT DOES
* ------------
* --run: Reads JSON from stdin: {} (no parameters)
* Applies a 5-level priority waterfall:
* 1. Flight lock if .sdlc/.dev-driver.lock < 2h old, exit waiting
* 2. Quality if quality-check fails, exit quality_failing
* 3. Features if features have active directives, dispatch /sdlc-next
* 4. Wave if a milestone wave is ready, dispatch /sdlc-run-wave
* 5. Idle nothing to do, exit idle
* Returns ToolResult<DevDriverOutput>.
*
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
*
* KEY INVARIANT
* -------------
* Level 3 dispatches /sdlc-next (one step), NOT /sdlc-run (to completion).
* The 4h recurrence IS the iteration rhythm. Each tick advances exactly one
* feature by one directive. This keeps the developer in control.
*
* HOW TO SKIP A FEATURE
* ---------------------
* Add a task to the feature with "skip:autonomous" in the title:
* sdlc task add <slug> --title "skip:autonomous: needs human review"
* The dev-driver will exclude this feature from Level 3 until the task is removed.
*
* LOCK FILE
* ---------
* Path: .sdlc/.dev-driver.lock
* Written before each dispatch. TTL: 2 hours. Format:
* { started_at: ISO, action: string, slug?: string, milestone?: string, pid: number }
*/
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
import { makeLogger } from '../_shared/log.ts'
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
import { execSync, spawn } from 'node:child_process'
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
import { join } from 'node:path'
const log = makeLogger('dev-driver')
// ---------------------------------------------------------------------------
// Tool metadata
// ---------------------------------------------------------------------------
export const meta: ToolMeta = {
name: 'dev-driver',
display_name: 'Dev Driver',
description: 'Finds the next development action and dispatches it — advances the project one step per tick',
version: '1.0.0',
requires_setup: false,
input_schema: {
type: 'object',
properties: {},
additionalProperties: false,
},
output_schema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['waiting', 'quality_failing', 'feature_advanced', 'wave_started', 'idle'],
description: 'What the dev-driver decided to do',
},
lock_age_mins: {
type: 'number',
description: 'Age of the flight lock in minutes (present when action=waiting from lock)',
},
reason: {
type: 'string',
description: 'Human-readable reason (present when action=waiting or idle)',
},
failed_checks: {
type: 'array',
items: { type: 'string' },
description: 'Names of failed quality checks (present when action=quality_failing)',
},
slug: {
type: 'string',
description: 'Feature slug that was advanced (present when action=feature_advanced)',
},
phase: {
type: 'string',
description: 'Current phase of the feature (present when action=feature_advanced)',
},
directive: {
type: 'string',
description: 'The /sdlc-next command that was dispatched (present when action=feature_advanced)',
},
milestone: {
type: 'string',
description: 'Milestone slug that started (present when action=wave_started)',
},
},
required: ['action'],
},
}
// ---------------------------------------------------------------------------
// Output types
// ---------------------------------------------------------------------------
type DevDriverOutput =
| { action: 'waiting'; lock_age_mins: number }
| { action: 'waiting'; reason: string }
| { action: 'quality_failing'; failed_checks: string[] }
| { action: 'feature_advanced'; slug: string; phase: string; directive: string }
| { action: 'wave_started'; milestone: string }
| { action: 'idle'; reason: string }
// ---------------------------------------------------------------------------
// Lock file (T2)
// ---------------------------------------------------------------------------
interface LockFile {
started_at: string
action: string
slug?: string
milestone?: string
pid: number
}
const LOCK_TTL_MINS = 120
function lockPath(root: string): string {
return join(root, '.sdlc', '.dev-driver.lock')
}
function readLock(root: string): LockFile | null {
const p = lockPath(root)
if (!existsSync(p)) return null
try {
return JSON.parse(readFileSync(p, 'utf8')) as LockFile
} catch {
return null
}
}
function isLockActive(lock: LockFile): boolean {
const ageMs = Date.now() - Date.parse(lock.started_at)
return ageMs < LOCK_TTL_MINS * 60 * 1000
}
function lockAgeMins(lock: LockFile): number {
return Math.floor((Date.now() - Date.parse(lock.started_at)) / 60000)
}
function writeLock(root: string, payload: Omit<LockFile, 'pid'> & { pid: number }): void {
writeFileSync(lockPath(root), JSON.stringify(payload, null, 2), 'utf8')
}
// ---------------------------------------------------------------------------
// Quality check (T3 - Level 2)
// ---------------------------------------------------------------------------
interface QCCheck {
name: string
status: 'passed' | 'failed'
}
interface QCResult {
passed: number
failed: number
checks: QCCheck[]
}
function runQualityCheck(root: string): { failed: number; failedNames: string[] } {
const toolPath = join(root, '.sdlc', 'tools', 'quality-check', 'tool.ts')
if (!existsSync(toolPath)) {
log.warn('quality-check tool not found — skipping quality gate')
return { failed: 0, failedNames: [] }
}
try {
const raw = execSync(`node ${toolPath} --run`, {
input: '{}',
encoding: 'utf8',
cwd: root,
timeout: 120_000,
stdio: ['pipe', 'pipe', 'pipe'],
})
const result = JSON.parse(raw) as ToolResult<QCResult>
if (!result.data) return { failed: 0, failedNames: [] }
const failedNames = result.data.checks
.filter(c => c.status === 'failed')
.map(c => c.name)
return { failed: result.data.failed, failedNames }
} catch (e) {
log.warn(`quality-check execution error: ${e} — treating as no failures`)
return { failed: 0, failedNames: [] }
}
}
// ---------------------------------------------------------------------------
// Active run check (T8)
// ---------------------------------------------------------------------------
function hasActiveRuns(root: string): boolean {
try {
const raw = execSync('sdlc run list --status running --json', {
encoding: 'utf8',
cwd: root,
timeout: 10_000,
})
const runs = JSON.parse(raw)
return Array.isArray(runs) && runs.length > 0
} catch {
// sdlc run list may not exist yet — skip this check gracefully
log.warn('sdlc run list not available — skipping active run check')
return false
}
}
// ---------------------------------------------------------------------------
// Feature selection (T3 - Level 3, T7, T9, T10)
// ---------------------------------------------------------------------------
interface FeatureDirective {
feature: string
current_phase: string
action: string
next_command: string
}
const ACTIVE_PHASES = new Set(['implementation', 'review', 'audit', 'qa'])
function hasSkipTag(slug: string, root: string): boolean {
const tasksPath = join(root, '.sdlc', 'features', slug, 'tasks.md')
if (!existsSync(tasksPath)) return false
try {
const content = readFileSync(tasksPath, 'utf8')
return /skip:autonomous/i.test(content)
} catch {
return false
}
}
function findActionableFeature(root: string): FeatureDirective | null {
try {
const raw = execSync('sdlc next --json', {
encoding: 'utf8',
cwd: root,
timeout: 30_000,
})
const all = JSON.parse(raw) as FeatureDirective[]
const actionable = all
.filter(d => d.action !== 'done')
.filter(d => ACTIVE_PHASES.has(d.current_phase))
.filter(d => !hasSkipTag(d.feature, root))
.sort((a, b) => a.feature.localeCompare(b.feature))
return actionable[0] ?? null
} catch (e) {
log.warn(`sdlc next --json failed: ${e}`)
return null
}
}
// ---------------------------------------------------------------------------
// Wave detection (T3 - Level 4)
// ---------------------------------------------------------------------------
interface MilestoneInfo {
slug: string
status: string
features: { phase: string; status: string }[]
done: number
total: number
}
const WAVE_READY_PHASES = new Set(['planned', 'ready'])
function findReadyWave(root: string): string | null {
try {
const raw = execSync('sdlc milestone list --json', {
encoding: 'utf8',
cwd: root,
timeout: 15_000,
})
const milestones = JSON.parse(raw) as MilestoneInfo[]
const ready = milestones
.filter(m => m.status !== 'released' && m.total > 0)
.filter(m =>
m.features.every(f =>
WAVE_READY_PHASES.has(f.phase) || f.phase === 'released'
)
)
.filter(m =>
m.features.some(f => WAVE_READY_PHASES.has(f.phase))
)
.sort((a, b) => a.slug.localeCompare(b.slug))
return ready[0]?.slug ?? null
} catch (e) {
log.warn(`sdlc milestone list failed: ${e}`)
return null
}
}
// ---------------------------------------------------------------------------
// Async spawn (T4)
// ---------------------------------------------------------------------------
function spawnClaude(command: string, root: string): number {
const child = spawn('claude', ['--print', command], {
detached: true,
stdio: 'ignore',
cwd: root,
env: { ...process.env, SDLC_ROOT: root },
})
child.unref()
return child.pid ?? 0
}
// ---------------------------------------------------------------------------
// Main run function (T1, T3, T5)
// ---------------------------------------------------------------------------
export async function run(
_input: Record<string, never>,
root: string,
): Promise<ToolResult<DevDriverOutput>> {
const start = Date.now()
// ── Level 1: Flight lock ──────────────────────────────────────────────────
const lock = readLock(root)
if (lock && isLockActive(lock)) {
const mins = lockAgeMins(lock)
log.info(`flight lock active (${mins}m old) — waiting`)
return { ok: true, data: { action: 'waiting', lock_age_mins: mins }, duration_ms: Date.now() - start }
}
if (lock) {
log.info(`stale lock found (${lockAgeMins(lock)}m old) — proceeding`)
}
// ── Level 2: Quality check ────────────────────────────────────────────────
log.info('running quality check')
const qc = runQualityCheck(root)
if (qc.failed > 0) {
log.info(`quality failing: ${qc.failedNames.join(', ')}`)
return { ok: true, data: { action: 'quality_failing', failed_checks: qc.failedNames }, duration_ms: Date.now() - start }
}
log.info('quality checks passed')
// ── Level 3: Features with active directives ──────────────────────────────
if (hasActiveRuns(root)) {
log.info('active sdlc agent run detected — waiting')
return { ok: true, data: { action: 'waiting', reason: 'agent run in progress' }, duration_ms: Date.now() - start }
}
const feature = findActionableFeature(root)
if (feature) {
log.info(`advancing feature: ${feature.feature} (${feature.current_phase})`)
// Write lock before spawning
writeLock(root, {
started_at: new Date().toISOString(),
action: 'feature_advanced',
slug: feature.feature,
pid: 0, // will be overwritten after spawn
})
// Intentionally /sdlc-next — one step per tick, not /sdlc-run to completion
const pid = spawnClaude(`/sdlc-next ${feature.feature}`, root)
// Update lock with actual PID
writeLock(root, {
started_at: new Date().toISOString(),
action: 'feature_advanced',
slug: feature.feature,
pid,
})
log.info(`dispatched /sdlc-next ${feature.feature} (pid: ${pid})`)
return {
ok: true,
data: {
action: 'feature_advanced',
slug: feature.feature,
phase: feature.current_phase,
directive: feature.next_command || `/sdlc-next ${feature.feature}`,
},
duration_ms: Date.now() - start,
}
}
// ── Level 4: Wave ready ───────────────────────────────────────────────────
const milestone = findReadyWave(root)
if (milestone) {
log.info(`wave ready for milestone: ${milestone}`)
writeLock(root, {
started_at: new Date().toISOString(),
action: 'wave_started',
milestone,
pid: 0,
})
const pid = spawnClaude(`/sdlc-run-wave ${milestone}`, root)
writeLock(root, {
started_at: new Date().toISOString(),
action: 'wave_started',
milestone,
pid,
})
log.info(`dispatched /sdlc-run-wave ${milestone} (pid: ${pid})`)
return {
ok: true,
data: { action: 'wave_started', milestone },
duration_ms: Date.now() - start,
}
}
// ── Level 5: Idle ─────────────────────────────────────────────────────────
log.info('no actionable work found — idle')
return {
ok: true,
data: { action: 'idle', reason: 'no actionable work found' },
duration_ms: Date.now() - start,
}
}
// ---------------------------------------------------------------------------
// CLI entrypoint
// ---------------------------------------------------------------------------
const mode = getArgs()[0] ?? '--run'
const root = process.env.SDLC_ROOT ?? process.cwd()
if (mode === '--meta') {
console.log(JSON.stringify(meta))
exit(0)
} else if (mode === '--run') {
readStdin()
.then(raw => run(JSON.parse(raw || '{}') as Record<string, never>, root))
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
} else {
console.error(`Unknown mode: ${mode}. Use --meta or --run.`)
exit(1)
}

View File

@ -0,0 +1,34 @@
# Quality Check
Runs checks defined in `.sdlc/tools/quality-check/config.yaml` and reports pass/fail.
## Usage
```bash
# Run all configured checks
sdlc tool run quality-check
# Filter to checks whose name matches a string
sdlc tool run quality-check --scope test
```
## How it works
Reads `checks` from `.sdlc/tools/quality-check/config.yaml`, runs each script as a shell
command in the project root, and reports pass/fail with the last 500 characters of output.
## Adding checks
Edit `.sdlc/tools/quality-check/config.yaml`:
```yaml
checks:
- name: test
description: Run unit tests
script: cargo test --all
- name: lint
description: Run linter
script: cargo clippy --all -- -D warnings
```
The quality-check tool picks them up automatically — no code changes needed.

View File

@ -0,0 +1,12 @@
# quality-check tool configuration
# Add your project's quality checks below.
# Each check runs its `script` as a shell command in the project root.
#
# Example:
# checks:
# - name: test
# description: Run unit tests
# script: cargo test --all
name: quality-check
version: "0.3.0"
checks:

View File

@ -0,0 +1,294 @@
/**
* Quality Check
* =============
* Runs checks defined in .sdlc/tools/quality-check/config.yaml and reports pass/fail.
*
* WHAT IT DOES
* ------------
* --run: Reads JSON from stdin: { "scope"?: "string" }
* Loads checks from .sdlc/tools/quality-check/config.yaml.
* Runs each check's script as a shell command, records pass/fail + output.
* If scope is provided, only runs checks whose name matches the filter string.
* Returns ToolResult<{ passed, failed, checks[] }>.
*
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
*
* WHAT IT READS
* -------------
* - .sdlc/tools/quality-check/config.yaml
* checks[]: { name, description, script }
*
* WHAT IT WRITES
* --------------
* - STDERR: structured log lines via _shared/log.ts
* - STDOUT: JSON only (ToolResult shape from _shared/types.ts)
*
* EXTENDING
* ---------
* Add or edit checks in .sdlc/tools/quality-check/config.yaml:
* checks:
* - name: test
* description: Run unit tests
* script: cargo test --all
* The quality-check tool picks them up automatically no code changes needed.
*/
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
import { makeLogger } from '../_shared/log.ts'
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
import { execSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
const log = makeLogger('quality-check')
// ---------------------------------------------------------------------------
// Tool metadata
// ---------------------------------------------------------------------------
export const meta: ToolMeta = {
name: 'quality-check',
display_name: 'Quality Check',
description: 'Runs checks from .sdlc/tools/quality-check/config.yaml and reports pass/fail',
version: '0.3.0',
requires_setup: false,
input_schema: {
type: 'object',
properties: {
scope: {
type: 'string',
description: 'Optional filter — only run checks whose name matches this string',
},
},
},
output_schema: {
type: 'object',
properties: {
passed: { type: 'number' },
failed: { type: 'number' },
checks: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
command: { type: 'string' },
status: { type: 'string', enum: ['passed', 'failed'] },
output: { type: 'string' },
duration_ms: { type: 'number' },
},
},
},
},
},
}
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
interface PlatformCommand {
name: string
description?: string
script: string
}
interface CheckResult {
name: string
description: string
command: string
status: 'passed' | 'failed'
output: string
duration_ms: number
}
interface QualityCheckOutput {
passed: number
failed: number
checks: CheckResult[]
}
// ---------------------------------------------------------------------------
// Config YAML parser — reads checks[] from tool-local config.yaml
// ---------------------------------------------------------------------------
/**
* Parse the `checks:` array from the tool's config.yaml.
* Handles the specific YAML shape used by quality-check:
* checks:
* - name: <string>
* description: <string>
* script: <single-quoted or bare string>
*/
function parseChecksFromYaml(content: string): PlatformCommand[] {
const checks: PlatformCommand[] = []
const lines = content.split('\n')
let inChecks = false
let current: Partial<PlatformCommand> | null = null
for (const line of lines) {
// Top-level `checks:` section header
if (/^checks:/.test(line)) {
inChecks = true
continue
}
// Any other top-level key ends the checks section
if (/^\S/.test(line) && !/^checks:/.test(line)) {
inChecks = false
}
if (!inChecks) continue
// New item: ` - name: <value>`
const itemMatch = line.match(/^\s{2}-\s+name:\s*(.*)$/)
if (itemMatch) {
if (current?.name && current?.script) {
checks.push(current as PlatformCommand)
}
current = { name: unquoteYaml(itemMatch[1].trim()), description: '', script: '' }
continue
}
if (!current) continue
const descMatch = line.match(/^\s+description:\s*(.*)$/)
if (descMatch) {
current.description = unquoteYaml(descMatch[1].trim())
continue
}
const scriptMatch = line.match(/^\s+script:\s*(.*)$/)
if (scriptMatch) {
current.script = unquoteYaml(scriptMatch[1].trim())
continue
}
}
if (current?.name && current?.script) {
checks.push(current as PlatformCommand)
}
return checks
}
/** Strip surrounding single or double quotes from a YAML scalar value. */
function unquoteYaml(s: string): string {
return s.replace(/^'([\s\S]*)'$/, '$1').replace(/^"([\s\S]*)"$/, '$1')
}
/** Load checks from the tool's own config.yaml. Returns [] on any error. */
function loadChecks(root: string): PlatformCommand[] {
const configPath = join(root, '.sdlc', 'tools', 'quality-check', 'config.yaml')
try {
const raw = readFileSync(configPath, 'utf8')
return parseChecksFromYaml(raw)
} catch (e) {
log.warn(`Could not read tool config at ${configPath}: ${e}`)
return []
}
}
// ---------------------------------------------------------------------------
// Run — execute platform checks
// ---------------------------------------------------------------------------
export async function run(
input: { scope?: string },
root: string,
): Promise<ToolResult<QualityCheckOutput>> {
const start = Date.now()
const commands = loadChecks(root)
if (commands.length === 0) {
log.warn('No checks configured in .sdlc/tools/quality-check/config.yaml — nothing to run')
const duration_ms = Date.now() - start
return {
ok: true,
data: { passed: 0, failed: 0, checks: [] },
duration_ms,
}
}
// Apply scope filter
const scope = input.scope?.trim()
const filtered = scope
? commands.filter(c => c.name.includes(scope))
: commands
log.info(`running ${filtered.length} check(s)${scope ? ` (scope: "${scope}")` : ''}`)
const checks: CheckResult[] = []
for (const cmd of filtered) {
const checkStart = Date.now()
log.info(`running check: ${cmd.name}`)
let status: 'passed' | 'failed' = 'passed'
let output = ''
try {
const result = execSync(cmd.script, {
cwd: root,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
})
output = result.slice(-500) // last 500 chars
} catch (e: unknown) {
status = 'failed'
if (e && typeof e === 'object' && 'stdout' in e && 'stderr' in e) {
const err = e as { stdout?: string; stderr?: string }
const combined = `${err.stdout ?? ''}${err.stderr ?? ''}`
output = combined.slice(-500)
} else {
output = String(e).slice(-500)
}
}
const duration_ms = Date.now() - checkStart
log.info(` ${cmd.name}: ${status} (${duration_ms}ms)`)
checks.push({
name: cmd.name,
description: cmd.description ?? '',
command: cmd.script,
status,
output,
duration_ms,
})
}
const passed = checks.filter(c => c.status === 'passed').length
const failed = checks.filter(c => c.status === 'failed').length
const duration_ms = Date.now() - start
log.info(`done: ${passed} passed, ${failed} failed in ${duration_ms}ms`)
return {
ok: failed === 0,
data: { passed, failed, checks },
duration_ms,
}
}
// ---------------------------------------------------------------------------
// CLI entrypoint
// ---------------------------------------------------------------------------
const mode = getArgs()[0] ?? '--run'
const root = process.env.SDLC_ROOT ?? process.cwd()
if (mode === '--meta') {
console.log(JSON.stringify(meta))
exit(0)
} else if (mode === '--run') {
readStdin()
.then(raw => run(JSON.parse(raw || '{}') as { scope?: string }, root))
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
} else {
console.error(`Unknown mode: ${mode}. Use --meta or --run.`)
exit(1)
}

View File

@ -0,0 +1,113 @@
# telegram-recap
Fetches Telegram chat messages from the configured time window and emails a digest via SMTP.
Delegates all logic to `sdlc telegram digest` — no duplicate implementation.
## Prerequisites
1. A Telegram bot created via [@BotFather](https://t.me/botfather)
2. The bot added to the chats you want to digest
3. `sdlc telegram poll` running (or having run) to populate the local message database
4. SMTP credentials (e.g. [Resend](https://resend.com), SendGrid, Gmail SMTP)
## Setup
### 1. Configure secrets
The tool requires these environment variables. Set them in your orchestrator secrets or shell:
| Variable | Description |
|---|---|
| `TELEGRAM_BOT_TOKEN` | Bot API token from @BotFather |
| `SMTP_HOST` | SMTP server hostname (e.g. `smtp.resend.com`) |
| `SMTP_PORT` | SMTP port (`587` for STARTTLS, `465` for SSL) |
| `SMTP_USERNAME` | SMTP auth username |
| `SMTP_PASSWORD` | SMTP auth password or API key |
| `SMTP_FROM` | From address (e.g. `digest@yourdomain.com`) |
| `SMTP_TO` | Recipient(s), comma-separated |
### 2. Run setup to verify
```bash
sdlc tool run telegram-recap --setup
```
This calls `sdlc telegram status` to verify the bot token and database connectivity.
Returns `{ ok: true, data: { status_output: "..." } }` on success.
## Usage
### Dry run (preview without sending)
```bash
sdlc tool run telegram-recap --input '{"dry_run": true}'
```
### Send digest
```bash
sdlc tool run telegram-recap --input '{}'
```
### Custom window
```bash
# Last 48 hours instead of the default 24
sdlc tool run telegram-recap --input '{"window_hours": 48}'
```
### Specific chat IDs
```bash
sdlc tool run telegram-recap --input '{"chat_ids": ["-100123456789"]}'
```
## Output
```json
{
"ok": true,
"data": {
"dry_run": false,
"total_messages": 42,
"chat_count": 3,
"period_start": "2026-03-01T08:00:00Z",
"period_end": "2026-03-02T08:00:00Z",
"sent_to": ["you@example.com"]
},
"duration_ms": 1234
}
```
## Scheduling with the orchestrator
Use the sdlc orchestrator for recurrence instead of systemd:
```bash
# Daily digest at the current time
sdlc orchestrate add telegram-recap \
--tool telegram-recap \
--input '{}' \
--at "now" \
--every 86400
```
This removes the need for systemd timers. The orchestrator runs the tool on schedule
and streams results to the sdlc UI via SSE.
## Troubleshooting
**"Bot token check failed"**
`TELEGRAM_BOT_TOKEN` is missing or invalid. Verify with `sdlc telegram status`.
**"sdlc telegram digest failed"**
→ Check that `sdlc telegram poll` has been running and the database exists at
`.sdlc/telegram/messages.db`. Run `sdlc telegram status` for diagnostics.
**SMTP errors**
→ Check `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`. Common mistake:
using port 465 with STARTTLS or port 587 with SSL — match port to protocol.
**No messages in digest**
→ The configured chat IDs may not match what the bot has access to, or the time
window contains no messages. Try `--input '{"window_hours": 168}'` (7 days) to widen the window.

View File

@ -0,0 +1,5 @@
name: telegram-recap
version: "1.0.0"
description: "Fetch and email a Telegram chat digest via SMTP"
# Add tool-specific config here (optional — env vars take precedence)
# See README.md for full configuration reference.

View File

@ -0,0 +1,303 @@
/**
* telegram-recap
* ==============
* Fetches Telegram chat messages from the configured window and emails a digest.
* Delegates all logic to `sdlc telegram digest --json`.
*
* WHAT IT DOES
* ------------
* --setup: Runs `sdlc telegram status` to verify bot token and DB connectivity.
* Returns success/failure with bot identity.
*
* --run: Reads JSON from stdin: { window_hours?, dry_run?, chat_ids? }
* Builds args and spawns `sdlc telegram digest --json [args]`.
* Maps the digest JSON summary ToolResult.
*
* --meta: Writes ToolMeta JSON to stdout. Declares required secrets.
*
* SECRETS (injected as env vars by the orchestrator)
* ---------------------------------------------------
* TELEGRAM_BOT_TOKEN Required Telegram bot API token (from @BotFather)
* SMTP_HOST Required SMTP server hostname (e.g. smtp.resend.com)
* SMTP_PORT Required SMTP port (e.g. 587 for STARTTLS, 465 for SSL)
* SMTP_USERNAME Required SMTP authentication username
* SMTP_PASSWORD Required SMTP authentication password or API key
* SMTP_FROM Required From address for digest emails
* SMTP_TO Required Recipient address(es), comma-separated
*
* WHAT IT READS
* -------------
* - $SDLC_ROOT/.sdlc/telegram/messages.db (populated by `sdlc telegram poll`)
*
* WHAT IT WRITES
* --------------
* - STDERR: structured log lines via _shared/log.ts
* - STDOUT: JSON only (ToolResult shape)
*/
import type { ToolMeta as BaseToolMeta, ToolResult } from '../_shared/types.ts'
import { makeLogger } from '../_shared/log.ts'
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
import { spawnSync } from 'node:child_process'
const log = makeLogger('telegram-recap')
// ---------------------------------------------------------------------------
// Extended ToolMeta type with secrets, tags, result_actions
// (the Rust runtime supports these fields; they're not yet in the shared type)
// ---------------------------------------------------------------------------
interface ToolMeta extends BaseToolMeta {
secrets?: Array<{ env_var: string; description: string; required?: boolean }>
tags?: string[]
result_actions?: Array<{
label: string
icon?: string
condition?: string
prompt_template: string
confirm?: string
}>
}
// ---------------------------------------------------------------------------
// Tool metadata
// ---------------------------------------------------------------------------
export const meta: ToolMeta = {
name: 'telegram-recap',
display_name: 'Telegram Recap',
description:
'Fetch and email a Telegram chat digest — pulls messages from the configured window and sends via SMTP',
version: '1.0.0',
requires_setup: true,
setup_description:
'Verifies TELEGRAM_BOT_TOKEN by calling Telegram getMe and checking database connectivity',
input_schema: {
type: 'object',
required: [],
properties: {
window_hours: {
type: 'number',
description: 'Time window in hours to include in the digest (default: 24)',
},
dry_run: {
type: 'boolean',
description: 'Print digest to stdout without sending email',
},
chat_ids: {
type: 'array',
items: { type: 'string' },
description: 'Override configured chat IDs (e.g. ["-100123456789"])',
},
},
},
output_schema: {
type: 'object',
properties: {
dry_run: { type: 'boolean', description: 'Whether this was a dry run' },
total_messages: { type: 'number', description: 'Messages included in digest' },
chat_count: { type: 'number', description: 'Number of chats included' },
period_start: { type: 'string', description: 'ISO 8601 period start timestamp' },
period_end: { type: 'string', description: 'ISO 8601 period end timestamp' },
sent_to: {
type: 'array',
items: { type: 'string' },
description: 'Recipient addresses (empty on dry run)',
},
},
},
secrets: [
{
env_var: 'TELEGRAM_BOT_TOKEN',
description: 'Telegram bot API token (from @BotFather)',
required: true,
},
{
env_var: 'SMTP_HOST',
description: 'SMTP server hostname (e.g. smtp.resend.com)',
required: true,
},
{
env_var: 'SMTP_PORT',
description: 'SMTP server port (e.g. 587 for STARTTLS, 465 for SSL)',
required: true,
},
{
env_var: 'SMTP_USERNAME',
description: 'SMTP authentication username',
required: true,
},
{
env_var: 'SMTP_PASSWORD',
description: 'SMTP authentication password or API key',
required: true,
},
{
env_var: 'SMTP_FROM',
description: 'From address for digest emails (e.g. digest@yourdomain.com)',
required: true,
},
{
env_var: 'SMTP_TO',
description: 'Recipient address(es), comma-separated',
required: true,
},
],
tags: ['telegram', 'email', 'digest'],
result_actions: [
{
label: 'Send test digest',
icon: 'send',
condition: '$.ok == true',
prompt_template:
'Run the telegram-recap tool with dry_run: true to preview the digest without sending email.',
confirm: 'This will fetch messages and display the digest without sending email.',
},
],
}
// ---------------------------------------------------------------------------
// Input / output types
// ---------------------------------------------------------------------------
interface Input {
window_hours?: number
dry_run?: boolean
chat_ids?: string[]
}
interface DigestOutput {
dry_run: boolean
total_messages: number
chat_count: number
period_start: string
period_end: string
sent_to: string[]
}
// ---------------------------------------------------------------------------
// setup() — verify bot token and database connectivity
// ---------------------------------------------------------------------------
export function setup(): ToolResult<{ status_output: string }> {
const start = Date.now()
log.info('running: sdlc telegram status')
const proc = spawnSync('sdlc', ['telegram', 'status'], {
env: process.env,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
const stdout = (proc.stdout ?? '').trim()
const stderr = (proc.stderr ?? '').trim()
const exitCode = proc.status ?? 1
if (exitCode !== 0) {
log.error(`sdlc telegram status failed (exit ${exitCode}): ${stderr}`)
return {
ok: false,
error: `Bot token check failed (exit ${exitCode}): ${stderr || stdout || 'no output'}`,
duration_ms: Date.now() - start,
}
}
log.info(`setup ok:\n${stdout}`)
return {
ok: true,
data: { status_output: stdout },
duration_ms: Date.now() - start,
}
}
// ---------------------------------------------------------------------------
// run() — fetch messages and send (or preview) the digest
// ---------------------------------------------------------------------------
export function run(input: Input): ToolResult<DigestOutput> {
const start = Date.now()
const args: string[] = ['telegram', 'digest', '--json']
if (input.dry_run) {
args.push('--dry-run')
}
if (input.window_hours !== undefined) {
args.push('--window', String(Math.round(input.window_hours)))
}
if (input.chat_ids && input.chat_ids.length > 0) {
for (const id of input.chat_ids) {
args.push('--chat', id)
}
}
log.info(`running: sdlc ${args.join(' ')}`)
const proc = spawnSync('sdlc', args, {
env: process.env,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
const stdout = (proc.stdout ?? '').trim()
const stderr = (proc.stderr ?? '').trim()
const exitCode = proc.status ?? 1
if (exitCode !== 0) {
log.error(`sdlc telegram digest failed (exit ${exitCode})`)
return {
ok: false,
error: `sdlc telegram digest failed (exit ${exitCode}): ${stderr || stdout || 'no output'}`,
duration_ms: Date.now() - start,
}
}
let parsed: DigestOutput
try {
parsed = JSON.parse(stdout) as DigestOutput
} catch (e) {
return {
ok: false,
error: `Failed to parse digest JSON output: ${e}. Raw stdout: ${stdout}`,
duration_ms: Date.now() - start,
}
}
log.info(
`digest ok: ${parsed.total_messages} messages across ${parsed.chat_count} chats, sent_to=${JSON.stringify(parsed.sent_to)}`,
)
return {
ok: true,
data: parsed,
duration_ms: Date.now() - start,
}
}
// ---------------------------------------------------------------------------
// CLI entrypoint
// ---------------------------------------------------------------------------
const mode = getArgs()[0] ?? '--run'
if (mode === '--meta') {
console.log(JSON.stringify(meta))
exit(0)
} else if (mode === '--setup') {
const result = setup()
console.log(JSON.stringify(result))
exit(result.ok ? 0 : 1)
} else if (mode === '--run') {
readStdin()
.then(raw => {
const result = run(JSON.parse(raw || '{}') as Input)
console.log(JSON.stringify(result))
exit(result.ok ? 0 : 1)
})
.catch(e => {
console.log(JSON.stringify({ ok: false, error: String(e) }))
exit(1)
})
} else {
console.error(`Unknown mode: ${mode}. Use --meta, --setup, or --run.`)
exit(1)
}

52
.sdlc/tools/tools.md Normal file
View File

@ -0,0 +1,52 @@
# SDLC Tools
Project-specific tools installed by sdlc. Use `sdlc tool run <name>` to invoke.
Run `sdlc tool sync` to regenerate this file from live tool metadata.
---
## ama — AMA — Ask Me Anything
Answers questions about the codebase by searching a pre-built keyword index.
**Run:** `sdlc tool run ama --question "..."`
**Setup required:** Yes — `sdlc tool run ama --setup`
_Indexes source files for keyword search (run once, then re-run when files change significantly)_
---
## quality-check — Quality Check
Runs checks from .sdlc/tools/quality-check/config.yaml and reports pass/fail.
**Run:** `sdlc tool run quality-check`
**Setup required:** No
_Edit `.sdlc/tools/quality-check/config.yaml` to add your project's checks_
---
## dev-driver — Dev Driver
Finds the next development action and dispatches it — advances the project one step per tick.
**Run:** `sdlc tool run dev-driver`
**Setup required:** No
_Configure via orchestrator: Label=dev-driver, Tool=dev-driver, Input={}, Recurrence=14400. See `.sdlc/tools/dev-driver/README.md` for full docs._
---
## telegram-recap — Telegram Recap
Fetch and email a Telegram chat digest — pulls messages from the configured window and sends via SMTP.
**Run:** `sdlc tool run telegram-recap --input '{}'`
**Setup required:** Yes — `sdlc tool run telegram-recap --setup`
_Requires 7 secrets: TELEGRAM_BOT_TOKEN, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM, SMTP_TO. Schedule with orchestrator (--every 86400) for a daily digest._
---
## Adding a Custom Tool
Run `sdlc tool scaffold <name> "<description>"` to create a new tool skeleton.
Then implement the `run()` function in `.sdlc/tools/<name>/tool.ts` and run `sdlc tool sync`.

98
AGENTS.md Normal file
View File

@ -0,0 +1,98 @@
# AGENTS.md
Agent instructions for tidalDB.
## Team
| Agent | Identity | Model | Invoke when |
|-------|----------|-------|-------------|
| `@tidal-engineer` | Jon Gjengset — principal Rust database engineer | opus | Implementing features, storage internals, signal system, query engine, debugging correctness |
| `@tidal-visionary` | Spencer Kimball — product and roadmap strategist | opus | Planning milestones, scoping phases, build-vs-defer decisions, roadmap sequencing |
| `@tidal-researcher` | Andy Pavlo — database systems researcher | opus | Prior art surveys, library evaluation, architectural research, producing `docs/research/` docs |
| `@tidal-storyteller` | Marketing and technical writer | sonnet | Marketing site (`site/`), blog posts, public-facing copy |
Agent definitions live in `.claude/agents/`. Full context in `CLAUDE.md §Agents`.
---
<!-- sdlc:start -->
## SDLC
> **Required reading:** `.sdlc/guidance.md` — engineering principles that govern all implementation decisions on this project. <!-- sdlc:guidance -->
This project uses `sdlc` as its SDLC state machine. `sdlc` manages feature lifecycle, artifacts, tasks, and milestones. It emits structured directives via `sdlc next --json` that any consumer (Claude Code, custom scripts, or humans) acts on to decide what to do next.
Consumer scaffolding is installed globally under `~/.claude/commands/`, `~/.gemini/commands/`, `~/.opencode/command/`, and `~/.agents/skills/` — available across all projects. Use `/sdlc-specialize` in Claude Code to generate a project-specific AI team (agents + skills) tailored to this project's tech stack and roles.
### Key Commands
- `sdlc feature create <slug> --title "..."` — create a new feature
- `sdlc next --for <slug> --json` — get the next action directive (JSON)
- `sdlc next` — show all active features and their next actions
- `sdlc artifact approve <slug> <type>` — approve an artifact to advance the phase
- `sdlc state` — show project state
- `sdlc feature list` — list all features and their phases
- `sdlc task list [<slug>]` — list tasks for a feature (or all tasks)
### Lifecycle
draft → specified → planned → ready → implementation → review → audit → qa → merge → released
Treat this lifecycle as the default pathway. You can use explicit manual transitions when needed, but approvals/artifacts are the recommended way to keep quality and traceability.
### Artifact Types
`spec` `design` `tasks` `qa_plan` `review` `audit` `qa_results`
### CRITICAL: Never edit .sdlc/ YAML directly
All state changes go through `sdlc` CLI commands. See §6 of `.sdlc/guidance.md` for the full command reference. Direct YAML edits corrupt state.
### Directive Interface
Use `sdlc next --for <slug> --json` to get the next directive. The JSON output tells the consumer what to do next (action, message, output_path, is_heavy, gates).
### Consumer Commands
- `/sdlc-next <slug>` — execute one step, then stop (human controls cadence)
- `/sdlc-run <slug>` — run autonomously to completion
- `/sdlc-status [<slug>]` — show current state
- `/sdlc-plan` — distribute a plan into milestones, features, and tasks
- `/sdlc-milestone-uat <milestone-slug>` — run the acceptance test for a milestone
- `/sdlc-pressure-test <milestone-slug>` — pressure-test a milestone against user perspectives
- `/sdlc-vision-adjustment [description]` — align all docs, sdlc state, and code to a vision change
- `/sdlc-architecture-adjustment [description]` — align all docs, code, and sdlc state to an architecture change
- `/sdlc-enterprise-readiness [--stage <stage>]` — analyze production readiness
- `/sdlc-setup-quality-gates` — set up pre-commit hooks and quality gates
- `/sdlc-cookbook <milestone-slug>` — create developer-scenario cookbook recipes
- `/sdlc-cookbook-run <milestone-slug>` — execute cookbook recipes and record results
- `/sdlc-ponder [slug]` — open the ideation workspace for exploring and committing ideas
- `/sdlc-ponder-commit <slug>` — crystallize a pondered idea into milestones and features
- `/sdlc-guideline <slug-or-problem>` — build an evidence-backed guideline through five research perspectives and TOC-first distillation
- `/sdlc-suggest` — analyze project state and suggest 3-5 ponder topics to explore next
- `/sdlc-beat [domain | feature:<slug> | --week]` — step back with a senior leadership lens; evaluate if we're building the right thing in the right direction; stores history in `.sdlc/beat.yaml`
- `/sdlc-recruit <role>` — recruit an expert thought partner as a persistent agent
- `/sdlc-empathy <subject>` — deep user perspective interviews before decisions
- `/sdlc-spike <slug> — <need>; [see <ref>]` — research, prototype, validate, and report; produces working prototype + findings in `.sdlc/spikes/<slug>/findings.md`
- `/sdlc-convo-mine [file or text]` — mine conversation dumps for signal; apply 5 perspective lenses, group themes, launch parallel ponder sessions per group
### Tool Suite
<!-- sdlc:tools -->
Project-scoped TypeScript tools in `.sdlc/tools/` — callable by agents and humans
during any lifecycle phase. Read `.sdlc/tools/tools.md` for the full help menu.
- `sdlc tool list` — show installed tools
- `sdlc tool run <name> [args]` — run a tool; pass `--json '{...}'` for complex input
- `sdlc tool sync` — regenerate `tools.md` after adding a custom tool
- `sdlc tool scaffold <name> "desc"` — create a new tool skeleton
**Core tools:** `ama` (codebase Q&A), `quality-check` (runs platform shell gates)
Use `/sdlc-tool-run`, `/sdlc-tool-build`, `/sdlc-tool-audit`, `/sdlc-tool-uat` in Claude Code for guided tool workflows.
<!-- /sdlc:tools -->
Project: tidalDB
<!-- sdlc:end -->