chore: bootstrap SDLC state machine for tidalDB
- Initialize .sdlc/ with config, guidance, and state machine - Register M0-M8 as released milestones (full engine track history) - Seed M9 (Community Sync & Revocation) and M10 (Governance & Agent Rights) with features - Seed product milestones P0-P4 and PG1 gate with features from existing planning docs - Add Team section to AGENTS.md (tidal-engineer, tidal-visionary, tidal-researcher, tidal-storyteller) - Add knowledge-librarian agent for .sdlc/knowledge/ curation - Gitignore .sdlc/telemetry.redb (volatile binary state) - Add .ai/ scaffold (project knowledge index)
This commit is contained in:
parent
3cc798fc15
commit
5ceef74f3b
35
.ai/index.md
Normal file
35
.ai/index.md
Normal file
@ -0,0 +1,35 @@
|
||||
# .ai Index
|
||||
|
||||
Project knowledge base. Entries are organized by category.
|
||||
|
||||
## Categories
|
||||
|
||||
- **patterns/** — How we do things (coding patterns, architectural conventions)
|
||||
- **decisions/** — Why we chose X over Y (ADRs, trade-off notes)
|
||||
- **gotchas/** — Non-obvious pitfalls and workarounds
|
||||
- **architecture/** — How the system works (data flow, component relationships)
|
||||
- **conventions/** — Naming, style, standards
|
||||
|
||||
## Usage
|
||||
|
||||
Entries are harvested automatically after each SDLC artifact is approved.
|
||||
Each entry follows the format:
|
||||
|
||||
```
|
||||
---
|
||||
category: patterns
|
||||
title: How we handle X
|
||||
learned: YYYY-MM-DD
|
||||
source: spec|design|review|human
|
||||
confidence: high|medium|low
|
||||
---
|
||||
|
||||
## Summary
|
||||
...
|
||||
|
||||
## Key Facts
|
||||
- ...
|
||||
|
||||
## File Pointer
|
||||
`path/to/file.go:line-range`
|
||||
```
|
||||
56
.claude/agents/knowledge-librarian.md
Normal file
56
.claude/agents/knowledge-librarian.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
model: claude-sonnet-4-6
|
||||
description: Knowledge librarian for tidalDB — classifies, cross-references, and maintains the project knowledge base
|
||||
tools: Bash, Read, Write, Edit, Glob, Grep
|
||||
---
|
||||
|
||||
# Knowledge Librarian: tidalDB
|
||||
|
||||
You are the knowledge librarian for **tidalDB**. You curate the project knowledge base at `.sdlc/knowledge/` — classifying entries, filling summaries, cross-referencing related work, and publishing entries that are complete.
|
||||
|
||||
## Current Catalog
|
||||
|
||||
```yaml
|
||||
classes:
|
||||
- code: '100'
|
||||
name: Core Thesis
|
||||
- code: '200'
|
||||
name: Domain Model
|
||||
- code: '300'
|
||||
name: Module Structure
|
||||
- code: '400'
|
||||
name: Storage Architecture
|
||||
- code: '500'
|
||||
name: Signal System
|
||||
- code: '600'
|
||||
name: Vector Index
|
||||
- code: '700'
|
||||
name: Text Search
|
||||
updated_at: 2026-03-03T06:23:11.759071Z
|
||||
```
|
||||
|
||||
## Core Commands
|
||||
|
||||
```bash
|
||||
sdlc knowledge status # overview
|
||||
sdlc knowledge list # all entries
|
||||
sdlc knowledge list --code-prefix 100 # filter by class
|
||||
sdlc knowledge show <slug> # read an entry
|
||||
sdlc knowledge update <slug> --code 100.20 # reclassify
|
||||
sdlc knowledge update <slug> --status published # publish
|
||||
sdlc knowledge search "<query>" # full-text search
|
||||
```
|
||||
|
||||
## Your Protocol
|
||||
|
||||
When asked to maintain the knowledge base:
|
||||
1. `sdlc knowledge list` — identify entries with `code: uncategorized`
|
||||
2. Classify each based on title, summary, and tags using the catalog above
|
||||
3. Fill missing summaries (1-2 sentences, key insight only)
|
||||
4. Find cross-references: entries with overlapping topics → add to `related[]`
|
||||
5. Publish entries that are complete and accurate
|
||||
|
||||
When adding new knowledge from a workspace:
|
||||
- Set `origin: harvested`, `harvested_from: "investigation/<slug>"` or `"ponder/<slug>"`
|
||||
- Write durable insights only — decisions, conclusions, patterns. Not raw dialogue.
|
||||
- Start with `status: draft`; publish when the content is solid
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -34,3 +34,5 @@ Thumbs.db
|
||||
# Ephemeral / scratch
|
||||
tmp/
|
||||
.claude/worktrees/
|
||||
.sdlc/tools/*/index/
|
||||
.sdlc/telemetry.redb
|
||||
|
||||
33
.sdlc/config.yaml
Normal file
33
.sdlc/config.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
version: 1
|
||||
project:
|
||||
name: tidalDB
|
||||
description: "Single-node embeddable Rust database for the personalized content ranking problem — replaces the 6-system stack (Elasticsearch + Redis + Kafka + feature store + vector DB + ranking service) with one process, one query interface, one operational model."
|
||||
phases:
|
||||
enabled:
|
||||
- draft
|
||||
- specified
|
||||
- planned
|
||||
- ready
|
||||
- implementation
|
||||
- review
|
||||
- audit
|
||||
- qa
|
||||
- merge
|
||||
- released
|
||||
required_artifacts:
|
||||
merge:
|
||||
- qa_results
|
||||
audit:
|
||||
- review
|
||||
specified:
|
||||
- spec
|
||||
qa:
|
||||
- audit
|
||||
review:
|
||||
- review
|
||||
planned:
|
||||
- spec
|
||||
- design
|
||||
- tasks
|
||||
- qa_plan
|
||||
sdlc_version: 0.1.0
|
||||
74
.sdlc/features/m10-agent-capability-boundaries/manifest.yaml
Normal file
74
.sdlc/features/m10-agent-capability-boundaries/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m10-agent-capability-boundaries
|
||||
title: Agent Capability Boundaries
|
||||
description: Per-agent permission scopes controlling read/write access to signal types, user attributes, and ranking profile overrides — enforced at the session layer
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:45.070060Z
|
||||
updated_at: 2026-03-03T06:29:45.070060Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m10-agent-capability-boundaries/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:45.070060Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m10-community-policy-engine/manifest.yaml
Normal file
74
.sdlc/features/m10-community-policy-engine/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m10-community-policy-engine
|
||||
title: Community Policy Engine
|
||||
description: Declarative schema-level rules governing which signal types community members can read/write; policy is versioned alongside data
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:45.064231Z
|
||||
updated_at: 2026-03-03T06:29:45.064231Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m10-community-policy-engine/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:45.064231Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m10-signal-revocation-controls/manifest.yaml
Normal file
74
.sdlc/features/m10-signal-revocation-controls/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m10-signal-revocation-controls
|
||||
title: Signal Revocation Controls
|
||||
description: User-facing controls to selectively revoke signal contributions from ranking — scoped by signal type, time range, or agent identity
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:45.075886Z
|
||||
updated_at: 2026-03-03T06:29:45.075886Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m10-signal-revocation-controls/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:45.075886Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m9-community-profile-sync/manifest.yaml
Normal file
74
.sdlc/features/m9-community-profile-sync/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m9-community-profile-sync
|
||||
title: Community Profile Sync
|
||||
description: Opt-in sharing from local embedded profiles to community personalization layers — events flow to shared aggregates while local WAL remains primary
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:37.975394Z
|
||||
updated_at: 2026-03-03T06:29:37.975394Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m9-community-profile-sync/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:37.975394Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m9-leave-revocation/manifest.yaml
Normal file
74
.sdlc/features/m9-leave-revocation/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m9-leave-revocation
|
||||
title: Leave & Stop-Forward
|
||||
description: 'User leaves community layer: stop forwarding new signals, snapshot current contribution boundary, allow opt-back-in path'
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:37.984740Z
|
||||
updated_at: 2026-03-03T06:29:37.984740Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m9-leave-revocation/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:37.984740Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m9-purge-rematerialization/manifest.yaml
Normal file
74
.sdlc/features/m9-purge-rematerialization/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m9-purge-rematerialization
|
||||
title: Re-materialization after Purge
|
||||
description: 'Background re-materialization engine: replay community WAL minus purged contributions, verify aggregate convergence, emit audit log'
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:37.999341Z
|
||||
updated_at: 2026-03-03T06:29:37.999341Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m9-purge-rematerialization/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:37.999341Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/m9-retroactive-purge/manifest.yaml
Normal file
74
.sdlc/features/m9-retroactive-purge/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: m9-retroactive-purge
|
||||
title: Retroactive Signal Purge
|
||||
description: 'On explicit purge request: remove user''s contributed signals from community aggregates with deterministic re-materialization, preserving correctness'
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:37.992175Z
|
||||
updated_at: 2026-03-03T06:29:37.992175Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/m9-retroactive-purge/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:37.992175Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p0-concierge-pilot-loop/manifest.yaml
Normal file
74
.sdlc/features/p0-concierge-pilot-loop/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p0-concierge-pilot-loop
|
||||
title: Concierge Pilot Loop
|
||||
description: Daily briefing workflow with manual QA process and interview cadence — run for 2 weeks with pilot cohort
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:53.125784Z
|
||||
updated_at: 2026-03-03T06:29:53.125784Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p0-concierge-pilot-loop/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:53.125784Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p0-target-segment-recruitment/manifest.yaml
Normal file
74
.sdlc/features/p0-target-segment-recruitment/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p0-target-segment-recruitment
|
||||
title: Target Segment & Recruitment
|
||||
description: Define persona, write recruitment script, build candidate pool of 20-50 target users for concierge pilot
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:53.119870Z
|
||||
updated_at: 2026-03-03T06:29:53.119870Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p0-target-segment-recruitment/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:53.119870Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p0-validation-readout/manifest.yaml
Normal file
74
.sdlc/features/p0-validation-readout/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p0-validation-readout
|
||||
title: Validation Readout
|
||||
description: Analyze retention metrics and qualitative interviews; produce go/no-go decision for P1 Concierge Alpha build
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:29:53.132476Z
|
||||
updated_at: 2026-03-03T06:29:53.132476Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p0-validation-readout/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:29:53.132476Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p1-briefing-ux-reason-labels/manifest.yaml
Normal file
74
.sdlc/features/p1-briefing-ux-reason-labels/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p1-briefing-ux-reason-labels
|
||||
title: Briefing UX & Reason Labels
|
||||
description: Card UI spec with reasons taxonomy and source exposure rules — users see why each item was surfaced
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:00.900430Z
|
||||
updated_at: 2026-03-03T06:30:00.900430Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p1-briefing-ux-reason-labels/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:00.900430Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p1-feedback-loop-ux/manifest.yaml
Normal file
74
.sdlc/features/p1-feedback-loop-ux/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p1-feedback-loop-ux
|
||||
title: Feedback Loop UX
|
||||
description: Mute/hide/like controls with immediate next-refresh reflection; negative feedback visible in ranking within one refresh cycle
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:00.907261Z
|
||||
updated_at: 2026-03-03T06:30:00.907261Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p1-feedback-loop-ux/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:00.907261Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p1-quality-diversity-baseline/manifest.yaml
Normal file
74
.sdlc/features/p1-quality-diversity-baseline/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p1-quality-diversity-baseline
|
||||
title: Quality & Diversity Baseline
|
||||
description: Quality gates (min completion rate, score threshold) and diversity constraints active in top results; no creator dominates the brief
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:00.913882Z
|
||||
updated_at: 2026-03-03T06:30:00.913882Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p1-quality-diversity-baseline/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:00.913882Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p2-cohort-context-views/manifest.yaml
Normal file
74
.sdlc/features/p2-cohort-context-views/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p2-cohort-context-views
|
||||
title: Cohort & Context Views
|
||||
description: Cohort-trending surface ('what's trending for people like you') and session context mode visible in the product
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:32.880500Z
|
||||
updated_at: 2026-03-03T06:30:32.880500Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p2-cohort-context-views/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:32.880500Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p2-self-serve-onboarding/manifest.yaml
Normal file
74
.sdlc/features/p2-self-serve-onboarding/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p2-self-serve-onboarding
|
||||
title: Self-Serve Onboarding
|
||||
description: Onboarding flow with defaults and profile bootstrap; new user goes from signup to first ranked brief in under 3 minutes
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:32.874164Z
|
||||
updated_at: 2026-03-03T06:30:32.874164Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p2-self-serve-onboarding/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:32.874164Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p2-trust-controls/manifest.yaml
Normal file
74
.sdlc/features/p2-trust-controls/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p2-trust-controls
|
||||
title: Trust Controls & Transparency
|
||||
description: Mute/hide persistence across sessions, quality signals visible to user, explanation UX for why items were surfaced
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:32.887225Z
|
||||
updated_at: 2026-03-03T06:30:32.887225Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p2-trust-controls/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:32.887225Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p3-launch-support-playbook/manifest.yaml
Normal file
74
.sdlc/features/p3-launch-support-playbook/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p3-launch-support-playbook
|
||||
title: Launch & Support Playbook
|
||||
description: Launch checklist, incident roles, communication templates — team can respond to production issues within defined SLA
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.118394Z
|
||||
updated_at: 2026-03-03T06:31:12.118394Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p3-launch-support-playbook/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.118394Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p3-quality-operations/manifest.yaml
Normal file
74
.sdlc/features/p3-quality-operations/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p3-quality-operations
|
||||
title: Quality Operations
|
||||
description: Quality floor checks, regression dashboards, and alerting — no launch-day quality regressions ship undetected
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.111740Z
|
||||
updated_at: 2026-03-03T06:31:12.111740Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p3-quality-operations/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.111740Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p3-reliability-slos/manifest.yaml
Normal file
74
.sdlc/features/p3-reliability-slos/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p3-reliability-slos
|
||||
title: Reliability & SLOs
|
||||
description: Launch SLOs defined, monitoring active, error budgets tracked — briefing generation latency and availability within targets
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.104755Z
|
||||
updated_at: 2026-03-03T06:31:12.104755Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p3-reliability-slos/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.104755Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p4-monetization-experiments/manifest.yaml
Normal file
74
.sdlc/features/p4-monetization-experiments/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p4-monetization-experiments
|
||||
title: Monetization Experiments
|
||||
description: Pricing tests, conversion funnel instrumentation — measure which monetization model converts without degrading quality metrics
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.151616Z
|
||||
updated_at: 2026-03-03T06:31:12.151616Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p4-monetization-experiments/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.151616Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p4-quality-safe-growth/manifest.yaml
Normal file
74
.sdlc/features/p4-quality-safe-growth/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p4-quality-safe-growth
|
||||
title: Quality-Safe Growth
|
||||
description: Guardrails that prevent revenue-over-quality regressions as user volume scales — D7 retention and useful-item rate monitored against growth rate
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.156872Z
|
||||
updated_at: 2026-03-03T06:31:12.156872Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p4-quality-safe-growth/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.156872Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/p4-segment-expansion-plan/manifest.yaml
Normal file
74
.sdlc/features/p4-segment-expansion-plan/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: p4-segment-expansion-plan
|
||||
title: Segment Expansion Plan
|
||||
description: Data-backed expansion strategy and milestone proposal for next target segment or market
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:31:12.162327Z
|
||||
updated_at: 2026-03-03T06:31:12.162327Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/p4-segment-expansion-plan/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:31:12.162327Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/pg1-baseline-comparison/manifest.yaml
Normal file
74
.sdlc/features/pg1-baseline-comparison/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: pg1-baseline-comparison
|
||||
title: Baseline Comparison Study
|
||||
description: A/B comparison against a non-personalized baseline feed — measure click-through, completion, and return rate lift
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:08.984440Z
|
||||
updated_at: 2026-03-03T06:30:08.984440Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-baseline-comparison/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:08.984440Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/pg1-instrumented-metrics/manifest.yaml
Normal file
74
.sdlc/features/pg1-instrumented-metrics/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: pg1-instrumented-metrics
|
||||
title: Instrumented Metrics Pipeline
|
||||
description: Signal write rates, ranking latency p50/p99, personalization staleness, and feedback-loop latency all observable in production
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:08.990467Z
|
||||
updated_at: 2026-03-03T06:30:08.990467Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-instrumented-metrics/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:08.990467Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
74
.sdlc/features/pg1-personalization-correctness/manifest.yaml
Normal file
74
.sdlc/features/pg1-personalization-correctness/manifest.yaml
Normal file
@ -0,0 +1,74 @@
|
||||
slug: pg1-personalization-correctness
|
||||
title: Personalization Correctness Verification
|
||||
description: Verify the personalization loop (signal write → decay → ranking) is mathematically correct and updates within 100ms of signal write
|
||||
phase: draft
|
||||
created_at: 2026-03-03T06:30:08.977848Z
|
||||
updated_at: 2026-03-03T06:30:08.977848Z
|
||||
artifacts:
|
||||
- artifact_type: spec
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/spec.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: design
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/design.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: tasks
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/tasks.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_plan
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/qa-plan.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: review
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/review.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: audit
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/audit.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
- artifact_type: qa_results
|
||||
status: missing
|
||||
path: .sdlc/features/pg1-personalization-correctness/qa-results.md
|
||||
created_at: null
|
||||
approved_at: null
|
||||
rejected_at: null
|
||||
rejection_reason: null
|
||||
approved_by: null
|
||||
tasks: []
|
||||
comments: []
|
||||
next_comment_seq: 0
|
||||
blockers: []
|
||||
phase_history:
|
||||
- phase: draft
|
||||
entered: 2026-03-03T06:30:08.977848Z
|
||||
exited: null
|
||||
dependencies: []
|
||||
archived: false
|
||||
schema_version: 2
|
||||
204
.sdlc/guidance.md
Normal file
204
.sdlc/guidance.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Engineering Guidance
|
||||
|
||||
Read this before any implementation, bug fix, or test action.
|
||||
|
||||
## North Star: Vision & Architecture
|
||||
|
||||
Before writing a single line of code, read:
|
||||
|
||||
- **`VISION.md`** — *what* we are building and *why*. Every feature, every tradeoff, every design decision must serve this vision. If a proposed change works against it, surface it before proceeding.
|
||||
- **`ARCHITECTURE.md`** — *how* the system works. Components, interfaces, data flows, and sequence diagrams showing how everything fits together. Code must conform to the architecture — never silently deviate.
|
||||
|
||||
These are the guiding light. When in doubt about any decision, return to them first.
|
||||
|
||||
## 1. Build It Right
|
||||
|
||||
Do it the proper way — not the quick way. The correct solution is one that
|
||||
will still be correct in six months. Favor proven patterns, clear
|
||||
abstractions, and designs that are easy to understand and extend. Never
|
||||
trade long-term correctness for short-term convenience.
|
||||
|
||||
## 2. Understand Bugs Before Fixing Them
|
||||
|
||||
Before touching a bug, trace its root cause holistically — read surrounding
|
||||
code, follow the data flow, understand why it broke. Fix the cause, not the
|
||||
symptom. A patch that introduces a new bug in three months is worse than
|
||||
no fix.
|
||||
|
||||
## 3. Enterprise Quality Bar
|
||||
|
||||
We build enterprise-grade software. The bar is Steve Jobs: relentless
|
||||
attention to detail, nothing ships that embarrasses us, correctness and
|
||||
reliability are non-negotiable. If something isn't right, make it right.
|
||||
|
||||
## 4. Philosophy of Software Design
|
||||
|
||||
Follow John Ousterhout's principles: deep modules, minimal exposed
|
||||
complexity, interfaces that hide implementation detail, and code readable
|
||||
in isolation. Complexity is the enemy — fight it at every level.
|
||||
|
||||
## 5. Meaningful, Reliable, Fast Tests
|
||||
|
||||
Tests must earn their place. When a test breaks, choose deliberately:
|
||||
- **Remove** — if it adds little value or tests implementation detail
|
||||
- **Rewrite** — if it was poorly structured for the scenario
|
||||
- **Refactor** — if the interface it tests changed legitimately
|
||||
- **Quick-fix** — only if the fix is obvious and the test is clearly valuable
|
||||
|
||||
Never keep a flaky or low-value test just to preserve coverage numbers.
|
||||
|
||||
## 6. Using sdlc
|
||||
|
||||
All state lives in `.sdlc/` YAML files. **Never edit them directly** — use the CLI.
|
||||
Direct edits cause deserialization failures and corrupt state.
|
||||
|
||||
| Action | Command |
|
||||
|---|---|
|
||||
| Create feature | `sdlc feature create <slug> --title "…"` |
|
||||
| Get next action | `sdlc next --for <slug> --json` |
|
||||
| Write artifact | Write Markdown to `output_path` from the directive |
|
||||
| Submit draft | `sdlc artifact draft <slug> <type>` |
|
||||
| Approve artifact | `sdlc artifact approve <slug> <type>` |
|
||||
| Reject artifact | `sdlc artifact reject <slug> <type>` |
|
||||
| Merge (release feature) | `sdlc merge <slug>` |
|
||||
| Add task | `sdlc task add <slug> "title"` |
|
||||
| Start task | `sdlc task start <slug> <task-id>` |
|
||||
| Complete task | `sdlc task complete <slug> <task-id>` |
|
||||
| Block task | `sdlc task block <slug> <task-id> "reason"` |
|
||||
| Add comment | `sdlc comment create <slug> "body"` |
|
||||
| Show feature | `sdlc feature show <slug> --json` |
|
||||
| List tasks | `sdlc task list <slug>` |
|
||||
| Project state | `sdlc state` |
|
||||
| Survey milestone waves | `sdlc project prepare [--milestone <slug>]` |
|
||||
| Mark milestone prepared | `sdlc milestone mark-prepared <slug>` |
|
||||
| Project phase | `sdlc project status` |
|
||||
| Escalate to human | `sdlc escalate create --kind <kind> --title "…" --context "…" [--feature <slug>]` |
|
||||
| List escalations | `sdlc escalate list` |
|
||||
| Resolve escalation | `sdlc escalate resolve <id> "resolution note"` |
|
||||
| Knowledge base status | `sdlc knowledge status` |
|
||||
| List knowledge entries | `sdlc knowledge list [--code-prefix <code>]` |
|
||||
| Search knowledge base | `sdlc knowledge search <query>` |
|
||||
| Show knowledge entry | `sdlc knowledge show <slug>` |
|
||||
| Add knowledge entry | `sdlc knowledge add --title "..." --code <code> --content "..."` |
|
||||
| Show catalog taxonomy | `sdlc knowledge catalog show` |
|
||||
| Seed from workspaces | `sdlc knowledge librarian init` |
|
||||
|
||||
Phases advance automatically from artifact approvals — never call `sdlc feature transition`.
|
||||
The only files you write directly are Markdown artifacts to `output_path`.
|
||||
|
||||
## 7. SDLC Tool Suite
|
||||
|
||||
Project-scoped TypeScript tools in `.sdlc/tools/` — callable by agents and humans during any lifecycle phase.
|
||||
Read `.sdlc/tools/tools.md` for the full list, or each tool's `README.md` for detailed docs.
|
||||
|
||||
| Tool | Command | Purpose |
|
||||
|---|---|---|
|
||||
| ama | `sdlc tool run ama --setup` then `sdlc tool run ama --question "..."` | Search codebase for relevant file excerpts |
|
||||
|
||||
Build a custom tool: `sdlc tool scaffold <name> "<description>"`
|
||||
Update the manifest after adding/changing tools: `sdlc tool sync`
|
||||
|
||||
## 8. Project Secrets
|
||||
|
||||
Encrypted secrets live in `.sdlc/secrets/`. The encrypted files (`.age`) and key
|
||||
name sidecars (`.meta.yaml`) are **safe to commit**. Plain `.env.*` files must never
|
||||
be committed — they are gitignored automatically.
|
||||
|
||||
| Action | Command |
|
||||
|---|---|
|
||||
| List environments | `sdlc secrets env list` |
|
||||
| List key names (no decrypt) | `sdlc secrets env names <env>` |
|
||||
| Load secrets into shell | `eval $(sdlc secrets env export <env>)` |
|
||||
| Set a secret | `sdlc secrets env set <env> KEY=value` |
|
||||
| List authorized keys | `sdlc secrets keys list` |
|
||||
| Add a key | `sdlc secrets keys add --name <n> --key "$(cat ~/.ssh/id_ed25519.pub)"` |
|
||||
| Rekey after key change | `sdlc secrets keys rekey` |
|
||||
|
||||
**For agents:** Check `sdlc secrets env names <env>` to see which variables are
|
||||
available. Load the matching env before any task or build step that needs credentials:
|
||||
- Feature/local work → `eval $(sdlc secrets env export development)`
|
||||
- Deploy tasks → `eval $(sdlc secrets env export production)`
|
||||
|
||||
Never log or hardcode secret values. Reference by env var name only (e.g. `$ANTHROPIC_API_KEY`).
|
||||
|
||||
**In builds:** The vault is for local and agent use only. CI/CD platforms (GitHub Actions,
|
||||
etc.) manage their own secrets separately — agents cannot inject into platform CI secrets.
|
||||
If a build needs a credential that must live in CI, use `secret_request` escalation (§9).
|
||||
|
||||
## 9. Escalating to the Human
|
||||
|
||||
Escalations are for **actions only a human can take**. They are rare and deliberate — not a
|
||||
general-purpose communication channel. Before escalating, ask: "Can I resolve this myself?"
|
||||
If yes, do it. If not, escalate.
|
||||
|
||||
| Kind | When to escalate | Example |
|
||||
|---|---|---|
|
||||
| `secret_request` | Need a credential or env var that doesn't exist | "Add STRIPE_API_KEY to production env in Secrets page" |
|
||||
| `question` | Strategic decision with no clear right answer | "Should checkout support crypto payments?" |
|
||||
| `vision` | Product direction is undefined or contradictory | "No vision defined — what is the milestone goal?" |
|
||||
| `manual_test` | Testing requires physical interaction | "Verify Google OAuth login in production browser" |
|
||||
|
||||
**Do NOT escalate:** code review findings, spec ambiguity you can resolve, implementation
|
||||
decisions, anything an agent can handle autonomously.
|
||||
|
||||
**How to escalate:**
|
||||
|
||||
```bash
|
||||
sdlc escalate create \
|
||||
--kind secret_request \
|
||||
--title "Need OPENAI_API_KEY in .env.production" \
|
||||
--context "AI summary feature calls OpenAI in prod. Dev works with a mock. Need the real key to test end-to-end." \
|
||||
--feature my-ai-feature # omit if not feature-specific
|
||||
```
|
||||
|
||||
**After creating:** stop the current run immediately. If `--feature` was specified, the feature
|
||||
is now gated by an auto-added Blocker comment. The escalation appears in the Dashboard under
|
||||
**"Needs Your Attention"**. The human must act before the feature can proceed.
|
||||
|
||||
**The difference from `comment --flag blocker`:**
|
||||
|
||||
- `comment --flag blocker` — an implementation concern the next agent cycle might fix
|
||||
- `sdlc escalate create` — an action only a human can perform; stop until resolved
|
||||
|
||||
## 10. Frontend API Calls
|
||||
|
||||
Never hardcode `http://localhost:PORT` in frontend code — CORS blocks cross-origin
|
||||
requests in development and the address is wrong in production.
|
||||
|
||||
**Pattern:**
|
||||
- Use a relative base URL (`/api`) in all fetch/client code
|
||||
- Configure the dev server proxy (Vite `server.proxy`, Next.js `rewrites`,
|
||||
webpack `devServer.proxy`) to forward `/api` → `http://localhost:<API_PORT>`
|
||||
- In production, frontend and API share the same origin — relative paths resolve correctly
|
||||
|
||||
When fixing a CORS error or adding a new API client, apply this pattern instead of
|
||||
adding CORS headers or introducing environment-specific URLs.
|
||||
|
||||
## 11. Production Safety
|
||||
|
||||
This is a live system with real users. Every change must leave the codebase healthier — not just correct, but cleaner.
|
||||
|
||||
**Migrations:** Add defensive deserialization before removing old formats. Never the reverse. Test that both old and new formats load cleanly before shipping.
|
||||
|
||||
**Stability hazards to avoid:**
|
||||
- Infinite loops: any polling, retry, or SSE reconnect loop must have a termination condition and backoff
|
||||
- Connection exhaustion: SSE subscriptions, DB connections, and broadcast channels must be bounded and cleaned up on drop
|
||||
- Complex failure modes: prefer simple, flat control flow over deeply nested async chains — when it breaks at 3am, you must be able to read the trace
|
||||
|
||||
**Quality bar:** if a change makes the code harder to reason about, makes logs less useful, or adds a failure mode with no clear recovery path — stop and reconsider. Simpler is always better.
|
||||
|
||||
## 12. Project Guidelines
|
||||
|
||||
Before writing implementation code, check if `.sdlc/guidelines/index.yaml` exists.
|
||||
If it does, read it and load any guidelines whose `scope` overlaps with the work at hand.
|
||||
|
||||
```bash
|
||||
# Check
|
||||
ls .sdlc/guidelines/index.yaml 2>/dev/null && cat .sdlc/guidelines/index.yaml
|
||||
```
|
||||
|
||||
Guidelines contain `⚑ Rule:` statements with `✓ Good:` and `✗ Bad:` code examples derived
|
||||
from this codebase. They are authoritative — if your implementation would violate a rule,
|
||||
fix the approach before proceeding, not after review catches it.
|
||||
|
||||
If no index exists, no guidelines have been published yet. Proceed normally.
|
||||
16
.sdlc/knowledge/catalog.yaml
Normal file
16
.sdlc/knowledge/catalog.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
classes:
|
||||
- code: '100'
|
||||
name: Core Thesis
|
||||
- code: '200'
|
||||
name: Domain Model
|
||||
- code: '300'
|
||||
name: Module Structure
|
||||
- code: '400'
|
||||
name: Storage Architecture
|
||||
- code: '500'
|
||||
name: Signal System
|
||||
- code: '600'
|
||||
name: Vector Index
|
||||
- code: '700'
|
||||
name: Text Search
|
||||
updated_at: 2026-03-03T06:23:11.759071Z
|
||||
4
.sdlc/knowledge/maintenance-log.yaml
Normal file
4
.sdlc/knowledge/maintenance-log.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
actions:
|
||||
- timestamp: 2026-03-03T06:23:11.759775Z
|
||||
action_type: harvest
|
||||
detail: librarian init
|
||||
8
.sdlc/milestones/m0/manifest.yaml
Normal file
8
.sdlc/milestones/m0/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m0
|
||||
title: Embeddable Runtime
|
||||
vision: A developer can cargo add tidalDB, run it in-process with zero config, and get structured diagnostics — proving the embeddable runtime baseline works.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.012810Z
|
||||
updated_at: 2026-03-03T06:29:28.060717Z
|
||||
released_at: 2026-03-03T06:29:28.060716Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m1/manifest.yaml
Normal file
8
.sdlc/milestones/m1/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m1
|
||||
title: Signal Engine
|
||||
vision: A developer can write engagement signals and see O(1) decay scores and windowed aggregates — proving signals are a database primitive, not application math.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.025474Z
|
||||
updated_at: 2026-03-03T06:29:28.068009Z
|
||||
released_at: 2026-03-03T06:29:28.068009Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/m10/manifest.yaml
Normal file
10
.sdlc/milestones/m10/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: m10
|
||||
title: Governance & Agent Rights
|
||||
vision: Community rules and agent-scoped permissions control what signals influence ranking — users and communities can control exactly which signals affect their experience and revoke them safely.
|
||||
features:
|
||||
- m10-community-policy-engine
|
||||
- m10-agent-capability-boundaries
|
||||
- m10-signal-revocation-controls
|
||||
created_at: 2026-03-03T06:29:45.049713Z
|
||||
updated_at: 2026-03-03T06:29:45.091585Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m2/manifest.yaml
Normal file
8
.sdlc/milestones/m2/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m2
|
||||
title: Ranked Retrieval
|
||||
vision: A single RETRIEVE query retrieves, scores, and ranks content using live signals — proving one query replaces what 6 systems currently produce.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.032247Z
|
||||
updated_at: 2026-03-03T06:29:28.074556Z
|
||||
released_at: 2026-03-03T06:29:28.074555Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m3/manifest.yaml
Normal file
8
.sdlc/milestones/m3/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m3
|
||||
title: Personalized Ranking
|
||||
vision: User context shapes retrieval and ranking — the 'For You' query works correctly, reflecting user preferences, relationships, and session context.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.038502Z
|
||||
updated_at: 2026-03-03T06:29:28.080975Z
|
||||
released_at: 2026-03-03T06:29:28.080974Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m4/manifest.yaml
Normal file
8
.sdlc/milestones/m4/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m4
|
||||
title: Agent Memory
|
||||
vision: Agents can create sessions, write signals with policy constraints, and enforce rate limits inside tidalDB — enabling RLHF loops and conversational memory.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.045226Z
|
||||
updated_at: 2026-03-03T06:29:28.086574Z
|
||||
released_at: 2026-03-03T06:29:28.086574Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m5/manifest.yaml
Normal file
8
.sdlc/milestones/m5/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m5
|
||||
title: Hybrid Search
|
||||
vision: Text + semantic + signal-ranked search in one query — BM25, ANN, and personalization fused via RRF, enabling UC-02, UC-10, UC-11.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.052823Z
|
||||
updated_at: 2026-03-03T06:29:28.091698Z
|
||||
released_at: 2026-03-03T06:29:28.091698Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m6/manifest.yaml
Normal file
8
.sdlc/milestones/m6/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m6
|
||||
title: Full Surface Coverage
|
||||
vision: Every use case, every sort mode, every filter, every feedback loop is operational — UC-01 through UC-14 complete in a single database.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.059993Z
|
||||
updated_at: 2026-03-03T06:29:28.097611Z
|
||||
released_at: 2026-03-03T06:29:28.097611Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m7/manifest.yaml
Normal file
8
.sdlc/milestones/m7/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m7
|
||||
title: Production Hardening
|
||||
vision: Crash safety, graceful degradation, rate limiting, and operational readiness — all UCs at production quality with diagnostics and telemetry.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.067328Z
|
||||
updated_at: 2026-03-03T06:29:28.103643Z
|
||||
released_at: 2026-03-03T06:29:28.103643Z
|
||||
schema_version: 1
|
||||
8
.sdlc/milestones/m8/manifest.yaml
Normal file
8
.sdlc/milestones/m8/manifest.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
slug: m8
|
||||
title: Distributed Fabric
|
||||
vision: Multi-region, multi-tenant WAL replication with CRDT reconciliation keeps agent-memory semantics intact across distributed deployments.
|
||||
features: []
|
||||
created_at: 2026-03-03T06:29:10.075155Z
|
||||
updated_at: 2026-03-03T06:29:28.109493Z
|
||||
released_at: 2026-03-03T06:29:28.109493Z
|
||||
schema_version: 1
|
||||
11
.sdlc/milestones/m9/manifest.yaml
Normal file
11
.sdlc/milestones/m9/manifest.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
slug: m9
|
||||
title: Community Sync & Revocation
|
||||
vision: A user can opt their local profile into community personalization, and later leave — removing their contributions from future ranking without destroying local history.
|
||||
features:
|
||||
- m9-community-profile-sync
|
||||
- m9-leave-revocation
|
||||
- m9-retroactive-purge
|
||||
- m9-purge-rematerialization
|
||||
created_at: 2026-03-03T06:29:37.962436Z
|
||||
updated_at: 2026-03-03T06:29:38.030047Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/p0/manifest.yaml
Normal file
10
.sdlc/milestones/p0/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: p0
|
||||
title: Beachhead Validation
|
||||
vision: 20-50 target users complete a 2-week concierge pilot with D2/D7 retention measured — proving the personal briefing concept is valuable enough for repeated use.
|
||||
features:
|
||||
- p0-target-segment-recruitment
|
||||
- p0-concierge-pilot-loop
|
||||
- p0-validation-readout
|
||||
created_at: 2026-03-03T06:29:53.105359Z
|
||||
updated_at: 2026-03-03T06:29:53.150315Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/p1/manifest.yaml
Normal file
10
.sdlc/milestones/p1/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: p1
|
||||
title: Concierge Alpha
|
||||
vision: Daily ranked brief is live for pilot cohort with reason labels, immediate feedback adaptation, and time-budget mode — proving 'Today Brief' usefulness and repeat behavior.
|
||||
features:
|
||||
- p1-briefing-ux-reason-labels
|
||||
- p1-feedback-loop-ux
|
||||
- p1-quality-diversity-baseline
|
||||
created_at: 2026-03-03T06:30:00.885199Z
|
||||
updated_at: 2026-03-03T06:30:00.932156Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/p2/manifest.yaml
Normal file
10
.sdlc/milestones/p2/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: p2
|
||||
title: Productized Beta
|
||||
vision: Self-serve onboarding under 3 minutes, cohort-scoped trending surfaces, and trust controls active — D7 retention and useful-item rate exceed baseline comparison feed.
|
||||
features:
|
||||
- p2-self-serve-onboarding
|
||||
- p2-cohort-context-views
|
||||
- p2-trust-controls
|
||||
created_at: 2026-03-03T06:30:32.861070Z
|
||||
updated_at: 2026-03-03T06:30:32.903751Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/p3/manifest.yaml
Normal file
10
.sdlc/milestones/p3/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: p3
|
||||
title: Public Launch
|
||||
vision: Briefing generation reliability SLOs met, quality floor enforced, support playbook active — product is ready for public users at real volume.
|
||||
features:
|
||||
- p3-reliability-slos
|
||||
- p3-quality-operations
|
||||
- p3-launch-support-playbook
|
||||
created_at: 2026-03-03T06:31:12.087940Z
|
||||
updated_at: 2026-03-03T06:31:12.135803Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/p4/manifest.yaml
Normal file
10
.sdlc/milestones/p4/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: p4
|
||||
title: Scale & Revenue Fit
|
||||
vision: Monetization model validated, quality-safe growth guardrails active, next expansion segment chosen with evidence — sustainable unit economics proven at scale.
|
||||
features:
|
||||
- p4-monetization-experiments
|
||||
- p4-quality-safe-growth
|
||||
- p4-segment-expansion-plan
|
||||
created_at: 2026-03-03T06:31:12.140928Z
|
||||
updated_at: 2026-03-03T06:31:12.178883Z
|
||||
schema_version: 1
|
||||
10
.sdlc/milestones/pg1/manifest.yaml
Normal file
10
.sdlc/milestones/pg1/manifest.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
slug: pg1
|
||||
title: Personalization Core Done Gate
|
||||
vision: Core personalization loop is correct, immediate, and measurably better than baseline — gate that must pass before P2 Productized Beta proceeds.
|
||||
features:
|
||||
- pg1-personalization-correctness
|
||||
- pg1-baseline-comparison
|
||||
- pg1-instrumented-metrics
|
||||
created_at: 2026-03-03T06:30:08.963318Z
|
||||
updated_at: 2026-03-03T06:30:09.007191Z
|
||||
schema_version: 1
|
||||
51
.sdlc/state.yaml
Normal file
51
.sdlc/state.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
version: 1
|
||||
project: tidalDB
|
||||
active_features:
|
||||
- m9-community-profile-sync
|
||||
- m9-leave-revocation
|
||||
- m9-retroactive-purge
|
||||
- m9-purge-rematerialization
|
||||
- m10-community-policy-engine
|
||||
- m10-agent-capability-boundaries
|
||||
- m10-signal-revocation-controls
|
||||
- p0-target-segment-recruitment
|
||||
- p0-concierge-pilot-loop
|
||||
- p0-validation-readout
|
||||
- p1-briefing-ux-reason-labels
|
||||
- p1-feedback-loop-ux
|
||||
- p1-quality-diversity-baseline
|
||||
- pg1-personalization-correctness
|
||||
- pg1-baseline-comparison
|
||||
- pg1-instrumented-metrics
|
||||
- p2-self-serve-onboarding
|
||||
- p2-cohort-context-views
|
||||
- p2-trust-controls
|
||||
- p3-reliability-slos
|
||||
- p3-quality-operations
|
||||
- p3-launch-support-playbook
|
||||
- p4-monetization-experiments
|
||||
- p4-quality-safe-growth
|
||||
- p4-segment-expansion-plan
|
||||
active_directives: []
|
||||
history: []
|
||||
blocked: []
|
||||
milestones:
|
||||
- m0
|
||||
- m1
|
||||
- m2
|
||||
- m3
|
||||
- m4
|
||||
- m5
|
||||
- m6
|
||||
- m7
|
||||
- m8
|
||||
- m9
|
||||
- m10
|
||||
- p0
|
||||
- p1
|
||||
- pg1
|
||||
- p2
|
||||
- p3
|
||||
- p4
|
||||
active_ponders: []
|
||||
last_updated: 2026-03-03T06:31:12.162637Z
|
||||
44
.sdlc/tools/_shared/config.ts
Normal file
44
.sdlc/tools/_shared/config.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* SDLC Tool Config Loader
|
||||
*
|
||||
* Reads .sdlc/tools/<name>/config.yaml. If the file is missing or unparseable,
|
||||
* returns the provided defaults — tools should never hard-fail on missing config.
|
||||
*
|
||||
* Supports flat key: value YAML only. Arrays and nested objects are intentionally
|
||||
* not supported — keep tool configs simple scalars.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
export function loadToolConfig<T extends Record<string, unknown>>(
|
||||
root: string,
|
||||
toolName: string,
|
||||
defaults: T,
|
||||
): T {
|
||||
const configPath = join(root, '.sdlc', 'tools', toolName, 'config.yaml')
|
||||
try {
|
||||
const raw = readFileSync(configPath, 'utf8')
|
||||
const parsed = parseSimpleYaml(raw)
|
||||
return { ...defaults, ...parsed } as T
|
||||
} catch {
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse a flat key: value YAML file. Skips blank lines, comments, and array items. */
|
||||
function parseSimpleYaml(content: string): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const line of content.split('\n')) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue
|
||||
const colonIdx = trimmed.indexOf(':')
|
||||
if (colonIdx === -1) continue
|
||||
const key = trimmed.slice(0, colonIdx).trim()
|
||||
const rawValue = trimmed.slice(colonIdx + 1).trim()
|
||||
if (!key || !rawValue) continue
|
||||
const value = rawValue.replace(/^["'](.*)["']$/, '$1')
|
||||
const num = Number(value)
|
||||
result[key] = Number.isNaN(num) ? value : num
|
||||
}
|
||||
return result
|
||||
}
|
||||
25
.sdlc/tools/_shared/log.ts
Normal file
25
.sdlc/tools/_shared/log.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Standard SDLC Tool Logger
|
||||
*
|
||||
* Writes structured log lines to STDERR (never stdout — stdout is reserved
|
||||
* for JSON output). Use this in every tool to produce consistent, parseable logs.
|
||||
*
|
||||
* Format: [sdlc-tool:<name>] LEVEL: message
|
||||
* Example: [sdlc-tool:ama] INFO: Indexed 312 files in 842ms
|
||||
*
|
||||
* Set SDLC_TOOL_DEBUG=1 to enable debug-level output.
|
||||
*/
|
||||
|
||||
export function makeLogger(toolName: string) {
|
||||
const prefix = `[sdlc-tool:${toolName}]`
|
||||
return {
|
||||
info: (msg: string) => console.error(`${prefix} INFO: ${msg}`),
|
||||
warn: (msg: string) => console.error(`${prefix} WARN: ${msg}`),
|
||||
error: (msg: string) => console.error(`${prefix} ERROR: ${msg}`),
|
||||
debug: (msg: string) => {
|
||||
if (process.env.SDLC_TOOL_DEBUG) console.error(`${prefix} DEBUG: ${msg}`)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type Logger = ReturnType<typeof makeLogger>
|
||||
68
.sdlc/tools/_shared/runtime.ts
Normal file
68
.sdlc/tools/_shared/runtime.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Cross-runtime helpers for Bun, Deno, and Node.
|
||||
*
|
||||
* Normalizes: argv access, stdin reading, env access, and process exit
|
||||
* across the three supported runtimes.
|
||||
*
|
||||
* Detection: checks for globalThis.Deno to identify Deno; falls back
|
||||
* to process (Node.js / Bun).
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/** Returns command-line arguments after the script name (process.argv[2+]). */
|
||||
export function getArgs(): string[] {
|
||||
if (typeof (globalThis as any).Deno !== 'undefined') {
|
||||
return [...(globalThis as any).Deno.args]
|
||||
}
|
||||
return process.argv.slice(2)
|
||||
}
|
||||
|
||||
/** Read all of stdin as a UTF-8 string. Returns empty string if stdin is a TTY or closed. */
|
||||
export async function readStdin(): Promise<string> {
|
||||
if (typeof (globalThis as any).Deno !== 'undefined') {
|
||||
const chunks: Uint8Array[] = []
|
||||
const reader = (globalThis as any).Deno.stdin.readable.getReader()
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
const total = chunks.reduce((sum: number, c: Uint8Array) => sum + c.length, 0)
|
||||
const merged = new Uint8Array(total)
|
||||
let offset = 0
|
||||
for (const chunk of chunks) {
|
||||
merged.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
return new TextDecoder().decode(merged)
|
||||
}
|
||||
// Node.js / Bun
|
||||
if ((process.stdin as any).isTTY) return ''
|
||||
const chunks: Buffer[] = []
|
||||
for await (const chunk of process.stdin) {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
||||
}
|
||||
return Buffer.concat(chunks).toString('utf8')
|
||||
}
|
||||
|
||||
/** Get a process environment variable. Works across Bun, Deno, and Node. */
|
||||
export function getEnv(key: string): string | undefined {
|
||||
if (typeof (globalThis as any).Deno !== 'undefined') {
|
||||
return (globalThis as any).Deno.env.get(key)
|
||||
}
|
||||
return process.env[key]
|
||||
}
|
||||
|
||||
/** Exit the process with the given code. */
|
||||
export function exit(code: number): never {
|
||||
if (typeof (globalThis as any).Deno !== 'undefined') {
|
||||
;(globalThis as any).Deno.exit(code)
|
||||
}
|
||||
process.exit(code)
|
||||
throw new Error('unreachable')
|
||||
}
|
||||
47
.sdlc/tools/_shared/types.ts
Normal file
47
.sdlc/tools/_shared/types.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* SDLC Tool Shared Interface
|
||||
*
|
||||
* Every SDLC tool imports from this file. It defines the full type contract
|
||||
* that tools must satisfy. Do not change the shape of these types without
|
||||
* updating all core tools and regenerating tools.md.
|
||||
*
|
||||
* Tool protocol (stdin/stdout):
|
||||
* - --meta No stdin. Writes ToolMeta JSON to stdout.
|
||||
* - --run Reads JSON from stdin. Writes ToolResult JSON to stdout. Exit 0 ok, 1 error.
|
||||
* - --setup No stdin. Writes ToolResult JSON to stdout. Exit 0 ok, 1 error.
|
||||
*
|
||||
* All log output goes to STDERR. STDOUT is reserved for JSON only.
|
||||
*/
|
||||
|
||||
/** Metadata describing a tool — returned by --meta mode. */
|
||||
export interface ToolMeta {
|
||||
/** Matches the directory name exactly (e.g. "ama", "quality-check") */
|
||||
name: string
|
||||
/** Human-readable title shown in the tools list */
|
||||
display_name: string
|
||||
/** One sentence, present tense, no trailing period */
|
||||
description: string
|
||||
/** Semver, mirrors sdlc binary version at install time */
|
||||
version: string
|
||||
/** JSON Schema describing valid input for --run */
|
||||
input_schema: JsonSchema
|
||||
/** JSON Schema describing the data field in ToolResult */
|
||||
output_schema: JsonSchema
|
||||
/** True if --setup must run before first --run */
|
||||
requires_setup: boolean
|
||||
/** One sentence describing what setup does (required if requires_setup = true) */
|
||||
setup_description?: string
|
||||
}
|
||||
|
||||
/** The result envelope returned by --run and --setup modes. */
|
||||
export interface ToolResult<T = unknown> {
|
||||
ok: boolean
|
||||
data?: T
|
||||
/** Present only when ok = false */
|
||||
error?: string
|
||||
/** Wall-clock milliseconds for the operation */
|
||||
duration_ms?: number
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type JsonSchema = Record<string, any>
|
||||
36
.sdlc/tools/ama/README.md
Normal file
36
.sdlc/tools/ama/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# AMA — Ask Me Anything
|
||||
|
||||
Answers questions about the codebase by searching a pre-built keyword index.
|
||||
|
||||
## Setup (run once)
|
||||
|
||||
```bash
|
||||
sdlc tool run ama --setup
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
sdlc tool run ama --question "where is JWT validation?"
|
||||
sdlc tool run ama --question "how does feature transition work?"
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. `--setup` walks source files, chunks them into 40-line windows, extracts keyword tokens,
|
||||
and writes `.sdlc/tools/ama/index/chunks.json`
|
||||
2. `--run` scores chunks by keyword overlap with your question, returns top file excerpts
|
||||
3. Your AI assistant reads the excerpts and synthesizes an answer
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `.sdlc/tools/ama/config.yaml` to change which file extensions are indexed
|
||||
or to adjust chunk size, overlap, and result count.
|
||||
|
||||
## Index location
|
||||
|
||||
`.sdlc/tools/ama/index/chunks.json` — gitignored, regenerate with `--setup`
|
||||
|
||||
## Re-index when needed
|
||||
|
||||
Re-run `--setup` after significant file changes. It's fast and safe to run any time.
|
||||
18
.sdlc/tools/ama/config.yaml
Normal file
18
.sdlc/tools/ama/config.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
name: ama
|
||||
version: 0.1.0
|
||||
description: Answers questions about the codebase using a pre-built keyword index
|
||||
|
||||
# File extensions to include in the index (comma-separated)
|
||||
extensions: .ts,.js,.tsx,.jsx,.rs,.go,.py,.rb,.java,.md,.txt,.yaml,.yml,.toml
|
||||
|
||||
# Number of lines per chunk
|
||||
chunk_lines: 40
|
||||
|
||||
# Lines of overlap between consecutive chunks (reduces missed context at boundaries)
|
||||
chunk_overlap: 5
|
||||
|
||||
# Maximum results to return per query
|
||||
max_results: 5
|
||||
|
||||
# Skip files larger than this size (kilobytes)
|
||||
max_file_kb: 500
|
||||
506
.sdlc/tools/ama/tool.ts
Normal file
506
.sdlc/tools/ama/tool.ts
Normal file
@ -0,0 +1,506 @@
|
||||
/**
|
||||
* AMA — Ask Me Anything
|
||||
* =====================
|
||||
* Answers questions about the codebase by searching a pre-built keyword index.
|
||||
*
|
||||
* WHAT IT DOES
|
||||
* ------------
|
||||
* --setup: Walks all source files matching configured extensions. On first run,
|
||||
* indexes every file. On subsequent runs, skips unchanged files (mtime
|
||||
* check), re-indexes changed/new files, and prunes deleted files.
|
||||
* Writes chunks.json (TF-IDF index) and last_indexed.json (mtime map).
|
||||
* Re-running --setup is always safe (incremental or full).
|
||||
*
|
||||
* --run: Reads JSON from stdin: { "question": "string" }
|
||||
* Loads the TF-IDF index, scores chunks by IDF-weighted keyword overlap,
|
||||
* returns top results as source excerpts with relevance scores.
|
||||
* Sources from files changed since last indexing are flagged stale.
|
||||
*
|
||||
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
|
||||
*
|
||||
* WHAT IT READS
|
||||
* -------------
|
||||
* - .sdlc/tools/ama/config.yaml (extensions, chunk settings)
|
||||
* - .sdlc/tools/ama/index/chunks.json (built by --setup)
|
||||
* - .sdlc/tools/ama/index/last_indexed.json (mtime map; built by --setup)
|
||||
* - Source files matching config.extensions (during --setup only)
|
||||
*
|
||||
* WHAT IT WRITES
|
||||
* --------------
|
||||
* - .sdlc/tools/ama/index/chunks.json (during --setup; TF-IDF index)
|
||||
* - .sdlc/tools/ama/index/last_indexed.json (during --setup; mtime map for incremental re-runs)
|
||||
* - STDERR: structured log lines via _shared/log.ts
|
||||
* - STDOUT: JSON only (ToolResult shape from _shared/types.ts)
|
||||
*
|
||||
* EXTENDING
|
||||
* ---------
|
||||
* Replace scoreChunks() with embedding-based cosine similarity to improve answer
|
||||
* quality. The rest of the pipeline (chunking, index format, protocol) stays the same.
|
||||
*
|
||||
* For LLM synthesis: call the Claude API in run() with the top excerpts as context.
|
||||
* Add "synthesis_model" to config.yaml to control which model is used.
|
||||
*/
|
||||
|
||||
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
|
||||
import { makeLogger } from '../_shared/log.ts'
|
||||
import { loadToolConfig } from '../_shared/config.ts'
|
||||
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
|
||||
import {
|
||||
readdirSync, readFileSync, writeFileSync, mkdirSync, statSync, existsSync,
|
||||
} from 'node:fs'
|
||||
import { join, extname, relative } from 'node:path'
|
||||
|
||||
const log = makeLogger('ama')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface AmaConfig {
|
||||
chunk_lines: number
|
||||
chunk_overlap: number
|
||||
max_results: number
|
||||
max_file_kb: number
|
||||
extensions: string
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: AmaConfig = {
|
||||
chunk_lines: 40,
|
||||
chunk_overlap: 5,
|
||||
max_results: 5,
|
||||
max_file_kb: 500,
|
||||
extensions: '.ts,.js,.tsx,.jsx,.rs,.go,.py,.rb,.java,.md,.txt,.yaml,.yml,.toml',
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool metadata
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const meta: ToolMeta = {
|
||||
name: 'ama',
|
||||
display_name: 'AMA — Ask Me Anything',
|
||||
description: 'Answers questions about the codebase using a pre-built TF-IDF keyword index',
|
||||
version: '0.2.1',
|
||||
requires_setup: true,
|
||||
setup_description: 'Indexes source files for keyword search (first run is full index; subsequent runs are incremental)',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: ['question'],
|
||||
properties: {
|
||||
question: { type: 'string', description: 'The question to answer about the codebase' },
|
||||
},
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sources: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: { type: 'string' },
|
||||
lines: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },
|
||||
excerpt: { type: 'string' },
|
||||
score: { type: 'number', description: 'TF-IDF relevance score (0.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<string, number>
|
||||
}
|
||||
|
||||
interface MtimeMap {
|
||||
version: number
|
||||
indexed_at: string
|
||||
files: Record<string, number>
|
||||
}
|
||||
|
||||
interface AmaSource {
|
||||
path: string
|
||||
lines: [number, number]
|
||||
excerpt: string
|
||||
score: number
|
||||
stale?: boolean
|
||||
}
|
||||
|
||||
interface AmaOutput {
|
||||
sources: AmaSource[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Setup — build the keyword index
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function setup(root: string): Promise<ToolResult<{
|
||||
files_indexed: number
|
||||
files_skipped: number
|
||||
files_pruned: number
|
||||
chunks_written: number
|
||||
total_chunks: number
|
||||
duration_ms: number
|
||||
index_size_kb: number
|
||||
}>> {
|
||||
const start = Date.now()
|
||||
const config = loadToolConfig(root, 'ama', DEFAULT_CONFIG)
|
||||
const extensions = new Set(
|
||||
String(config.extensions).split(',').map(e => e.trim()).filter(Boolean),
|
||||
)
|
||||
|
||||
const indexDir = join(root, '.sdlc', 'tools', 'ama', 'index')
|
||||
mkdirSync(indexDir, { recursive: true })
|
||||
|
||||
const chunksPath = join(indexDir, 'chunks.json')
|
||||
const mtimePath = join(indexDir, 'last_indexed.json')
|
||||
|
||||
// Load previous index and mtime map for incremental re-indexing
|
||||
let prevChunks: Chunk[] = []
|
||||
let prevMtimes: Record<string, number> = {}
|
||||
const isIncremental = existsSync(chunksPath) && existsSync(mtimePath)
|
||||
if (isIncremental) {
|
||||
try {
|
||||
const prevIndex = JSON.parse(readFileSync(chunksPath, 'utf8')) as Index
|
||||
prevChunks = prevIndex.chunks ?? []
|
||||
const mtimeData = JSON.parse(readFileSync(mtimePath, 'utf8')) as MtimeMap
|
||||
prevMtimes = mtimeData.files ?? {}
|
||||
log.info(`incremental mode: ${prevChunks.length} existing chunks, ${Object.keys(prevMtimes).length} tracked files`)
|
||||
} catch {
|
||||
log.warn('could not load previous index — falling back to full re-index')
|
||||
prevChunks = []
|
||||
prevMtimes = {}
|
||||
}
|
||||
} else {
|
||||
log.info('full index mode (no previous index found)')
|
||||
}
|
||||
|
||||
log.info(`indexing with extensions: ${[...extensions].join(', ')}`)
|
||||
|
||||
const allFiles = walkFiles(root, extensions, Number(config.max_file_kb))
|
||||
log.info(`found ${allFiles.length} files to consider`)
|
||||
|
||||
// Group previous chunks by file for efficient lookup
|
||||
const prevChunksByFile = new Map<string, Chunk[]>()
|
||||
for (const chunk of prevChunks) {
|
||||
const arr = prevChunksByFile.get(chunk.path) ?? []
|
||||
arr.push(chunk)
|
||||
prevChunksByFile.set(chunk.path, arr)
|
||||
}
|
||||
|
||||
const newMtimes: Record<string, number> = {}
|
||||
const unchangedChunks: Chunk[] = []
|
||||
const freshChunks: Chunk[] = []
|
||||
let filesSkipped = 0
|
||||
let filesIndexed = 0
|
||||
|
||||
for (const filePath of allFiles) {
|
||||
const relPath = relative(root, filePath)
|
||||
const mtime = statSync(filePath).mtimeMs
|
||||
if (isIncremental && prevMtimes[relPath] === mtime) {
|
||||
unchangedChunks.push(...(prevChunksByFile.get(relPath) ?? []))
|
||||
newMtimes[relPath] = mtime
|
||||
filesSkipped++
|
||||
} else {
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf8')
|
||||
const fileChunks = chunkFile(relPath, content, Number(config.chunk_lines), Number(config.chunk_overlap))
|
||||
freshChunks.push(...fileChunks)
|
||||
newMtimes[relPath] = mtime
|
||||
filesIndexed++
|
||||
} catch (e) {
|
||||
log.warn(`skipping ${relPath}: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count pruned files (tracked before but no longer on disk)
|
||||
const currentPaths = new Set(allFiles.map(f => relative(root, f)))
|
||||
const filesPruned = Object.keys(prevMtimes).filter(p => !currentPaths.has(p)).length
|
||||
if (filesPruned > 0) log.info(`pruned ${filesPruned} deleted/moved file(s) from index`)
|
||||
|
||||
const allChunks = [...unchangedChunks, ...freshChunks]
|
||||
log.info(`${filesIndexed} indexed, ${filesSkipped} skipped, ${filesPruned} pruned — ${allChunks.length} total chunks`)
|
||||
|
||||
// Compute smoothed IDF: log((N+1)/(df+1)) + 1 for each term
|
||||
const N = allChunks.length
|
||||
const df: Record<string, number> = {}
|
||||
for (const chunk of allChunks) {
|
||||
for (const token of chunk.tokens) {
|
||||
df[token] = (df[token] ?? 0) + 1
|
||||
}
|
||||
}
|
||||
const idf: Record<string, number> = {}
|
||||
for (const [term, freq] of Object.entries(df)) {
|
||||
idf[term] = Math.log((N + 1) / (freq + 1)) + 1
|
||||
}
|
||||
|
||||
// Write index and mtime map
|
||||
const index: Index = { version: 2, generated: new Date().toISOString(), chunks: allChunks, idf }
|
||||
const indexJson = JSON.stringify(index)
|
||||
writeFileSync(chunksPath, indexJson)
|
||||
|
||||
const mtimeMap: MtimeMap = { version: 1, indexed_at: new Date().toISOString(), files: newMtimes }
|
||||
writeFileSync(mtimePath, JSON.stringify(mtimeMap))
|
||||
|
||||
const duration_ms = Date.now() - start
|
||||
const index_size_kb = Math.round(indexJson.length / 1024)
|
||||
log.info(`done in ${duration_ms}ms — index size: ${index_size_kb}KB`)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
files_indexed: filesIndexed,
|
||||
files_skipped: filesSkipped,
|
||||
files_pruned: filesPruned,
|
||||
chunks_written: freshChunks.length,
|
||||
total_chunks: allChunks.length,
|
||||
duration_ms,
|
||||
index_size_kb,
|
||||
},
|
||||
duration_ms,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run — answer a question using the index
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function run(
|
||||
input: { question?: string },
|
||||
root: string,
|
||||
): Promise<ToolResult<AmaOutput>> {
|
||||
const start = Date.now()
|
||||
const config = loadToolConfig(root, 'ama', DEFAULT_CONFIG)
|
||||
|
||||
const question = input.question?.trim()
|
||||
if (!question) {
|
||||
return { ok: false, error: 'input.question is required' }
|
||||
}
|
||||
|
||||
const indexPath = join(root, '.sdlc', 'tools', 'ama', 'index', 'chunks.json')
|
||||
if (!existsSync(indexPath)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Index not built. Run setup first: sdlc tool run ama --setup',
|
||||
}
|
||||
}
|
||||
|
||||
let index: Index
|
||||
try {
|
||||
index = JSON.parse(readFileSync(indexPath, 'utf8')) as Index
|
||||
} catch (e) {
|
||||
return { ok: false, error: `Failed to load index: ${e}. Re-run: sdlc tool run ama --setup` }
|
||||
}
|
||||
|
||||
// Load mtime map for stale source detection (non-fatal if absent)
|
||||
let mtimes: Record<string, number> = {}
|
||||
try {
|
||||
const mtimePath = join(root, '.sdlc', 'tools', 'ama', 'index', 'last_indexed.json')
|
||||
if (existsSync(mtimePath)) {
|
||||
mtimes = (JSON.parse(readFileSync(mtimePath, 'utf8')) as MtimeMap).files ?? {}
|
||||
}
|
||||
} catch { /* stale detection skipped */ }
|
||||
|
||||
log.info(`scoring ${index.chunks.length} chunks for: "${question}"`)
|
||||
|
||||
// idf falls back gracefully to 1.0 weights for v1 indexes without IDF
|
||||
const idf = index.idf ?? {}
|
||||
const topChunks = scoreChunks(question, index.chunks, idf).slice(0, Number(config.max_results))
|
||||
|
||||
const sources: AmaSource[] = []
|
||||
for (const { chunk, score } of topChunks) {
|
||||
const fullPath = join(root, chunk.path)
|
||||
try {
|
||||
const lines = readFileSync(fullPath, 'utf8').split('\n')
|
||||
const excerpt = lines.slice(chunk.start - 1, chunk.end).join('\n')
|
||||
|
||||
// Stale detection: flag if file changed since last index run
|
||||
let stale = false
|
||||
try {
|
||||
if (mtimes[chunk.path] !== undefined && statSync(fullPath).mtimeMs !== mtimes[chunk.path]) {
|
||||
stale = true
|
||||
log.warn(`stale source: ${chunk.path} changed since last index run`)
|
||||
}
|
||||
} catch { /* file may not exist — handled above */ }
|
||||
|
||||
const source: AmaSource = { path: chunk.path, lines: [chunk.start, chunk.end], excerpt, score }
|
||||
if (stale) source.stale = true
|
||||
sources.push(source)
|
||||
} catch {
|
||||
log.warn(`skipping deleted/moved file: ${chunk.path}`)
|
||||
}
|
||||
}
|
||||
|
||||
const duration_ms = Date.now() - start
|
||||
log.info(`returned ${sources.length} sources in ${duration_ms}ms`)
|
||||
|
||||
return { ok: true, data: { sources }, duration_ms }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const SKIP_DIRS = new Set([
|
||||
'node_modules', '.git', 'target', 'dist', 'build', '.sdlc',
|
||||
'.next', '__pycache__', '.cache', 'coverage',
|
||||
])
|
||||
|
||||
function walkFiles(root: string, extensions: Set<string>, maxFileKb: number): string[] {
|
||||
const results: string[] = []
|
||||
|
||||
function walk(dir: string) {
|
||||
let entries: ReturnType<typeof readdirSync>
|
||||
try {
|
||||
entries = readdirSync(dir, { withFileTypes: true })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.name.startsWith('.')) continue
|
||||
const full = join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (!SKIP_DIRS.has(entry.name)) walk(full)
|
||||
} else if (entry.isFile()) {
|
||||
if (!extensions.has(extname(entry.name))) continue
|
||||
try {
|
||||
if (statSync(full).size > maxFileKb * 1024) {
|
||||
log.warn(`skipping large file (${Math.round(statSync(full).size / 1024)}KB): ${relative(root, full)}`)
|
||||
continue
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
results.push(full)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(root)
|
||||
return results
|
||||
}
|
||||
|
||||
function chunkFile(
|
||||
relPath: string,
|
||||
content: string,
|
||||
chunkLines: number,
|
||||
overlap: number,
|
||||
): Chunk[] {
|
||||
const lines = content.split('\n')
|
||||
const chunks: Chunk[] = []
|
||||
const step = Math.max(1, chunkLines - overlap)
|
||||
|
||||
for (let i = 0; i < lines.length; i += step) {
|
||||
const start = i + 1 // 1-based line numbers
|
||||
const end = Math.min(i + chunkLines, lines.length)
|
||||
const tokens = extractTokens(lines.slice(i, end).join(' '))
|
||||
if (tokens.length > 0) {
|
||||
chunks.push({ path: relPath, start, end, tokens })
|
||||
}
|
||||
if (end >= lines.length) break
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract lowercase tokens from text, splitting on camelCase and snake_case
|
||||
* boundaries to enable code-aware search. Words < 4 chars are omitted as noise.
|
||||
*
|
||||
* Examples:
|
||||
* featureTransition → ['feature', 'transition']
|
||||
* SdlcError → ['sdlc', 'error']
|
||||
* auth_token → ['auth', 'token']
|
||||
* authenticate → ['authenticate']
|
||||
*/
|
||||
function extractTokens(text: string): string[] {
|
||||
// Split on camelCase and acronym boundaries before lowercasing
|
||||
const expanded = text
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // XMLParser → XML Parser
|
||||
const seen = new Set<string>()
|
||||
const tokens: string[] = []
|
||||
for (const word of expanded.toLowerCase().split(/[^a-z0-9]+/)) {
|
||||
if (word.length >= 3 && !seen.has(word)) {
|
||||
seen.add(word)
|
||||
tokens.push(word)
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
/**
|
||||
* Score chunks using TF-IDF weighted overlap.
|
||||
* IDF is precomputed at index time (stored in chunks.json v2+).
|
||||
* Falls back to uniform weights (raw overlap) for v1 indexes without IDF.
|
||||
*/
|
||||
function scoreChunks(
|
||||
question: string,
|
||||
chunks: Chunk[],
|
||||
idf: Record<string, number>,
|
||||
): { chunk: Chunk; score: number }[] {
|
||||
const queryTokens = extractTokens(question)
|
||||
if (queryTokens.length === 0) return []
|
||||
|
||||
const hasIdf = Object.keys(idf).length > 0
|
||||
const results: { chunk: Chunk; score: number }[] = []
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const chunkSet = new Set(chunk.tokens)
|
||||
let score = 0
|
||||
let totalWeight = 0
|
||||
|
||||
for (const token of queryTokens) {
|
||||
const weight = hasIdf ? (idf[token] ?? 1.0) : 1.0
|
||||
totalWeight += weight
|
||||
if (chunkSet.has(token)) score += weight
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
results.push({ chunk, score: totalWeight > 0 ? score / totalWeight : 0 })
|
||||
}
|
||||
}
|
||||
|
||||
return results.sort((a, b) => b.score - a.score)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI entrypoint
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mode = getArgs()[0] ?? '--run'
|
||||
const root = process.env.SDLC_ROOT ?? process.cwd()
|
||||
|
||||
if (mode === '--meta') {
|
||||
console.log(JSON.stringify(meta))
|
||||
exit(0)
|
||||
} else if (mode === '--setup') {
|
||||
setup(root)
|
||||
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
|
||||
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
|
||||
} else if (mode === '--run') {
|
||||
readStdin()
|
||||
.then(raw => run(JSON.parse(raw || '{}') as { question?: string }, root))
|
||||
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
|
||||
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
|
||||
} else {
|
||||
console.error(`Unknown mode: ${mode}. Use --meta, --setup, or --run.`)
|
||||
exit(1)
|
||||
}
|
||||
169
.sdlc/tools/dev-driver/README.md
Normal file
169
.sdlc/tools/dev-driver/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
# dev-driver
|
||||
|
||||
A stock sdlc tool that reads project state, finds the single most important next
|
||||
development action, dispatches it asynchronously, and exits. Paired with a recurring
|
||||
orchestrator action (every 4 hours), it makes your sdlc project self-advancing.
|
||||
|
||||
---
|
||||
|
||||
## What it does
|
||||
|
||||
On each invocation, dev-driver applies a 5-level priority waterfall and takes exactly one action:
|
||||
|
||||
1. **Flight lock** — if a previous dispatch is still in flight (< 2h), do nothing
|
||||
2. **Quality check** — if `quality-check` reports failures, do nothing (fix quality first)
|
||||
3. **Feature advancement** — if any feature has an active directive, advance it one step
|
||||
4. **Wave start** — if a milestone has all features PLANNED/READY, start the wave
|
||||
5. **Idle** — nothing actionable, exit cleanly
|
||||
|
||||
One action per tick. The 4-hour recurrence IS the iteration rhythm.
|
||||
|
||||
---
|
||||
|
||||
## Default action recipe
|
||||
|
||||
Create this action once in the sdlc UI or via CLI:
|
||||
|
||||
```
|
||||
Label: dev-driver
|
||||
Tool: dev-driver
|
||||
Input: {}
|
||||
Recurrence: 14400 (4 hours in seconds)
|
||||
```
|
||||
|
||||
Then run `sdlc ui --run-actions` to enable the orchestrator.
|
||||
|
||||
---
|
||||
|
||||
## Priority waterfall (detail)
|
||||
|
||||
### Level 1: Flight lock
|
||||
|
||||
Reads `.sdlc/.dev-driver.lock`. If the lock exists and is less than 2 hours old,
|
||||
exits immediately with `{ action: "waiting", lock_age_mins: N }`.
|
||||
|
||||
This prevents double-dispatch when a previous Claude agent is still running.
|
||||
|
||||
### Level 2: Quality check
|
||||
|
||||
Runs the `quality-check` tool. If any checks fail, exits with:
|
||||
```json
|
||||
{ "action": "quality_failing", "failed_checks": ["test", "lint"] }
|
||||
```
|
||||
|
||||
Fix the failing checks before dev-driver will advance features.
|
||||
|
||||
### Level 3: Feature advancement
|
||||
|
||||
Finds features in `implementation`, `review`, `audit`, or `qa` phase with a
|
||||
pending directive. Picks the first one alphabetically. Dispatches:
|
||||
|
||||
```bash
|
||||
claude --print "/sdlc-next <slug>"
|
||||
```
|
||||
|
||||
**This is `/sdlc-next` — one step only.** The 4-hour recurrence advances the feature
|
||||
step by step over time. This is intentional: it keeps you in control and lets you
|
||||
course-correct between steps.
|
||||
|
||||
Returns:
|
||||
```json
|
||||
{ "action": "feature_advanced", "slug": "my-feature", "phase": "implementation", "directive": "/sdlc-next my-feature" }
|
||||
```
|
||||
|
||||
### Level 4: Wave start
|
||||
|
||||
If no features have active directives but a milestone has all features in PLANNED or READY
|
||||
phase, starts the next wave:
|
||||
|
||||
```bash
|
||||
claude --print "/sdlc-run-wave <milestone>"
|
||||
```
|
||||
|
||||
Returns:
|
||||
```json
|
||||
{ "action": "wave_started", "milestone": "v21-dev-driver" }
|
||||
```
|
||||
|
||||
### Level 5: Idle
|
||||
|
||||
No actionable work found. Returns:
|
||||
```json
|
||||
{ "action": "idle", "reason": "no actionable work found" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to skip a feature
|
||||
|
||||
If you don't want dev-driver to autonomously advance a specific feature, add a task
|
||||
with `skip:autonomous` in the title:
|
||||
|
||||
```bash
|
||||
sdlc task add <slug> --title "skip:autonomous: needs human review before proceeding"
|
||||
```
|
||||
|
||||
Dev-driver will exclude this feature from Level 3 selection until the task is removed
|
||||
or marked done. You retain full control over which features advance autonomously.
|
||||
|
||||
---
|
||||
|
||||
## One step, not full run
|
||||
|
||||
Dev-driver dispatches `/sdlc-next <slug>`, NOT `/sdlc-run <slug>`.
|
||||
|
||||
`/sdlc-next` executes exactly one directive (write a spec, approve a design, implement
|
||||
a task, etc.) and exits. The next tick, dev-driver will pick the same feature again
|
||||
and advance it one more step.
|
||||
|
||||
This means:
|
||||
- Each tick = one atomic state machine step
|
||||
- You can review after each step in the Actions page
|
||||
- No surprise full-feature runs that take hours
|
||||
|
||||
---
|
||||
|
||||
## Lock file
|
||||
|
||||
Path: `.sdlc/.dev-driver.lock`
|
||||
TTL: 2 hours
|
||||
|
||||
```json
|
||||
{
|
||||
"started_at": "2026-03-02T04:00:00.000Z",
|
||||
"action": "feature_advanced",
|
||||
"slug": "my-feature",
|
||||
"pid": 12345
|
||||
}
|
||||
```
|
||||
|
||||
The lock is written before each dispatch and cleared automatically after 2 hours.
|
||||
You can delete it manually if you need to run dev-driver before the TTL expires.
|
||||
|
||||
---
|
||||
|
||||
## Output reference
|
||||
|
||||
All five possible outputs:
|
||||
|
||||
```json
|
||||
// Level 1 (lock)
|
||||
{ "action": "waiting", "lock_age_mins": 45 }
|
||||
|
||||
// Level 1 (active run)
|
||||
{ "action": "waiting", "reason": "agent run in progress" }
|
||||
|
||||
// Level 2
|
||||
{ "action": "quality_failing", "failed_checks": ["test", "clippy"] }
|
||||
|
||||
// Level 3
|
||||
{ "action": "feature_advanced", "slug": "my-feature", "phase": "implementation", "directive": "/sdlc-next my-feature" }
|
||||
|
||||
// Level 4
|
||||
{ "action": "wave_started", "milestone": "v21-dev-driver" }
|
||||
|
||||
// Level 5
|
||||
{ "action": "idle", "reason": "no actionable work found" }
|
||||
```
|
||||
|
||||
All wrapped in: `{ "ok": true, "data": { ... }, "duration_ms": N }`
|
||||
447
.sdlc/tools/dev-driver/tool.ts
Normal file
447
.sdlc/tools/dev-driver/tool.ts
Normal file
@ -0,0 +1,447 @@
|
||||
/**
|
||||
* Dev Driver
|
||||
* ==========
|
||||
* Finds the single most important next development action and dispatches it
|
||||
* asynchronously. Designed to run on a schedule (e.g. every 4 hours) via the
|
||||
* sdlc orchestrator to make development self-advancing.
|
||||
*
|
||||
* WHAT IT DOES
|
||||
* ------------
|
||||
* --run: Reads JSON from stdin: {} (no parameters)
|
||||
* Applies a 5-level priority waterfall:
|
||||
* 1. Flight lock — if .sdlc/.dev-driver.lock < 2h old, exit waiting
|
||||
* 2. Quality — if quality-check fails, exit quality_failing
|
||||
* 3. Features — if features have active directives, dispatch /sdlc-next
|
||||
* 4. Wave — if a milestone wave is ready, dispatch /sdlc-run-wave
|
||||
* 5. Idle — nothing to do, exit idle
|
||||
* Returns ToolResult<DevDriverOutput>.
|
||||
*
|
||||
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
|
||||
*
|
||||
* KEY INVARIANT
|
||||
* -------------
|
||||
* Level 3 dispatches /sdlc-next (one step), NOT /sdlc-run (to completion).
|
||||
* The 4h recurrence IS the iteration rhythm. Each tick advances exactly one
|
||||
* feature by one directive. This keeps the developer in control.
|
||||
*
|
||||
* HOW TO SKIP A FEATURE
|
||||
* ---------------------
|
||||
* Add a task to the feature with "skip:autonomous" in the title:
|
||||
* sdlc task add <slug> --title "skip:autonomous: needs human review"
|
||||
* The dev-driver will exclude this feature from Level 3 until the task is removed.
|
||||
*
|
||||
* LOCK FILE
|
||||
* ---------
|
||||
* Path: .sdlc/.dev-driver.lock
|
||||
* Written before each dispatch. TTL: 2 hours. Format:
|
||||
* { started_at: ISO, action: string, slug?: string, milestone?: string, pid: number }
|
||||
*/
|
||||
|
||||
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
|
||||
import { makeLogger } from '../_shared/log.ts'
|
||||
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
|
||||
import { execSync, spawn } from 'node:child_process'
|
||||
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const log = makeLogger('dev-driver')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool metadata
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const meta: ToolMeta = {
|
||||
name: 'dev-driver',
|
||||
display_name: 'Dev Driver',
|
||||
description: 'Finds the next development action and dispatches it — advances the project one step per tick',
|
||||
version: '1.0.0',
|
||||
requires_setup: false,
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['waiting', 'quality_failing', 'feature_advanced', 'wave_started', 'idle'],
|
||||
description: 'What the dev-driver decided to do',
|
||||
},
|
||||
lock_age_mins: {
|
||||
type: 'number',
|
||||
description: 'Age of the flight lock in minutes (present when action=waiting from lock)',
|
||||
},
|
||||
reason: {
|
||||
type: 'string',
|
||||
description: 'Human-readable reason (present when action=waiting or idle)',
|
||||
},
|
||||
failed_checks: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Names of failed quality checks (present when action=quality_failing)',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
description: 'Feature slug that was advanced (present when action=feature_advanced)',
|
||||
},
|
||||
phase: {
|
||||
type: 'string',
|
||||
description: 'Current phase of the feature (present when action=feature_advanced)',
|
||||
},
|
||||
directive: {
|
||||
type: 'string',
|
||||
description: 'The /sdlc-next command that was dispatched (present when action=feature_advanced)',
|
||||
},
|
||||
milestone: {
|
||||
type: 'string',
|
||||
description: 'Milestone slug that started (present when action=wave_started)',
|
||||
},
|
||||
},
|
||||
required: ['action'],
|
||||
},
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Output types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type DevDriverOutput =
|
||||
| { action: 'waiting'; lock_age_mins: number }
|
||||
| { action: 'waiting'; reason: string }
|
||||
| { action: 'quality_failing'; failed_checks: string[] }
|
||||
| { action: 'feature_advanced'; slug: string; phase: string; directive: string }
|
||||
| { action: 'wave_started'; milestone: string }
|
||||
| { action: 'idle'; reason: string }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lock file (T2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface LockFile {
|
||||
started_at: string
|
||||
action: string
|
||||
slug?: string
|
||||
milestone?: string
|
||||
pid: number
|
||||
}
|
||||
|
||||
const LOCK_TTL_MINS = 120
|
||||
|
||||
function lockPath(root: string): string {
|
||||
return join(root, '.sdlc', '.dev-driver.lock')
|
||||
}
|
||||
|
||||
function readLock(root: string): LockFile | null {
|
||||
const p = lockPath(root)
|
||||
if (!existsSync(p)) return null
|
||||
try {
|
||||
return JSON.parse(readFileSync(p, 'utf8')) as LockFile
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isLockActive(lock: LockFile): boolean {
|
||||
const ageMs = Date.now() - Date.parse(lock.started_at)
|
||||
return ageMs < LOCK_TTL_MINS * 60 * 1000
|
||||
}
|
||||
|
||||
function lockAgeMins(lock: LockFile): number {
|
||||
return Math.floor((Date.now() - Date.parse(lock.started_at)) / 60000)
|
||||
}
|
||||
|
||||
function writeLock(root: string, payload: Omit<LockFile, 'pid'> & { pid: number }): void {
|
||||
writeFileSync(lockPath(root), JSON.stringify(payload, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Quality check (T3 - Level 2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface QCCheck {
|
||||
name: string
|
||||
status: 'passed' | 'failed'
|
||||
}
|
||||
|
||||
interface QCResult {
|
||||
passed: number
|
||||
failed: number
|
||||
checks: QCCheck[]
|
||||
}
|
||||
|
||||
function runQualityCheck(root: string): { failed: number; failedNames: string[] } {
|
||||
const toolPath = join(root, '.sdlc', 'tools', 'quality-check', 'tool.ts')
|
||||
if (!existsSync(toolPath)) {
|
||||
log.warn('quality-check tool not found — skipping quality gate')
|
||||
return { failed: 0, failedNames: [] }
|
||||
}
|
||||
try {
|
||||
const raw = execSync(`node ${toolPath} --run`, {
|
||||
input: '{}',
|
||||
encoding: 'utf8',
|
||||
cwd: root,
|
||||
timeout: 120_000,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
const result = JSON.parse(raw) as ToolResult<QCResult>
|
||||
if (!result.data) return { failed: 0, failedNames: [] }
|
||||
const failedNames = result.data.checks
|
||||
.filter(c => c.status === 'failed')
|
||||
.map(c => c.name)
|
||||
return { failed: result.data.failed, failedNames }
|
||||
} catch (e) {
|
||||
log.warn(`quality-check execution error: ${e} — treating as no failures`)
|
||||
return { failed: 0, failedNames: [] }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Active run check (T8)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function hasActiveRuns(root: string): boolean {
|
||||
try {
|
||||
const raw = execSync('sdlc run list --status running --json', {
|
||||
encoding: 'utf8',
|
||||
cwd: root,
|
||||
timeout: 10_000,
|
||||
})
|
||||
const runs = JSON.parse(raw)
|
||||
return Array.isArray(runs) && runs.length > 0
|
||||
} catch {
|
||||
// sdlc run list may not exist yet — skip this check gracefully
|
||||
log.warn('sdlc run list not available — skipping active run check')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Feature selection (T3 - Level 3, T7, T9, T10)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface FeatureDirective {
|
||||
feature: string
|
||||
current_phase: string
|
||||
action: string
|
||||
next_command: string
|
||||
}
|
||||
|
||||
const ACTIVE_PHASES = new Set(['implementation', 'review', 'audit', 'qa'])
|
||||
|
||||
function hasSkipTag(slug: string, root: string): boolean {
|
||||
const tasksPath = join(root, '.sdlc', 'features', slug, 'tasks.md')
|
||||
if (!existsSync(tasksPath)) return false
|
||||
try {
|
||||
const content = readFileSync(tasksPath, 'utf8')
|
||||
return /skip:autonomous/i.test(content)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function findActionableFeature(root: string): FeatureDirective | null {
|
||||
try {
|
||||
const raw = execSync('sdlc next --json', {
|
||||
encoding: 'utf8',
|
||||
cwd: root,
|
||||
timeout: 30_000,
|
||||
})
|
||||
const all = JSON.parse(raw) as FeatureDirective[]
|
||||
const actionable = all
|
||||
.filter(d => d.action !== 'done')
|
||||
.filter(d => ACTIVE_PHASES.has(d.current_phase))
|
||||
.filter(d => !hasSkipTag(d.feature, root))
|
||||
.sort((a, b) => a.feature.localeCompare(b.feature))
|
||||
return actionable[0] ?? null
|
||||
} catch (e) {
|
||||
log.warn(`sdlc next --json failed: ${e}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Wave detection (T3 - Level 4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface MilestoneInfo {
|
||||
slug: string
|
||||
status: string
|
||||
features: { phase: string; status: string }[]
|
||||
done: number
|
||||
total: number
|
||||
}
|
||||
|
||||
const WAVE_READY_PHASES = new Set(['planned', 'ready'])
|
||||
|
||||
function findReadyWave(root: string): string | null {
|
||||
try {
|
||||
const raw = execSync('sdlc milestone list --json', {
|
||||
encoding: 'utf8',
|
||||
cwd: root,
|
||||
timeout: 15_000,
|
||||
})
|
||||
const milestones = JSON.parse(raw) as MilestoneInfo[]
|
||||
const ready = milestones
|
||||
.filter(m => m.status !== 'released' && m.total > 0)
|
||||
.filter(m =>
|
||||
m.features.every(f =>
|
||||
WAVE_READY_PHASES.has(f.phase) || f.phase === 'released'
|
||||
)
|
||||
)
|
||||
.filter(m =>
|
||||
m.features.some(f => WAVE_READY_PHASES.has(f.phase))
|
||||
)
|
||||
.sort((a, b) => a.slug.localeCompare(b.slug))
|
||||
return ready[0]?.slug ?? null
|
||||
} catch (e) {
|
||||
log.warn(`sdlc milestone list failed: ${e}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Async spawn (T4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function spawnClaude(command: string, root: string): number {
|
||||
const child = spawn('claude', ['--print', command], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
cwd: root,
|
||||
env: { ...process.env, SDLC_ROOT: root },
|
||||
})
|
||||
child.unref()
|
||||
return child.pid ?? 0
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main run function (T1, T3, T5)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function run(
|
||||
_input: Record<string, never>,
|
||||
root: string,
|
||||
): Promise<ToolResult<DevDriverOutput>> {
|
||||
const start = Date.now()
|
||||
|
||||
// ── Level 1: Flight lock ──────────────────────────────────────────────────
|
||||
const lock = readLock(root)
|
||||
if (lock && isLockActive(lock)) {
|
||||
const mins = lockAgeMins(lock)
|
||||
log.info(`flight lock active (${mins}m old) — waiting`)
|
||||
return { ok: true, data: { action: 'waiting', lock_age_mins: mins }, duration_ms: Date.now() - start }
|
||||
}
|
||||
if (lock) {
|
||||
log.info(`stale lock found (${lockAgeMins(lock)}m old) — proceeding`)
|
||||
}
|
||||
|
||||
// ── Level 2: Quality check ────────────────────────────────────────────────
|
||||
log.info('running quality check')
|
||||
const qc = runQualityCheck(root)
|
||||
if (qc.failed > 0) {
|
||||
log.info(`quality failing: ${qc.failedNames.join(', ')}`)
|
||||
return { ok: true, data: { action: 'quality_failing', failed_checks: qc.failedNames }, duration_ms: Date.now() - start }
|
||||
}
|
||||
log.info('quality checks passed')
|
||||
|
||||
// ── Level 3: Features with active directives ──────────────────────────────
|
||||
if (hasActiveRuns(root)) {
|
||||
log.info('active sdlc agent run detected — waiting')
|
||||
return { ok: true, data: { action: 'waiting', reason: 'agent run in progress' }, duration_ms: Date.now() - start }
|
||||
}
|
||||
|
||||
const feature = findActionableFeature(root)
|
||||
if (feature) {
|
||||
log.info(`advancing feature: ${feature.feature} (${feature.current_phase})`)
|
||||
|
||||
// Write lock before spawning
|
||||
writeLock(root, {
|
||||
started_at: new Date().toISOString(),
|
||||
action: 'feature_advanced',
|
||||
slug: feature.feature,
|
||||
pid: 0, // will be overwritten after spawn
|
||||
})
|
||||
|
||||
// Intentionally /sdlc-next — one step per tick, not /sdlc-run to completion
|
||||
const pid = spawnClaude(`/sdlc-next ${feature.feature}`, root)
|
||||
|
||||
// Update lock with actual PID
|
||||
writeLock(root, {
|
||||
started_at: new Date().toISOString(),
|
||||
action: 'feature_advanced',
|
||||
slug: feature.feature,
|
||||
pid,
|
||||
})
|
||||
|
||||
log.info(`dispatched /sdlc-next ${feature.feature} (pid: ${pid})`)
|
||||
return {
|
||||
ok: true,
|
||||
data: {
|
||||
action: 'feature_advanced',
|
||||
slug: feature.feature,
|
||||
phase: feature.current_phase,
|
||||
directive: feature.next_command || `/sdlc-next ${feature.feature}`,
|
||||
},
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Level 4: Wave ready ───────────────────────────────────────────────────
|
||||
const milestone = findReadyWave(root)
|
||||
if (milestone) {
|
||||
log.info(`wave ready for milestone: ${milestone}`)
|
||||
|
||||
writeLock(root, {
|
||||
started_at: new Date().toISOString(),
|
||||
action: 'wave_started',
|
||||
milestone,
|
||||
pid: 0,
|
||||
})
|
||||
|
||||
const pid = spawnClaude(`/sdlc-run-wave ${milestone}`, root)
|
||||
|
||||
writeLock(root, {
|
||||
started_at: new Date().toISOString(),
|
||||
action: 'wave_started',
|
||||
milestone,
|
||||
pid,
|
||||
})
|
||||
|
||||
log.info(`dispatched /sdlc-run-wave ${milestone} (pid: ${pid})`)
|
||||
return {
|
||||
ok: true,
|
||||
data: { action: 'wave_started', milestone },
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Level 5: Idle ─────────────────────────────────────────────────────────
|
||||
log.info('no actionable work found — idle')
|
||||
return {
|
||||
ok: true,
|
||||
data: { action: 'idle', reason: 'no actionable work found' },
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI entrypoint
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mode = getArgs()[0] ?? '--run'
|
||||
const root = process.env.SDLC_ROOT ?? process.cwd()
|
||||
|
||||
if (mode === '--meta') {
|
||||
console.log(JSON.stringify(meta))
|
||||
exit(0)
|
||||
} else if (mode === '--run') {
|
||||
readStdin()
|
||||
.then(raw => run(JSON.parse(raw || '{}') as Record<string, never>, root))
|
||||
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
|
||||
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
|
||||
} else {
|
||||
console.error(`Unknown mode: ${mode}. Use --meta or --run.`)
|
||||
exit(1)
|
||||
}
|
||||
34
.sdlc/tools/quality-check/README.md
Normal file
34
.sdlc/tools/quality-check/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Quality Check
|
||||
|
||||
Runs checks defined in `.sdlc/tools/quality-check/config.yaml` and reports pass/fail.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run all configured checks
|
||||
sdlc tool run quality-check
|
||||
|
||||
# Filter to checks whose name matches a string
|
||||
sdlc tool run quality-check --scope test
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
Reads `checks` from `.sdlc/tools/quality-check/config.yaml`, runs each script as a shell
|
||||
command in the project root, and reports pass/fail with the last 500 characters of output.
|
||||
|
||||
## Adding checks
|
||||
|
||||
Edit `.sdlc/tools/quality-check/config.yaml`:
|
||||
|
||||
```yaml
|
||||
checks:
|
||||
- name: test
|
||||
description: Run unit tests
|
||||
script: cargo test --all
|
||||
- name: lint
|
||||
description: Run linter
|
||||
script: cargo clippy --all -- -D warnings
|
||||
```
|
||||
|
||||
The quality-check tool picks them up automatically — no code changes needed.
|
||||
12
.sdlc/tools/quality-check/config.yaml
Normal file
12
.sdlc/tools/quality-check/config.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
# quality-check tool configuration
|
||||
# Add your project's quality checks below.
|
||||
# Each check runs its `script` as a shell command in the project root.
|
||||
#
|
||||
# Example:
|
||||
# checks:
|
||||
# - name: test
|
||||
# description: Run unit tests
|
||||
# script: cargo test --all
|
||||
name: quality-check
|
||||
version: "0.3.0"
|
||||
checks:
|
||||
294
.sdlc/tools/quality-check/tool.ts
Normal file
294
.sdlc/tools/quality-check/tool.ts
Normal file
@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Quality Check
|
||||
* =============
|
||||
* Runs checks defined in .sdlc/tools/quality-check/config.yaml and reports pass/fail.
|
||||
*
|
||||
* WHAT IT DOES
|
||||
* ------------
|
||||
* --run: Reads JSON from stdin: { "scope"?: "string" }
|
||||
* Loads checks from .sdlc/tools/quality-check/config.yaml.
|
||||
* Runs each check's script as a shell command, records pass/fail + output.
|
||||
* If scope is provided, only runs checks whose name matches the filter string.
|
||||
* Returns ToolResult<{ passed, failed, checks[] }>.
|
||||
*
|
||||
* --meta: Writes ToolMeta JSON to stdout. Used by `sdlc tool sync`.
|
||||
*
|
||||
* WHAT IT READS
|
||||
* -------------
|
||||
* - .sdlc/tools/quality-check/config.yaml
|
||||
* → checks[]: { name, description, script }
|
||||
*
|
||||
* WHAT IT WRITES
|
||||
* --------------
|
||||
* - STDERR: structured log lines via _shared/log.ts
|
||||
* - STDOUT: JSON only (ToolResult shape from _shared/types.ts)
|
||||
*
|
||||
* EXTENDING
|
||||
* ---------
|
||||
* Add or edit checks in .sdlc/tools/quality-check/config.yaml:
|
||||
* checks:
|
||||
* - name: test
|
||||
* description: Run unit tests
|
||||
* script: cargo test --all
|
||||
* The quality-check tool picks them up automatically — no code changes needed.
|
||||
*/
|
||||
|
||||
import type { ToolMeta, ToolResult } from '../_shared/types.ts'
|
||||
import { makeLogger } from '../_shared/log.ts'
|
||||
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const log = makeLogger('quality-check')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool metadata
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const meta: ToolMeta = {
|
||||
name: 'quality-check',
|
||||
display_name: 'Quality Check',
|
||||
description: 'Runs checks from .sdlc/tools/quality-check/config.yaml and reports pass/fail',
|
||||
version: '0.3.0',
|
||||
requires_setup: false,
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
scope: {
|
||||
type: 'string',
|
||||
description: 'Optional filter — only run checks whose name matches this string',
|
||||
},
|
||||
},
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
passed: { type: 'number' },
|
||||
failed: { type: 'number' },
|
||||
checks: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
command: { type: 'string' },
|
||||
status: { type: 'string', enum: ['passed', 'failed'] },
|
||||
output: { type: 'string' },
|
||||
duration_ms: { type: 'number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface PlatformCommand {
|
||||
name: string
|
||||
description?: string
|
||||
script: string
|
||||
}
|
||||
|
||||
interface CheckResult {
|
||||
name: string
|
||||
description: string
|
||||
command: string
|
||||
status: 'passed' | 'failed'
|
||||
output: string
|
||||
duration_ms: number
|
||||
}
|
||||
|
||||
interface QualityCheckOutput {
|
||||
passed: number
|
||||
failed: number
|
||||
checks: CheckResult[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config YAML parser — reads checks[] from tool-local config.yaml
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parse the `checks:` array from the tool's config.yaml.
|
||||
* Handles the specific YAML shape used by quality-check:
|
||||
* checks:
|
||||
* - name: <string>
|
||||
* description: <string>
|
||||
* script: <single-quoted or bare string>
|
||||
*/
|
||||
function parseChecksFromYaml(content: string): PlatformCommand[] {
|
||||
const checks: PlatformCommand[] = []
|
||||
const lines = content.split('\n')
|
||||
|
||||
let inChecks = false
|
||||
let current: Partial<PlatformCommand> | null = null
|
||||
|
||||
for (const line of lines) {
|
||||
// Top-level `checks:` section header
|
||||
if (/^checks:/.test(line)) {
|
||||
inChecks = true
|
||||
continue
|
||||
}
|
||||
// Any other top-level key ends the checks section
|
||||
if (/^\S/.test(line) && !/^checks:/.test(line)) {
|
||||
inChecks = false
|
||||
}
|
||||
|
||||
if (!inChecks) continue
|
||||
|
||||
// New item: ` - name: <value>`
|
||||
const itemMatch = line.match(/^\s{2}-\s+name:\s*(.*)$/)
|
||||
if (itemMatch) {
|
||||
if (current?.name && current?.script) {
|
||||
checks.push(current as PlatformCommand)
|
||||
}
|
||||
current = { name: unquoteYaml(itemMatch[1].trim()), description: '', script: '' }
|
||||
continue
|
||||
}
|
||||
|
||||
if (!current) continue
|
||||
|
||||
const descMatch = line.match(/^\s+description:\s*(.*)$/)
|
||||
if (descMatch) {
|
||||
current.description = unquoteYaml(descMatch[1].trim())
|
||||
continue
|
||||
}
|
||||
|
||||
const scriptMatch = line.match(/^\s+script:\s*(.*)$/)
|
||||
if (scriptMatch) {
|
||||
current.script = unquoteYaml(scriptMatch[1].trim())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (current?.name && current?.script) {
|
||||
checks.push(current as PlatformCommand)
|
||||
}
|
||||
|
||||
return checks
|
||||
}
|
||||
|
||||
/** Strip surrounding single or double quotes from a YAML scalar value. */
|
||||
function unquoteYaml(s: string): string {
|
||||
return s.replace(/^'([\s\S]*)'$/, '$1').replace(/^"([\s\S]*)"$/, '$1')
|
||||
}
|
||||
|
||||
/** Load checks from the tool's own config.yaml. Returns [] on any error. */
|
||||
function loadChecks(root: string): PlatformCommand[] {
|
||||
const configPath = join(root, '.sdlc', 'tools', 'quality-check', 'config.yaml')
|
||||
try {
|
||||
const raw = readFileSync(configPath, 'utf8')
|
||||
return parseChecksFromYaml(raw)
|
||||
} catch (e) {
|
||||
log.warn(`Could not read tool config at ${configPath}: ${e}`)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run — execute platform checks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function run(
|
||||
input: { scope?: string },
|
||||
root: string,
|
||||
): Promise<ToolResult<QualityCheckOutput>> {
|
||||
const start = Date.now()
|
||||
|
||||
const commands = loadChecks(root)
|
||||
|
||||
if (commands.length === 0) {
|
||||
log.warn('No checks configured in .sdlc/tools/quality-check/config.yaml — nothing to run')
|
||||
const duration_ms = Date.now() - start
|
||||
return {
|
||||
ok: true,
|
||||
data: { passed: 0, failed: 0, checks: [] },
|
||||
duration_ms,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply scope filter
|
||||
const scope = input.scope?.trim()
|
||||
const filtered = scope
|
||||
? commands.filter(c => c.name.includes(scope))
|
||||
: commands
|
||||
|
||||
log.info(`running ${filtered.length} check(s)${scope ? ` (scope: "${scope}")` : ''}`)
|
||||
|
||||
const checks: CheckResult[] = []
|
||||
|
||||
for (const cmd of filtered) {
|
||||
const checkStart = Date.now()
|
||||
log.info(`running check: ${cmd.name}`)
|
||||
|
||||
let status: 'passed' | 'failed' = 'passed'
|
||||
let output = ''
|
||||
|
||||
try {
|
||||
const result = execSync(cmd.script, {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
output = result.slice(-500) // last 500 chars
|
||||
} catch (e: unknown) {
|
||||
status = 'failed'
|
||||
if (e && typeof e === 'object' && 'stdout' in e && 'stderr' in e) {
|
||||
const err = e as { stdout?: string; stderr?: string }
|
||||
const combined = `${err.stdout ?? ''}${err.stderr ?? ''}`
|
||||
output = combined.slice(-500)
|
||||
} else {
|
||||
output = String(e).slice(-500)
|
||||
}
|
||||
}
|
||||
|
||||
const duration_ms = Date.now() - checkStart
|
||||
log.info(` ${cmd.name}: ${status} (${duration_ms}ms)`)
|
||||
|
||||
checks.push({
|
||||
name: cmd.name,
|
||||
description: cmd.description ?? '',
|
||||
command: cmd.script,
|
||||
status,
|
||||
output,
|
||||
duration_ms,
|
||||
})
|
||||
}
|
||||
|
||||
const passed = checks.filter(c => c.status === 'passed').length
|
||||
const failed = checks.filter(c => c.status === 'failed').length
|
||||
const duration_ms = Date.now() - start
|
||||
|
||||
log.info(`done: ${passed} passed, ${failed} failed in ${duration_ms}ms`)
|
||||
|
||||
return {
|
||||
ok: failed === 0,
|
||||
data: { passed, failed, checks },
|
||||
duration_ms,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI entrypoint
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mode = getArgs()[0] ?? '--run'
|
||||
const root = process.env.SDLC_ROOT ?? process.cwd()
|
||||
|
||||
if (mode === '--meta') {
|
||||
console.log(JSON.stringify(meta))
|
||||
exit(0)
|
||||
} else if (mode === '--run') {
|
||||
readStdin()
|
||||
.then(raw => run(JSON.parse(raw || '{}') as { scope?: string }, root))
|
||||
.then(result => { console.log(JSON.stringify(result)); exit(result.ok ? 0 : 1) })
|
||||
.catch(e => { console.log(JSON.stringify({ ok: false, error: String(e) })); exit(1) })
|
||||
} else {
|
||||
console.error(`Unknown mode: ${mode}. Use --meta or --run.`)
|
||||
exit(1)
|
||||
}
|
||||
113
.sdlc/tools/telegram-recap/README.md
Normal file
113
.sdlc/tools/telegram-recap/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
# telegram-recap
|
||||
|
||||
Fetches Telegram chat messages from the configured time window and emails a digest via SMTP.
|
||||
Delegates all logic to `sdlc telegram digest` — no duplicate implementation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A Telegram bot created via [@BotFather](https://t.me/botfather)
|
||||
2. The bot added to the chats you want to digest
|
||||
3. `sdlc telegram poll` running (or having run) to populate the local message database
|
||||
4. SMTP credentials (e.g. [Resend](https://resend.com), SendGrid, Gmail SMTP)
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Configure secrets
|
||||
|
||||
The tool requires these environment variables. Set them in your orchestrator secrets or shell:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `TELEGRAM_BOT_TOKEN` | Bot API token from @BotFather |
|
||||
| `SMTP_HOST` | SMTP server hostname (e.g. `smtp.resend.com`) |
|
||||
| `SMTP_PORT` | SMTP port (`587` for STARTTLS, `465` for SSL) |
|
||||
| `SMTP_USERNAME` | SMTP auth username |
|
||||
| `SMTP_PASSWORD` | SMTP auth password or API key |
|
||||
| `SMTP_FROM` | From address (e.g. `digest@yourdomain.com`) |
|
||||
| `SMTP_TO` | Recipient(s), comma-separated |
|
||||
|
||||
### 2. Run setup to verify
|
||||
|
||||
```bash
|
||||
sdlc tool run telegram-recap --setup
|
||||
```
|
||||
|
||||
This calls `sdlc telegram status` to verify the bot token and database connectivity.
|
||||
Returns `{ ok: true, data: { status_output: "..." } }` on success.
|
||||
|
||||
## Usage
|
||||
|
||||
### Dry run (preview without sending)
|
||||
|
||||
```bash
|
||||
sdlc tool run telegram-recap --input '{"dry_run": true}'
|
||||
```
|
||||
|
||||
### Send digest
|
||||
|
||||
```bash
|
||||
sdlc tool run telegram-recap --input '{}'
|
||||
```
|
||||
|
||||
### Custom window
|
||||
|
||||
```bash
|
||||
# Last 48 hours instead of the default 24
|
||||
sdlc tool run telegram-recap --input '{"window_hours": 48}'
|
||||
```
|
||||
|
||||
### Specific chat IDs
|
||||
|
||||
```bash
|
||||
sdlc tool run telegram-recap --input '{"chat_ids": ["-100123456789"]}'
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"dry_run": false,
|
||||
"total_messages": 42,
|
||||
"chat_count": 3,
|
||||
"period_start": "2026-03-01T08:00:00Z",
|
||||
"period_end": "2026-03-02T08:00:00Z",
|
||||
"sent_to": ["you@example.com"]
|
||||
},
|
||||
"duration_ms": 1234
|
||||
}
|
||||
```
|
||||
|
||||
## Scheduling with the orchestrator
|
||||
|
||||
Use the sdlc orchestrator for recurrence instead of systemd:
|
||||
|
||||
```bash
|
||||
# Daily digest at the current time
|
||||
sdlc orchestrate add telegram-recap \
|
||||
--tool telegram-recap \
|
||||
--input '{}' \
|
||||
--at "now" \
|
||||
--every 86400
|
||||
```
|
||||
|
||||
This removes the need for systemd timers. The orchestrator runs the tool on schedule
|
||||
and streams results to the sdlc UI via SSE.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Bot token check failed"**
|
||||
→ `TELEGRAM_BOT_TOKEN` is missing or invalid. Verify with `sdlc telegram status`.
|
||||
|
||||
**"sdlc telegram digest failed"**
|
||||
→ Check that `sdlc telegram poll` has been running and the database exists at
|
||||
`.sdlc/telegram/messages.db`. Run `sdlc telegram status` for diagnostics.
|
||||
|
||||
**SMTP errors**
|
||||
→ Check `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`. Common mistake:
|
||||
using port 465 with STARTTLS or port 587 with SSL — match port to protocol.
|
||||
|
||||
**No messages in digest**
|
||||
→ The configured chat IDs may not match what the bot has access to, or the time
|
||||
window contains no messages. Try `--input '{"window_hours": 168}'` (7 days) to widen the window.
|
||||
5
.sdlc/tools/telegram-recap/config.yaml
Normal file
5
.sdlc/tools/telegram-recap/config.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
name: telegram-recap
|
||||
version: "1.0.0"
|
||||
description: "Fetch and email a Telegram chat digest via SMTP"
|
||||
# Add tool-specific config here (optional — env vars take precedence)
|
||||
# See README.md for full configuration reference.
|
||||
303
.sdlc/tools/telegram-recap/tool.ts
Normal file
303
.sdlc/tools/telegram-recap/tool.ts
Normal file
@ -0,0 +1,303 @@
|
||||
/**
|
||||
* telegram-recap
|
||||
* ==============
|
||||
* Fetches Telegram chat messages from the configured window and emails a digest.
|
||||
* Delegates all logic to `sdlc telegram digest --json`.
|
||||
*
|
||||
* WHAT IT DOES
|
||||
* ------------
|
||||
* --setup: Runs `sdlc telegram status` to verify bot token and DB connectivity.
|
||||
* Returns success/failure with bot identity.
|
||||
*
|
||||
* --run: Reads JSON from stdin: { window_hours?, dry_run?, chat_ids? }
|
||||
* Builds args and spawns `sdlc telegram digest --json [args]`.
|
||||
* Maps the digest JSON summary → ToolResult.
|
||||
*
|
||||
* --meta: Writes ToolMeta JSON to stdout. Declares required secrets.
|
||||
*
|
||||
* SECRETS (injected as env vars by the orchestrator)
|
||||
* ---------------------------------------------------
|
||||
* TELEGRAM_BOT_TOKEN Required — Telegram bot API token (from @BotFather)
|
||||
* SMTP_HOST Required — SMTP server hostname (e.g. smtp.resend.com)
|
||||
* SMTP_PORT Required — SMTP port (e.g. 587 for STARTTLS, 465 for SSL)
|
||||
* SMTP_USERNAME Required — SMTP authentication username
|
||||
* SMTP_PASSWORD Required — SMTP authentication password or API key
|
||||
* SMTP_FROM Required — From address for digest emails
|
||||
* SMTP_TO Required — Recipient address(es), comma-separated
|
||||
*
|
||||
* WHAT IT READS
|
||||
* -------------
|
||||
* - $SDLC_ROOT/.sdlc/telegram/messages.db (populated by `sdlc telegram poll`)
|
||||
*
|
||||
* WHAT IT WRITES
|
||||
* --------------
|
||||
* - STDERR: structured log lines via _shared/log.ts
|
||||
* - STDOUT: JSON only (ToolResult shape)
|
||||
*/
|
||||
|
||||
import type { ToolMeta as BaseToolMeta, ToolResult } from '../_shared/types.ts'
|
||||
import { makeLogger } from '../_shared/log.ts'
|
||||
import { getArgs, readStdin, exit } from '../_shared/runtime.ts'
|
||||
import { spawnSync } from 'node:child_process'
|
||||
|
||||
const log = makeLogger('telegram-recap')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extended ToolMeta type with secrets, tags, result_actions
|
||||
// (the Rust runtime supports these fields; they're not yet in the shared type)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface ToolMeta extends BaseToolMeta {
|
||||
secrets?: Array<{ env_var: string; description: string; required?: boolean }>
|
||||
tags?: string[]
|
||||
result_actions?: Array<{
|
||||
label: string
|
||||
icon?: string
|
||||
condition?: string
|
||||
prompt_template: string
|
||||
confirm?: string
|
||||
}>
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool metadata
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const meta: ToolMeta = {
|
||||
name: 'telegram-recap',
|
||||
display_name: 'Telegram Recap',
|
||||
description:
|
||||
'Fetch and email a Telegram chat digest — pulls messages from the configured window and sends via SMTP',
|
||||
version: '1.0.0',
|
||||
requires_setup: true,
|
||||
setup_description:
|
||||
'Verifies TELEGRAM_BOT_TOKEN by calling Telegram getMe and checking database connectivity',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
required: [],
|
||||
properties: {
|
||||
window_hours: {
|
||||
type: 'number',
|
||||
description: 'Time window in hours to include in the digest (default: 24)',
|
||||
},
|
||||
dry_run: {
|
||||
type: 'boolean',
|
||||
description: 'Print digest to stdout without sending email',
|
||||
},
|
||||
chat_ids: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Override configured chat IDs (e.g. ["-100123456789"])',
|
||||
},
|
||||
},
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
dry_run: { type: 'boolean', description: 'Whether this was a dry run' },
|
||||
total_messages: { type: 'number', description: 'Messages included in digest' },
|
||||
chat_count: { type: 'number', description: 'Number of chats included' },
|
||||
period_start: { type: 'string', description: 'ISO 8601 period start timestamp' },
|
||||
period_end: { type: 'string', description: 'ISO 8601 period end timestamp' },
|
||||
sent_to: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Recipient addresses (empty on dry run)',
|
||||
},
|
||||
},
|
||||
},
|
||||
secrets: [
|
||||
{
|
||||
env_var: 'TELEGRAM_BOT_TOKEN',
|
||||
description: 'Telegram bot API token (from @BotFather)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_HOST',
|
||||
description: 'SMTP server hostname (e.g. smtp.resend.com)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_PORT',
|
||||
description: 'SMTP server port (e.g. 587 for STARTTLS, 465 for SSL)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_USERNAME',
|
||||
description: 'SMTP authentication username',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_PASSWORD',
|
||||
description: 'SMTP authentication password or API key',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_FROM',
|
||||
description: 'From address for digest emails (e.g. digest@yourdomain.com)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
env_var: 'SMTP_TO',
|
||||
description: 'Recipient address(es), comma-separated',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
tags: ['telegram', 'email', 'digest'],
|
||||
result_actions: [
|
||||
{
|
||||
label: 'Send test digest',
|
||||
icon: 'send',
|
||||
condition: '$.ok == true',
|
||||
prompt_template:
|
||||
'Run the telegram-recap tool with dry_run: true to preview the digest without sending email.',
|
||||
confirm: 'This will fetch messages and display the digest without sending email.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input / output types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface Input {
|
||||
window_hours?: number
|
||||
dry_run?: boolean
|
||||
chat_ids?: string[]
|
||||
}
|
||||
|
||||
interface DigestOutput {
|
||||
dry_run: boolean
|
||||
total_messages: number
|
||||
chat_count: number
|
||||
period_start: string
|
||||
period_end: string
|
||||
sent_to: string[]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// setup() — verify bot token and database connectivity
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function setup(): ToolResult<{ status_output: string }> {
|
||||
const start = Date.now()
|
||||
log.info('running: sdlc telegram status')
|
||||
|
||||
const proc = spawnSync('sdlc', ['telegram', 'status'], {
|
||||
env: process.env,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
|
||||
const stdout = (proc.stdout ?? '').trim()
|
||||
const stderr = (proc.stderr ?? '').trim()
|
||||
const exitCode = proc.status ?? 1
|
||||
|
||||
if (exitCode !== 0) {
|
||||
log.error(`sdlc telegram status failed (exit ${exitCode}): ${stderr}`)
|
||||
return {
|
||||
ok: false,
|
||||
error: `Bot token check failed (exit ${exitCode}): ${stderr || stdout || 'no output'}`,
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`setup ok:\n${stdout}`)
|
||||
return {
|
||||
ok: true,
|
||||
data: { status_output: stdout },
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// run() — fetch messages and send (or preview) the digest
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function run(input: Input): ToolResult<DigestOutput> {
|
||||
const start = Date.now()
|
||||
|
||||
const args: string[] = ['telegram', 'digest', '--json']
|
||||
|
||||
if (input.dry_run) {
|
||||
args.push('--dry-run')
|
||||
}
|
||||
if (input.window_hours !== undefined) {
|
||||
args.push('--window', String(Math.round(input.window_hours)))
|
||||
}
|
||||
if (input.chat_ids && input.chat_ids.length > 0) {
|
||||
for (const id of input.chat_ids) {
|
||||
args.push('--chat', id)
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`running: sdlc ${args.join(' ')}`)
|
||||
|
||||
const proc = spawnSync('sdlc', args, {
|
||||
env: process.env,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
|
||||
const stdout = (proc.stdout ?? '').trim()
|
||||
const stderr = (proc.stderr ?? '').trim()
|
||||
const exitCode = proc.status ?? 1
|
||||
|
||||
if (exitCode !== 0) {
|
||||
log.error(`sdlc telegram digest failed (exit ${exitCode})`)
|
||||
return {
|
||||
ok: false,
|
||||
error: `sdlc telegram digest failed (exit ${exitCode}): ${stderr || stdout || 'no output'}`,
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
let parsed: DigestOutput
|
||||
try {
|
||||
parsed = JSON.parse(stdout) as DigestOutput
|
||||
} catch (e) {
|
||||
return {
|
||||
ok: false,
|
||||
error: `Failed to parse digest JSON output: ${e}. Raw stdout: ${stdout}`,
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
log.info(
|
||||
`digest ok: ${parsed.total_messages} messages across ${parsed.chat_count} chats, sent_to=${JSON.stringify(parsed.sent_to)}`,
|
||||
)
|
||||
return {
|
||||
ok: true,
|
||||
data: parsed,
|
||||
duration_ms: Date.now() - start,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CLI entrypoint
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const mode = getArgs()[0] ?? '--run'
|
||||
|
||||
if (mode === '--meta') {
|
||||
console.log(JSON.stringify(meta))
|
||||
exit(0)
|
||||
} else if (mode === '--setup') {
|
||||
const result = setup()
|
||||
console.log(JSON.stringify(result))
|
||||
exit(result.ok ? 0 : 1)
|
||||
} else if (mode === '--run') {
|
||||
readStdin()
|
||||
.then(raw => {
|
||||
const result = run(JSON.parse(raw || '{}') as Input)
|
||||
console.log(JSON.stringify(result))
|
||||
exit(result.ok ? 0 : 1)
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(JSON.stringify({ ok: false, error: String(e) }))
|
||||
exit(1)
|
||||
})
|
||||
} else {
|
||||
console.error(`Unknown mode: ${mode}. Use --meta, --setup, or --run.`)
|
||||
exit(1)
|
||||
}
|
||||
52
.sdlc/tools/tools.md
Normal file
52
.sdlc/tools/tools.md
Normal file
@ -0,0 +1,52 @@
|
||||
# SDLC Tools
|
||||
|
||||
Project-specific tools installed by sdlc. Use `sdlc tool run <name>` to invoke.
|
||||
|
||||
Run `sdlc tool sync` to regenerate this file from live tool metadata.
|
||||
|
||||
---
|
||||
|
||||
## ama — AMA — Ask Me Anything
|
||||
|
||||
Answers questions about the codebase by searching a pre-built keyword index.
|
||||
|
||||
**Run:** `sdlc tool run ama --question "..."`
|
||||
**Setup required:** Yes — `sdlc tool run ama --setup`
|
||||
_Indexes source files for keyword search (run once, then re-run when files change significantly)_
|
||||
|
||||
---
|
||||
|
||||
## quality-check — Quality Check
|
||||
|
||||
Runs checks from .sdlc/tools/quality-check/config.yaml and reports pass/fail.
|
||||
|
||||
**Run:** `sdlc tool run quality-check`
|
||||
**Setup required:** No
|
||||
_Edit `.sdlc/tools/quality-check/config.yaml` to add your project's checks_
|
||||
|
||||
---
|
||||
|
||||
## dev-driver — Dev Driver
|
||||
|
||||
Finds the next development action and dispatches it — advances the project one step per tick.
|
||||
|
||||
**Run:** `sdlc tool run dev-driver`
|
||||
**Setup required:** No
|
||||
_Configure via orchestrator: Label=dev-driver, Tool=dev-driver, Input={}, Recurrence=14400. See `.sdlc/tools/dev-driver/README.md` for full docs._
|
||||
|
||||
---
|
||||
|
||||
## telegram-recap — Telegram Recap
|
||||
|
||||
Fetch and email a Telegram chat digest — pulls messages from the configured window and sends via SMTP.
|
||||
|
||||
**Run:** `sdlc tool run telegram-recap --input '{}'`
|
||||
**Setup required:** Yes — `sdlc tool run telegram-recap --setup`
|
||||
_Requires 7 secrets: TELEGRAM_BOT_TOKEN, SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM, SMTP_TO. Schedule with orchestrator (--every 86400) for a daily digest._
|
||||
|
||||
---
|
||||
|
||||
## Adding a Custom Tool
|
||||
|
||||
Run `sdlc tool scaffold <name> "<description>"` to create a new tool skeleton.
|
||||
Then implement the `run()` function in `.sdlc/tools/<name>/tool.ts` and run `sdlc tool sync`.
|
||||
98
AGENTS.md
Normal file
98
AGENTS.md
Normal file
@ -0,0 +1,98 @@
|
||||
# AGENTS.md
|
||||
|
||||
Agent instructions for tidalDB.
|
||||
|
||||
## Team
|
||||
|
||||
| Agent | Identity | Model | Invoke when |
|
||||
|-------|----------|-------|-------------|
|
||||
| `@tidal-engineer` | Jon Gjengset — principal Rust database engineer | opus | Implementing features, storage internals, signal system, query engine, debugging correctness |
|
||||
| `@tidal-visionary` | Spencer Kimball — product and roadmap strategist | opus | Planning milestones, scoping phases, build-vs-defer decisions, roadmap sequencing |
|
||||
| `@tidal-researcher` | Andy Pavlo — database systems researcher | opus | Prior art surveys, library evaluation, architectural research, producing `docs/research/` docs |
|
||||
| `@tidal-storyteller` | Marketing and technical writer | sonnet | Marketing site (`site/`), blog posts, public-facing copy |
|
||||
|
||||
Agent definitions live in `.claude/agents/`. Full context in `CLAUDE.md §Agents`.
|
||||
|
||||
---
|
||||
|
||||
<!-- sdlc:start -->
|
||||
|
||||
## SDLC
|
||||
|
||||
> **Required reading:** `.sdlc/guidance.md` — engineering principles that govern all implementation decisions on this project. <!-- sdlc:guidance -->
|
||||
|
||||
This project uses `sdlc` as its SDLC state machine. `sdlc` manages feature lifecycle, artifacts, tasks, and milestones. It emits structured directives via `sdlc next --json` that any consumer (Claude Code, custom scripts, or humans) acts on to decide what to do next.
|
||||
|
||||
Consumer scaffolding is installed globally under `~/.claude/commands/`, `~/.gemini/commands/`, `~/.opencode/command/`, and `~/.agents/skills/` — available across all projects. Use `/sdlc-specialize` in Claude Code to generate a project-specific AI team (agents + skills) tailored to this project's tech stack and roles.
|
||||
|
||||
### Key Commands
|
||||
|
||||
- `sdlc feature create <slug> --title "..."` — create a new feature
|
||||
- `sdlc next --for <slug> --json` — get the next action directive (JSON)
|
||||
- `sdlc next` — show all active features and their next actions
|
||||
- `sdlc artifact approve <slug> <type>` — approve an artifact to advance the phase
|
||||
- `sdlc state` — show project state
|
||||
- `sdlc feature list` — list all features and their phases
|
||||
- `sdlc task list [<slug>]` — list tasks for a feature (or all tasks)
|
||||
|
||||
### Lifecycle
|
||||
|
||||
draft → specified → planned → ready → implementation → review → audit → qa → merge → released
|
||||
|
||||
Treat this lifecycle as the default pathway. You can use explicit manual transitions when needed, but approvals/artifacts are the recommended way to keep quality and traceability.
|
||||
|
||||
### Artifact Types
|
||||
|
||||
`spec` `design` `tasks` `qa_plan` `review` `audit` `qa_results`
|
||||
|
||||
### CRITICAL: Never edit .sdlc/ YAML directly
|
||||
|
||||
All state changes go through `sdlc` CLI commands. See §6 of `.sdlc/guidance.md` for the full command reference. Direct YAML edits corrupt state.
|
||||
|
||||
### Directive Interface
|
||||
|
||||
Use `sdlc next --for <slug> --json` to get the next directive. The JSON output tells the consumer what to do next (action, message, output_path, is_heavy, gates).
|
||||
|
||||
### Consumer Commands
|
||||
|
||||
- `/sdlc-next <slug>` — execute one step, then stop (human controls cadence)
|
||||
- `/sdlc-run <slug>` — run autonomously to completion
|
||||
- `/sdlc-status [<slug>]` — show current state
|
||||
- `/sdlc-plan` — distribute a plan into milestones, features, and tasks
|
||||
- `/sdlc-milestone-uat <milestone-slug>` — run the acceptance test for a milestone
|
||||
- `/sdlc-pressure-test <milestone-slug>` — pressure-test a milestone against user perspectives
|
||||
- `/sdlc-vision-adjustment [description]` — align all docs, sdlc state, and code to a vision change
|
||||
- `/sdlc-architecture-adjustment [description]` — align all docs, code, and sdlc state to an architecture change
|
||||
- `/sdlc-enterprise-readiness [--stage <stage>]` — analyze production readiness
|
||||
- `/sdlc-setup-quality-gates` — set up pre-commit hooks and quality gates
|
||||
- `/sdlc-cookbook <milestone-slug>` — create developer-scenario cookbook recipes
|
||||
- `/sdlc-cookbook-run <milestone-slug>` — execute cookbook recipes and record results
|
||||
- `/sdlc-ponder [slug]` — open the ideation workspace for exploring and committing ideas
|
||||
- `/sdlc-ponder-commit <slug>` — crystallize a pondered idea into milestones and features
|
||||
- `/sdlc-guideline <slug-or-problem>` — build an evidence-backed guideline through five research perspectives and TOC-first distillation
|
||||
- `/sdlc-suggest` — analyze project state and suggest 3-5 ponder topics to explore next
|
||||
- `/sdlc-beat [domain | feature:<slug> | --week]` — step back with a senior leadership lens; evaluate if we're building the right thing in the right direction; stores history in `.sdlc/beat.yaml`
|
||||
- `/sdlc-recruit <role>` — recruit an expert thought partner as a persistent agent
|
||||
- `/sdlc-empathy <subject>` — deep user perspective interviews before decisions
|
||||
- `/sdlc-spike <slug> — <need>; [see <ref>]` — research, prototype, validate, and report; produces working prototype + findings in `.sdlc/spikes/<slug>/findings.md`
|
||||
- `/sdlc-convo-mine [file or text]` — mine conversation dumps for signal; apply 5 perspective lenses, group themes, launch parallel ponder sessions per group
|
||||
|
||||
### Tool Suite
|
||||
|
||||
<!-- sdlc:tools -->
|
||||
Project-scoped TypeScript tools in `.sdlc/tools/` — callable by agents and humans
|
||||
during any lifecycle phase. Read `.sdlc/tools/tools.md` for the full help menu.
|
||||
|
||||
- `sdlc tool list` — show installed tools
|
||||
- `sdlc tool run <name> [args]` — run a tool; pass `--json '{...}'` for complex input
|
||||
- `sdlc tool sync` — regenerate `tools.md` after adding a custom tool
|
||||
- `sdlc tool scaffold <name> "desc"` — create a new tool skeleton
|
||||
|
||||
**Core tools:** `ama` (codebase Q&A), `quality-check` (runs platform shell gates)
|
||||
|
||||
Use `/sdlc-tool-run`, `/sdlc-tool-build`, `/sdlc-tool-audit`, `/sdlc-tool-uat` in Claude Code for guided tool workflows.
|
||||
<!-- /sdlc:tools -->
|
||||
|
||||
Project: tidalDB
|
||||
|
||||
<!-- sdlc:end -->
|
||||
Loading…
Reference in New Issue
Block a user