@happyvertical/smrt-projects
Provider-agnostic project management -- GitHub-style issues, PRs, projects, and repositories with Living Spec AI synthesis.
Overview
smrt-projects manages repositories, issues, pull requests, and project boards with sync support for external providers (GitHub primary, GitLab/Bitbucket/Azure types defined). Features AI-powered Living Spec pattern where comments are synthesized into issue bodies, and PullRequest extends Issue via STI.
Installation
npm install @happyvertical/smrt-projectsQuick Start
import {
Repository, RepositoryCollection,
Issue, IssueCollection,
PullRequest, PullRequestCollection,
Project, ProjectCollection
} from '@happyvertical/smrt-projects';
// Initialize
const repos = new RepositoryCollection(db);
const issues = new IssueCollection(db);
// Connect repository
const repo = await repos.create({
owner: 'happyvertical',
name: 'smrt',
providerType: 'github',
tokenConfigKey: 'GITHUB_TOKEN' // From env, not stored in DB
});
await repo.save();
// Sync repository metadata
await repo.sync();
// Discover issues
const discoveredIssues = await repo.getIssues({ state: 'open' });
// Get or create tracked issue
const issue = await issues.create({
repositoryId: repo.id,
number: 123,
title: 'Add dark mode',
body: 'We should support dark mode',
state: 'open'
});
await issue.save();
// Living Spec: Synthesize comments into body
const result = await issue.incorporateFeedback({
applyUpdate: true, // Update issue on GitHub
model: 'sonnet'
});
console.log(result.synthesized); // Updated body with commentsCore Models
Repository
class Repository extends SmrtObject {
owner: string
name: string
providerType: 'github' | 'gitlab' | 'bitbucket' | 'azure'
tokenConfigKey: string // Env var name (not the token!) -- resolved at runtime
lastSyncedAt?: Date
async sync(options?): Promise<void>
async getIssues(filters?): Promise<Issue[]>
async getPullRequests(filters?): Promise<PullRequest[]>
async createIssue(data): Promise<Issue>
async createPullRequest(data): Promise<PullRequest>
async summarizeActivity(): Promise<string> // AI
}Issue (Living Spec -- STI Base)
PullRequest extends Issue via single-table inheritance. Both share the same table, discriminated by _meta_type.
class Issue extends SmrtObject {
repositoryId: string // FK
number: number
title: string
body: string
state: 'open' | 'closed'
labels: string[]
originalBody?: string // Before synthesis
synthesisCount: number // Incremented on each incorporateFeedback apply
// Living Spec Pattern
async incorporateFeedback(options): Promise<IncorporateFeedbackResult>
async rollback(): Promise<void>
// AI-Powered
async suggestLabels(): Promise<string[]>
// Operations
async sync(options?): Promise<void>
async close(): Promise<void>
async addLabels(labels: string[]): Promise<void>
async addComment(body: string): Promise<Comment>
}
class PullRequest extends Issue {
headRef: string
baseRef: string
merged: boolean
draft: boolean
additions: number
deletions: number
changedFiles: number
async summarize(): Promise<string>
async merge(): Promise<void>
}Project (GitHub Projects V2)
class Project extends SmrtObject {
projectId: string // Provider-specific ID
title: string
providerType: 'github' | 'jira' | 'linear' | 'zenhub'
statuses: string[] // Available columns/statuses
statusFieldId?: string
async sync(): Promise<void>
async addItem(issue | pr): Promise<void>
async moveItem(issue, status): Promise<void>
async listItems(filters?): Promise<ProjectItem[]>
async updateItemStatus(itemId, status): Promise<void>
async analyzeHealth(): Promise<string> // AI
}Living Spec Pattern
The Living Spec pattern automatically incorporates comment feedback into issue bodies using AI synthesis.
// 1. Create issue on GitHub
const issue = await issues.create({
repositoryId: repo.id,
number: 42,
title: 'Add user authentication',
body: 'We need to add login functionality'
});
await issue.save();
// 2. Users comment on the issue with feedback:
// - "Should support OAuth2"
// - "Need password reset flow"
// - "2FA required for admins"
// 3. Incorporate feedback using AI
const result = await issue.incorporateFeedback({
applyUpdate: true, // Update issue on GitHub
model: 'sonnet', // Claude Sonnet
synthesisStrategy: 'append' // or 'replace', 'merge'
});
console.log(result.synthesized);
// "We need to add login functionality with OAuth2 support,
// password reset flow, and 2FA for admins."
// 4. Original body is preserved
console.log(issue.originalBody); // "We need to add login functionality"
console.log(issue.synthesisCount); // 1
// 5. Rollback if needed
await issue.rollback();
// Restores originalBody to body fieldTime Tracking Components (NEW v0.19.0)
TimeEntryCard
<script>
import { TimeEntryCard } from '@happyvertical/smrt-projects/svelte';
const entry = {
id: 'entry-1',
date: new Date('2025-01-15'),
hours: 8.5,
description: 'Developed new feature',
status: 'submitted',
amount: 1062.50,
workerName: 'John Doe',
hourlyRate: 125
};
</script>
<TimeEntryCard {entry} href="/entries/entry-1" />TimeSummary
<TimeSummary
totalHours={160}
totalValue={20000}
pendingHours={40}
approvedHours={120}
entryCount={20}
layout="grid"
/>ApprovalActions
<ApprovalActions
status="submitted"
onapprove={() => approve()}
onreject={() => showRejectDialog()}
onedit={() => edit()}
ondelete={() => confirmDelete()}
/>BulkActions
<BulkActions
selectedCount={5}
onclear={() => clearSelection()}
onapproveall={() => bulkApprove()}
onrejectall={() => bulkReject()}
onexport={() => exportSelected()}
/>UI Components
Seven specialized Svelte components for time tracking and project management workflows. Each component is fully typed, accessible, and follows the SMRT design system.
TimeEntryCard
Display individual time entry with status and amount
TimeEntryList
List of time entries with filtering and pagination
TimeSummary
Aggregated time statistics and totals display
DurationDisplay
Formatted duration with configurable units
ApprovalActions
Approve/reject controls for time entries
BulkActions
Batch operations on selected entries
RejectDialog
Modal for rejection with reason input
import {
TimeEntryCard,
TimeEntryList,
TimeSummary,
DurationDisplay,
ApprovalActions,
BulkActions,
RejectDialog
} from '@happyvertical/smrt-projects/svelte';
// Example: Time entry with approval workflow
<TimeEntryCard {entry} href="/entries/{entry.id}">
<svelte:fragment slot="actions">
<ApprovalActions
status={entry.status}
onapprove={() => approve(entry.id)}
onreject={() => openRejectDialog(entry)}
/>
</svelte:fragment>
</TimeEntryCard>AI-Powered Features
// Classify issues
const isBug = await issue.isBugReport();
const isFeature = await issue.isFeatureRequest();
const needsAttention = await issue.needsReview();
// Suggest labels
const suggestedLabels = await issue.suggestLabels();
// ['bug', 'priority:high', 'needs-design']
// Analyze comment sentiment
const sentiment = await comment.getSentiment();
// 'positive' | 'negative' | 'neutral'
// Extract action items from comments
const actionItems = await comment.extractActionItems();
// ['Add OAuth2 support', 'Implement password reset']
// Suggest PR reviewers
const reviewers = await pr.suggestReviewers();
// Check merge readiness
const isReady = await pr.isReadyToMerge();
// Analyze project health
const health = await project.analyzeHealth();
// "3 blocked issues, 2 PRs need review, on track for Q1"