@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.
Files changed (231) hide show
  1. package/.agent/CheatSheet.md +350 -0
  2. package/.agent/README.md +76 -0
  3. package/.agent/agents/README.md +155 -0
  4. package/.agent/agents/architect.md +185 -0
  5. package/.agent/agents/backend-specialist.md +276 -0
  6. package/.agent/agents/build-error-resolver.md +207 -0
  7. package/.agent/agents/code-reviewer.md +162 -0
  8. package/.agent/agents/database-architect.md +138 -0
  9. package/.agent/agents/devops-engineer.md +144 -0
  10. package/.agent/agents/doc-updater.md +229 -0
  11. package/.agent/agents/e2e-runner.md +145 -0
  12. package/.agent/agents/explorer-agent.md +143 -0
  13. package/.agent/agents/frontend-specialist.md +144 -0
  14. package/.agent/agents/go-reviewer.md +128 -0
  15. package/.agent/agents/knowledge-agent.md +197 -0
  16. package/.agent/agents/mobile-developer.md +150 -0
  17. package/.agent/agents/performance-optimizer.md +175 -0
  18. package/.agent/agents/planner.md +133 -0
  19. package/.agent/agents/pr-reviewer.md +148 -0
  20. package/.agent/agents/python-reviewer.md +123 -0
  21. package/.agent/agents/refactor-cleaner.md +201 -0
  22. package/.agent/agents/reliability-engineer.md +156 -0
  23. package/.agent/agents/security-reviewer.md +141 -0
  24. package/.agent/agents/sprint-orchestrator.md +124 -0
  25. package/.agent/agents/tdd-guide.md +179 -0
  26. package/.agent/agents/typescript-reviewer.md +110 -0
  27. package/.agent/checklists/README.md +102 -0
  28. package/.agent/checklists/pre-commit.md +93 -0
  29. package/.agent/checklists/session-end.md +99 -0
  30. package/.agent/checklists/session-start.md +102 -0
  31. package/.agent/checklists/task-complete.md +81 -0
  32. package/.agent/commands/README.md +130 -0
  33. package/.agent/commands/adr.md +29 -0
  34. package/.agent/commands/ask.md +28 -0
  35. package/.agent/commands/build.md +30 -0
  36. package/.agent/commands/changelog.md +40 -0
  37. package/.agent/commands/checkpoint.md +28 -0
  38. package/.agent/commands/code-review.md +65 -0
  39. package/.agent/commands/compact.md +28 -0
  40. package/.agent/commands/cook.md +30 -0
  41. package/.agent/commands/db.md +30 -0
  42. package/.agent/commands/debug.md +31 -0
  43. package/.agent/commands/deploy.md +37 -0
  44. package/.agent/commands/design.md +29 -0
  45. package/.agent/commands/doc.md +30 -0
  46. package/.agent/commands/eval.md +30 -0
  47. package/.agent/commands/fix.md +32 -0
  48. package/.agent/commands/git.md +32 -0
  49. package/.agent/commands/help.md +273 -0
  50. package/.agent/commands/implement.md +30 -0
  51. package/.agent/commands/integrate.md +32 -0
  52. package/.agent/commands/learn.md +29 -0
  53. package/.agent/commands/perf.md +31 -0
  54. package/.agent/commands/plan.md +56 -0
  55. package/.agent/commands/pr-describe.md +65 -0
  56. package/.agent/commands/pr-fix.md +45 -0
  57. package/.agent/commands/pr-merge.md +45 -0
  58. package/.agent/commands/pr-review.md +50 -0
  59. package/.agent/commands/pr-split.md +54 -0
  60. package/.agent/commands/pr-status.md +56 -0
  61. package/.agent/commands/pr.md +58 -0
  62. package/.agent/commands/refactor.md +32 -0
  63. package/.agent/commands/research.md +28 -0
  64. package/.agent/commands/scout.md +30 -0
  65. package/.agent/commands/security-scan.md +33 -0
  66. package/.agent/commands/setup.md +31 -0
  67. package/.agent/commands/status.md +59 -0
  68. package/.agent/commands/tdd.md +73 -0
  69. package/.agent/commands/verify.md +58 -0
  70. package/.agent/contexts/brainstorm.md +26 -0
  71. package/.agent/contexts/debug.md +28 -0
  72. package/.agent/contexts/implement.md +29 -0
  73. package/.agent/contexts/plan-quality-log.md +30 -0
  74. package/.agent/contexts/review.md +27 -0
  75. package/.agent/contexts/ship.md +28 -0
  76. package/.agent/decisions/001-trust-grade-governance.md +46 -0
  77. package/.agent/decisions/002-cross-ide-generation.md +15 -0
  78. package/.agent/engine/identity.json +4 -0
  79. package/.agent/engine/loading-rules.json +193 -0
  80. package/.agent/engine/marketplace-index.json +29 -0
  81. package/.agent/engine/mcp-servers/filesystem.json +9 -0
  82. package/.agent/engine/mcp-servers/github.json +11 -0
  83. package/.agent/engine/mcp-servers/postgres.json +11 -0
  84. package/.agent/engine/mcp-servers/supabase.json +11 -0
  85. package/.agent/engine/mcp-servers/vercel.json +11 -0
  86. package/.agent/engine/reliability-config.json +14 -0
  87. package/.agent/engine/sdlc-map.json +50 -0
  88. package/.agent/engine/workflow-state.json +167 -0
  89. package/.agent/hooks/README.md +101 -0
  90. package/.agent/hooks/hooks.json +104 -0
  91. package/.agent/hooks/templates/session-end.md +110 -0
  92. package/.agent/hooks/templates/session-start.md +95 -0
  93. package/.agent/manifest.json +466 -0
  94. package/.agent/rules/agent-upgrade-policy.md +56 -0
  95. package/.agent/rules/architecture.md +111 -0
  96. package/.agent/rules/coding-style.md +75 -0
  97. package/.agent/rules/documentation.md +74 -0
  98. package/.agent/rules/git-workflow.md +140 -0
  99. package/.agent/rules/quality-gate.md +117 -0
  100. package/.agent/rules/security.md +67 -0
  101. package/.agent/rules/sprint-tracking.md +103 -0
  102. package/.agent/rules/testing.md +80 -0
  103. package/.agent/rules/workflow-standards.md +30 -0
  104. package/.agent/rules.md +293 -0
  105. package/.agent/session-context.md +69 -0
  106. package/.agent/session-state.json +27 -0
  107. package/.agent/skills/README.md +135 -0
  108. package/.agent/skills/api-patterns/SKILL.md +117 -0
  109. package/.agent/skills/app-builder/SKILL.md +202 -0
  110. package/.agent/skills/architecture/SKILL.md +101 -0
  111. package/.agent/skills/behavioral-modes/SKILL.md +295 -0
  112. package/.agent/skills/brainstorming/SKILL.md +156 -0
  113. package/.agent/skills/clean-code/SKILL.md +142 -0
  114. package/.agent/skills/context-budget/SKILL.md +78 -0
  115. package/.agent/skills/continuous-learning/SKILL.md +145 -0
  116. package/.agent/skills/database-design/SKILL.md +303 -0
  117. package/.agent/skills/debugging-strategies/SKILL.md +158 -0
  118. package/.agent/skills/deployment-procedures/SKILL.md +191 -0
  119. package/.agent/skills/docker-patterns/SKILL.md +161 -0
  120. package/.agent/skills/eval-harness/SKILL.md +89 -0
  121. package/.agent/skills/frontend-patterns/SKILL.md +141 -0
  122. package/.agent/skills/git-workflow/SKILL.md +159 -0
  123. package/.agent/skills/i18n-localization/SKILL.md +191 -0
  124. package/.agent/skills/intelligent-routing/SKILL.md +180 -0
  125. package/.agent/skills/mcp-integration/SKILL.md +240 -0
  126. package/.agent/skills/mobile-design/SKILL.md +191 -0
  127. package/.agent/skills/nodejs-patterns/SKILL.md +164 -0
  128. package/.agent/skills/parallel-agents/SKILL.md +200 -0
  129. package/.agent/skills/performance-profiling/SKILL.md +134 -0
  130. package/.agent/skills/plan-validation/SKILL.md +192 -0
  131. package/.agent/skills/plan-writing/SKILL.md +183 -0
  132. package/.agent/skills/plan-writing/domain-enhancers.md +184 -0
  133. package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
  134. package/.agent/skills/plan-writing/plan-schema.md +119 -0
  135. package/.agent/skills/pr-toolkit/SKILL.md +174 -0
  136. package/.agent/skills/production-readiness/SKILL.md +126 -0
  137. package/.agent/skills/security-practices/SKILL.md +109 -0
  138. package/.agent/skills/shell-conventions/SKILL.md +92 -0
  139. package/.agent/skills/strategic-compact/SKILL.md +62 -0
  140. package/.agent/skills/testing-patterns/SKILL.md +141 -0
  141. package/.agent/skills/typescript-expert/SKILL.md +160 -0
  142. package/.agent/skills/ui-ux-pro-max/SKILL.md +137 -0
  143. package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
  144. package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
  145. package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
  146. package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
  147. package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
  148. package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  149. package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  150. package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  151. package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  152. package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  153. package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  154. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  155. package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  156. package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  157. package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  158. package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  159. package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  160. package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  161. package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  162. package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
  163. package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
  164. package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  165. package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  166. package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  167. package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
  168. package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  169. package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
  170. package/.agent/skills/verification-loop/SKILL.md +89 -0
  171. package/.agent/skills/webapp-testing/SKILL.md +175 -0
  172. package/.agent/templates/adr-template.md +32 -0
  173. package/.agent/templates/bug-report.md +37 -0
  174. package/.agent/templates/feature-request.md +32 -0
  175. package/.agent/workflows/README.md +101 -0
  176. package/.agent/workflows/brainstorm.md +86 -0
  177. package/.agent/workflows/create.md +85 -0
  178. package/.agent/workflows/debug.md +83 -0
  179. package/.agent/workflows/deploy.md +114 -0
  180. package/.agent/workflows/enhance.md +85 -0
  181. package/.agent/workflows/orchestrate.md +106 -0
  182. package/.agent/workflows/plan.md +105 -0
  183. package/.agent/workflows/pr-fix.md +163 -0
  184. package/.agent/workflows/pr-merge.md +117 -0
  185. package/.agent/workflows/pr-review.md +178 -0
  186. package/.agent/workflows/pr-split.md +118 -0
  187. package/.agent/workflows/pr.md +184 -0
  188. package/.agent/workflows/preflight.md +107 -0
  189. package/.agent/workflows/preview.md +95 -0
  190. package/.agent/workflows/quality-gate.md +103 -0
  191. package/.agent/workflows/retrospective.md +100 -0
  192. package/.agent/workflows/review.md +104 -0
  193. package/.agent/workflows/status.md +89 -0
  194. package/.agent/workflows/test.md +98 -0
  195. package/.agent/workflows/ui-ux-pro-max.md +93 -0
  196. package/.agent/workflows/upgrade.md +97 -0
  197. package/LICENSE +21 -0
  198. package/README.md +218 -0
  199. package/bin/kit.js +773 -0
  200. package/lib/agent-registry.js +228 -0
  201. package/lib/agent-reputation.js +343 -0
  202. package/lib/circuit-breaker.js +195 -0
  203. package/lib/cli-commands.js +322 -0
  204. package/lib/config-validator.js +274 -0
  205. package/lib/conflict-detector.js +252 -0
  206. package/lib/constants.js +47 -0
  207. package/lib/engineering-manager.js +336 -0
  208. package/lib/error-budget.js +370 -0
  209. package/lib/hook-system.js +256 -0
  210. package/lib/ide-generator.js +434 -0
  211. package/lib/identity.js +240 -0
  212. package/lib/io.js +146 -0
  213. package/lib/learning-engine.js +163 -0
  214. package/lib/loading-engine.js +421 -0
  215. package/lib/logger.js +118 -0
  216. package/lib/marketplace.js +321 -0
  217. package/lib/plugin-system.js +604 -0
  218. package/lib/plugin-verifier.js +197 -0
  219. package/lib/rate-limiter.js +113 -0
  220. package/lib/security-scanner.js +312 -0
  221. package/lib/self-healing.js +468 -0
  222. package/lib/session-manager.js +264 -0
  223. package/lib/skill-sandbox.js +244 -0
  224. package/lib/task-governance.js +522 -0
  225. package/lib/task-model.js +332 -0
  226. package/lib/updater.js +240 -0
  227. package/lib/verify.js +279 -0
  228. package/lib/workflow-engine.js +373 -0
  229. package/lib/workflow-events.js +166 -0
  230. package/lib/workflow-persistence.js +160 -0
  231. 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
+ };