@haoyiyin/workflow 0.2.2 → 0.2.3

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 (61) hide show
  1. package/package.json +9 -8
  2. package/src/agents/contracts.ts +559 -0
  3. package/src/agents/dispatcher-enhanced.ts +350 -0
  4. package/src/agents/dispatcher.ts +680 -0
  5. package/src/agents/index.ts +48 -0
  6. package/src/agents/resilience.ts +255 -0
  7. package/src/agents/token-budget.ts +83 -0
  8. package/src/agents/types.ts +73 -0
  9. package/src/guard/main-agent.ts +245 -0
  10. package/src/hooks/builtin/index.ts +8 -0
  11. package/src/hooks/builtin/on-error.ts +23 -0
  12. package/src/hooks/builtin/post-execute.ts +40 -0
  13. package/src/hooks/builtin/post-plan.ts +23 -0
  14. package/src/hooks/builtin/pre-execute.ts +30 -0
  15. package/src/hooks/builtin/pre-plan.ts +26 -0
  16. package/src/hooks/index.ts +7 -0
  17. package/src/hooks/loader.ts +98 -0
  18. package/src/hooks/manager.ts +99 -0
  19. package/src/hooks/types-enhanced.ts +38 -0
  20. package/src/hooks/types.ts +35 -0
  21. package/src/index.ts +127 -0
  22. package/src/persistence/index.ts +17 -0
  23. package/src/persistence/plan-md.ts +141 -0
  24. package/src/persistence/state-md.ts +167 -0
  25. package/src/persistence/types.ts +89 -0
  26. package/src/router/classifier.ts +610 -0
  27. package/src/router/guard.ts +483 -0
  28. package/src/router/index.ts +22 -0
  29. package/src/router/router.ts +108 -0
  30. package/src/router/types.ts +127 -0
  31. package/src/skills/agents-md/SKILL.md +45 -0
  32. package/src/skills/agents-md/index.ts +33 -0
  33. package/src/skills/execute-plan/SKILL.md +60 -0
  34. package/src/skills/execute-plan/index.ts +970 -0
  35. package/src/skills/index.ts +13 -0
  36. package/src/skills/quick-task/SKILL.md +54 -0
  37. package/src/skills/quick-task/index.ts +346 -0
  38. package/src/skills/registry.ts +59 -0
  39. package/src/skills/review-diff/SKILL.md +53 -0
  40. package/src/skills/review-diff/index.ts +394 -0
  41. package/src/skills/skill.ts +59 -0
  42. package/src/skills/systematic-debugging/SKILL.md +56 -0
  43. package/src/skills/systematic-debugging/index.ts +404 -0
  44. package/src/skills/tdd/SKILL.md +52 -0
  45. package/src/skills/tdd/index.ts +409 -0
  46. package/src/skills/to-plan/SKILL.md +56 -0
  47. package/src/skills/to-plan/index-enhanced.ts +551 -0
  48. package/src/skills/to-plan/index.ts +586 -0
  49. package/src/skills/types.ts +47 -0
  50. package/src/state/cleanup.ts +118 -0
  51. package/src/state/index.ts +8 -0
  52. package/src/state/manager.ts +96 -0
  53. package/src/state/persistence.ts +77 -0
  54. package/src/state/types.ts +30 -0
  55. package/src/state/validator.ts +78 -0
  56. package/src/types.ts +102 -0
  57. package/src/utils/compress.ts +347 -0
  58. package/src/utils/git.ts +82 -0
  59. package/src/utils/index.ts +6 -0
  60. package/src/utils/logger.ts +23 -0
  61. package/src/utils/paths.ts +55 -0
