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
|
# Ephemeral / scratch
|
||||||
tmp/
|
tmp/
|
||||||
.claude/worktrees/
|
.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