@thxgg/steward 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/app/app.vue +14 -0
  5. package/app/assets/css/main.css +129 -0
  6. package/app/components/CommandPalette.vue +182 -0
  7. package/app/components/ShortcutsHelp.vue +85 -0
  8. package/app/components/git/ChangesMinimap.vue +143 -0
  9. package/app/components/git/CommitList.vue +224 -0
  10. package/app/components/git/DiffPanel.vue +402 -0
  11. package/app/components/git/DiffViewer.vue +803 -0
  12. package/app/components/layout/RepoSelector.vue +358 -0
  13. package/app/components/layout/Sidebar.vue +91 -0
  14. package/app/components/prd/Meta.vue +69 -0
  15. package/app/components/prd/Viewer.vue +285 -0
  16. package/app/components/tasks/Board.vue +86 -0
  17. package/app/components/tasks/Card.vue +108 -0
  18. package/app/components/tasks/Column.vue +108 -0
  19. package/app/components/tasks/Detail.vue +291 -0
  20. package/app/components/ui/badge/Badge.vue +26 -0
  21. package/app/components/ui/badge/index.ts +26 -0
  22. package/app/components/ui/button/Button.vue +29 -0
  23. package/app/components/ui/button/index.ts +38 -0
  24. package/app/components/ui/card/Card.vue +22 -0
  25. package/app/components/ui/card/CardAction.vue +17 -0
  26. package/app/components/ui/card/CardContent.vue +17 -0
  27. package/app/components/ui/card/CardDescription.vue +17 -0
  28. package/app/components/ui/card/CardFooter.vue +17 -0
  29. package/app/components/ui/card/CardHeader.vue +17 -0
  30. package/app/components/ui/card/CardTitle.vue +17 -0
  31. package/app/components/ui/card/index.ts +7 -0
  32. package/app/components/ui/combobox/Combobox.vue +19 -0
  33. package/app/components/ui/combobox/ComboboxAnchor.vue +23 -0
  34. package/app/components/ui/combobox/ComboboxEmpty.vue +21 -0
  35. package/app/components/ui/combobox/ComboboxGroup.vue +27 -0
  36. package/app/components/ui/combobox/ComboboxInput.vue +42 -0
  37. package/app/components/ui/combobox/ComboboxItem.vue +24 -0
  38. package/app/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
  39. package/app/components/ui/combobox/ComboboxList.vue +33 -0
  40. package/app/components/ui/combobox/ComboboxSeparator.vue +21 -0
  41. package/app/components/ui/combobox/ComboboxTrigger.vue +24 -0
  42. package/app/components/ui/combobox/ComboboxViewport.vue +23 -0
  43. package/app/components/ui/combobox/index.ts +13 -0
  44. package/app/components/ui/command/Command.vue +103 -0
  45. package/app/components/ui/command/CommandDialog.vue +33 -0
  46. package/app/components/ui/command/CommandEmpty.vue +27 -0
  47. package/app/components/ui/command/CommandGroup.vue +45 -0
  48. package/app/components/ui/command/CommandInput.vue +54 -0
  49. package/app/components/ui/command/CommandItem.vue +76 -0
  50. package/app/components/ui/command/CommandList.vue +25 -0
  51. package/app/components/ui/command/CommandSeparator.vue +21 -0
  52. package/app/components/ui/command/CommandShortcut.vue +17 -0
  53. package/app/components/ui/command/index.ts +25 -0
  54. package/app/components/ui/dialog/Dialog.vue +19 -0
  55. package/app/components/ui/dialog/DialogClose.vue +15 -0
  56. package/app/components/ui/dialog/DialogContent.vue +53 -0
  57. package/app/components/ui/dialog/DialogDescription.vue +23 -0
  58. package/app/components/ui/dialog/DialogFooter.vue +15 -0
  59. package/app/components/ui/dialog/DialogHeader.vue +17 -0
  60. package/app/components/ui/dialog/DialogOverlay.vue +21 -0
  61. package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
  62. package/app/components/ui/dialog/DialogTitle.vue +23 -0
  63. package/app/components/ui/dialog/DialogTrigger.vue +15 -0
  64. package/app/components/ui/dialog/index.ts +10 -0
  65. package/app/components/ui/input/Input.vue +33 -0
  66. package/app/components/ui/input/index.ts +1 -0
  67. package/app/components/ui/scroll-area/ScrollArea.vue +33 -0
  68. package/app/components/ui/scroll-area/ScrollBar.vue +32 -0
  69. package/app/components/ui/scroll-area/index.ts +2 -0
  70. package/app/components/ui/separator/Separator.vue +29 -0
  71. package/app/components/ui/separator/index.ts +1 -0
  72. package/app/components/ui/sheet/Sheet.vue +19 -0
  73. package/app/components/ui/sheet/SheetClose.vue +15 -0
  74. package/app/components/ui/sheet/SheetContent.vue +62 -0
  75. package/app/components/ui/sheet/SheetDescription.vue +21 -0
  76. package/app/components/ui/sheet/SheetFooter.vue +16 -0
  77. package/app/components/ui/sheet/SheetHeader.vue +15 -0
  78. package/app/components/ui/sheet/SheetOverlay.vue +21 -0
  79. package/app/components/ui/sheet/SheetTitle.vue +21 -0
  80. package/app/components/ui/sheet/SheetTrigger.vue +15 -0
  81. package/app/components/ui/sheet/index.ts +8 -0
  82. package/app/components/ui/tabs/Tabs.vue +24 -0
  83. package/app/components/ui/tabs/TabsContent.vue +21 -0
  84. package/app/components/ui/tabs/TabsList.vue +24 -0
  85. package/app/components/ui/tabs/TabsTrigger.vue +26 -0
  86. package/app/components/ui/tabs/index.ts +4 -0
  87. package/app/components/ui/tooltip/Tooltip.vue +19 -0
  88. package/app/components/ui/tooltip/TooltipContent.vue +34 -0
  89. package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
  90. package/app/components/ui/tooltip/TooltipTrigger.vue +15 -0
  91. package/app/components/ui/tooltip/index.ts +4 -0
  92. package/app/composables/useFileWatch.ts +78 -0
  93. package/app/composables/useGit.ts +180 -0
  94. package/app/composables/useKeyboard.ts +180 -0
  95. package/app/composables/usePrd.ts +86 -0
  96. package/app/composables/useRepos.ts +108 -0
  97. package/app/composables/useThemeMode.ts +38 -0
  98. package/app/composables/useToast.ts +31 -0
  99. package/app/layouts/default.vue +197 -0
  100. package/app/lib/utils.ts +7 -0
  101. package/app/pages/[repo]/[prd].vue +263 -0
  102. package/app/pages/index.vue +257 -0
  103. package/app/types/git.ts +81 -0
  104. package/app/types/index.ts +29 -0
  105. package/app/types/prd.ts +49 -0
  106. package/app/types/repo.ts +37 -0
  107. package/app/types/task.ts +134 -0
  108. package/bin/prd +21 -0
  109. package/components.json +21 -0
  110. package/dist/app/types/git.js +1 -0
  111. package/dist/app/types/prd.js +1 -0
  112. package/dist/app/types/repo.js +1 -0
  113. package/dist/app/types/task.js +1 -0
  114. package/dist/host/src/api/git.js +96 -0
  115. package/dist/host/src/api/index.js +4 -0
  116. package/dist/host/src/api/prds.js +195 -0
  117. package/dist/host/src/api/repos.js +47 -0
  118. package/dist/host/src/api/state.js +63 -0
  119. package/dist/host/src/executor.js +109 -0
  120. package/dist/host/src/index.js +95 -0
  121. package/dist/host/src/mcp.js +62 -0
  122. package/dist/host/src/ui.js +64 -0
  123. package/dist/server/utils/db.js +125 -0
  124. package/dist/server/utils/git.js +396 -0
  125. package/dist/server/utils/prd-state.js +229 -0
  126. package/dist/server/utils/repos.js +256 -0
  127. package/docs/MCP.md +180 -0
  128. package/nuxt.config.ts +34 -0
  129. package/package.json +88 -0
  130. package/public/favicon.ico +0 -0
  131. package/public/robots.txt +1 -0
  132. package/server/api/browse.get.ts +52 -0
  133. package/server/api/repos/[repoId]/git/commits.get.ts +103 -0
  134. package/server/api/repos/[repoId]/git/diff.get.ts +77 -0
  135. package/server/api/repos/[repoId]/git/file-content.get.ts +66 -0
  136. package/server/api/repos/[repoId]/git/file-diff.get.ts +109 -0
  137. package/server/api/repos/[repoId]/prd/[prdSlug]/progress.get.ts +36 -0
  138. package/server/api/repos/[repoId]/prd/[prdSlug]/tasks/[taskId]/commits.get.ts +146 -0
  139. package/server/api/repos/[repoId]/prd/[prdSlug]/tasks.get.ts +36 -0
  140. package/server/api/repos/[repoId]/prd/[prdSlug].get.ts +97 -0
  141. package/server/api/repos/[repoId]/prds.get.ts +85 -0
  142. package/server/api/repos/[repoId]/refresh-git-repos.post.ts +42 -0
  143. package/server/api/repos/[repoId].delete.ts +27 -0
  144. package/server/api/repos/index.get.ts +5 -0
  145. package/server/api/repos/index.post.ts +39 -0
  146. package/server/api/watch.get.ts +63 -0
  147. package/server/plugins/migrate-legacy-state.ts +19 -0
  148. package/server/tsconfig.json +3 -0
  149. package/server/utils/db.ts +169 -0
  150. package/server/utils/git.ts +478 -0
  151. package/server/utils/prd-state.ts +335 -0
  152. package/server/utils/repos.ts +322 -0
  153. package/server/utils/watcher.ts +179 -0
  154. package/tsconfig.json +4 -0
