@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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/app/app.vue +14 -0
- package/app/assets/css/main.css +129 -0
- package/app/components/CommandPalette.vue +182 -0
- package/app/components/ShortcutsHelp.vue +85 -0
- package/app/components/git/ChangesMinimap.vue +143 -0
- package/app/components/git/CommitList.vue +224 -0
- package/app/components/git/DiffPanel.vue +402 -0
- package/app/components/git/DiffViewer.vue +803 -0
- package/app/components/layout/RepoSelector.vue +358 -0
- package/app/components/layout/Sidebar.vue +91 -0
- package/app/components/prd/Meta.vue +69 -0
- package/app/components/prd/Viewer.vue +285 -0
- package/app/components/tasks/Board.vue +86 -0
- package/app/components/tasks/Card.vue +108 -0
- package/app/components/tasks/Column.vue +108 -0
- package/app/components/tasks/Detail.vue +291 -0
- package/app/components/ui/badge/Badge.vue +26 -0
- package/app/components/ui/badge/index.ts +26 -0
- package/app/components/ui/button/Button.vue +29 -0
- package/app/components/ui/button/index.ts +38 -0
- package/app/components/ui/card/Card.vue +22 -0
- package/app/components/ui/card/CardAction.vue +17 -0
- package/app/components/ui/card/CardContent.vue +17 -0
- package/app/components/ui/card/CardDescription.vue +17 -0
- package/app/components/ui/card/CardFooter.vue +17 -0
- package/app/components/ui/card/CardHeader.vue +17 -0
- package/app/components/ui/card/CardTitle.vue +17 -0
- package/app/components/ui/card/index.ts +7 -0
- package/app/components/ui/combobox/Combobox.vue +19 -0
- package/app/components/ui/combobox/ComboboxAnchor.vue +23 -0
- package/app/components/ui/combobox/ComboboxEmpty.vue +21 -0
- package/app/components/ui/combobox/ComboboxGroup.vue +27 -0
- package/app/components/ui/combobox/ComboboxInput.vue +42 -0
- package/app/components/ui/combobox/ComboboxItem.vue +24 -0
- package/app/components/ui/combobox/ComboboxItemIndicator.vue +23 -0
- package/app/components/ui/combobox/ComboboxList.vue +33 -0
- package/app/components/ui/combobox/ComboboxSeparator.vue +21 -0
- package/app/components/ui/combobox/ComboboxTrigger.vue +24 -0
- package/app/components/ui/combobox/ComboboxViewport.vue +23 -0
- package/app/components/ui/combobox/index.ts +13 -0
- package/app/components/ui/command/Command.vue +103 -0
- package/app/components/ui/command/CommandDialog.vue +33 -0
- package/app/components/ui/command/CommandEmpty.vue +27 -0
- package/app/components/ui/command/CommandGroup.vue +45 -0
- package/app/components/ui/command/CommandInput.vue +54 -0
- package/app/components/ui/command/CommandItem.vue +76 -0
- package/app/components/ui/command/CommandList.vue +25 -0
- package/app/components/ui/command/CommandSeparator.vue +21 -0
- package/app/components/ui/command/CommandShortcut.vue +17 -0
- package/app/components/ui/command/index.ts +25 -0
- package/app/components/ui/dialog/Dialog.vue +19 -0
- package/app/components/ui/dialog/DialogClose.vue +15 -0
- package/app/components/ui/dialog/DialogContent.vue +53 -0
- package/app/components/ui/dialog/DialogDescription.vue +23 -0
- package/app/components/ui/dialog/DialogFooter.vue +15 -0
- package/app/components/ui/dialog/DialogHeader.vue +17 -0
- package/app/components/ui/dialog/DialogOverlay.vue +21 -0
- package/app/components/ui/dialog/DialogScrollContent.vue +59 -0
- package/app/components/ui/dialog/DialogTitle.vue +23 -0
- package/app/components/ui/dialog/DialogTrigger.vue +15 -0
- package/app/components/ui/dialog/index.ts +10 -0
- package/app/components/ui/input/Input.vue +33 -0
- package/app/components/ui/input/index.ts +1 -0
- package/app/components/ui/scroll-area/ScrollArea.vue +33 -0
- package/app/components/ui/scroll-area/ScrollBar.vue +32 -0
- package/app/components/ui/scroll-area/index.ts +2 -0
- package/app/components/ui/separator/Separator.vue +29 -0
- package/app/components/ui/separator/index.ts +1 -0
- package/app/components/ui/sheet/Sheet.vue +19 -0
- package/app/components/ui/sheet/SheetClose.vue +15 -0
- package/app/components/ui/sheet/SheetContent.vue +62 -0
- package/app/components/ui/sheet/SheetDescription.vue +21 -0
- package/app/components/ui/sheet/SheetFooter.vue +16 -0
- package/app/components/ui/sheet/SheetHeader.vue +15 -0
- package/app/components/ui/sheet/SheetOverlay.vue +21 -0
- package/app/components/ui/sheet/SheetTitle.vue +21 -0
- package/app/components/ui/sheet/SheetTrigger.vue +15 -0
- package/app/components/ui/sheet/index.ts +8 -0
- package/app/components/ui/tabs/Tabs.vue +24 -0
- package/app/components/ui/tabs/TabsContent.vue +21 -0
- package/app/components/ui/tabs/TabsList.vue +24 -0
- package/app/components/ui/tabs/TabsTrigger.vue +26 -0
- package/app/components/ui/tabs/index.ts +4 -0
- package/app/components/ui/tooltip/Tooltip.vue +19 -0
- package/app/components/ui/tooltip/TooltipContent.vue +34 -0
- package/app/components/ui/tooltip/TooltipProvider.vue +14 -0
- package/app/components/ui/tooltip/TooltipTrigger.vue +15 -0
- package/app/components/ui/tooltip/index.ts +4 -0
- package/app/composables/useFileWatch.ts +78 -0
- package/app/composables/useGit.ts +180 -0
- package/app/composables/useKeyboard.ts +180 -0
- package/app/composables/usePrd.ts +86 -0
- package/app/composables/useRepos.ts +108 -0
- package/app/composables/useThemeMode.ts +38 -0
- package/app/composables/useToast.ts +31 -0
- package/app/layouts/default.vue +197 -0
- package/app/lib/utils.ts +7 -0
- package/app/pages/[repo]/[prd].vue +263 -0
- package/app/pages/index.vue +257 -0
- package/app/types/git.ts +81 -0
- package/app/types/index.ts +29 -0
- package/app/types/prd.ts +49 -0
- package/app/types/repo.ts +37 -0
- package/app/types/task.ts +134 -0
- package/bin/prd +21 -0
- package/components.json +21 -0
- package/dist/app/types/git.js +1 -0
- package/dist/app/types/prd.js +1 -0
- package/dist/app/types/repo.js +1 -0
- package/dist/app/types/task.js +1 -0
- package/dist/host/src/api/git.js +96 -0
- package/dist/host/src/api/index.js +4 -0
- package/dist/host/src/api/prds.js +195 -0
- package/dist/host/src/api/repos.js +47 -0
- package/dist/host/src/api/state.js +63 -0
- package/dist/host/src/executor.js +109 -0
- package/dist/host/src/index.js +95 -0
- package/dist/host/src/mcp.js +62 -0
- package/dist/host/src/ui.js +64 -0
- package/dist/server/utils/db.js +125 -0
- package/dist/server/utils/git.js +396 -0
- package/dist/server/utils/prd-state.js +229 -0
- package/dist/server/utils/repos.js +256 -0
- package/docs/MCP.md +180 -0
- package/nuxt.config.ts +34 -0
- package/package.json +88 -0
- package/public/favicon.ico +0 -0
- package/public/robots.txt +1 -0
- package/server/api/browse.get.ts +52 -0
- package/server/api/repos/[repoId]/git/commits.get.ts +103 -0
- package/server/api/repos/[repoId]/git/diff.get.ts +77 -0
- package/server/api/repos/[repoId]/git/file-content.get.ts +66 -0
- package/server/api/repos/[repoId]/git/file-diff.get.ts +109 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/progress.get.ts +36 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/tasks/[taskId]/commits.get.ts +146 -0
- package/server/api/repos/[repoId]/prd/[prdSlug]/tasks.get.ts +36 -0
- package/server/api/repos/[repoId]/prd/[prdSlug].get.ts +97 -0
- package/server/api/repos/[repoId]/prds.get.ts +85 -0
- package/server/api/repos/[repoId]/refresh-git-repos.post.ts +42 -0
- package/server/api/repos/[repoId].delete.ts +27 -0
- package/server/api/repos/index.get.ts +5 -0
- package/server/api/repos/index.post.ts +39 -0
- package/server/api/watch.get.ts +63 -0
- package/server/plugins/migrate-legacy-state.ts +19 -0
- package/server/tsconfig.json +3 -0
- package/server/utils/db.ts +169 -0
- package/server/utils/git.ts +478 -0
- package/server/utils/prd-state.ts +335 -0
- package/server/utils/repos.ts +322 -0
- package/server/utils/watcher.ts +179 -0
- 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