@stilero/bankan 1.0.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/LICENSE +21 -0
- package/README.md +363 -0
- package/bin/bankan.js +148 -0
- package/client/dist/assets/index-BZkAflU1.css +32 -0
- package/client/dist/assets/index-hxSMA1kc.js +48 -0
- package/client/dist/index.html +16 -0
- package/package.json +57 -0
- package/scripts/setup.js +232 -0
- package/server/src/agents.js +401 -0
- package/server/src/config.js +228 -0
- package/server/src/events.js +6 -0
- package/server/src/index.js +774 -0
- package/server/src/orchestrator.js +1193 -0
- package/server/src/paths.js +55 -0
- package/server/src/store.js +287 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { getRuntimePaths } from './paths.js';
|
|
3
|
+
|
|
4
|
+
const runtimePaths = getRuntimePaths();
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_WORKSPACES_DIR = runtimePaths.workspacesDir;
|
|
7
|
+
|
|
8
|
+
let envVars = {};
|
|
9
|
+
try {
|
|
10
|
+
const content = readFileSync(runtimePaths.envFile, 'utf-8');
|
|
11
|
+
for (const line of content.split('\n')) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
14
|
+
const eqIdx = trimmed.indexOf('=');
|
|
15
|
+
if (eqIdx === -1) continue;
|
|
16
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
17
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
18
|
+
envVars[key] = value;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// .env.local not found, rely on process.env
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function get(key, fallback = '') {
|
|
25
|
+
return envVars[key] || process.env[key] || fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const config = {
|
|
29
|
+
PORT: parseInt(get('PORT', '3001'), 10),
|
|
30
|
+
REPOS: get('REPOS').split(',').map(s => s.trim()).filter(Boolean),
|
|
31
|
+
IMPLEMENTOR_1_CLI: get('IMPLEMENTOR_1_CLI', 'claude'),
|
|
32
|
+
IMPLEMENTOR_2_CLI: get('IMPLEMENTOR_2_CLI', 'codex'),
|
|
33
|
+
ROOT_DIR: runtimePaths.rootDir,
|
|
34
|
+
DATA_DIR: runtimePaths.dataDir,
|
|
35
|
+
CLIENT_DIST_DIR: runtimePaths.clientDistDir,
|
|
36
|
+
BRIDGES_DIR: runtimePaths.bridgesDir,
|
|
37
|
+
ENV_FILE: runtimePaths.envFile,
|
|
38
|
+
PACKAGED_RUNTIME: runtimePaths.packaged,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DEFAULT_PROMPTS = {
|
|
42
|
+
planning: `Plan Mode Instructions
|
|
43
|
+
|
|
44
|
+
Core constraints:
|
|
45
|
+
- Do not edit files, change system state, or use non-readonly tools while planning
|
|
46
|
+
- Treat this stage as planning only; implementation happens after plan approval
|
|
47
|
+
- Focus on discovering reusable existing code before proposing new structures
|
|
48
|
+
|
|
49
|
+
Workflow:
|
|
50
|
+
1. Initial understanding
|
|
51
|
+
- Explore the codebase with the minimum investigation needed to understand the task
|
|
52
|
+
- Prioritize finding existing modules, helpers, patterns, and file locations that can be reused
|
|
53
|
+
2. Design
|
|
54
|
+
- Design the implementation approach in enough detail that another engineer can execute it without making product or architectural decisions
|
|
55
|
+
- Skip unnecessary complexity for trivial tasks, but still capture the concrete change and verification
|
|
56
|
+
3. Review
|
|
57
|
+
- Read the critical files needed to validate the design
|
|
58
|
+
- If prior plan feedback exists, incorporate it directly into the revised plan
|
|
59
|
+
4. Final plan
|
|
60
|
+
- Produce a plan that includes context, the recommended approach, critical file paths, reusable utilities or patterns, and verification
|
|
61
|
+
5. Exit
|
|
62
|
+
- End by returning the final plan in the required structured format only
|
|
63
|
+
|
|
64
|
+
Key rules:
|
|
65
|
+
- Ask for clarification only when a blocking unknown cannot be resolved from the repository context
|
|
66
|
+
- Do not ask for approval in free-form prose; the human approval flow happens outside your response
|
|
67
|
+
- Keep the plan specific, implementation-ready, and grounded in the current codebase`,
|
|
68
|
+
implementation: `Follow the plan step by step
|
|
69
|
+
- If required tools or dependencies are missing in the workspace, install them before continuing
|
|
70
|
+
- Commit after each logical unit of work with descriptive commit messages
|
|
71
|
+
- Run existing tests after implementation to verify nothing broke`,
|
|
72
|
+
review: `You are an expert code reviewer.
|
|
73
|
+
|
|
74
|
+
Step 1 — Gather the diff
|
|
75
|
+
- Run: git diff main
|
|
76
|
+
- Run: git diff --name-only main
|
|
77
|
+
- Review only the changes on the current branch versus main; do not flag pre-existing issues in unchanged code
|
|
78
|
+
- If a project rules file such as CLAUDE.md exists in the repository, read it and apply those rules during review
|
|
79
|
+
|
|
80
|
+
Step 2 — Review dimensions
|
|
81
|
+
- Correctness and bugs: check logic errors, edge cases, async misuse, null or undefined handling, and other behavioral regressions
|
|
82
|
+
- Project pattern compliance: verify changed code follows the repository's established architecture and avoids unnecessary abstractions or legacy patterns
|
|
83
|
+
- Test quality: verify tests meaningfully cover the changed behavior and relevant edge cases
|
|
84
|
+
- Silent failures and error handling: catch swallowed errors, missing propagation, and fallback values that can leak into user-visible behavior
|
|
85
|
+
- Code clarity and simplicity: prefer direct, maintainable code over over-engineered abstractions
|
|
86
|
+
- API and contract behavior: verify serialization, ordering, and other observable behavior remain intentional and consistent
|
|
87
|
+
|
|
88
|
+
Step 3 — Confidence scoring
|
|
89
|
+
- Score each potential issue from 0 to 100
|
|
90
|
+
- Only report issues with confidence 76 or higher
|
|
91
|
+
- Treat 91 to 100 as must-fix critical issues
|
|
92
|
+
- Treat 76 to 90 as important issues that should be fixed
|
|
93
|
+
|
|
94
|
+
Step 4 — Output requirements
|
|
95
|
+
- Include the changed files from git diff --name-only main in your review summary
|
|
96
|
+
- For each reported issue, include the file and line when possible, what is wrong, why it matters, and a concrete fix
|
|
97
|
+
- Include strengths observed in the branch
|
|
98
|
+
- Set VERDICT to PASS only when there are no critical issues`,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export function getDefaults() {
|
|
102
|
+
return {
|
|
103
|
+
repos: config.REPOS.length > 0 ? [...config.REPOS] : [],
|
|
104
|
+
defaultRepoPath: config.REPOS[0] || '',
|
|
105
|
+
workspaceRoot: DEFAULT_WORKSPACES_DIR,
|
|
106
|
+
agents: {
|
|
107
|
+
planners: { max: 4, cli: 'claude' },
|
|
108
|
+
implementors: { max: 8, cli: config.IMPLEMENTOR_1_CLI },
|
|
109
|
+
reviewers: { max: 4, cli: 'claude' },
|
|
110
|
+
},
|
|
111
|
+
prompts: { ...DEFAULT_PROMPTS },
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeDefaultRepoPath(repos, defaultRepoPath) {
|
|
116
|
+
if (!Array.isArray(repos) || repos.length === 0) return '';
|
|
117
|
+
if (typeof defaultRepoPath === 'string' && repos.includes(defaultRepoPath)) {
|
|
118
|
+
return defaultRepoPath;
|
|
119
|
+
}
|
|
120
|
+
return repos[0];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeSettingsShape(data) {
|
|
124
|
+
const defaults = getDefaults();
|
|
125
|
+
if (!Array.isArray(data.repos)) data.repos = defaults.repos;
|
|
126
|
+
if (typeof data.workspaceRoot !== 'string' || !data.workspaceRoot.trim()) {
|
|
127
|
+
data.workspaceRoot = typeof data.reposDir === 'string' && data.reposDir.trim()
|
|
128
|
+
? data.reposDir
|
|
129
|
+
: defaults.workspaceRoot;
|
|
130
|
+
}
|
|
131
|
+
data.defaultRepoPath = normalizeDefaultRepoPath(data.repos, data.defaultRepoPath);
|
|
132
|
+
|
|
133
|
+
for (const role of Object.keys(defaults.agents)) {
|
|
134
|
+
if (!data.agents?.[role]) {
|
|
135
|
+
data.agents = data.agents || {};
|
|
136
|
+
data.agents[role] = defaults.agents[role];
|
|
137
|
+
} else {
|
|
138
|
+
delete data.agents[role].count;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
data.prompts = {
|
|
143
|
+
...defaults.prompts,
|
|
144
|
+
...(data.prompts || {}),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return data;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function loadSettings() {
|
|
151
|
+
try {
|
|
152
|
+
if (existsSync(runtimePaths.settingsFile)) {
|
|
153
|
+
const data = JSON.parse(readFileSync(runtimePaths.settingsFile, 'utf-8'));
|
|
154
|
+
return normalizeSettingsShape(data);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Fall through to defaults
|
|
158
|
+
}
|
|
159
|
+
return getDefaults();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function saveSettings(settings) {
|
|
163
|
+
mkdirSync(runtimePaths.dataDir, { recursive: true });
|
|
164
|
+
writeFileSync(runtimePaths.settingsFile, JSON.stringify(normalizeSettingsShape(settings), null, 2));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function validateSettings(settings) {
|
|
168
|
+
const errors = [];
|
|
169
|
+
if (!settings?.agents) {
|
|
170
|
+
return ['Missing agents configuration'];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (typeof settings.workspaceRoot !== 'string' || !settings.workspaceRoot.trim()) {
|
|
174
|
+
errors.push('workspaceRoot is required');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!Array.isArray(settings.repos)) {
|
|
178
|
+
errors.push('repos must be an array');
|
|
179
|
+
}
|
|
180
|
+
if (typeof settings.defaultRepoPath !== 'string') {
|
|
181
|
+
errors.push('defaultRepoPath must be a string');
|
|
182
|
+
} else if (Array.isArray(settings.repos) && settings.defaultRepoPath && !settings.repos.includes(settings.defaultRepoPath)) {
|
|
183
|
+
errors.push('defaultRepoPath must match one of the configured repos');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const validClis = ['claude', 'codex'];
|
|
187
|
+
const allowedRanges = {
|
|
188
|
+
planners: { min: 0, max: 10 },
|
|
189
|
+
implementors: { min: 1, max: 10 },
|
|
190
|
+
reviewers: { min: 0, max: 10 },
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
for (const role of ['planners', 'implementors', 'reviewers']) {
|
|
194
|
+
const cfg = settings.agents[role];
|
|
195
|
+
if (!cfg) { errors.push(`Missing ${role} configuration`); continue; }
|
|
196
|
+
|
|
197
|
+
const range = allowedRanges[role];
|
|
198
|
+
if (typeof cfg.max !== 'number' || cfg.max < range.min || cfg.max > range.max) {
|
|
199
|
+
errors.push(`${role}.max must be between ${range.min} and ${range.max}`);
|
|
200
|
+
}
|
|
201
|
+
if (!validClis.includes(cfg.cli)) {
|
|
202
|
+
errors.push(`${role}.cli must be one of: ${validClis.join(', ')}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!settings.prompts || typeof settings.prompts !== 'object') {
|
|
207
|
+
errors.push('prompts configuration is required');
|
|
208
|
+
} else {
|
|
209
|
+
for (const stage of Object.keys(DEFAULT_PROMPTS)) {
|
|
210
|
+
if (typeof settings.prompts[stage] !== 'string') {
|
|
211
|
+
errors.push(`prompts.${stage} must be a string`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return errors;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getWorkspacesDir(settings = loadSettings()) {
|
|
220
|
+
return settings.workspaceRoot || settings.reposDir || DEFAULT_WORKSPACES_DIR;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function getRuntimeStatePaths() {
|
|
224
|
+
return { ...runtimePaths };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { DEFAULT_PROMPTS };
|
|
228
|
+
export default config;
|