@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
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Automates session-state.json updates so it is no longer
|
|
5
|
+
* a blank template. Tracks active sessions, tasks, and
|
|
6
|
+
* repository state.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/session-manager
|
|
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 crypto = require('crypto');
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
const { AGENT_DIR } = require('./constants');
|
|
21
|
+
const { writeJsonAtomic } = require('./io');
|
|
22
|
+
const STATE_FILENAME = 'session-state.json';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the absolute path to session-state.json.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} projectRoot - Root directory of the project
|
|
28
|
+
* @returns {string} Absolute path to session-state.json
|
|
29
|
+
*/
|
|
30
|
+
function resolveStatePath(projectRoot) {
|
|
31
|
+
return path.join(projectRoot, AGENT_DIR, STATE_FILENAME);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Loads session state from disk.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} projectRoot - Root directory of the project
|
|
38
|
+
* @returns {object} Parsed session state
|
|
39
|
+
*/
|
|
40
|
+
function loadSessionState(projectRoot) {
|
|
41
|
+
const filePath = resolveStatePath(projectRoot);
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(filePath)) {
|
|
44
|
+
throw new Error(`Session state file not found: ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Writes session state to disk atomically.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} projectRoot - Root directory of the project
|
|
55
|
+
* @param {object} state - Session state object to write
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*/
|
|
58
|
+
function writeSessionState(projectRoot, state) {
|
|
59
|
+
const filePath = resolveStatePath(projectRoot);
|
|
60
|
+
const updatedState = { ...state, lastUpdated: new Date().toISOString() };
|
|
61
|
+
writeJsonAtomic(filePath, updatedState);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Retrieves current Git branch name safely.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} projectRoot - Root directory of the project
|
|
68
|
+
* @returns {string | null} Branch name or null if not a git repo
|
|
69
|
+
*/
|
|
70
|
+
function getGitBranch(projectRoot) {
|
|
71
|
+
try {
|
|
72
|
+
const result = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
73
|
+
cwd: projectRoot,
|
|
74
|
+
encoding: 'utf-8',
|
|
75
|
+
timeout: 5000,
|
|
76
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
77
|
+
});
|
|
78
|
+
return result.trim() || null;
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Retrieves the last Git commit SHA safely.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} projectRoot - Root directory of the project
|
|
88
|
+
* @returns {string | null} Commit SHA or null
|
|
89
|
+
*/
|
|
90
|
+
function getGitLastCommit(projectRoot) {
|
|
91
|
+
try {
|
|
92
|
+
const result = execSync('git log -1 --format=%H', {
|
|
93
|
+
cwd: projectRoot,
|
|
94
|
+
encoding: 'utf-8',
|
|
95
|
+
timeout: 5000,
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
98
|
+
return result.trim() || null;
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Starts a new session. Generates a session ID, populates
|
|
106
|
+
* date, Git info, and sets status to "active".
|
|
107
|
+
*
|
|
108
|
+
* @param {string} projectRoot - Root directory of the project
|
|
109
|
+
* @param {string} [focus] - Optional session focus description
|
|
110
|
+
* @returns {{ sessionId: string, state: object }}
|
|
111
|
+
*/
|
|
112
|
+
function startSession(projectRoot, focus) {
|
|
113
|
+
const loadedState = loadSessionState(projectRoot);
|
|
114
|
+
const sessionId = crypto.randomUUID();
|
|
115
|
+
|
|
116
|
+
const state = {
|
|
117
|
+
...loadedState,
|
|
118
|
+
session: {
|
|
119
|
+
id: sessionId,
|
|
120
|
+
date: new Date().toISOString(),
|
|
121
|
+
focus: focus || null,
|
|
122
|
+
status: 'active',
|
|
123
|
+
},
|
|
124
|
+
repository: {
|
|
125
|
+
currentBranch: getGitBranch(projectRoot),
|
|
126
|
+
lastCommit: getGitLastCommit(projectRoot),
|
|
127
|
+
remoteSynced: false,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
writeSessionState(projectRoot, state);
|
|
132
|
+
|
|
133
|
+
return { sessionId, state };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Ends the current session. Sets status to "completed".
|
|
138
|
+
*
|
|
139
|
+
* @param {string} projectRoot - Root directory of the project
|
|
140
|
+
* @returns {{ success: boolean, sessionId: string | null }}
|
|
141
|
+
*/
|
|
142
|
+
function endSession(projectRoot) {
|
|
143
|
+
const loadedState = loadSessionState(projectRoot);
|
|
144
|
+
|
|
145
|
+
if (!loadedState.session || !loadedState.session.id) {
|
|
146
|
+
return { success: false, sessionId: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const sessionId = loadedState.session.id;
|
|
150
|
+
const state = {
|
|
151
|
+
...loadedState,
|
|
152
|
+
session: { ...loadedState.session, status: 'completed' },
|
|
153
|
+
notes: `Session ${sessionId} completed at ${new Date().toISOString()}`,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
writeSessionState(projectRoot, state);
|
|
157
|
+
|
|
158
|
+
return { success: true, sessionId };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Adds a task to the open tasks list.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} projectRoot - Root directory of the project
|
|
165
|
+
* @param {string} title - Task title
|
|
166
|
+
* @param {string} [description] - Optional description
|
|
167
|
+
* @returns {{ taskId: string }}
|
|
168
|
+
*/
|
|
169
|
+
function addTask(projectRoot, title, description) {
|
|
170
|
+
const loadedState = loadSessionState(projectRoot);
|
|
171
|
+
const taskId = `TASK-${Date.now().toString(36).toUpperCase()}`;
|
|
172
|
+
|
|
173
|
+
const task = {
|
|
174
|
+
id: taskId,
|
|
175
|
+
title,
|
|
176
|
+
description: description || null,
|
|
177
|
+
createdAt: new Date().toISOString(),
|
|
178
|
+
status: 'open',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const existingTasks = Array.isArray(loadedState.openTasks) ? loadedState.openTasks : [];
|
|
182
|
+
const state = {
|
|
183
|
+
...loadedState,
|
|
184
|
+
openTasks: [...existingTasks, task],
|
|
185
|
+
currentTask: taskId,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
writeSessionState(projectRoot, state);
|
|
189
|
+
|
|
190
|
+
return { taskId };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Marks a task as completed, moving it from openTasks to completedTasks.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} projectRoot - Root directory of the project
|
|
197
|
+
* @param {string} taskId - ID of the task to complete
|
|
198
|
+
* @returns {{ success: boolean }}
|
|
199
|
+
*/
|
|
200
|
+
function completeTask(projectRoot, taskId) {
|
|
201
|
+
const loadedState = loadSessionState(projectRoot);
|
|
202
|
+
|
|
203
|
+
if (!Array.isArray(loadedState.openTasks)) {
|
|
204
|
+
return { success: false };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const taskIndex = loadedState.openTasks.findIndex((task) => task.id === taskId);
|
|
208
|
+
|
|
209
|
+
if (taskIndex === -1) {
|
|
210
|
+
return { success: false };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const completedTask = {
|
|
214
|
+
...loadedState.openTasks[taskIndex],
|
|
215
|
+
status: 'completed',
|
|
216
|
+
completedAt: new Date().toISOString(),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const remainingTasks = loadedState.openTasks.filter((_, i) => i !== taskIndex);
|
|
220
|
+
const existingCompleted = Array.isArray(loadedState.completedTasks) ? loadedState.completedTasks : [];
|
|
221
|
+
|
|
222
|
+
const state = {
|
|
223
|
+
...loadedState,
|
|
224
|
+
openTasks: remainingTasks,
|
|
225
|
+
completedTasks: [...existingCompleted, completedTask],
|
|
226
|
+
currentTask: loadedState.currentTask === taskId
|
|
227
|
+
? (remainingTasks.length > 0 ? remainingTasks[0].id : null)
|
|
228
|
+
: loadedState.currentTask,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
writeSessionState(projectRoot, state);
|
|
232
|
+
|
|
233
|
+
return { success: true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Returns a summary of the current session state.
|
|
238
|
+
*
|
|
239
|
+
* @param {string} projectRoot - Root directory of the project
|
|
240
|
+
* @returns {object} Session summary
|
|
241
|
+
*/
|
|
242
|
+
function getSessionSummary(projectRoot) {
|
|
243
|
+
const state = loadSessionState(projectRoot);
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
sessionId: state.session?.id || null,
|
|
247
|
+
status: state.session?.status || 'new',
|
|
248
|
+
focus: state.session?.focus || null,
|
|
249
|
+
branch: state.repository?.currentBranch || null,
|
|
250
|
+
openTaskCount: Array.isArray(state.openTasks) ? state.openTasks.length : 0,
|
|
251
|
+
completedTaskCount: Array.isArray(state.completedTasks) ? state.completedTasks.length : 0,
|
|
252
|
+
currentTask: state.currentTask || null,
|
|
253
|
+
lastUpdated: state.lastUpdated || null,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
loadSessionState,
|
|
259
|
+
startSession,
|
|
260
|
+
endSession,
|
|
261
|
+
addTask,
|
|
262
|
+
completeTask,
|
|
263
|
+
getSessionSummary,
|
|
264
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devran AI Kit — Skill Sandboxing
|
|
3
|
+
*
|
|
4
|
+
* Enforces allowed-tools declarations from skill SKILL.md frontmatter.
|
|
5
|
+
* Validates that skills only reference tools within their permission set.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/skill-sandbox
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const { AGENT_DIR } = require('./constants');
|
|
18
|
+
const SKILLS_DIR = 'skills';
|
|
19
|
+
|
|
20
|
+
/** Valid permission levels (ordered from least to most privileged) */
|
|
21
|
+
const PERMISSION_LEVELS = ['read-only', 'read-write', 'execute', 'network'];
|
|
22
|
+
|
|
23
|
+
/** Tool patterns that indicate permission level requirements */
|
|
24
|
+
const TOOL_PERMISSION_MAP = {
|
|
25
|
+
'network': [
|
|
26
|
+
/\bfetch\b/i, /\bhttp\b/i, /\bapi\b/i, /\bcurl\b/i, /\brequest\b/i,
|
|
27
|
+
/\bwebhook\b/i, /\bsocket\b/i, /\bdownload\b/i, /\bupload\b/i,
|
|
28
|
+
],
|
|
29
|
+
'execute': [
|
|
30
|
+
/\bexec\b/i, /\bspawn\b/i, /\bchild_process\b/i, /\bshell\b/i,
|
|
31
|
+
/\brun_command\b/i, /\bterminal\b/i, /\bscript\b/i,
|
|
32
|
+
],
|
|
33
|
+
'read-write': [
|
|
34
|
+
/\bwrite\b/i, /\bcreate\b/i, /\bdelete\b/i, /\bmodify\b/i,
|
|
35
|
+
/\bedit\b/i, /\bsave\b/i, /\bremove\b/i, /\bupdate\b/i,
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {object} SkillPermissions
|
|
41
|
+
* @property {string} skillName - Name of the skill
|
|
42
|
+
* @property {string[]} allowedTools - Declared allowed tools
|
|
43
|
+
* @property {string} permissionLevel - Highest permission level
|
|
44
|
+
* @property {boolean} hasFrontmatter - Whether frontmatter was found
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} SandboxViolation
|
|
49
|
+
* @property {string} skillName - Skill that violated
|
|
50
|
+
* @property {string} violation - Description of violation
|
|
51
|
+
* @property {'critical' | 'high' | 'medium' | 'low'} severity - Violation severity
|
|
52
|
+
* @property {string} file - File where violation was found
|
|
53
|
+
* @property {number} [line] - Line number (if applicable)
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extracts permissions from a SKILL.md frontmatter.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} content - Raw SKILL.md content
|
|
60
|
+
* @returns {{ allowedTools: string[], permissionLevel: string }}
|
|
61
|
+
*/
|
|
62
|
+
function extractPermissionsFromFrontmatter(content) {
|
|
63
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
64
|
+
|
|
65
|
+
if (!frontmatterMatch) {
|
|
66
|
+
return { allowedTools: [], permissionLevel: 'read-only' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const frontmatter = frontmatterMatch[1];
|
|
70
|
+
/** @type {string[]} */
|
|
71
|
+
const allowedTools = [];
|
|
72
|
+
let permissionLevel = 'read-only';
|
|
73
|
+
|
|
74
|
+
// Parse allowed-tools from frontmatter
|
|
75
|
+
const toolsMatch = frontmatter.match(/allowed-tools:\s*\[(.*?)\]/);
|
|
76
|
+
if (toolsMatch) {
|
|
77
|
+
const toolsList = toolsMatch[1].split(',').map((t) => t.trim().replace(/['"]/g, '')).filter(Boolean);
|
|
78
|
+
allowedTools.push(...toolsList);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Parse permission-level from frontmatter
|
|
82
|
+
const permMatch = frontmatter.match(/permission-level:\s*(\S+)/);
|
|
83
|
+
if (permMatch && PERMISSION_LEVELS.includes(permMatch[1])) {
|
|
84
|
+
permissionLevel = permMatch[1];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { allowedTools, permissionLevel };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the permissions declared by a specific skill.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} skillName - Name of the skill
|
|
94
|
+
* @param {string} projectRoot - Root directory of the project
|
|
95
|
+
* @returns {SkillPermissions}
|
|
96
|
+
*/
|
|
97
|
+
function getSkillPermissions(skillName, projectRoot) {
|
|
98
|
+
const skillPath = path.join(projectRoot, AGENT_DIR, SKILLS_DIR, skillName, 'SKILL.md');
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(skillPath)) {
|
|
101
|
+
return {
|
|
102
|
+
skillName,
|
|
103
|
+
allowedTools: [],
|
|
104
|
+
permissionLevel: 'read-only',
|
|
105
|
+
hasFrontmatter: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const content = fs.readFileSync(skillPath, 'utf-8');
|
|
110
|
+
const { allowedTools, permissionLevel } = extractPermissionsFromFrontmatter(content);
|
|
111
|
+
const hasFrontmatter = content.startsWith('---');
|
|
112
|
+
|
|
113
|
+
return { skillName, allowedTools, permissionLevel, hasFrontmatter };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Scans a skill's content for tool usage that exceeds its declared permissions.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} skillName - Name of the skill
|
|
120
|
+
* @param {string} projectRoot - Root directory of the project
|
|
121
|
+
* @returns {SandboxViolation[]}
|
|
122
|
+
*/
|
|
123
|
+
function validateSkillPermissions(skillName, projectRoot) {
|
|
124
|
+
const permissions = getSkillPermissions(skillName, projectRoot);
|
|
125
|
+
const skillDir = path.join(projectRoot, AGENT_DIR, SKILLS_DIR, skillName);
|
|
126
|
+
/** @type {SandboxViolation[]} */
|
|
127
|
+
const violations = [];
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(skillDir)) {
|
|
130
|
+
violations.push({
|
|
131
|
+
skillName,
|
|
132
|
+
violation: `Skill directory not found: ${skillName}`,
|
|
133
|
+
severity: 'critical',
|
|
134
|
+
file: skillDir,
|
|
135
|
+
});
|
|
136
|
+
return violations;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Get the permission level index for comparison
|
|
140
|
+
const declaredLevelIndex = PERMISSION_LEVELS.indexOf(permissions.permissionLevel);
|
|
141
|
+
|
|
142
|
+
// Scan all files in the skill directory
|
|
143
|
+
const files = scanSkillFiles(skillDir);
|
|
144
|
+
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
147
|
+
const lines = content.split('\n');
|
|
148
|
+
|
|
149
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
150
|
+
const line = lines[lineIndex];
|
|
151
|
+
|
|
152
|
+
// Check each permission level higher than declared
|
|
153
|
+
for (const [level, patterns] of Object.entries(TOOL_PERMISSION_MAP)) {
|
|
154
|
+
const levelIndex = PERMISSION_LEVELS.indexOf(level);
|
|
155
|
+
|
|
156
|
+
if (levelIndex > declaredLevelIndex) {
|
|
157
|
+
for (const pattern of patterns) {
|
|
158
|
+
if (pattern.test(line)) {
|
|
159
|
+
violations.push({
|
|
160
|
+
skillName,
|
|
161
|
+
violation: `Uses ${level}-level tool pattern "${pattern.source}" but declared permission is "${permissions.permissionLevel}"`,
|
|
162
|
+
severity: levelIndex - declaredLevelIndex >= 2 ? 'critical' : 'high',
|
|
163
|
+
file: path.relative(projectRoot, file),
|
|
164
|
+
line: lineIndex + 1,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return violations;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Recursively scans a skill directory for readable files.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} dirPath - Directory to scan
|
|
180
|
+
* @returns {string[]} Array of file paths
|
|
181
|
+
*/
|
|
182
|
+
function scanSkillFiles(dirPath) {
|
|
183
|
+
/** @type {string[]} */
|
|
184
|
+
const files = [];
|
|
185
|
+
|
|
186
|
+
if (!fs.existsSync(dirPath)) {
|
|
187
|
+
return files;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
191
|
+
|
|
192
|
+
for (const entry of entries) {
|
|
193
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
194
|
+
|
|
195
|
+
if (entry.isDirectory()) {
|
|
196
|
+
files.push(...scanSkillFiles(fullPath));
|
|
197
|
+
} else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.json') || entry.name.endsWith('.js'))) {
|
|
198
|
+
files.push(fullPath);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return files;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Enforces allowed-tools compliance across all skills.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} projectRoot - Root directory of the project
|
|
209
|
+
* @returns {{ total: number, compliant: number, violations: SandboxViolation[] }}
|
|
210
|
+
*/
|
|
211
|
+
function enforceAllowedTools(projectRoot) {
|
|
212
|
+
const skillsDir = path.join(projectRoot, AGENT_DIR, SKILLS_DIR);
|
|
213
|
+
|
|
214
|
+
if (!fs.existsSync(skillsDir)) {
|
|
215
|
+
return { total: 0, compliant: 0, violations: [] };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
219
|
+
.filter((d) => d.isDirectory())
|
|
220
|
+
.map((d) => d.name);
|
|
221
|
+
|
|
222
|
+
/** @type {SandboxViolation[]} */
|
|
223
|
+
const allViolations = [];
|
|
224
|
+
|
|
225
|
+
for (const skillName of skillDirs) {
|
|
226
|
+
const violations = validateSkillPermissions(skillName, projectRoot);
|
|
227
|
+
allViolations.push(...violations);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const violatedSkills = new Set(allViolations.map((v) => v.skillName));
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
total: skillDirs.length,
|
|
234
|
+
compliant: skillDirs.length - violatedSkills.size,
|
|
235
|
+
violations: allViolations,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
getSkillPermissions,
|
|
241
|
+
validateSkillPermissions,
|
|
242
|
+
enforceAllowedTools,
|
|
243
|
+
extractPermissionsFromFrontmatter,
|
|
244
|
+
};
|