diff --git a/.ai/index.md b/.ai/index.md new file mode 100644 index 0000000..05c4ed4 --- /dev/null +++ b/.ai/index.md @@ -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` +``` diff --git a/.claude/agents/knowledge-librarian.md b/.claude/agents/knowledge-librarian.md new file mode 100644 index 0000000..23320f2 --- /dev/null +++ b/.claude/agents/knowledge-librarian.md @@ -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 # read an entry +sdlc knowledge update --code 100.20 # reclassify +sdlc knowledge update --status published # publish +sdlc knowledge search "" # 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/"` or `"ponder/"` +- Write durable insights only — decisions, conclusions, patterns. Not raw dialogue. +- Start with `status: draft`; publish when the content is solid diff --git a/.gitignore b/.gitignore index dded964..6e4eb4a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ Thumbs.db # Ephemeral / scratch tmp/ .claude/worktrees/ +.sdlc/tools/*/index/ +.sdlc/telemetry.redb diff --git a/.sdlc/config.yaml b/.sdlc/config.yaml new file mode 100644 index 0000000..d501b04 --- /dev/null +++ b/.sdlc/config.yaml @@ -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 diff --git a/.sdlc/features/m10-agent-capability-boundaries/manifest.yaml b/.sdlc/features/m10-agent-capability-boundaries/manifest.yaml new file mode 100644 index 0000000..2b3f992 --- /dev/null +++ b/.sdlc/features/m10-agent-capability-boundaries/manifest.yaml @@ -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 diff --git a/.sdlc/features/m10-community-policy-engine/manifest.yaml b/.sdlc/features/m10-community-policy-engine/manifest.yaml new file mode 100644 index 0000000..3ef6598 --- /dev/null +++ b/.sdlc/features/m10-community-policy-engine/manifest.yaml @@ -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 diff --git a/.sdlc/features/m10-signal-revocation-controls/manifest.yaml b/.sdlc/features/m10-signal-revocation-controls/manifest.yaml new file mode 100644 index 0000000..6e9777d --- /dev/null +++ b/.sdlc/features/m10-signal-revocation-controls/manifest.yaml @@ -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 diff --git a/.sdlc/features/m9-community-profile-sync/manifest.yaml b/.sdlc/features/m9-community-profile-sync/manifest.yaml new file mode 100644 index 0000000..7f6257b --- /dev/null +++ b/.sdlc/features/m9-community-profile-sync/manifest.yaml @@ -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 diff --git a/.sdlc/features/m9-leave-revocation/manifest.yaml b/.sdlc/features/m9-leave-revocation/manifest.yaml new file mode 100644 index 0000000..8018c44 --- /dev/null +++ b/.sdlc/features/m9-leave-revocation/manifest.yaml @@ -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 diff --git a/.sdlc/features/m9-purge-rematerialization/manifest.yaml b/.sdlc/features/m9-purge-rematerialization/manifest.yaml new file mode 100644 index 0000000..7ab5092 --- /dev/null +++ b/.sdlc/features/m9-purge-rematerialization/manifest.yaml @@ -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 diff --git a/.sdlc/features/m9-retroactive-purge/manifest.yaml b/.sdlc/features/m9-retroactive-purge/manifest.yaml new file mode 100644 index 0000000..3a340e4 --- /dev/null +++ b/.sdlc/features/m9-retroactive-purge/manifest.yaml @@ -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 diff --git a/.sdlc/features/p0-concierge-pilot-loop/manifest.yaml b/.sdlc/features/p0-concierge-pilot-loop/manifest.yaml new file mode 100644 index 0000000..89227ec --- /dev/null +++ b/.sdlc/features/p0-concierge-pilot-loop/manifest.yaml @@ -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 diff --git a/.sdlc/features/p0-target-segment-recruitment/manifest.yaml b/.sdlc/features/p0-target-segment-recruitment/manifest.yaml new file mode 100644 index 0000000..7bc8495 --- /dev/null +++ b/.sdlc/features/p0-target-segment-recruitment/manifest.yaml @@ -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 diff --git a/.sdlc/features/p0-validation-readout/manifest.yaml b/.sdlc/features/p0-validation-readout/manifest.yaml new file mode 100644 index 0000000..e87d55d --- /dev/null +++ b/.sdlc/features/p0-validation-readout/manifest.yaml @@ -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 diff --git a/.sdlc/features/p1-briefing-ux-reason-labels/manifest.yaml b/.sdlc/features/p1-briefing-ux-reason-labels/manifest.yaml new file mode 100644 index 0000000..8438224 --- /dev/null +++ b/.sdlc/features/p1-briefing-ux-reason-labels/manifest.yaml @@ -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 diff --git a/.sdlc/features/p1-feedback-loop-ux/manifest.yaml b/.sdlc/features/p1-feedback-loop-ux/manifest.yaml new file mode 100644 index 0000000..c02bf8d --- /dev/null +++ b/.sdlc/features/p1-feedback-loop-ux/manifest.yaml @@ -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 diff --git a/.sdlc/features/p1-quality-diversity-baseline/manifest.yaml b/.sdlc/features/p1-quality-diversity-baseline/manifest.yaml new file mode 100644 index 0000000..22eb323 --- /dev/null +++ b/.sdlc/features/p1-quality-diversity-baseline/manifest.yaml @@ -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 diff --git a/.sdlc/features/p2-cohort-context-views/manifest.yaml b/.sdlc/features/p2-cohort-context-views/manifest.yaml new file mode 100644 index 0000000..93d63f0 --- /dev/null +++ b/.sdlc/features/p2-cohort-context-views/manifest.yaml @@ -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 diff --git a/.sdlc/features/p2-self-serve-onboarding/manifest.yaml b/.sdlc/features/p2-self-serve-onboarding/manifest.yaml new file mode 100644 index 0000000..b7423f2 --- /dev/null +++ b/.sdlc/features/p2-self-serve-onboarding/manifest.yaml @@ -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 diff --git a/.sdlc/features/p2-trust-controls/manifest.yaml b/.sdlc/features/p2-trust-controls/manifest.yaml new file mode 100644 index 0000000..c129bea --- /dev/null +++ b/.sdlc/features/p2-trust-controls/manifest.yaml @@ -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 diff --git a/.sdlc/features/p3-launch-support-playbook/manifest.yaml b/.sdlc/features/p3-launch-support-playbook/manifest.yaml new file mode 100644 index 0000000..6181975 --- /dev/null +++ b/.sdlc/features/p3-launch-support-playbook/manifest.yaml @@ -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 diff --git a/.sdlc/features/p3-quality-operations/manifest.yaml b/.sdlc/features/p3-quality-operations/manifest.yaml new file mode 100644 index 0000000..7f71e90 --- /dev/null +++ b/.sdlc/features/p3-quality-operations/manifest.yaml @@ -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 diff --git a/.sdlc/features/p3-reliability-slos/manifest.yaml b/.sdlc/features/p3-reliability-slos/manifest.yaml new file mode 100644 index 0000000..a19a557 --- /dev/null +++ b/.sdlc/features/p3-reliability-slos/manifest.yaml @@ -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 diff --git a/.sdlc/features/p4-monetization-experiments/manifest.yaml b/.sdlc/features/p4-monetization-experiments/manifest.yaml new file mode 100644 index 0000000..cb6a671 --- /dev/null +++ b/.sdlc/features/p4-monetization-experiments/manifest.yaml @@ -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 diff --git a/.sdlc/features/p4-quality-safe-growth/manifest.yaml b/.sdlc/features/p4-quality-safe-growth/manifest.yaml new file mode 100644 index 0000000..ef8199f --- /dev/null +++ b/.sdlc/features/p4-quality-safe-growth/manifest.yaml @@ -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 diff --git a/.sdlc/features/p4-segment-expansion-plan/manifest.yaml b/.sdlc/features/p4-segment-expansion-plan/manifest.yaml new file mode 100644 index 0000000..98154a5 --- /dev/null +++ b/.sdlc/features/p4-segment-expansion-plan/manifest.yaml @@ -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 diff --git a/.sdlc/features/pg1-baseline-comparison/manifest.yaml b/.sdlc/features/pg1-baseline-comparison/manifest.yaml new file mode 100644 index 0000000..4cbe51d --- /dev/null +++ b/.sdlc/features/pg1-baseline-comparison/manifest.yaml @@ -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 diff --git a/.sdlc/features/pg1-instrumented-metrics/manifest.yaml b/.sdlc/features/pg1-instrumented-metrics/manifest.yaml new file mode 100644 index 0000000..7984927 --- /dev/null +++ b/.sdlc/features/pg1-instrumented-metrics/manifest.yaml @@ -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 diff --git a/.sdlc/features/pg1-personalization-correctness/manifest.yaml b/.sdlc/features/pg1-personalization-correctness/manifest.yaml new file mode 100644 index 0000000..f8b2266 --- /dev/null +++ b/.sdlc/features/pg1-personalization-correctness/manifest.yaml @@ -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 diff --git a/.sdlc/guidance.md b/.sdlc/guidance.md new file mode 100644 index 0000000..497c774 --- /dev/null +++ b/.sdlc/guidance.md @@ -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 --title "…"` | +| Get next action | `sdlc next --for --json` | +| Write artifact | Write Markdown to `output_path` from the directive | +| Submit draft | `sdlc artifact draft ` | +| Approve artifact | `sdlc artifact approve ` | +| Reject artifact | `sdlc artifact reject ` | +| Merge (release feature) | `sdlc merge ` | +| Add task | `sdlc task add "title"` | +| Start task | `sdlc task start ` | +| Complete task | `sdlc task complete ` | +| Block task | `sdlc task block "reason"` | +| Add comment | `sdlc comment create "body"` | +| Show feature | `sdlc feature show --json` | +| List tasks | `sdlc task list ` | +| Project state | `sdlc state` | +| Survey milestone waves | `sdlc project prepare [--milestone ]` | +| Mark milestone prepared | `sdlc milestone mark-prepared ` | +| Project phase | `sdlc project status` | +| Escalate to human | `sdlc escalate create --kind --title "…" --context "…" [--feature ]` | +| List escalations | `sdlc escalate list` | +| Resolve escalation | `sdlc escalate resolve "resolution note"` | +| Knowledge base status | `sdlc knowledge status` | +| List knowledge entries | `sdlc knowledge list [--code-prefix ]` | +| Search knowledge base | `sdlc knowledge search ` | +| Show knowledge entry | `sdlc knowledge show ` | +| Add knowledge entry | `sdlc knowledge add --title "..." --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 ""` +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 ` | +| Load secrets into shell | `eval $(sdlc secrets env export )` | +| Set a secret | `sdlc secrets env set KEY=value` | +| List authorized keys | `sdlc secrets keys list` | +| Add a key | `sdlc secrets keys add --name --key "$(cat ~/.ssh/id_ed25519.pub)"` | +| Rekey after key change | `sdlc secrets keys rekey` | + +**For agents:** Check `sdlc secrets env names ` 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:` +- 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. diff --git a/.sdlc/knowledge/catalog.yaml b/.sdlc/knowledge/catalog.yaml new file mode 100644 index 0000000..5a2df5f --- /dev/null +++ b/.sdlc/knowledge/catalog.yaml @@ -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 diff --git a/.sdlc/knowledge/maintenance-log.yaml b/.sdlc/knowledge/maintenance-log.yaml new file mode 100644 index 0000000..95963dc --- /dev/null +++ b/.sdlc/knowledge/maintenance-log.yaml @@ -0,0 +1,4 @@ +actions: +- timestamp: 2026-03-03T06:23:11.759775Z + action_type: harvest + detail: librarian init diff --git a/.sdlc/milestones/m0/manifest.yaml b/.sdlc/milestones/m0/manifest.yaml new file mode 100644 index 0000000..c7dff52 --- /dev/null +++ b/.sdlc/milestones/m0/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m1/manifest.yaml b/.sdlc/milestones/m1/manifest.yaml new file mode 100644 index 0000000..a27b4e7 --- /dev/null +++ b/.sdlc/milestones/m1/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m10/manifest.yaml b/.sdlc/milestones/m10/manifest.yaml new file mode 100644 index 0000000..1f8d587 --- /dev/null +++ b/.sdlc/milestones/m10/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m2/manifest.yaml b/.sdlc/milestones/m2/manifest.yaml new file mode 100644 index 0000000..d94a60f --- /dev/null +++ b/.sdlc/milestones/m2/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m3/manifest.yaml b/.sdlc/milestones/m3/manifest.yaml new file mode 100644 index 0000000..b3f8736 --- /dev/null +++ b/.sdlc/milestones/m3/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m4/manifest.yaml b/.sdlc/milestones/m4/manifest.yaml new file mode 100644 index 0000000..0dad46d --- /dev/null +++ b/.sdlc/milestones/m4/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m5/manifest.yaml b/.sdlc/milestones/m5/manifest.yaml new file mode 100644 index 0000000..dde7f7a --- /dev/null +++ b/.sdlc/milestones/m5/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m6/manifest.yaml b/.sdlc/milestones/m6/manifest.yaml new file mode 100644 index 0000000..54472b5 --- /dev/null +++ b/.sdlc/milestones/m6/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m7/manifest.yaml b/.sdlc/milestones/m7/manifest.yaml new file mode 100644 index 0000000..03c8cdd --- /dev/null +++ b/.sdlc/milestones/m7/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m8/manifest.yaml b/.sdlc/milestones/m8/manifest.yaml new file mode 100644 index 0000000..d5bd287 --- /dev/null +++ b/.sdlc/milestones/m8/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/m9/manifest.yaml b/.sdlc/milestones/m9/manifest.yaml new file mode 100644 index 0000000..075d485 --- /dev/null +++ b/.sdlc/milestones/m9/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/p0/manifest.yaml b/.sdlc/milestones/p0/manifest.yaml new file mode 100644 index 0000000..1094a5d --- /dev/null +++ b/.sdlc/milestones/p0/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/p1/manifest.yaml b/.sdlc/milestones/p1/manifest.yaml new file mode 100644 index 0000000..b2b8a8a --- /dev/null +++ b/.sdlc/milestones/p1/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/p2/manifest.yaml b/.sdlc/milestones/p2/manifest.yaml new file mode 100644 index 0000000..23ab7f0 --- /dev/null +++ b/.sdlc/milestones/p2/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/p3/manifest.yaml b/.sdlc/milestones/p3/manifest.yaml new file mode 100644 index 0000000..030525f --- /dev/null +++ b/.sdlc/milestones/p3/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/p4/manifest.yaml b/.sdlc/milestones/p4/manifest.yaml new file mode 100644 index 0000000..817121b --- /dev/null +++ b/.sdlc/milestones/p4/manifest.yaml @@ -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 diff --git a/.sdlc/milestones/pg1/manifest.yaml b/.sdlc/milestones/pg1/manifest.yaml new file mode 100644 index 0000000..3620b60 --- /dev/null +++ b/.sdlc/milestones/pg1/manifest.yaml @@ -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 diff --git a/.sdlc/state.yaml b/.sdlc/state.yaml new file mode 100644 index 0000000..0e554d3 --- /dev/null +++ b/.sdlc/state.yaml @@ -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 diff --git a/.sdlc/tools/_shared/config.ts b/.sdlc/tools/_shared/config.ts new file mode 100644 index 0000000..7603a39 --- /dev/null +++ b/.sdlc/tools/_shared/config.ts @@ -0,0 +1,44 @@ +/** + * SDLC Tool Config Loader + * + * Reads .sdlc/tools//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>( + 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 { + const result: Record = {} + 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 +} diff --git a/.sdlc/tools/_shared/log.ts b/.sdlc/tools/_shared/log.ts new file mode 100644 index 0000000..9ef154a --- /dev/null +++ b/.sdlc/tools/_shared/log.ts @@ -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:] 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 diff --git a/.sdlc/tools/_shared/runtime.ts b/.sdlc/tools/_shared/runtime.ts new file mode 100644 index 0000000..5d19c7b --- /dev/null +++ b/.sdlc/tools/_shared/runtime.ts @@ -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 { + 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') +} diff --git a/.sdlc/tools/_shared/types.ts b/.sdlc/tools/_shared/types.ts new file mode 100644 index 0000000..a62dd99 --- /dev/null +++ b/.sdlc/tools/_shared/types.ts @@ -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 { + 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 diff --git a/.sdlc/tools/ama/README.md b/.sdlc/tools/ama/README.md new file mode 100644 index 0000000..819d4cf --- /dev/null +++ b/.sdlc/tools/ama/README.md @@ -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. diff --git a/.sdlc/tools/ama/config.yaml b/.sdlc/tools/ama/config.yaml new file mode 100644 index 0000000..26719b6 --- /dev/null +++ b/.sdlc/tools/ama/config.yaml @@ -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 diff --git a/.sdlc/tools/ama/tool.ts b/.sdlc/tools/ama/tool.ts new file mode 100644 index 0000000..946b4d0 --- /dev/null +++ b/.sdlc/tools/ama/tool.ts @@ -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.0–1.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 +} + +interface MtimeMap { + version: number + indexed_at: string + files: Record +} + +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> { + 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 = {} + 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() + for (const chunk of prevChunks) { + const arr = prevChunksByFile.get(chunk.path) ?? [] + arr.push(chunk) + prevChunksByFile.set(chunk.path, arr) + } + + const newMtimes: Record = {} + 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 = {} + for (const chunk of allChunks) { + for (const token of chunk.tokens) { + df[token] = (df[token] ?? 0) + 1 + } + } + const idf: Record = {} + 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> { + 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 = {} + 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, maxFileKb: number): string[] { + const results: string[] = [] + + function walk(dir: string) { + let entries: ReturnType + 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() + 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, +): { 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) +} diff --git a/.sdlc/tools/dev-driver/README.md b/.sdlc/tools/dev-driver/README.md new file mode 100644 index 0000000..4d12cf6 --- /dev/null +++ b/.sdlc/tools/dev-driver/README.md @@ -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 " +``` + +**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 " +``` + +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 --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 `, NOT `/sdlc-run `. + +`/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 }` diff --git a/.sdlc/tools/dev-driver/tool.ts b/.sdlc/tools/dev-driver/tool.ts new file mode 100644 index 0000000..99b0a3f --- /dev/null +++ b/.sdlc/tools/dev-driver/tool.ts @@ -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. + * + * --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 --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 & { 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 + 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, + root: string, +): Promise> { + 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, 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) +} diff --git a/.sdlc/tools/quality-check/README.md b/.sdlc/tools/quality-check/README.md new file mode 100644 index 0000000..bb03502 --- /dev/null +++ b/.sdlc/tools/quality-check/README.md @@ -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. diff --git a/.sdlc/tools/quality-check/config.yaml b/.sdlc/tools/quality-check/config.yaml new file mode 100644 index 0000000..4f04501 --- /dev/null +++ b/.sdlc/tools/quality-check/config.yaml @@ -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: diff --git a/.sdlc/tools/quality-check/tool.ts b/.sdlc/tools/quality-check/tool.ts new file mode 100644 index 0000000..4ffcc98 --- /dev/null +++ b/.sdlc/tools/quality-check/tool.ts @@ -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: + * description: + * script: + */ +function parseChecksFromYaml(content: string): PlatformCommand[] { + const checks: PlatformCommand[] = [] + const lines = content.split('\n') + + let inChecks = false + let current: Partial | 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: ` + 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> { + 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) +} diff --git a/.sdlc/tools/telegram-recap/README.md b/.sdlc/tools/telegram-recap/README.md new file mode 100644 index 0000000..dad026e --- /dev/null +++ b/.sdlc/tools/telegram-recap/README.md @@ -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. diff --git a/.sdlc/tools/telegram-recap/config.yaml b/.sdlc/tools/telegram-recap/config.yaml new file mode 100644 index 0000000..5645028 --- /dev/null +++ b/.sdlc/tools/telegram-recap/config.yaml @@ -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. diff --git a/.sdlc/tools/telegram-recap/tool.ts b/.sdlc/tools/telegram-recap/tool.ts new file mode 100644 index 0000000..f673b40 --- /dev/null +++ b/.sdlc/tools/telegram-recap/tool.ts @@ -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 { + 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) +} diff --git a/.sdlc/tools/tools.md b/.sdlc/tools/tools.md new file mode 100644 index 0000000..a51f87d --- /dev/null +++ b/.sdlc/tools/tools.md @@ -0,0 +1,52 @@ +# SDLC Tools + +Project-specific tools installed by sdlc. Use `sdlc tool run ` 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 ""` to create a new tool skeleton. +Then implement the `run()` function in `.sdlc/tools//tool.ts` and run `sdlc tool sync`. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c0b38be --- /dev/null +++ b/AGENTS.md @@ -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 + +> **Required reading:** `.sdlc/guidance.md` — engineering principles that govern all implementation decisions on this project. + +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 --title "..."` — create a new feature +- `sdlc next --for --json` — get the next action directive (JSON) +- `sdlc next` — show all active features and their next actions +- `sdlc artifact approve ` — approve an artifact to advance the phase +- `sdlc state` — show project state +- `sdlc feature list` — list all features and their phases +- `sdlc task list []` — 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 --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 ` — execute one step, then stop (human controls cadence) +- `/sdlc-run ` — run autonomously to completion +- `/sdlc-status []` — show current state +- `/sdlc-plan` — distribute a plan into milestones, features, and tasks +- `/sdlc-milestone-uat ` — run the acceptance test for a milestone +- `/sdlc-pressure-test ` — 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 ]` — analyze production readiness +- `/sdlc-setup-quality-gates` — set up pre-commit hooks and quality gates +- `/sdlc-cookbook ` — create developer-scenario cookbook recipes +- `/sdlc-cookbook-run ` — execute cookbook recipes and record results +- `/sdlc-ponder [slug]` — open the ideation workspace for exploring and committing ideas +- `/sdlc-ponder-commit ` — crystallize a pondered idea into milestones and features +- `/sdlc-guideline ` — 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: | --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 ` — recruit an expert thought partner as a persistent agent +- `/sdlc-empathy ` — deep user perspective interviews before decisions +- `/sdlc-spike ; [see ]` — research, prototype, validate, and report; produces working prototype + findings in `.sdlc/spikes//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 + + +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 [args]` — run a tool; pass `--json '{...}'` for complex input +- `sdlc tool sync` — regenerate `tools.md` after adding a custom tool +- `sdlc tool scaffold "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. + + +Project: tidalDB + +