s-m-r-t

@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-projects

Quick 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 comments

Core 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 field

Time 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"

Best Practices

Related Modules