@happyvertical/smrt-projects
Provider-agnostic project management with time tracking, task management, and team collaboration.
v0.19.0Project ManagementTime Tracking
Overview
smrt-projects provides project board management, repository integration, issue/PR tracking, and AI-powered Living Spec pattern where comments are synthesized into issue bodies. Now includes 7 new time tracking components for approval workflows.
Installation
bash
npm install @happyvertical/smrt-projectsQuick Start
typescript
import {
RepositoryCollection, IssueCollection, ProjectCollection
} from '@happyvertical/smrt-projects';
// Initialize
const repos = await RepositoryCollection.create({ db: {...} });
const issues = await IssueCollection.create({ db: {...} });
const projects = await ProjectCollection.create({ 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
typescript
class Repository extends SmrtObject {
owner: string
name: string
fullName: string // owner/name
description?: string
defaultBranch: string
isPrivate: boolean
providerType: 'github' | 'gitlab' | 'bitbucket' | 'azure'
baseUrl?: string // For self-hosted
tokenConfigKey: string // Env var name (not the token itself!)
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 hasOpenIssuesMatching(criteria): Promise<boolean> // AI
async summarizeActivity(): Promise<string> // AI
}Issue (Living Spec)
typescript
class Issue extends SmrtObject {
repositoryId: string
number: number
nodeId: string
title: string
body: string
state: 'open' | 'closed'
author: string
labels: string[]
assignees: string[]
commentsCount: number
originalBody?: string // Before synthesis
synthesisCount: number // How many times synthesized
lastSyncedAt?: Date
// Living Spec Pattern
async incorporateFeedback(options): Promise<IncorporateFeedbackResult>
async rollback(): Promise<void>
// AI-Powered
async needsReview(): Promise<boolean>
async isBugReport(): Promise<boolean>
async isFeatureRequest(): Promise<boolean>
async suggestLabels(): Promise<string[]>
// Operations
async sync(options?): Promise<void>
async getComments(): Promise<Comment[]>
async addComment(body: string): Promise<Comment>
async close(): Promise<void>
async addLabels(labels: string[]): Promise<void>
async assign(username: string): Promise<void>
getUrl(): string
}Project
typescript
class Project extends SmrtObject {
projectId: string // Provider-specific ID
projectNumber?: number
title: string
owner: string
url?: string
providerType: 'github' | 'jira' | 'linear' | 'zenhub'
statuses: string[] // Available columns/statuses
fields: Array<{id, name, type}>
statusFieldId?: string
statusOptions?: Array<{id, name}>
lastSyncedAt?: Date
async sync(): Promise<void>
async addItem(issue | pr): Promise<void>
async listItems(filters?): Promise<ProjectItem[]>
async updateItemStatus(itemId, status): Promise<void>
async updateItemField(itemId, fieldId, value): Promise<void>
async getStatuses(): Promise<string[]>
async getFields(): Promise<Field[]>
async getItemsByStatus(status): Promise<ProjectItem[]>
async moveItem(issue, 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.
typescript
// 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
svelte
<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
svelte
<TimeSummary
totalHours={160}
totalValue={20000}
pendingHours={40}
approvedHours={120}
entryCount={20}
layout="grid"
/>ApprovalActions
svelte
<ApprovalActions
status="submitted"
onapprove={() => approve()}
onreject={() => showRejectDialog()}
onedit={() => edit()}
ondelete={() => confirmDelete()}
/>BulkActions
svelte
<BulkActions
selectedCount={5}
onclear={() => clearSelection()}
onapproveall={() => bulkApprove()}
onrejectall={() => bulkReject()}
onexport={() => exportSelected()}
/>AI-Powered Features
typescript
// 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"