@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.
@@ -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;
@@ -0,0 +1,6 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ const bus = new EventEmitter();
4
+ bus.setMaxListeners(50);
5
+
6
+ export default bus;