@devran-ai/kit 4.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/.agent/CheatSheet.md +350 -0
- package/.agent/README.md +76 -0
- package/.agent/agents/README.md +155 -0
- package/.agent/agents/architect.md +185 -0
- package/.agent/agents/backend-specialist.md +276 -0
- package/.agent/agents/build-error-resolver.md +207 -0
- package/.agent/agents/code-reviewer.md +162 -0
- package/.agent/agents/database-architect.md +138 -0
- package/.agent/agents/devops-engineer.md +144 -0
- package/.agent/agents/doc-updater.md +229 -0
- package/.agent/agents/e2e-runner.md +145 -0
- package/.agent/agents/explorer-agent.md +143 -0
- package/.agent/agents/frontend-specialist.md +144 -0
- package/.agent/agents/go-reviewer.md +128 -0
- package/.agent/agents/knowledge-agent.md +197 -0
- package/.agent/agents/mobile-developer.md +150 -0
- package/.agent/agents/performance-optimizer.md +175 -0
- package/.agent/agents/planner.md +133 -0
- package/.agent/agents/pr-reviewer.md +148 -0
- package/.agent/agents/python-reviewer.md +123 -0
- package/.agent/agents/refactor-cleaner.md +201 -0
- package/.agent/agents/reliability-engineer.md +156 -0
- package/.agent/agents/security-reviewer.md +141 -0
- package/.agent/agents/sprint-orchestrator.md +124 -0
- package/.agent/agents/tdd-guide.md +179 -0
- package/.agent/agents/typescript-reviewer.md +110 -0
- package/.agent/checklists/README.md +102 -0
- package/.agent/checklists/pre-commit.md +93 -0
- package/.agent/checklists/session-end.md +99 -0
- package/.agent/checklists/session-start.md +102 -0
- package/.agent/checklists/task-complete.md +81 -0
- package/.agent/commands/README.md +130 -0
- package/.agent/commands/adr.md +29 -0
- package/.agent/commands/ask.md +28 -0
- package/.agent/commands/build.md +30 -0
- package/.agent/commands/changelog.md +40 -0
- package/.agent/commands/checkpoint.md +28 -0
- package/.agent/commands/code-review.md +65 -0
- package/.agent/commands/compact.md +28 -0
- package/.agent/commands/cook.md +30 -0
- package/.agent/commands/db.md +30 -0
- package/.agent/commands/debug.md +31 -0
- package/.agent/commands/deploy.md +37 -0
- package/.agent/commands/design.md +29 -0
- package/.agent/commands/doc.md +30 -0
- package/.agent/commands/eval.md +30 -0
- package/.agent/commands/fix.md +32 -0
- package/.agent/commands/git.md +32 -0
- package/.agent/commands/help.md +273 -0
- package/.agent/commands/implement.md +30 -0
- package/.agent/commands/integrate.md +32 -0
- package/.agent/commands/learn.md +29 -0
- package/.agent/commands/perf.md +31 -0
- package/.agent/commands/plan.md +56 -0
- package/.agent/commands/pr-describe.md +65 -0
- package/.agent/commands/pr-fix.md +45 -0
- package/.agent/commands/pr-merge.md +45 -0
- package/.agent/commands/pr-review.md +50 -0
- package/.agent/commands/pr-split.md +54 -0
- package/.agent/commands/pr-status.md +56 -0
- package/.agent/commands/pr.md +58 -0
- package/.agent/commands/refactor.md +32 -0
- package/.agent/commands/research.md +28 -0
- package/.agent/commands/scout.md +30 -0
- package/.agent/commands/security-scan.md +33 -0
- package/.agent/commands/setup.md +31 -0
- package/.agent/commands/status.md +59 -0
- package/.agent/commands/tdd.md +73 -0
- package/.agent/commands/verify.md +58 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/decisions/001-trust-grade-governance.md +46 -0
- package/.agent/decisions/002-cross-ide-generation.md +15 -0
- package/.agent/engine/identity.json +4 -0
- package/.agent/engine/loading-rules.json +193 -0
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/mcp-servers/filesystem.json +9 -0
- package/.agent/engine/mcp-servers/github.json +11 -0
- package/.agent/engine/mcp-servers/postgres.json +11 -0
- package/.agent/engine/mcp-servers/supabase.json +11 -0
- package/.agent/engine/mcp-servers/vercel.json +11 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +50 -0
- package/.agent/engine/workflow-state.json +167 -0
- package/.agent/hooks/README.md +101 -0
- package/.agent/hooks/hooks.json +104 -0
- package/.agent/hooks/templates/session-end.md +110 -0
- package/.agent/hooks/templates/session-start.md +95 -0
- package/.agent/manifest.json +466 -0
- package/.agent/rules/agent-upgrade-policy.md +56 -0
- package/.agent/rules/architecture.md +111 -0
- package/.agent/rules/coding-style.md +75 -0
- package/.agent/rules/documentation.md +74 -0
- package/.agent/rules/git-workflow.md +140 -0
- package/.agent/rules/quality-gate.md +117 -0
- package/.agent/rules/security.md +67 -0
- package/.agent/rules/sprint-tracking.md +103 -0
- package/.agent/rules/testing.md +80 -0
- package/.agent/rules/workflow-standards.md +30 -0
- package/.agent/rules.md +293 -0
- package/.agent/session-context.md +69 -0
- package/.agent/session-state.json +27 -0
- package/.agent/skills/README.md +135 -0
- package/.agent/skills/api-patterns/SKILL.md +117 -0
- package/.agent/skills/app-builder/SKILL.md +202 -0
- package/.agent/skills/architecture/SKILL.md +101 -0
- package/.agent/skills/behavioral-modes/SKILL.md +295 -0
- package/.agent/skills/brainstorming/SKILL.md +156 -0
- package/.agent/skills/clean-code/SKILL.md +142 -0
- package/.agent/skills/context-budget/SKILL.md +78 -0
- package/.agent/skills/continuous-learning/SKILL.md +145 -0
- package/.agent/skills/database-design/SKILL.md +303 -0
- package/.agent/skills/debugging-strategies/SKILL.md +158 -0
- package/.agent/skills/deployment-procedures/SKILL.md +191 -0
- package/.agent/skills/docker-patterns/SKILL.md +161 -0
- package/.agent/skills/eval-harness/SKILL.md +89 -0
- package/.agent/skills/frontend-patterns/SKILL.md +141 -0
- package/.agent/skills/git-workflow/SKILL.md +159 -0
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/intelligent-routing/SKILL.md +180 -0
- package/.agent/skills/mcp-integration/SKILL.md +240 -0
- package/.agent/skills/mobile-design/SKILL.md +191 -0
- package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
- package/.agent/skills/parallel-agents/SKILL.md +200 -0
- package/.agent/skills/performance-profiling/SKILL.md +134 -0
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +183 -0
- package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/skills/pr-toolkit/SKILL.md +174 -0
- package/.agent/skills/production-readiness/SKILL.md +126 -0
- package/.agent/skills/security-practices/SKILL.md +109 -0
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/strategic-compact/SKILL.md +62 -0
- package/.agent/skills/testing-patterns/SKILL.md +141 -0
- package/.agent/skills/typescript-expert/SKILL.md +160 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/skills/verification-loop/SKILL.md +89 -0
- package/.agent/skills/webapp-testing/SKILL.md +175 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +101 -0
- package/.agent/workflows/brainstorm.md +86 -0
- package/.agent/workflows/create.md +85 -0
- package/.agent/workflows/debug.md +83 -0
- package/.agent/workflows/deploy.md +114 -0
- package/.agent/workflows/enhance.md +85 -0
- package/.agent/workflows/orchestrate.md +106 -0
- package/.agent/workflows/plan.md +105 -0
- package/.agent/workflows/pr-fix.md +163 -0
- package/.agent/workflows/pr-merge.md +117 -0
- package/.agent/workflows/pr-review.md +178 -0
- package/.agent/workflows/pr-split.md +118 -0
- package/.agent/workflows/pr.md +184 -0
- package/.agent/workflows/preflight.md +107 -0
- package/.agent/workflows/preview.md +95 -0
- package/.agent/workflows/quality-gate.md +103 -0
- package/.agent/workflows/retrospective.md +100 -0
- package/.agent/workflows/review.md +104 -0
- package/.agent/workflows/status.md +89 -0
- package/.agent/workflows/test.md +98 -0
- package/.agent/workflows/ui-ux-pro-max.md +93 -0
- package/.agent/workflows/upgrade.md +97 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/bin/kit.js +773 -0
- package/lib/agent-registry.js +228 -0
- package/lib/agent-reputation.js +343 -0
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +322 -0
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +252 -0
- package/lib/constants.js +47 -0
- package/lib/engineering-manager.js +336 -0
- package/lib/error-budget.js +370 -0
- package/lib/hook-system.js +256 -0
- package/lib/ide-generator.js +434 -0
- package/lib/identity.js +240 -0
- package/lib/io.js +146 -0
- package/lib/learning-engine.js +163 -0
- package/lib/loading-engine.js +421 -0
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +321 -0
- package/lib/plugin-system.js +604 -0
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +312 -0
- package/lib/self-healing.js +468 -0
- package/lib/session-manager.js +264 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +522 -0
- package/lib/task-model.js +332 -0
- package/lib/updater.js +240 -0
- package/lib/verify.js +279 -0
- package/lib/workflow-engine.js +373 -0
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +160 -0
- package/package.json +57 -0
package/lib/verify.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Manifest Verification
|
|
3
|
+
*
|
|
4
|
+
* Validates the integrity of the .agent/ framework by checking
|
|
5
|
+
* manifest ↔ filesystem consistency, JSON validity, and
|
|
6
|
+
* cross-reference integrity.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/verify
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const { AGENT_DIR, ENGINE_DIR, HOOKS_DIR } = require('./constants');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} CheckResult
|
|
22
|
+
* @property {string} name - Check name
|
|
23
|
+
* @property {'pass' | 'fail' | 'warn'} status - Result status
|
|
24
|
+
* @property {string} message - Human-readable result message
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {object} VerificationReport
|
|
29
|
+
* @property {number} passed - Number of passed checks
|
|
30
|
+
* @property {number} failed - Number of failed checks
|
|
31
|
+
* @property {number} warnings - Number of warnings
|
|
32
|
+
* @property {CheckResult[]} results - Individual check results
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks that a JSON file exists and is valid.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} filePath - Absolute path to JSON file
|
|
39
|
+
* @param {string} checkName - Name for the check result
|
|
40
|
+
* @returns {CheckResult}
|
|
41
|
+
*/
|
|
42
|
+
function checkJsonFile(filePath, checkName) {
|
|
43
|
+
if (!fs.existsSync(filePath)) {
|
|
44
|
+
return { name: checkName, status: 'fail', message: `File not found: ${filePath}` };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
49
|
+
JSON.parse(raw);
|
|
50
|
+
return { name: checkName, status: 'pass', message: `Valid JSON: ${path.basename(filePath)}` };
|
|
51
|
+
} catch (parseError) {
|
|
52
|
+
return { name: checkName, status: 'fail', message: `Invalid JSON: ${parseError.message}` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Runs all manifest integrity checks.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} projectRoot - Root directory of the project
|
|
60
|
+
* @returns {VerificationReport}
|
|
61
|
+
*/
|
|
62
|
+
function runAllChecks(projectRoot) {
|
|
63
|
+
const agentDir = path.join(projectRoot, AGENT_DIR);
|
|
64
|
+
/** @type {CheckResult[]} */
|
|
65
|
+
const results = [];
|
|
66
|
+
|
|
67
|
+
// --- Check 1: Manifest exists and is valid JSON ---
|
|
68
|
+
const manifestPath = path.join(agentDir, 'manifest.json');
|
|
69
|
+
results.push(checkJsonFile(manifestPath, 'manifest-exists'));
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(manifestPath)) {
|
|
72
|
+
return buildReport(results);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @type {object} */
|
|
76
|
+
let manifest;
|
|
77
|
+
try {
|
|
78
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
79
|
+
} catch {
|
|
80
|
+
return buildReport(results);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Check 2: Schema version is valid ---
|
|
84
|
+
const schemaVersion = manifest.schemaVersion || '';
|
|
85
|
+
const semverPattern = /^\d+\.\d+\.\d+$/;
|
|
86
|
+
results.push({
|
|
87
|
+
name: 'schema-version',
|
|
88
|
+
status: semverPattern.test(schemaVersion) ? 'pass' : 'fail',
|
|
89
|
+
message: semverPattern.test(schemaVersion)
|
|
90
|
+
? `Schema version valid: ${schemaVersion}`
|
|
91
|
+
: `Invalid schema version: "${schemaVersion}"`,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// --- Check 3: Agent files exist ---
|
|
95
|
+
const agents = manifest.capabilities?.agents?.items || [];
|
|
96
|
+
for (const agent of agents) {
|
|
97
|
+
const agentPath = path.join(agentDir, agent.file);
|
|
98
|
+
const exists = fs.existsSync(agentPath);
|
|
99
|
+
results.push({
|
|
100
|
+
name: `agent-file:${agent.name}`,
|
|
101
|
+
status: exists ? 'pass' : 'fail',
|
|
102
|
+
message: exists ? `Agent exists: ${agent.name}` : `Missing agent file: ${agent.file}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- Check 4: Agent count matches ---
|
|
107
|
+
const agentCountManifest = manifest.capabilities?.agents?.count || 0;
|
|
108
|
+
const agentCountFS = fs.existsSync(path.join(agentDir, 'agents'))
|
|
109
|
+
? fs.readdirSync(path.join(agentDir, 'agents')).filter((f) => f.endsWith('.md') && f !== 'README.md').length
|
|
110
|
+
: 0;
|
|
111
|
+
results.push({
|
|
112
|
+
name: 'agent-count',
|
|
113
|
+
status: agentCountManifest === agentCountFS ? 'pass' : 'fail',
|
|
114
|
+
message:
|
|
115
|
+
agentCountManifest === agentCountFS
|
|
116
|
+
? `Agent count matches: ${agentCountFS}`
|
|
117
|
+
: `Agent count mismatch: manifest=${agentCountManifest}, filesystem=${agentCountFS}`,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// --- Check 5: Skill directories and SKILL.md exist ---
|
|
121
|
+
const skills = manifest.capabilities?.skills?.items || [];
|
|
122
|
+
for (const skill of skills) {
|
|
123
|
+
const skillPath = path.join(agentDir, skill.directory, 'SKILL.md');
|
|
124
|
+
const exists = fs.existsSync(skillPath);
|
|
125
|
+
results.push({
|
|
126
|
+
name: `skill-file:${skill.name}`,
|
|
127
|
+
status: exists ? 'pass' : 'fail',
|
|
128
|
+
message: exists ? `Skill exists: ${skill.name}` : `Missing SKILL.md: ${skill.directory}SKILL.md`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Check 6: Skill count matches ---
|
|
133
|
+
const skillCountManifest = manifest.capabilities?.skills?.count || 0;
|
|
134
|
+
const skillCountFS = fs.existsSync(path.join(agentDir, 'skills'))
|
|
135
|
+
? fs.readdirSync(path.join(agentDir, 'skills'), { withFileTypes: true }).filter((d) => d.isDirectory()).length
|
|
136
|
+
: 0;
|
|
137
|
+
results.push({
|
|
138
|
+
name: 'skill-count',
|
|
139
|
+
status: skillCountManifest === skillCountFS ? 'pass' : 'fail',
|
|
140
|
+
message:
|
|
141
|
+
skillCountManifest === skillCountFS
|
|
142
|
+
? `Skill count matches: ${skillCountFS}`
|
|
143
|
+
: `Skill count mismatch: manifest=${skillCountManifest}, filesystem=${skillCountFS}`,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// --- Check 7: Workflow files exist ---
|
|
147
|
+
const workflows = manifest.capabilities?.workflows?.items || [];
|
|
148
|
+
for (const workflow of workflows) {
|
|
149
|
+
const wfPath = path.join(agentDir, workflow.file);
|
|
150
|
+
const exists = fs.existsSync(wfPath);
|
|
151
|
+
results.push({
|
|
152
|
+
name: `workflow-file:${workflow.name}`,
|
|
153
|
+
status: exists ? 'pass' : 'fail',
|
|
154
|
+
message: exists ? `Workflow exists: ${workflow.name}` : `Missing workflow: ${workflow.file}`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Check 8: Workflow count matches ---
|
|
159
|
+
const wfCountManifest = manifest.capabilities?.workflows?.count || 0;
|
|
160
|
+
const wfCountFS = fs.existsSync(path.join(agentDir, 'workflows'))
|
|
161
|
+
? fs.readdirSync(path.join(agentDir, 'workflows')).filter((f) => f.endsWith('.md') && f !== 'README.md').length
|
|
162
|
+
: 0;
|
|
163
|
+
results.push({
|
|
164
|
+
name: 'workflow-count',
|
|
165
|
+
status: wfCountManifest === wfCountFS ? 'pass' : 'fail',
|
|
166
|
+
message:
|
|
167
|
+
wfCountManifest === wfCountFS
|
|
168
|
+
? `Workflow count matches: ${wfCountFS}`
|
|
169
|
+
: `Workflow count mismatch: manifest=${wfCountManifest}, filesystem=${wfCountFS}`,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// --- Check 9: Command count matches ---
|
|
173
|
+
const cmdCountManifest = manifest.capabilities?.commands?.count || 0;
|
|
174
|
+
const cmdCountFS = fs.existsSync(path.join(agentDir, 'commands'))
|
|
175
|
+
? fs.readdirSync(path.join(agentDir, 'commands')).filter((f) => f.endsWith('.md') && f !== 'README.md').length
|
|
176
|
+
: 0;
|
|
177
|
+
results.push({
|
|
178
|
+
name: 'command-count',
|
|
179
|
+
status: cmdCountManifest === cmdCountFS ? 'pass' : 'fail',
|
|
180
|
+
message:
|
|
181
|
+
cmdCountManifest === cmdCountFS
|
|
182
|
+
? `Command count matches: ${cmdCountFS}`
|
|
183
|
+
: `Command count mismatch: manifest=${cmdCountManifest}, filesystem=${cmdCountFS}`,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// --- Check 10: Engine JSON files valid ---
|
|
187
|
+
const engineFiles = ['workflow-state.json', 'loading-rules.json', 'sdlc-map.json', 'reliability-config.json'];
|
|
188
|
+
for (const engineFile of engineFiles) {
|
|
189
|
+
results.push(checkJsonFile(path.join(agentDir, ENGINE_DIR, engineFile), `engine:${engineFile}`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --- Check 10a: Rule files exist ---
|
|
193
|
+
const manifestRules = manifest.capabilities?.rules?.items || [];
|
|
194
|
+
for (const rule of manifestRules) {
|
|
195
|
+
const rulePath = path.join(agentDir, rule.file);
|
|
196
|
+
const exists = fs.existsSync(rulePath);
|
|
197
|
+
results.push({
|
|
198
|
+
name: `rule-file:${rule.name}`,
|
|
199
|
+
status: exists ? 'pass' : 'fail',
|
|
200
|
+
message: exists ? `Rule exists: ${rule.name}` : `Missing rule file: ${rule.file}`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- Check 10b: Rule count matches ---
|
|
205
|
+
const ruleCountManifest = manifest.capabilities?.rules?.count || 0;
|
|
206
|
+
const ruleCountFS = fs.existsSync(path.join(agentDir, 'rules'))
|
|
207
|
+
? fs.readdirSync(path.join(agentDir, 'rules')).filter((f) => f.endsWith('.md') && f !== 'README.md').length
|
|
208
|
+
: 0;
|
|
209
|
+
results.push({
|
|
210
|
+
name: 'rule-count',
|
|
211
|
+
status: ruleCountManifest === ruleCountFS ? 'pass' : 'fail',
|
|
212
|
+
message:
|
|
213
|
+
ruleCountManifest === ruleCountFS
|
|
214
|
+
? `Rule count matches: ${ruleCountFS}`
|
|
215
|
+
: `Rule count mismatch: manifest=${ruleCountManifest}, filesystem=${ruleCountFS}`,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// --- Check 11: Hooks file valid ---
|
|
219
|
+
results.push(checkJsonFile(path.join(agentDir, HOOKS_DIR, 'hooks.json'), 'hooks-json'));
|
|
220
|
+
|
|
221
|
+
// --- Check 12: Cross-reference — loading-rules agents exist in manifest ---
|
|
222
|
+
const loadingRulesPath = path.join(agentDir, ENGINE_DIR, 'loading-rules.json');
|
|
223
|
+
if (fs.existsSync(loadingRulesPath)) {
|
|
224
|
+
try {
|
|
225
|
+
const loadingRules = JSON.parse(fs.readFileSync(loadingRulesPath, 'utf-8'));
|
|
226
|
+
const manifestAgentNames = new Set(agents.map((agent) => agent.name));
|
|
227
|
+
const domainRules = loadingRules.domainRules || [];
|
|
228
|
+
|
|
229
|
+
for (const rule of domainRules) {
|
|
230
|
+
for (const agentName of (rule.loadAgents || [])) {
|
|
231
|
+
const exists = manifestAgentNames.has(agentName);
|
|
232
|
+
results.push({
|
|
233
|
+
name: `xref:loading-rules:${agentName}`,
|
|
234
|
+
status: exists ? 'pass' : 'warn',
|
|
235
|
+
message: exists
|
|
236
|
+
? `Loading-rules agent "${agentName}" exists in manifest`
|
|
237
|
+
: `Loading-rules references agent "${agentName}" not in manifest`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// --- Check 13: Cross-reference — alwaysLoadRules files exist ---
|
|
242
|
+
const mandatoryRules = loadingRules.planningMandates?.alwaysLoadRules || [];
|
|
243
|
+
for (const ruleName of mandatoryRules) {
|
|
244
|
+
const rulePath = path.join(agentDir, 'rules', `${ruleName}.md`);
|
|
245
|
+
const ruleExists = fs.existsSync(rulePath);
|
|
246
|
+
results.push({
|
|
247
|
+
name: `xref:mandatory-rule:${ruleName}`,
|
|
248
|
+
status: ruleExists ? 'pass' : 'fail',
|
|
249
|
+
message: ruleExists
|
|
250
|
+
? `Mandatory rule "${ruleName}" exists`
|
|
251
|
+
: `Mandatory rule "${ruleName}" referenced in alwaysLoadRules but file missing: rules/${ruleName}.md`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
results.push({ name: 'xref:loading-rules', status: 'warn', message: 'Could not parse loading-rules.json for cross-reference check' });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return buildReport(results);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Builds a summary report from individual check results.
|
|
264
|
+
*
|
|
265
|
+
* @param {CheckResult[]} results - Array of individual check results
|
|
266
|
+
* @returns {VerificationReport}
|
|
267
|
+
*/
|
|
268
|
+
function buildReport(results) {
|
|
269
|
+
const passed = results.filter((result) => result.status === 'pass').length;
|
|
270
|
+
const failed = results.filter((result) => result.status === 'fail').length;
|
|
271
|
+
const warnings = results.filter((result) => result.status === 'warn').length;
|
|
272
|
+
|
|
273
|
+
return { passed, failed, warnings, results };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = {
|
|
277
|
+
runAllChecks,
|
|
278
|
+
checkJsonFile,
|
|
279
|
+
};
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Workflow Engine
|
|
3
|
+
*
|
|
4
|
+
* Runtime module that enforces workflow-state.json transitions.
|
|
5
|
+
* This is the first true runtime enforcement layer — transitions
|
|
6
|
+
* are validated against the defined state machine before being applied.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/workflow-engine
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const workflowEvents = require('./workflow-events');
|
|
18
|
+
const { writeJsonAtomic } = require('./io');
|
|
19
|
+
const { createLogger } = require('./logger');
|
|
20
|
+
const log = createLogger('workflow-engine');
|
|
21
|
+
|
|
22
|
+
/** @typedef {'IDLE' | 'EXPLORE' | 'PLAN' | 'IMPLEMENT' | 'VERIFY' | 'CHECKPOINT' | 'REVIEW' | 'DEPLOY' | 'MAINTAIN'} WorkflowPhase */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} TransitionResult
|
|
26
|
+
* @property {boolean} success - Whether the transition was applied
|
|
27
|
+
* @property {string} fromPhase - Phase before transition
|
|
28
|
+
* @property {string} toPhase - Target phase
|
|
29
|
+
* @property {string} trigger - What triggered the transition
|
|
30
|
+
* @property {string} guard - Guard condition for this transition
|
|
31
|
+
* @property {string} [timestamp] - ISO timestamp of when transition occurred
|
|
32
|
+
* @property {string} [error] - Error message if transition failed
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} HistoryEntry
|
|
37
|
+
* @property {string} from - Source phase
|
|
38
|
+
* @property {string} to - Target phase
|
|
39
|
+
* @property {string} trigger - Transition trigger
|
|
40
|
+
* @property {string} timestamp - ISO timestamp
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const WORKFLOW_STATE_FILENAME = 'workflow-state.json';
|
|
44
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the absolute path to workflow-state.json for a given project root.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} projectRoot - Root directory of the project
|
|
50
|
+
* @returns {string} Absolute path to workflow-state.json
|
|
51
|
+
*/
|
|
52
|
+
function resolveStatePath(projectRoot) {
|
|
53
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, WORKFLOW_STATE_FILENAME);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Loads and parses the workflow state from disk.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} projectRoot - Root directory of the project
|
|
60
|
+
* @returns {{ state: object, filePath: string }}
|
|
61
|
+
* @throws {Error} If file does not exist or contains invalid JSON
|
|
62
|
+
*/
|
|
63
|
+
function loadWorkflowState(projectRoot) {
|
|
64
|
+
const filePath = resolveStatePath(projectRoot);
|
|
65
|
+
|
|
66
|
+
if (!fs.existsSync(filePath)) {
|
|
67
|
+
throw new Error(`Workflow state file not found: ${filePath}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const state = JSON.parse(raw);
|
|
74
|
+
return { state, filePath };
|
|
75
|
+
} catch (parseError) {
|
|
76
|
+
throw new Error(`Invalid JSON in workflow state file: ${parseError.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns the current workflow phase for a project.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} projectRoot - Root directory of the project
|
|
84
|
+
* @returns {string} Current phase name (e.g., 'IDLE', 'PLAN')
|
|
85
|
+
*/
|
|
86
|
+
function getCurrentPhase(projectRoot) {
|
|
87
|
+
const { state } = loadWorkflowState(projectRoot);
|
|
88
|
+
return state.currentPhase;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the full transition history.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} projectRoot - Root directory of the project
|
|
95
|
+
* @returns {HistoryEntry[]} Array of historical transitions
|
|
96
|
+
*/
|
|
97
|
+
function getTransitionHistory(projectRoot) {
|
|
98
|
+
const { state } = loadWorkflowState(projectRoot);
|
|
99
|
+
return state.history || [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Finds a matching transition definition from the state machine.
|
|
104
|
+
*
|
|
105
|
+
* @param {object[]} transitions - Array of transition definitions
|
|
106
|
+
* @param {string} fromPhase - Source phase
|
|
107
|
+
* @param {string} toPhase - Target phase
|
|
108
|
+
* @returns {object | null} Matching transition or null
|
|
109
|
+
*/
|
|
110
|
+
function findTransition(transitions, fromPhase, toPhase) {
|
|
111
|
+
return transitions.find(
|
|
112
|
+
(transition) => transition.from === fromPhase && transition.to === toPhase
|
|
113
|
+
) || null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Validates whether a transition from the current phase to the target phase
|
|
118
|
+
* is permitted by the state machine definition.
|
|
119
|
+
*
|
|
120
|
+
* Does NOT execute the transition — use executeTransition() for that.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} projectRoot - Root directory of the project
|
|
123
|
+
* @param {string} toPhase - Target phase to transition to
|
|
124
|
+
* @returns {TransitionResult} Validation result (success does not mean executed)
|
|
125
|
+
*/
|
|
126
|
+
function validateTransition(projectRoot, toPhase) {
|
|
127
|
+
const { state } = loadWorkflowState(projectRoot);
|
|
128
|
+
const currentPhase = state.currentPhase;
|
|
129
|
+
const transitions = state.transitions || [];
|
|
130
|
+
|
|
131
|
+
if (currentPhase === toPhase) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
fromPhase: currentPhase,
|
|
135
|
+
toPhase,
|
|
136
|
+
trigger: '',
|
|
137
|
+
guard: '',
|
|
138
|
+
error: `Already in phase ${toPhase} — no transition needed`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const match = findTransition(transitions, currentPhase, toPhase);
|
|
143
|
+
|
|
144
|
+
if (!match) {
|
|
145
|
+
const validTargets = transitions
|
|
146
|
+
.filter((transition) => transition.from === currentPhase)
|
|
147
|
+
.map((transition) => transition.to);
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
fromPhase: currentPhase,
|
|
152
|
+
toPhase,
|
|
153
|
+
trigger: '',
|
|
154
|
+
guard: '',
|
|
155
|
+
error: `Invalid transition: ${currentPhase} → ${toPhase}. Valid targets from ${currentPhase}: [${validTargets.join(', ')}]`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
success: true,
|
|
161
|
+
fromPhase: currentPhase,
|
|
162
|
+
toPhase,
|
|
163
|
+
trigger: match.trigger,
|
|
164
|
+
guard: match.guard,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Executes a workflow transition atomically.
|
|
170
|
+
*
|
|
171
|
+
* 1. Validates the transition is permitted
|
|
172
|
+
* 2. Updates the current phase
|
|
173
|
+
* 3. Records timestamps on phase records
|
|
174
|
+
* 4. Appends to history
|
|
175
|
+
* 5. Writes updated state to disk
|
|
176
|
+
*
|
|
177
|
+
* @param {string} projectRoot - Root directory of the project
|
|
178
|
+
* @param {string} toPhase - Target phase to transition to
|
|
179
|
+
* @param {string} [triggerOverride] - Optional override for the trigger description
|
|
180
|
+
* @returns {TransitionResult} Result of the transition attempt
|
|
181
|
+
*/
|
|
182
|
+
function executeTransition(projectRoot, toPhase, triggerOverride) {
|
|
183
|
+
const { state: loadedState, filePath } = loadWorkflowState(projectRoot);
|
|
184
|
+
const currentPhase = loadedState.currentPhase;
|
|
185
|
+
const transitions = loadedState.transitions || [];
|
|
186
|
+
|
|
187
|
+
if (currentPhase === toPhase) {
|
|
188
|
+
return {
|
|
189
|
+
success: false,
|
|
190
|
+
fromPhase: currentPhase,
|
|
191
|
+
toPhase,
|
|
192
|
+
trigger: '',
|
|
193
|
+
guard: '',
|
|
194
|
+
error: `Already in phase ${toPhase} — no transition needed`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const match = findTransition(transitions, currentPhase, toPhase);
|
|
199
|
+
|
|
200
|
+
if (!match) {
|
|
201
|
+
const validTargets = transitions
|
|
202
|
+
.filter((transition) => transition.from === currentPhase)
|
|
203
|
+
.map((transition) => transition.to);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
fromPhase: currentPhase,
|
|
208
|
+
toPhase,
|
|
209
|
+
trigger: '',
|
|
210
|
+
guard: '',
|
|
211
|
+
error: `Invalid transition: ${currentPhase} → ${toPhase}. Valid targets from ${currentPhase}: [${validTargets.join(', ')}]`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const timestamp = new Date().toISOString();
|
|
216
|
+
const trigger = triggerOverride || match.trigger;
|
|
217
|
+
|
|
218
|
+
// Build updated state immutably
|
|
219
|
+
const updatedPhases = { ...loadedState.phases };
|
|
220
|
+
|
|
221
|
+
// Update previous phase completion timestamp
|
|
222
|
+
if (currentPhase !== 'IDLE' && updatedPhases[currentPhase]) {
|
|
223
|
+
updatedPhases[currentPhase] = {
|
|
224
|
+
...updatedPhases[currentPhase],
|
|
225
|
+
completedAt: timestamp,
|
|
226
|
+
status: 'completed',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Update target phase start timestamp
|
|
231
|
+
if (updatedPhases[toPhase]) {
|
|
232
|
+
updatedPhases[toPhase] = {
|
|
233
|
+
...updatedPhases[toPhase],
|
|
234
|
+
startedAt: timestamp,
|
|
235
|
+
status: 'active',
|
|
236
|
+
completedAt: null,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const existingHistory = Array.isArray(loadedState.history) ? loadedState.history : [];
|
|
241
|
+
|
|
242
|
+
const state = {
|
|
243
|
+
...loadedState,
|
|
244
|
+
currentPhase: toPhase,
|
|
245
|
+
startedAt: (!loadedState.startedAt && currentPhase === 'IDLE') ? timestamp : loadedState.startedAt,
|
|
246
|
+
phases: updatedPhases,
|
|
247
|
+
history: [
|
|
248
|
+
...existingHistory,
|
|
249
|
+
{
|
|
250
|
+
from: currentPhase,
|
|
251
|
+
to: toPhase,
|
|
252
|
+
trigger,
|
|
253
|
+
timestamp,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Emit transition start event
|
|
259
|
+
workflowEvents.emitTransitionStart(currentPhase, toPhase, trigger);
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
writeJsonAtomic(filePath, state);
|
|
263
|
+
} catch (writeError) {
|
|
264
|
+
workflowEvents.emitTransitionFailed(currentPhase, toPhase, writeError.message);
|
|
265
|
+
return {
|
|
266
|
+
success: false,
|
|
267
|
+
fromPhase: currentPhase,
|
|
268
|
+
toPhase,
|
|
269
|
+
trigger,
|
|
270
|
+
guard: match.guard,
|
|
271
|
+
error: `Failed to write state: ${writeError.message}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Emit transition complete event
|
|
276
|
+
workflowEvents.emitTransitionComplete(currentPhase, toPhase, trigger);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
fromPhase: currentPhase,
|
|
281
|
+
toPhase,
|
|
282
|
+
trigger,
|
|
283
|
+
guard: match.guard,
|
|
284
|
+
timestamp,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Resets the workflow to IDLE state with clean phase records.
|
|
290
|
+
*
|
|
291
|
+
* @param {string} projectRoot - Root directory of the project
|
|
292
|
+
* @param {boolean} [preserveHistory=true] - Whether to keep transition history
|
|
293
|
+
* @returns {{ success: boolean, previousPhase: string }}
|
|
294
|
+
*/
|
|
295
|
+
function resetWorkflow(projectRoot, preserveHistory = true) {
|
|
296
|
+
const { state: loadedState, filePath } = loadWorkflowState(projectRoot);
|
|
297
|
+
const previousPhase = loadedState.currentPhase;
|
|
298
|
+
const timestamp = new Date().toISOString();
|
|
299
|
+
|
|
300
|
+
// Build reset phases immutably
|
|
301
|
+
const resetPhases = {};
|
|
302
|
+
for (const phaseName of Object.keys(loadedState.phases || {})) {
|
|
303
|
+
resetPhases[phaseName] = {
|
|
304
|
+
...loadedState.phases[phaseName],
|
|
305
|
+
status: 'pending',
|
|
306
|
+
startedAt: null,
|
|
307
|
+
completedAt: null,
|
|
308
|
+
artifact: null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const existingHistory = Array.isArray(loadedState.history) ? loadedState.history : [];
|
|
313
|
+
const history = preserveHistory
|
|
314
|
+
? [
|
|
315
|
+
...existingHistory,
|
|
316
|
+
{
|
|
317
|
+
from: previousPhase,
|
|
318
|
+
to: 'IDLE',
|
|
319
|
+
trigger: 'Workflow reset',
|
|
320
|
+
timestamp,
|
|
321
|
+
},
|
|
322
|
+
]
|
|
323
|
+
: [];
|
|
324
|
+
|
|
325
|
+
const updatedState = {
|
|
326
|
+
...loadedState,
|
|
327
|
+
currentPhase: 'IDLE',
|
|
328
|
+
startedAt: null,
|
|
329
|
+
phases: resetPhases,
|
|
330
|
+
history,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
writeJsonAtomic(filePath, updatedState);
|
|
335
|
+
} catch (writeError) {
|
|
336
|
+
return { success: false, previousPhase, error: `Failed to write state: ${writeError.message}` };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
workflowEvents.emitWorkflowReset(previousPhase);
|
|
340
|
+
return { success: true, previousPhase };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Returns all valid transitions from the current phase.
|
|
345
|
+
*
|
|
346
|
+
* @param {string} projectRoot - Root directory of the project
|
|
347
|
+
* @returns {{ currentPhase: string, validTransitions: object[] }}
|
|
348
|
+
*/
|
|
349
|
+
function getAvailableTransitions(projectRoot) {
|
|
350
|
+
const { state } = loadWorkflowState(projectRoot);
|
|
351
|
+
const currentPhase = state.currentPhase;
|
|
352
|
+
const transitions = state.transitions || [];
|
|
353
|
+
|
|
354
|
+
const validTransitions = transitions
|
|
355
|
+
.filter((transition) => transition.from === currentPhase)
|
|
356
|
+
.map((transition) => ({
|
|
357
|
+
to: transition.to,
|
|
358
|
+
trigger: transition.trigger,
|
|
359
|
+
guard: transition.guard,
|
|
360
|
+
}));
|
|
361
|
+
|
|
362
|
+
return { currentPhase, validTransitions };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
module.exports = {
|
|
366
|
+
loadWorkflowState,
|
|
367
|
+
getCurrentPhase,
|
|
368
|
+
getTransitionHistory,
|
|
369
|
+
validateTransition,
|
|
370
|
+
executeTransition,
|
|
371
|
+
resetWorkflow,
|
|
372
|
+
getAvailableTransitions,
|
|
373
|
+
};
|