@@ -0,0 +1,179 @@
1
+ import chokidar from 'chokidar'
2
+ import type { FSWatcher } from 'chokidar'
3
+ import { getRepos } from './repos'
4
+ import { migrateLegacyStateForRepo } from './prd-state'
5
+
6
+ export type FileChangeEvent = {
7
+ type: 'change' | 'add' | 'unlink'
8
+ path: string
9
+ repoId: string
10
+ category: 'prd' | 'tasks' | 'progress'
11
+ }
12
+
13
+ type Listener = (event: FileChangeEvent) => void
14
+
15
+ // Singleton watcher instance
16
+ let watcher: FSWatcher | null = null
17
+ const listeners = new Set<Listener>()
18
+ let watchedPaths: string[] = []
19
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null
20
+ let pendingEvents: FileChangeEvent[] = []
21
+
22
+ // Debounce delay in ms
23
+ const DEBOUNCE_MS = 300
24
+
25
+ function getRepoIdFromPath(filePath: string, repos: { id: string; path: string }[]): string | null {
26
+ for (const repo of repos) {
27
+ if (filePath.startsWith(repo.path)) {
28
+ return repo.id
29
+ }
30
+ }
31
+ return null
32
+ }
33
+
34
+ function getCategoryFromPath(filePath: string): FileChangeEvent['category'] | null {
35
+ if (filePath.includes('/docs/prd/') && filePath.endsWith('.md')) {
36
+ return 'prd'
37
+ }
38
+ // Legacy state files are still watched so they can be migrated into SQLite.
39
+ if (filePath.includes('/.claude/state/') && filePath.endsWith('tasks.json')) {
40
+ return 'tasks'
41
+ }
42
+ if (filePath.includes('/.claude/state/') && filePath.endsWith('progress.json')) {
43
+ return 'progress'
44
+ }
45
+ return null
46
+ }
47
+
48
+ function emitDebounced(event: FileChangeEvent) {
49
+ pendingEvents.push(event)
50
+
51
+ if (debounceTimer) {
52
+ clearTimeout(debounceTimer)
53
+ }
54
+
55
+ debounceTimer = setTimeout(() => {
56
+ // Deduplicate events by path
57
+ const uniqueEvents = new Map<string, FileChangeEvent>()
58
+ for (const e of pendingEvents) {
59
+ uniqueEvents.set(e.path, e)
60
+ }
61
+
62
+ // Emit to all listeners
63
+ for (const e of uniqueEvents.values()) {
64
+ for (const listener of listeners) {
65
+ listener(e)
66
+ }
67
+ }
68
+
69
+ pendingEvents = []
70
+ debounceTimer = null
71
+ }, DEBOUNCE_MS)
72
+ }
73
+
74
+ export async function initWatcher() {
75
+ if (watcher) {
76
+ return // Already initialized
77
+ }
78
+
79
+ const repos = await getRepos()
80
+ if (repos.length === 0) {
81
+ return // No repos to watch
82
+ }
83
+
84
+ // Build watch paths - docs + legacy state path for migration triggers.
85
+ watchedPaths = repos.flatMap(repo => [
86
+ `${repo.path}/docs/prd`,
87
+ `${repo.path}/.claude/state`
88
+ ])
89
+
90
+ watcher = chokidar.watch(watchedPaths, {
91
+ ignoreInitial: true,
92
+ persistent: true,
93
+ depth: 2, // Watch subdirectories (state/<prd-name>/tasks.json)
94
+ awaitWriteFinish: {
95
+ stabilityThreshold: 200,
96
+ pollInterval: 100
97
+ },
98
+ // Allow legacy .claude directory (chokidar ignores dotfiles by default)
99
+ ignored: (path: string) => {
100
+ // Never ignore paths containing .claude
101
+ if (path.includes('.claude')) return false
102
+ // Ignore other dotfiles
103
+ const basename = path.split('/').pop() || ''
104
+ return basename.startsWith('.') && basename !== '.claude'
105
+ },
106
+ followSymlinks: true
107
+ })
108
+
109
+ watcher.on('error', (error) => {
110
+ console.error('[watcher] Error:', error)
111
+ })
112
+
113
+ watcher.on('all', async (eventType, filePath) => {
114
+ if (eventType !== 'change' && eventType !== 'add' && eventType !== 'unlink') {
115
+ return
116
+ }
117
+
118
+ const repos = await getRepos()
119
+ const repoId = getRepoIdFromPath(filePath, repos)
120
+ const category = getCategoryFromPath(filePath)
121
+
122
+ if (!repoId || !category) {
123
+ return
124
+ }
125
+
126
+ if (category === 'tasks' || category === 'progress') {
127
+ const repo = repos.find(r => r.id === repoId)
128
+ if (repo) {
129
+ try {
130
+ await migrateLegacyStateForRepo(repo, {
131
+ cleanupLegacyFiles: false,
132
+ minFileAgeMs: 0
133
+ })
134
+ } catch (error) {
135
+ console.warn('[watcher] Failed to sync legacy state:', error)
136
+ }
137
+ }
138
+ }
139
+
140
+ const event: FileChangeEvent = {
141
+ type: eventType,
142
+ path: filePath,
143
+ repoId,
144
+ category
145
+ }
146
+
147
+ emitDebounced(event)
148
+ })
149
+
150
+ console.log('[watcher] File watcher initialized, watching', watchedPaths.length, 'directories')
151
+ }
152
+
153
+ export async function refreshWatcher() {
154
+ // Close existing watcher
155
+ if (watcher) {
156
+ await watcher.close()
157
+ watcher = null
158
+ }
159
+
160
+ // Re-initialize with updated repo list
161
+ await initWatcher()
162
+ }
163
+
164
+ export function addListener(listener: Listener): () => void {
165
+ listeners.add(listener)
166
+
167
+ // Return cleanup function
168
+ return () => {
169
+ listeners.delete(listener)
170
+ }
171
+ }
172
+
173
+ export function getWatcherStatus() {
174
+ return {
175
+ active: watcher !== null,
176
+ watchedPaths,
177
+ listenerCount: listeners.size
178
+ }
179
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ // https://nuxt.com/docs/guide/concepts/typescript
3
+ "extends": "./.nuxt/tsconfig.json"
4
+ }