@@ -0,0 +1,23 @@
1
+ /**
2
+ * On-error hook - runs when errors occur
3
+ */
4
+ import type { HookContext } from '../types.js'
5
+
6
+ export const name = 'on-error'
7
+
8
+ export async function execute(context: HookContext): Promise<void> {
9
+ const { error, state } = context
10
+
11
+ console.error('[on-error] Error occurred:', error?.message || 'Unknown error')
12
+
13
+ // Save error state
14
+ if (state) {
15
+ console.log(`[on-error] State preserved: ${state.id}`)
16
+ }
17
+
18
+ // Suggest recovery actions
19
+ console.log('[on-error] Suggested actions:')
20
+ console.log(' 1. Check error logs')
21
+ console.log(' 2. Review state file')
22
+ console.log(' 3. Resume with: yi-workflow resume')
23
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Post-execute hook - runs after plan execution
3
+ */
4
+ import type { HookContext } from '../types.js'
5
+
6
+ export const name = 'post-execute'
7
+
8
+ export async function execute(context: HookContext): Promise<void> {
9
+ const { config, state, plan } = context
10
+
11
+ if (!state) {
12
+ console.warn('[post-execute] No execution state')
13
+ return
14
+ }
15
+
16
+ console.log(`[post-execute] Execution ${state.status}`)
17
+
18
+ // Auto-commit if configured
19
+ if (config.autoMerge && state.status === 'completed') {
20
+ try {
21
+ const { hasUncommittedChanges, commitChanges } = await import(
22
+ '../../utils/git.js'
23
+ )
24
+ if (hasUncommittedChanges(process.cwd())) {
25
+ const message = plan
26
+ ? `feat: implement ${plan.title}`
27
+ : 'feat: workflow implementation'
28
+ commitChanges(process.cwd(), message)
29
+ console.log('[post-execute] Changes committed')
30
+ }
31
+ } catch {
32
+ console.warn('[post-execute] Git operations failed')
33
+ }
34
+ }
35
+
36
+ // Cleanup if configured
37
+ if (config.autoCleanup) {
38
+ console.log('[post-execute] Cleanup scheduled')
39
+ }
40
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Post-plan hook - runs after plan creation
3
+ */
4
+ import type { HookContext } from '../types.js'
5
+
6
+ export const name = 'post-plan'
7
+
8
+ export async function execute(context: HookContext): Promise<void> {
9
+ const { config, plan } = context
10
+
11
+ if (!plan) {
12
+ console.warn('[post-plan] No plan in context')
13
+ return
14
+ }
15
+
16
+ console.log(`[post-plan] Plan created: ${plan.title}`)
17
+ console.log(`[post-plan] Tasks: ${plan.tasks.length}`)
18
+
19
+ // Auto-trigger execute-plan if configured
20
+ if (config.autoMerge && plan.status === 'approved') {
21
+ console.log('[post-plan] Plan approved, ready for execution')
22
+ }
23
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pre-execute hook - runs before plan execution
3
+ */
4
+ import type { HookContext } from '../types.js'
5
+
6
+ export const name = 'pre-execute'
7
+
8
+ export async function execute(context: HookContext): Promise<void> {
9
+ const { plan } = context
10
+
11
+ if (!plan) {
12
+ throw new Error('No plan to execute')
13
+ }
14
+
15
+ // Load plan into state
16
+ console.log(`[pre-execute] Loading plan: ${plan.title}`)
17
+
18
+ // Check for uncommitted changes
19
+ try {
20
+ const { hasUncommittedChanges } = await import('../../utils/git.js')
21
+ if (hasUncommittedChanges(process.cwd())) {
22
+ console.warn('[pre-execute] Uncommitted changes detected')
23
+ }
24
+ } catch {
25
+ // Not a git repo, ignore
26
+ }
27
+
28
+ // Initialize execution state
29
+ console.log('[pre-execute] Execution state initialized')
30
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pre-plan hook - runs before plan creation
3
+ */
4
+ import type { HookContext } from '../types.js'
5
+
6
+ export const name = 'pre-plan'
7
+
8
+ export async function execute(context: HookContext): Promise<void> {
9
+ const { config } = context
10
+
11
+ // Ensure plan directory exists
12
+ const fs = await import('fs/promises')
13
+ await fs.mkdir(config.planPath, { recursive: true })
14
+
15
+ // Check git repository
16
+ try {
17
+ const { getCurrentBranch } = await import('../../utils/git.js')
18
+ const branch = getCurrentBranch(process.cwd())
19
+ console.log(`[pre-plan] Current branch: ${branch}`)
20
+ } catch {
21
+ console.warn('[pre-plan] Not in a git repository')
22
+ }
23
+
24
+ // Load any existing context
25
+ console.log('[pre-plan] Environment ready')
26
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Hooks module index
3
+ */
4
+ export type { HookDefinition, HookType, HookContext, HookHandler, HookManagerInterface, HookRegistry, BuiltInHookConfig } from './types.js'
5
+ export { HookManager, createHookManager } from './manager.js'
6
+ export { HookLoader, createHookLoader } from './loader.js'
7
+ export * as builtin from './builtin/index.js'
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Hook loader - loads hooks from the filesystem
3
+ */
4
+ import { readdir } from 'fs/promises'
5
+ import { join } from 'path'
6
+ import type { HookDefinition, HookManagerInterface } from './types.js'
7
+
8
+ export interface HookLoaderOptions {
9
+ builtinPath: string
10
+ customPath?: string
11
+ }
12
+
13
+ export class HookLoader {
14
+ private options: HookLoaderOptions
15
+
16
+ constructor(options: HookLoaderOptions) {
17
+ this.options = options
18
+ }
19
+
20
+ async loadInto(manager: HookManagerInterface): Promise<void> {
21
+ // Load built-in hooks
22
+ await this.loadBuiltinHooks(manager)
23
+
24
+ // Load custom hooks if path provided
25
+ if (this.options.customPath) {
26
+ await this.loadCustomHooks(manager)
27
+ }
28
+ }
29
+
30
+ private async loadBuiltinHooks(manager: HookManagerInterface): Promise<void> {
31
+ try {
32
+ const files = await readdir(this.options.builtinPath)
33
+ const hookFiles = files.filter(
34
+ (f) => f.endsWith('.js') && !f.endsWith('.d.ts') && f !== 'index.js'
35
+ )
36
+
37
+ for (const file of hookFiles) {
38
+ const hookPath = join(this.options.builtinPath, file)
39
+ await this.loadHookFile(manager, hookPath)
40
+ }
41
+ } catch {
42
+ // No built-in hooks directory
43
+ }
44
+ }
45
+
46
+ private async loadCustomHooks(manager: HookManagerInterface): Promise<void> {
47
+ try {
48
+ const files = await readdir(this.options.customPath!)
49
+ const hookFiles = files.filter((f) => f.endsWith('.js') && !f.endsWith('.d.ts'))
50
+
51
+ for (const file of hookFiles) {
52
+ const hookPath = join(this.options.customPath!, file)
53
+ await this.loadHookFile(manager, hookPath)
54
+ }
55
+ } catch {
56
+ // No custom hooks directory
57
+ }
58
+ }
59
+
60
+ private async loadHookFile(
61
+ manager: HookManagerInterface,
62
+ hookPath: string
63
+ ): Promise<void> {
64
+ try {
65
+ const module = await import(hookPath)
66
+
67
+ // Support both named exports and default export
68
+ const hook: HookDefinition = module.default || {
69
+ type: module.type,
70
+ name: module.name,
71
+ execute: module.execute,
72
+ }
73
+
74
+ if (this.validateHook(hook)) {
75
+ manager.register(hook)
76
+ } else {
77
+ console.warn(`[HookLoader] Invalid hook in ${hookPath}`)
78
+ }
79
+ } catch (error) {
80
+ console.error(`[HookLoader] Failed to load ${hookPath}:`, error)
81
+ }
82
+ }
83
+
84
+ private validateHook(hook: unknown): hook is HookDefinition {
85
+ if (!hook || typeof hook !== 'object') return false
86
+
87
+ const h = hook as Record<string, unknown>
88
+ return (
89
+ typeof h.type === 'string' &&
90
+ typeof h.name === 'string' &&
91
+ typeof h.execute === 'function'
92
+ )
93
+ }
94
+ }
95
+
96
+ export function createHookLoader(options: HookLoaderOptions): HookLoader {
97
+ return new HookLoader(options)
98
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Hook manager - manages hook registration and execution
3
+ */
4
+ import type {
5
+ HookDefinition,
6
+ HookType,
7
+ HookContext,
8
+ HookManagerInterface,
9
+ HookRegistry,
10
+ } from './types.js'
11
+
12
+ export class HookManager implements HookManagerInterface {
13
+ private registry: HookRegistry
14
+ private enabled: Map<string, boolean>
15
+ private priorities: Map<string, number>
16
+
17
+ constructor() {
18
+ this.registry = {
19
+ prePlan: [],
20
+ postPlan: [],
21
+ preExecute: [],
22
+ postExecute: [],
23
+ onError: [],
24
+ }
25
+ this.enabled = new Map()
26
+ this.priorities = new Map()
27
+ }
28
+
29
+ register(hook: HookDefinition, priority = 0): void {
30
+ const key = this.getRegistryKey(hook.type)
31
+ this.registry[key].push(hook)
32
+ this.priorities.set(hook.name, priority)
33
+ this.enabled.set(hook.name, true)
34
+
35
+ // Sort by priority (higher first)
36
+ this.registry[key].sort((a, b) => {
37
+ const pa = this.priorities.get(a.name) ?? 0
38
+ const pb = this.priorities.get(b.name) ?? 0
39
+ return pb - pa
40
+ })
41
+
42
+ console.log(`[HookManager] Registered: ${hook.name} (${hook.type}, priority: ${priority})`)
43
+ }
44
+
45
+ enable(name: string): void {
46
+ this.enabled.set(name, true)
47
+ }
48
+
49
+ disable(name: string): void {
50
+ this.enabled.set(name, false)
51
+ }
52
+
53
+ isEnabled(name: string): boolean {
54
+ return this.enabled.get(name) ?? true
55
+ }
56
+
57
+ unregister(name: string): void {
58
+ for (const key of Object.keys(this.registry) as Array<keyof HookRegistry>) {
59
+ this.registry[key] = this.registry[key].filter((h) => h.name !== name)
60
+ }
61
+ console.log(`[HookManager] Unregistered: ${name}`)
62
+ }
63
+
64
+ async execute(type: HookType, context: HookContext): Promise<void> {
65
+ const key = this.getRegistryKey(type)
66
+ const hooks = this.registry[key].filter((h) => this.isEnabled(h.name))
67
+
68
+ console.log(`[HookManager] Executing ${hooks.length} hooks for ${type}`)
69
+
70
+ for (const hook of hooks) {
71
+ try {
72
+ await hook.execute(context)
73
+ } catch (error) {
74
+ console.error(`[HookManager] Hook ${hook.name} failed:`, error)
75
+ throw error
76
+ }
77
+ }
78
+ }
79
+
80
+ getHooks(type: HookType): HookDefinition[] {
81
+ const key = this.getRegistryKey(type)
82
+ return [...this.registry[key]]
83
+ }
84
+
85
+ private getRegistryKey(type: HookType): keyof HookRegistry {
86
+ const mapping: Record<HookType, keyof HookRegistry> = {
87
+ 'pre-plan': 'prePlan',
88
+ 'post-plan': 'postPlan',
89
+ 'pre-execute': 'preExecute',
90
+ 'post-execute': 'postExecute',
91
+ 'on-error': 'onError',
92
+ }
93
+ return mapping[type]
94
+ }
95
+ }
96
+
97
+ export function createHookManager(): HookManager {
98
+ return new HookManager()
99
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Enhanced hook type definitions
3
+ */
4
+
5
+ export enum HookType {
6
+ // Existing hooks
7
+ PRE_PLAN = 'pre-plan',
8
+ POST_PLAN = 'post-plan',
9
+ PRE_EXECUTE = 'pre-execute',
10
+ POST_EXECUTE = 'post-execute',
11
+ ON_ERROR = 'on-error',
12
+
13
+ // Persistence hooks
14
+ STATE_CHANGE = 'state-change',
15
+ TASK_COMPLETE = 'task-complete',
16
+ WAVE_START = 'wave-start',
17
+ WAVE_COMPLETE = 'wave-complete',
18
+
19
+ // Lifecycle hooks
20
+ MILESTONE_START = 'milestone-start',
21
+ MILESTONE_COMPLETE = 'milestone-complete',
22
+ MILESTONE_FAIL = 'milestone-fail'
23
+ }
24
+
25
+ export interface HookContext {
26
+ [key: string]: unknown
27
+ }
28
+
29
+ export interface HookDefinition {
30
+ type: HookType
31
+ name: string
32
+ priority: number
33
+ execute: (context: HookContext) => Promise<void>
34
+ }
35
+
36
+ export interface HookManager {
37
+ execute(type: HookType, context: HookContext): Promise<void>
38
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hook type definitions
3
+ */
4
+ import type {
5
+ HookDefinition,
6
+ HookType,
7
+ HookContext,
8
+ } from '../types.js'
9
+
10
+ export { HookDefinition, HookType, HookContext }
11
+
12
+ export interface HookManagerInterface {
13
+ register(hook: HookDefinition, priority?: number): void
14
+ unregister(name: string): void
15
+ execute(type: HookType, context: HookContext): Promise<void>
16
+ getHooks(type: HookType): HookDefinition[]
17
+ enable(name: string): void
18
+ disable(name: string): void
19
+ isEnabled(name: string): boolean
20
+ }
21
+
22
+ export interface HookRegistry {
23
+ prePlan: HookDefinition[]
24
+ postPlan: HookDefinition[]
25
+ preExecute: HookDefinition[]
26
+ postExecute: HookDefinition[]
27
+ onError: HookDefinition[]
28
+ }
29
+
30
+ export type HookHandler = (context: HookContext) => Promise<void> | void
31
+
32
+ export interface BuiltInHookConfig {
33
+ enabled: boolean
34
+ priority?: number
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * yi-workflow - Production workflow skills for coding agents
3
+ *
4
+ * Core Architecture:
5
+ * 1. Main Agent = Orchestrator only (NEVER executes work)
6
+ * 2. Router = Classifies ALL user input and dispatches to subagents
7
+ * 3. Subagents = Do ALL actual work
8
+ * 4. Guard = Enforces Main Agent never does prohibited operations
9
+ */
10
+
11
+ // Export core types from global types
12
+ export type {
13
+ WorkflowConfig,
14
+ Plan,
15
+ PlanStatus,
16
+ Task,
17
+ TaskStatus,
18
+ ExecutionState,
19
+ ExecutionStatus,
20
+ HookType,
21
+ HookContext,
22
+ HookDefinition,
23
+ Logger,
24
+ CLIOptions,
25
+ } from './types.js'
26
+
27
+ // Export hooks
28
+ export { HookManager, createHookManager } from './hooks/manager.js'
29
+ export { HookLoader, createHookLoader } from './hooks/loader.js'
30
+ export type {
31
+ HookHandler,
32
+ HookManagerInterface,
33
+ HookRegistry,
34
+ BuiltInHookConfig,
35
+ } from './hooks/types.js'
36
+
37
+ // Export state
38
+ export { StateManagerImpl, createStateManager } from './state/manager.js'
39
+ export { FilePersistence, createPersistence } from './state/persistence.js'
40
+ export { validateState, isValidState, sanitizeState } from './state/validator.js'
41
+ export { cleanupOldStates, cleanupEmptyDirs, getStateStats } from './state/cleanup.js'
42
+ export type {
43
+ StateManager,
44
+ PersistenceLayer,
45
+ StateOptions,
46
+ } from './state/types.js'
47
+
48
+ // Export skills
49
+ export { Skill } from './skills/skill.js'
50
+ export { Registry, createRegistry } from './skills/registry.js'
51
+ export { toPlanSkill } from './skills/to-plan/index.js'
52
+ export { executePlanSkill } from './skills/execute-plan/index.js'
53
+ export { quickTaskSkill } from './skills/quick-task/index.js'
54
+ export { reviewDiffSkill } from './skills/review-diff/index.js'
55
+ export { tddSkill } from './skills/tdd/index.js'
56
+ export { systematicDebuggingSkill } from './skills/systematic-debugging/index.js'
57
+ export { agentsMdSkill } from './skills/agents-md/index.js'
58
+ export type {
59
+ SkillDefinition,
60
+ SkillRegistry,
61
+ SkillContext,
62
+ ExecutionResult,
63
+ } from './skills/types.js'
64
+
65
+ // Export agents - dispatcher
66
+ export { SubagentDispatcher, createDispatcher, createProcessExecutor } from './agents/dispatcher.js'
67
+ export type {
68
+ SubagentExecutor,
69
+ ExecutorOptions,
70
+ ExecutorResult,
71
+ ProcessExecutorConfig,
72
+ DispatcherOptions,
73
+ } from './agents/dispatcher.js'
74
+
75
+ // Export agents - types
76
+ export type {
77
+ SubagentConfig,
78
+ SubagentResult,
79
+ SubagentArtifact,
80
+ SubagentRole,
81
+ MainAgentGuard,
82
+ } from './agents/types.js'
83
+
84
+ // Export agents - contracts
85
+ export {
86
+ explorerContract,
87
+ implementerContract,
88
+ reviewerContract,
89
+ debuggerContract,
90
+ verifierContract,
91
+ } from './agents/contracts.js'
92
+ export type {
93
+ ExplorerParams,
94
+ ImplementerParams,
95
+ ReviewerParams,
96
+ DebuggerParams,
97
+ VerifierParams,
98
+ } from './agents/contracts.js'
99
+
100
+ // Export router
101
+ export { createRouter } from './router/router.js'
102
+ export type { Router } from './router/router.js'
103
+ export { ROUTE_KEYWORDS, MAIN_AGENT_PROHIBITED } from './router/types.js'
104
+ export type {
105
+ IntentClassification,
106
+ IntentType,
107
+ RouterConfig,
108
+ RoutingDecision,
109
+ RouterInput,
110
+ RouterOutput,
111
+ SubagentContract,
112
+ PermissionSet,
113
+ } from './router/types.js'
114
+
115
+ // Export guard
116
+ export { createMainAgentGuard } from './guard/main-agent.js'
117
+ export type {
118
+ GuardConfig,
119
+ ProhibitedOperations,
120
+ OperationRequest,
121
+ GuardDecision,
122
+ } from './guard/main-agent.js'
123
+
124
+ // Export utils
125
+ export { createLogger } from './utils/logger.js'
126
+ export { getCurrentBranch, getBaseBranch, isWorktree } from './utils/git.js'
127
+ export { getPlansPath, getStatePath } from './utils/paths.js'
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Persistence layer exports
3
+ */
4
+ export { StateMdManager } from './state-md.js'
5
+ export { PlanMdManager } from './plan-md.js'
6
+ export type {
7
+ Task,
8
+ Wave,
9
+ Plan,
10
+ TaskResult,
11
+ VerificationResult,
12
+ DimensionResult,
13
+ WorkflowState,
14
+ StateMdManager as IStateMdManager,
15
+ PlanMdManager as IPlanMdManager,
16
+ PersistenceManager
17
+ } from './types.js'