feat: add DDD scan, sync, and update commands for document indexing

- Implemented `/ddd:scan` command to analyze existing codebases and generate document indices without specifications. This includes phases for project structure analysis, component discovery, feature inference, and requirement extraction.
- Introduced `/ddd:sync` command for post-task synchronization, updating document indices, generating action logs, and refreshing feature/component documentation after development tasks.
- Added `/ddd:update` command for lightweight incremental updates to the document index, allowing for quick impact checks during development and pre-commit validation.
- Created `execute.md` for the coordinator role in the team lifecycle, detailing the spawning of executor team-workers for IMPL tasks.
- Added `useHasHydrated` hook to determine if the Zustand workflow store has been rehydrated from localStorage, improving state management reliability.
This commit is contained in:
catlog22
2026-03-07 00:00:18 +08:00
parent a9469a5e3b
commit 7ee9b579fa
18 changed files with 2739 additions and 155 deletions

View File

@@ -4,7 +4,6 @@
// Manages workflow sessions, tasks, and related data
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import type {
WorkflowStore,
WorkflowState,
@@ -16,6 +15,35 @@ import type {
} from '../types/store';
import { switchWorkspace as apiSwitchWorkspace, fetchRecentPaths, removeRecentPath as apiRemoveRecentPath } from '../lib/api';
// LocalStorage key for persisting projectPath
const STORAGE_KEY = 'ccw-workflow-store';
// Helper to load persisted projectPath from localStorage
const loadPersistedPath = (): string => {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const data = JSON.parse(stored);
return data?.state?.projectPath || '';
}
} catch {
// Ignore parse errors
}
return '';
};
// Helper to persist projectPath to localStorage
const persistPath = (projectPath: string): void => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
state: { projectPath },
version: 1,
}));
} catch {
// Ignore storage errors
}
};
// Helper to generate session key from ID
const sessionKey = (sessionId: string): string => {
return `session-${sessionId}`.replace(/[^a-zA-Z0-9-]/g, '-');
@@ -34,14 +62,14 @@ const defaultSorting: WorkflowSorting = {
direction: 'desc',
};
// Initial state
// Initial state - load persisted projectPath from localStorage
const initialState: WorkflowState = {
// Core data
workflowData: {
activeSessions: [],
archivedSessions: [],
},
projectPath: '',
projectPath: loadPersistedPath(),
recentPaths: [],
serverPlatform: 'win32',
@@ -57,14 +85,11 @@ const initialState: WorkflowState = {
filters: defaultFilters,
sorting: defaultSorting,
// Hydration state (internal)
_hasHydrated: false,
};
export const useWorkflowStore = create<WorkflowStore>()(
devtools(
persist(
(set, get) => ({
persist(
(set, get) => ({
...initialState,
// ========== Session Actions ==========
@@ -396,6 +421,9 @@ export const useWorkflowStore = create<WorkflowStore>()(
'switchWorkspace'
);
// Persist projectPath to localStorage manually
persistPath(response.projectPath);
// Trigger query invalidation callback
const callback = get()._invalidateQueriesCallback;
if (callback) {
@@ -417,10 +445,6 @@ export const useWorkflowStore = create<WorkflowStore>()(
set({ _invalidateQueriesCallback: callback }, false, 'registerQueryInvalidator');
},
setHasHydrated: (state: boolean) => {
set({ _hasHydrated: state }, false, 'setHasHydrated');
},
// ========== Filters and Sorting ==========
setFilters: (filters: Partial<WorkflowFilters>) => {
@@ -521,46 +545,15 @@ export const useWorkflowStore = create<WorkflowStore>()(
}),
{
name: 'ccw-workflow-store',
version: 1, // State version for migration support
// Only persist projectPath - minimal state for workspace switching
partialize: (state) => ({
projectPath: state.projectPath,
}),
migrate: (persistedState, version) => {
// Migration logic for future state shape changes
if (version < 1) {
// No migrations needed for initial version
// Example: if (version === 0) { persistedState.newField = defaultValue; }
}
return persistedState as typeof persistedState;
},
onRehydrateStorage: () => {
// Only log in development to avoid noise in production
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.log('[WorkflowStore] Hydrating from localStorage...');
}
return (state, error) => {
if (error) {
// eslint-disable-next-line no-console
console.error('[WorkflowStore] Rehydration error:', error);
return;
}
// Mark hydration as complete
useWorkflowStore.getState().setHasHydrated(true);
if (state?.projectPath) {
if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-console
console.log('[WorkflowStore] Rehydrated with persisted projectPath:', state.projectPath);
}
// The initialization logic is now handled by AppShell.tsx
// to correctly prioritize URL parameters over localStorage.
}
};
},
// Skip automatic hydration to avoid TDZ error during module initialization
// Hydration will be triggered manually in AppShell after mount
skipHydration: true,
}
),
{ name: 'WorkflowStore' }
)
)
);
// Selectors for common access patterns