@unson/brainbase-mcp 0.44.0 → 0.44.1
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/contracts/canonical-task/operation-schema.sql +58 -0
- package/contracts/canonical-task/search-indexes.sql +10 -0
- package/contracts/canonical-task/store-schema.sql +77 -0
- package/dist/automation-run.d.ts +213 -0
- package/dist/automation-run.js +479 -0
- package/dist/canonical-task-operations.d.ts +205 -0
- package/dist/canonical-task-operations.js +700 -0
- package/dist/canonical-task-postgres.d.ts +139 -0
- package/dist/canonical-task-postgres.js +554 -0
- package/dist/canonical-task-service.d.ts +13 -0
- package/dist/canonical-task-service.js +55 -3
- package/dist/canonical-task-store-config.d.ts +31 -0
- package/dist/canonical-task-store-config.js +53 -0
- package/dist/config-parser.d.ts +298 -0
- package/dist/config-parser.js +593 -0
- package/dist/connected-onboarding.d.ts +57 -0
- package/dist/connected-onboarding.js +37 -0
- package/dist/durable-wait-http.d.ts +3 -1
- package/dist/durable-wait-http.js +73 -6
- package/dist/durable-waits.d.ts +93 -3
- package/dist/durable-waits.js +320 -8
- package/dist/honcho.d.ts +25 -0
- package/dist/honcho.js +81 -0
- package/dist/judgment-event-adapter.d.ts +72 -0
- package/dist/judgment-event-adapter.js +114 -0
- package/dist/judgment-event-outbox.d.ts +182 -0
- package/dist/judgment-event-outbox.js +568 -0
- package/dist/judgment-execution-outcome.d.ts +46 -0
- package/dist/judgment-execution-outcome.js +149 -0
- package/dist/judgment-host.d.ts +18 -7
- package/dist/judgment-host.js +82 -34
- package/dist/judgment-lifecycle-core.d.ts +198 -0
- package/dist/judgment-lifecycle-core.js +464 -0
- package/dist/judgment-node-evidence.d.ts +57 -0
- package/dist/judgment-node-evidence.js +297 -0
- package/dist/judgment-runtime-resolution.d.ts +264 -0
- package/dist/judgment-runtime-resolution.js +1248 -0
- package/dist/judgment-runtime-resolution.public.d.ts +255 -0
- package/dist/knowledge-event.d.ts +28 -0
- package/dist/knowledge-event.js +114 -0
- package/dist/mcp-runtime.d.ts +75 -0
- package/dist/mcp-runtime.js +188 -0
- package/dist/meeting-automation-runtime.d.ts +102 -0
- package/dist/meeting-automation-runtime.js +249 -0
- package/dist/meeting-automation.d.ts +155 -0
- package/dist/meeting-automation.js +341 -0
- package/dist/meeting-minutes-context-receipt.d.ts +91 -0
- package/dist/meeting-minutes-context-receipt.js +280 -0
- package/dist/meeting-source-sync.d.ts +89 -0
- package/dist/meeting-source-sync.js +230 -0
- package/dist/mesh.d.ts +58 -0
- package/dist/mesh.js +134 -0
- package/dist/onboarding-runtime.d.ts +47 -0
- package/dist/onboarding-runtime.js +4719 -0
- package/dist/routine-cycle.d.ts +51 -0
- package/dist/routine-cycle.js +153 -0
- package/dist/routine-liveness.d.ts +49 -0
- package/dist/routine-liveness.js +227 -0
- package/dist/routine-runtime.d.ts +101 -0
- package/dist/routine-runtime.js +237 -0
- package/dist/routines.d.ts +3 -0
- package/dist/routines.js +3 -0
- package/dist/run-receipt.d.ts +40 -0
- package/dist/run-receipt.js +635 -0
- package/dist/runtime-host.d.ts +64 -0
- package/dist/runtime-host.js +70 -0
- package/dist/world-work.d.ts +16 -0
- package/package.json +115 -3
- package/ui/objective-editor.js +47 -11
- package/ui/world/world-district.js +18 -3
- package/ui/world/world-placement.js +13 -0
- package/ui/world/world-work-rail.js +7 -1
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Parser - brainbaseの設定ファイル(YAML)を読み取り、統合ビューを提供
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'fs/promises';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import yaml from 'js-yaml';
|
|
7
|
+
import { logger as defaultLogger } from './server-support.js';
|
|
8
|
+
function requirePath(name, value) {
|
|
9
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
10
|
+
throw new TypeError(`ConfigParser requires an explicit ${name} path`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
export class ConfigParser {
|
|
15
|
+
/**
|
|
16
|
+
* Construct a parser with every filesystem boundary supplied by the host.
|
|
17
|
+
*
|
|
18
|
+
* The OSS package deliberately has no workspace or repository defaults. A
|
|
19
|
+
* host that needs compatibility defaults must provide them in its adapter.
|
|
20
|
+
*
|
|
21
|
+
* @param paths filesystem boundaries and optional catalog mode supplied by the host
|
|
22
|
+
*/
|
|
23
|
+
configPath;
|
|
24
|
+
slackDir;
|
|
25
|
+
brainbaseRoot;
|
|
26
|
+
projectsRoot;
|
|
27
|
+
workspaceRoot;
|
|
28
|
+
catalogMode;
|
|
29
|
+
logger;
|
|
30
|
+
_configSourceStatus;
|
|
31
|
+
_configCache;
|
|
32
|
+
_configPromise;
|
|
33
|
+
constructor(paths) {
|
|
34
|
+
const { configPath, slackDir, brainbaseRoot, projectsRoot, catalogMode = 'required', logger = defaultLogger } = paths ?? {};
|
|
35
|
+
this.configPath = requirePath('configPath', configPath);
|
|
36
|
+
this.slackDir = requirePath('slackDir', slackDir);
|
|
37
|
+
this.brainbaseRoot = requirePath('brainbaseRoot', brainbaseRoot);
|
|
38
|
+
this.projectsRoot = requirePath('projectsRoot', projectsRoot);
|
|
39
|
+
this.catalogMode = catalogMode;
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this._configSourceStatus = this.catalogMode === 'disabled' ? 'not_applicable' : 'not_loaded';
|
|
42
|
+
// workspaceRoot = parent directory of brainbaseRoot (where 'general' project resides)
|
|
43
|
+
this.workspaceRoot = path.dirname(this.brainbaseRoot);
|
|
44
|
+
this._configCache = null;
|
|
45
|
+
this._configPromise = null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @template T
|
|
49
|
+
* @param {string} filePath
|
|
50
|
+
* @param {T} defaultValue
|
|
51
|
+
* @param {string} [label]
|
|
52
|
+
* @returns {Promise<T>}
|
|
53
|
+
*/
|
|
54
|
+
async _loadYamlFile(filePath, defaultValue, label) {
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
57
|
+
const data = yaml.load(content);
|
|
58
|
+
return data ?? defaultValue;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const target = label || filePath;
|
|
62
|
+
const error = err;
|
|
63
|
+
if (error?.code === 'ENOENT' && filePath.startsWith(this.slackDir)) {
|
|
64
|
+
this.logger.debug(`Optional config missing: ${target}`, { filePath });
|
|
65
|
+
return defaultValue;
|
|
66
|
+
}
|
|
67
|
+
this.logger.error(`Failed to load ${target}:`, { error: error.message || String(error) });
|
|
68
|
+
return defaultValue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* @template T
|
|
73
|
+
* @param {string} filename
|
|
74
|
+
* @param {T} defaultValue
|
|
75
|
+
* @returns {Promise<T>}
|
|
76
|
+
*/
|
|
77
|
+
async _loadSlackYaml(filename, defaultValue) {
|
|
78
|
+
return this._loadYamlFile(path.join(this.slackDir, filename), defaultValue, filename);
|
|
79
|
+
}
|
|
80
|
+
async _loadConfig() {
|
|
81
|
+
if (this._configCache) {
|
|
82
|
+
return this._configCache;
|
|
83
|
+
}
|
|
84
|
+
if (this.catalogMode === 'disabled') {
|
|
85
|
+
this._configSourceStatus = 'not_applicable';
|
|
86
|
+
this._configCache = {};
|
|
87
|
+
this.logger.debug('Project catalog is disabled for this runtime', {
|
|
88
|
+
mode: this.catalogMode
|
|
89
|
+
});
|
|
90
|
+
return this._configCache;
|
|
91
|
+
}
|
|
92
|
+
if (!this._configPromise) {
|
|
93
|
+
this._configPromise = fs.readFile(this.configPath, 'utf-8')
|
|
94
|
+
.then((content) => {
|
|
95
|
+
const data = yaml.load(content);
|
|
96
|
+
this._configSourceStatus = 'loaded';
|
|
97
|
+
return data ?? {};
|
|
98
|
+
})
|
|
99
|
+
.catch((err) => {
|
|
100
|
+
const error = err;
|
|
101
|
+
this._configSourceStatus = error?.code === 'ENOENT' ? 'missing' : 'invalid';
|
|
102
|
+
this.logger.error('Failed to load config.yml:', {
|
|
103
|
+
error: error.message || String(error),
|
|
104
|
+
configPath: this.configPath
|
|
105
|
+
});
|
|
106
|
+
return {};
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
this._configCache = await this._configPromise;
|
|
110
|
+
return this._configCache;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* SPEC-settings-phase0-guards INV-4: ConfigService.write 後に呼ぶことで stale cache を回避
|
|
114
|
+
*/
|
|
115
|
+
invalidateCache() {
|
|
116
|
+
this._configCache = null;
|
|
117
|
+
this._configPromise = null;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @param {ProjectConfig[]} [projects]
|
|
121
|
+
* @returns {ProjectConfig[]}
|
|
122
|
+
*/
|
|
123
|
+
_expandProjectPaths(projects = []) {
|
|
124
|
+
return projects.map(project => {
|
|
125
|
+
if (project.local?.path) {
|
|
126
|
+
const expandedPath = project.local.path.replace(/\$\{PROJECTS_ROOT:-[^}]+\}/g, this.projectsRoot);
|
|
127
|
+
return {
|
|
128
|
+
...project,
|
|
129
|
+
local: {
|
|
130
|
+
...project.local,
|
|
131
|
+
path: expandedPath
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return project;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Slackワークスペース一覧を取得
|
|
140
|
+
*/
|
|
141
|
+
async getWorkspaces() {
|
|
142
|
+
const data = await this._loadSlackYaml('workspaces.yml', { workspaces: {} });
|
|
143
|
+
return data?.workspaces || {};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Slackチャンネル一覧を取得
|
|
147
|
+
*/
|
|
148
|
+
async getChannels() {
|
|
149
|
+
const data = await this._loadSlackYaml('channels.yml', { channels: [] });
|
|
150
|
+
return data?.channels || [];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Slackメンバー一覧を取得
|
|
154
|
+
*/
|
|
155
|
+
async getMembers() {
|
|
156
|
+
const data = await this._loadSlackYaml('members.yml', { members: [] });
|
|
157
|
+
return data?.members || [];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* プロジェクト設定(config.yml)を取得
|
|
161
|
+
*/
|
|
162
|
+
async getProjects() {
|
|
163
|
+
const data = await this._loadConfig();
|
|
164
|
+
const projects = this._expandProjectPaths(data.projects || []).map(project => {
|
|
165
|
+
// Legacyの未認証設定APIへProject Profileのtenant・人物・接続意図を露出させない。
|
|
166
|
+
const { capabilities: _capabilities, people: _people, organization: _organization, created_by: _createdBy, success_criteria: _successCriteria, project_code: _projectCode, ...legacyProject } = project;
|
|
167
|
+
return legacyProject;
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
root: this.workspaceRoot, // Workspace root directory (where 'general' project resides)
|
|
171
|
+
projects
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* GitHubマッピングを取得(config.ymlから抽出)
|
|
176
|
+
*/
|
|
177
|
+
async getGitHubMappings() {
|
|
178
|
+
const data = await this._loadConfig();
|
|
179
|
+
const projects = data.projects || [];
|
|
180
|
+
return projects
|
|
181
|
+
.filter(p => p.github)
|
|
182
|
+
.map(p => ({
|
|
183
|
+
project_id: p.id,
|
|
184
|
+
owner: p.github?.owner,
|
|
185
|
+
repo: p.github?.repo,
|
|
186
|
+
branch: p.github?.branch || 'main',
|
|
187
|
+
url: `https://github.com/${p.github?.owner}/${p.github?.repo}`
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Airtableマッピングを取得(config.ymlから抽出)
|
|
192
|
+
*/
|
|
193
|
+
async getAirtableMappings() {
|
|
194
|
+
const data = await this._loadConfig();
|
|
195
|
+
const projects = data.projects || [];
|
|
196
|
+
return projects
|
|
197
|
+
.filter(p => p.airtable)
|
|
198
|
+
.map(p => ({
|
|
199
|
+
project_id: p.id,
|
|
200
|
+
base_id: p.airtable?.base_id,
|
|
201
|
+
base_name: p.airtable?.base_name,
|
|
202
|
+
url: `https://airtable.com/${p.airtable?.base_id}`
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* UI Plugins設定を取得(config.ymlから抽出)
|
|
207
|
+
* @returns {Promise<any>} { enabled: string[] } - 有効なプラグインID一覧
|
|
208
|
+
*/
|
|
209
|
+
async getPlugins() {
|
|
210
|
+
const data = await this._loadConfig();
|
|
211
|
+
const plugins = data.plugins || {};
|
|
212
|
+
if (!plugins.enabled && !plugins.disabled) {
|
|
213
|
+
// OSSの初期状態: 必要最小限のプラグインのみ有効化
|
|
214
|
+
return {
|
|
215
|
+
enabled: ['bb-inbox'],
|
|
216
|
+
disabled: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
enabled: Array.isArray(plugins.enabled) ? plugins.enabled : [],
|
|
221
|
+
disabled: Array.isArray(plugins.disabled) ? plugins.disabled : []
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 統合ビュー: Workspace → Project → Slack/GitHub/Airtable の関係を一覧
|
|
226
|
+
*/
|
|
227
|
+
async getUnifiedView() {
|
|
228
|
+
const [workspaces, channels, projectConfig, githubMappings] = await Promise.all([
|
|
229
|
+
this.getWorkspaces(),
|
|
230
|
+
this.getChannels(),
|
|
231
|
+
this.getProjects(),
|
|
232
|
+
this.getGitHubMappings()
|
|
233
|
+
]);
|
|
234
|
+
// プロジェクト情報をIDでインデックス化
|
|
235
|
+
const projectsById = {};
|
|
236
|
+
for (const p of projectConfig.projects) {
|
|
237
|
+
if (p.id) {
|
|
238
|
+
projectsById[p.id] = p;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// チャンネルをproject_idでグループ化
|
|
242
|
+
const channelsByProjectId = {};
|
|
243
|
+
for (const ch of channels) {
|
|
244
|
+
// proj_xxx → xxx に正規化
|
|
245
|
+
const normalizedId = ch.project_id?.replace(/^proj_/, '') || '';
|
|
246
|
+
if (!channelsByProjectId[normalizedId]) {
|
|
247
|
+
channelsByProjectId[normalizedId] = [];
|
|
248
|
+
}
|
|
249
|
+
channelsByProjectId[normalizedId].push({
|
|
250
|
+
id: ch.channel_id,
|
|
251
|
+
name: ch.channel_name,
|
|
252
|
+
type: ch.type || 'general',
|
|
253
|
+
workspace: ch.workspace
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// GitHubマッピングをproject_idでインデックス化
|
|
257
|
+
const githubByProjectId = {};
|
|
258
|
+
for (const g of githubMappings) {
|
|
259
|
+
if (g.project_id) {
|
|
260
|
+
githubByProjectId[g.project_id] = g;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ワークスペースごとにプロジェクトを整理
|
|
264
|
+
const result = {
|
|
265
|
+
workspaces: [],
|
|
266
|
+
orphanedChannels: [],
|
|
267
|
+
orphanedProjects: []
|
|
268
|
+
};
|
|
269
|
+
// 使用済みプロジェクトを追跡(正規化後のID)
|
|
270
|
+
const usedProjectIds = new Set();
|
|
271
|
+
// ID正規化ヘルパー: proj_zeims → zeims
|
|
272
|
+
const normalizeId = (id) => id.replace(/^proj_/, '');
|
|
273
|
+
for (const [wsKey, ws] of Object.entries(workspaces)) {
|
|
274
|
+
const wsProjects = [];
|
|
275
|
+
// ワークスペースに紐づくプロジェクトを処理
|
|
276
|
+
const projectIds = ws.projects || [];
|
|
277
|
+
for (const projId of projectIds) {
|
|
278
|
+
// proj_zeims → zeims に正規化してマッチング
|
|
279
|
+
const normalizedProjId = normalizeId(projId);
|
|
280
|
+
usedProjectIds.add(normalizedProjId);
|
|
281
|
+
const project = projectsById[normalizedProjId];
|
|
282
|
+
const github = githubByProjectId[normalizedProjId];
|
|
283
|
+
const projectChannels = channelsByProjectId[normalizedProjId] || [];
|
|
284
|
+
// glob_include からパスを抽出
|
|
285
|
+
let paths = null;
|
|
286
|
+
if (project?.local?.glob_include) {
|
|
287
|
+
paths = project.local.glob_include.map(g => {
|
|
288
|
+
const match = g.match(/^([^*]+)/);
|
|
289
|
+
return match ? match[1].replace(/\/$/, '') + '/' : null;
|
|
290
|
+
}).filter((p) => p !== null);
|
|
291
|
+
}
|
|
292
|
+
wsProjects.push({
|
|
293
|
+
id: projId,
|
|
294
|
+
emoji: project?.emoji || null,
|
|
295
|
+
archived: project?.archived || false,
|
|
296
|
+
channels: projectChannels.filter(ch => ch.workspace === wsKey),
|
|
297
|
+
github: github ? {
|
|
298
|
+
owner: github.owner,
|
|
299
|
+
repo: github.repo,
|
|
300
|
+
branch: github.branch,
|
|
301
|
+
url: github.url,
|
|
302
|
+
paths: paths
|
|
303
|
+
} : null
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
result.workspaces.push({
|
|
307
|
+
key: wsKey,
|
|
308
|
+
name: ws.name,
|
|
309
|
+
id: ws.id,
|
|
310
|
+
projects: wsProjects
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
// 孤立プロジェクト(どのワークスペースにも属していない)
|
|
314
|
+
for (const p of projectConfig.projects) {
|
|
315
|
+
const projectId = p.id;
|
|
316
|
+
if (!projectId || !usedProjectIds.has(projectId)) {
|
|
317
|
+
result.orphanedProjects.push({
|
|
318
|
+
id: projectId,
|
|
319
|
+
hasGithub: projectId ? !!githubByProjectId[projectId] : false,
|
|
320
|
+
channelCount: projectId ? (channelsByProjectId[projectId] || []).length : 0
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// 孤立チャンネル(proj_otherや未マッピング)
|
|
325
|
+
const allMappedProjectIds = new Set([
|
|
326
|
+
...Object.keys(projectsById),
|
|
327
|
+
...Object.values(workspaces).flatMap(ws => ws.projects || [])
|
|
328
|
+
]);
|
|
329
|
+
for (const ch of channels) {
|
|
330
|
+
const normalizedId = ch.project_id?.replace(/^proj_/, '') || '';
|
|
331
|
+
if (!allMappedProjectIds.has(normalizedId) && normalizedId !== 'other') {
|
|
332
|
+
result.orphanedChannels.push({
|
|
333
|
+
id: ch.channel_id,
|
|
334
|
+
name: ch.channel_name,
|
|
335
|
+
workspace: ch.workspace,
|
|
336
|
+
project_id: ch.project_id
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return result;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Organizations(法人)設定を取得
|
|
344
|
+
* @returns {Promise<any[]>} 法人一覧
|
|
345
|
+
*/
|
|
346
|
+
async getOrganizations() {
|
|
347
|
+
const data = await this._loadConfig();
|
|
348
|
+
return data.organizations || [];
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Dependencies(依存関係)設定を取得
|
|
352
|
+
* @returns {Promise<any>} 依存関係マッピング
|
|
353
|
+
*/
|
|
354
|
+
async getDependencies() {
|
|
355
|
+
const data = await this._loadConfig();
|
|
356
|
+
return data.dependencies || {};
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Notifications(通知)設定を取得
|
|
360
|
+
* @returns {Promise<any>} 通知設定
|
|
361
|
+
*/
|
|
362
|
+
async getNotifications() {
|
|
363
|
+
const data = await this._loadConfig();
|
|
364
|
+
return data.notifications || {
|
|
365
|
+
channels: {},
|
|
366
|
+
dnd: {}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 全設定を統合して取得
|
|
371
|
+
*/
|
|
372
|
+
async getAll() {
|
|
373
|
+
const [workspaces, channels, members, projectConfig, githubMappings, airtableMappings, plugins] = await Promise.all([
|
|
374
|
+
this.getWorkspaces(),
|
|
375
|
+
this.getChannels(),
|
|
376
|
+
this.getMembers(),
|
|
377
|
+
this.getProjects(),
|
|
378
|
+
this.getGitHubMappings(),
|
|
379
|
+
this.getAirtableMappings(),
|
|
380
|
+
this.getPlugins()
|
|
381
|
+
]);
|
|
382
|
+
return {
|
|
383
|
+
slack: {
|
|
384
|
+
workspaces,
|
|
385
|
+
channels,
|
|
386
|
+
members
|
|
387
|
+
},
|
|
388
|
+
projects: projectConfig,
|
|
389
|
+
github: githubMappings,
|
|
390
|
+
airtable: airtableMappings,
|
|
391
|
+
plugins
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* 整合性チェック
|
|
396
|
+
* - チャンネルに定義されているが、プロジェクトに存在しないproject_id
|
|
397
|
+
* - ワークスペースに定義されているが、チャンネルがないワークスペース
|
|
398
|
+
* - 重複したSlack ID
|
|
399
|
+
* - GitHubマッピングの検証
|
|
400
|
+
*/
|
|
401
|
+
async checkIntegrity() {
|
|
402
|
+
if (this.catalogMode === 'disabled') {
|
|
403
|
+
return {
|
|
404
|
+
applicability: 'not_applicable',
|
|
405
|
+
source: {
|
|
406
|
+
status: 'not_applicable',
|
|
407
|
+
path: this.configPath,
|
|
408
|
+
mode: this.catalogMode
|
|
409
|
+
},
|
|
410
|
+
issues: [],
|
|
411
|
+
summary: { total: 0, errors: 0, warnings: 0, info: 0 },
|
|
412
|
+
stats: {
|
|
413
|
+
workspaces: 0,
|
|
414
|
+
channels: 0,
|
|
415
|
+
members: 0,
|
|
416
|
+
projects: 0,
|
|
417
|
+
github: 0,
|
|
418
|
+
airtable: 0
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const [workspaces, channels, members, projectConfig, githubMappings, airtableMappings] = await Promise.all([
|
|
423
|
+
this.getWorkspaces(),
|
|
424
|
+
this.getChannels(),
|
|
425
|
+
this.getMembers(),
|
|
426
|
+
this.getProjects(),
|
|
427
|
+
this.getGitHubMappings(),
|
|
428
|
+
this.getAirtableMappings()
|
|
429
|
+
]);
|
|
430
|
+
const issues = [];
|
|
431
|
+
if (this._configSourceStatus !== 'loaded') {
|
|
432
|
+
issues.push({
|
|
433
|
+
type: 'config_source_unavailable',
|
|
434
|
+
severity: 'error',
|
|
435
|
+
message: `Required project catalog is ${this._configSourceStatus}: ${this.configPath}`,
|
|
436
|
+
source: 'config.yml'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
// プロジェクトIDのセットを作成
|
|
440
|
+
const configProjectIds = new Set(projectConfig.projects
|
|
441
|
+
.map(p => p.id)
|
|
442
|
+
.filter((id) => typeof id === 'string'));
|
|
443
|
+
// ワークスペースで定義されているプロジェクトIDのセット
|
|
444
|
+
const workspaceProjectIds = new Set();
|
|
445
|
+
Object.values(workspaces).forEach(ws => {
|
|
446
|
+
(ws.projects || []).forEach(pid => workspaceProjectIds.add(pid));
|
|
447
|
+
});
|
|
448
|
+
// チャンネルで使われているプロジェクトIDのセット
|
|
449
|
+
const channelProjectIds = new Set();
|
|
450
|
+
channels.forEach(ch => {
|
|
451
|
+
if (ch.project_id) {
|
|
452
|
+
channelProjectIds.add(ch.project_id);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
// 1. チャンネルのproject_idがconfig.ymlに存在するか
|
|
456
|
+
// proj_otherは意図的な「その他」カテゴリなので除外
|
|
457
|
+
const ignoredProjectIds = new Set(['proj_other', 'other']);
|
|
458
|
+
channelProjectIds.forEach(pid => {
|
|
459
|
+
// 除外リストにあるプロジェクトはスキップ
|
|
460
|
+
if (ignoredProjectIds.has(pid))
|
|
461
|
+
return;
|
|
462
|
+
// proj_xxx → xxxに変換してチェック
|
|
463
|
+
const shortId = pid.replace(/^proj_/, '');
|
|
464
|
+
if (!configProjectIds.has(shortId) && !configProjectIds.has(pid)) {
|
|
465
|
+
issues.push({
|
|
466
|
+
type: 'missing_project',
|
|
467
|
+
severity: 'warning',
|
|
468
|
+
message: `Channel uses project_id "${pid}" but not found in config.yml`,
|
|
469
|
+
source: 'channels.yml'
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
// 2. ワークスペースごとのチャンネル数をカウント
|
|
474
|
+
const workspaceChannelCount = {};
|
|
475
|
+
channels.forEach(ch => {
|
|
476
|
+
if (ch.workspace) {
|
|
477
|
+
workspaceChannelCount[ch.workspace] = (workspaceChannelCount[ch.workspace] || 0) + 1;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
Object.keys(workspaces).forEach(wsKey => {
|
|
481
|
+
if (!workspaceChannelCount[wsKey]) {
|
|
482
|
+
issues.push({
|
|
483
|
+
type: 'empty_workspace',
|
|
484
|
+
severity: 'info',
|
|
485
|
+
message: `Workspace "${wsKey}" has no mapped channels`,
|
|
486
|
+
source: 'workspaces.yml'
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
// 3. メンバーの重複チェック(同じslack_idが複数ある場合)
|
|
491
|
+
const slackIdCount = {};
|
|
492
|
+
members.forEach(m => {
|
|
493
|
+
if (m.slack_id) {
|
|
494
|
+
slackIdCount[m.slack_id] = (slackIdCount[m.slack_id] || 0) + 1;
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
Object.entries(slackIdCount).forEach(([slackId, count]) => {
|
|
498
|
+
if (count > 1) {
|
|
499
|
+
const memberNames = members
|
|
500
|
+
.filter(m => m.slack_id === slackId)
|
|
501
|
+
.map(m => m.brainbase_name)
|
|
502
|
+
.filter((name) => typeof name === 'string');
|
|
503
|
+
// 同一人物のサブアカウントの場合は問題なし
|
|
504
|
+
const uniqueNames = [...new Set(memberNames)];
|
|
505
|
+
if (uniqueNames.length > 1) {
|
|
506
|
+
issues.push({
|
|
507
|
+
type: 'duplicate_slack_id',
|
|
508
|
+
severity: 'error',
|
|
509
|
+
message: `Slack ID "${slackId}" is mapped to multiple people: ${uniqueNames.join(', ')}`,
|
|
510
|
+
source: 'members.yml'
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
// 4. チャンネルの重複チェック
|
|
516
|
+
const channelIdCount = {};
|
|
517
|
+
channels.forEach(ch => {
|
|
518
|
+
if (ch.channel_id) {
|
|
519
|
+
channelIdCount[ch.channel_id] = (channelIdCount[ch.channel_id] || 0) + 1;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
Object.entries(channelIdCount).forEach(([channelId, count]) => {
|
|
523
|
+
if (count > 1) {
|
|
524
|
+
issues.push({
|
|
525
|
+
type: 'duplicate_channel_id',
|
|
526
|
+
severity: 'warning',
|
|
527
|
+
message: `Channel ID "${channelId}" appears ${count} times`,
|
|
528
|
+
source: 'channels.yml'
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
// 5. GitHubマッピングの検証
|
|
533
|
+
// 5a. ローカルパスがあるがGitHubがないプロジェクト
|
|
534
|
+
const projectsWithLocal = projectConfig.projects.filter(p => p.local?.path);
|
|
535
|
+
const githubProjectIds = new Set(githubMappings
|
|
536
|
+
.map(g => g.project_id)
|
|
537
|
+
.filter((id) => typeof id === 'string'));
|
|
538
|
+
projectsWithLocal.forEach(p => {
|
|
539
|
+
if (!p.id || !githubProjectIds.has(p.id)) {
|
|
540
|
+
issues.push({
|
|
541
|
+
type: 'missing_github',
|
|
542
|
+
severity: 'info',
|
|
543
|
+
message: `Project "${p.id}" has local path but no GitHub mapping`,
|
|
544
|
+
source: 'config.yml'
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
// 5b. GitHubマッピングの重複チェック
|
|
549
|
+
const repoCount = {};
|
|
550
|
+
githubMappings.forEach(g => {
|
|
551
|
+
const key = `${g.owner}/${g.repo}`;
|
|
552
|
+
if (!repoCount[key])
|
|
553
|
+
repoCount[key] = [];
|
|
554
|
+
if (g.project_id) {
|
|
555
|
+
repoCount[key].push(g.project_id);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
Object.entries(repoCount).forEach(([repo, projectIds]) => {
|
|
559
|
+
if (projectIds.length > 1) {
|
|
560
|
+
issues.push({
|
|
561
|
+
type: 'shared_github_repo',
|
|
562
|
+
severity: 'info',
|
|
563
|
+
message: `GitHub repo "${repo}" is shared by projects: ${projectIds.join(', ')}`,
|
|
564
|
+
source: 'config.yml'
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
// 6. Airtableマッピングの検証は無効化(2026-01-17)
|
|
569
|
+
return {
|
|
570
|
+
applicability: 'applicable',
|
|
571
|
+
source: {
|
|
572
|
+
status: this._configSourceStatus,
|
|
573
|
+
path: this.configPath,
|
|
574
|
+
mode: this.catalogMode
|
|
575
|
+
},
|
|
576
|
+
issues,
|
|
577
|
+
summary: {
|
|
578
|
+
total: issues.length,
|
|
579
|
+
errors: issues.filter(i => i.severity === 'error').length,
|
|
580
|
+
warnings: issues.filter(i => i.severity === 'warning').length,
|
|
581
|
+
info: issues.filter(i => i.severity === 'info').length
|
|
582
|
+
},
|
|
583
|
+
stats: {
|
|
584
|
+
workspaces: Object.keys(workspaces).length,
|
|
585
|
+
channels: channels.length,
|
|
586
|
+
members: members.length,
|
|
587
|
+
projects: projectConfig.projects.length,
|
|
588
|
+
github: githubMappings.length,
|
|
589
|
+
airtable: airtableMappings.length
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
@@ -1,3 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Candidate content scanning shared by onboarding hosts.
|
|
3
|
+
*
|
|
4
|
+
* The Org server keeps the same legacy result shape (`block`, `review`, and
|
|
5
|
+
* named counts), while the implementation and detector vocabulary live in the
|
|
6
|
+
* OSS onboarding boundary. Keep these detectors separate from the recursive
|
|
7
|
+
* persistent-value guard above: the former reports review findings, while the
|
|
8
|
+
* latter rejects unsafe ledger values before they are written.
|
|
9
|
+
*/
|
|
10
|
+
export type OnboardingScanKind = 'block' | 'review';
|
|
11
|
+
export interface OnboardingScanFinding {
|
|
12
|
+
kind: OnboardingScanKind;
|
|
13
|
+
name: string;
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
export interface OnboardingContentScan {
|
|
17
|
+
block: boolean;
|
|
18
|
+
review: boolean;
|
|
19
|
+
findings: OnboardingScanFinding[];
|
|
20
|
+
}
|
|
21
|
+
export declare const ONBOARDING_BLOCK_PATTERNS: readonly [{
|
|
22
|
+
readonly name: "openai_api_key";
|
|
23
|
+
readonly regex: RegExp;
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "github_pat";
|
|
26
|
+
readonly regex: RegExp;
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "anthropic_api_key";
|
|
29
|
+
readonly regex: RegExp;
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "aws_access_key";
|
|
32
|
+
readonly regex: RegExp;
|
|
33
|
+
}, {
|
|
34
|
+
readonly name: "slack_token";
|
|
35
|
+
readonly regex: RegExp;
|
|
36
|
+
}, {
|
|
37
|
+
readonly name: "private_key_block";
|
|
38
|
+
readonly regex: RegExp;
|
|
39
|
+
}, {
|
|
40
|
+
readonly name: "password_assignment";
|
|
41
|
+
readonly regex: RegExp;
|
|
42
|
+
}, {
|
|
43
|
+
readonly name: "infisical_ref";
|
|
44
|
+
readonly regex: RegExp;
|
|
45
|
+
}];
|
|
46
|
+
export declare const ONBOARDING_PII_PATTERNS: readonly [{
|
|
47
|
+
readonly name: "jp_phone";
|
|
48
|
+
readonly regex: RegExp;
|
|
49
|
+
}, {
|
|
50
|
+
readonly name: "email";
|
|
51
|
+
readonly regex: RegExp;
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "credit_card_like";
|
|
54
|
+
readonly regex: RegExp;
|
|
55
|
+
}];
|
|
56
|
+
/** Scan candidate text without exposing raw content in the finding output. */
|
|
57
|
+
export declare function scanOnboardingContent(content: unknown): OnboardingContentScan;
|
|
1
58
|
declare const WORKFLOW_STATES: readonly ["initialized", "source_ready", "candidates_ready", "promotion_reviewed", "first_value_ready", "first_value_answer_reviewed"];
|
|
2
59
|
export type SourceMode = 'mcp' | 'drive' | 'gmail' | 'local_folder' | 'single_document';
|
|
3
60
|
export type SourceStatus = 'ready' | 'waiting_for_authorization' | 'unavailable' | 'error' | 'unconfirmed';
|
|
@@ -15,6 +15,43 @@ const RAW_CONTENT_KEY = /^(?:raw(?:_body|_content|_text)?|body|content|document|
|
|
|
15
15
|
const SECRET_VALUE = /(?:^|\s)Bearer\s+[A-Za-z0-9._~+/-]+=*|\bsk-[A-Za-z0-9_-]{16,}\b/i;
|
|
16
16
|
const SECRET_ASSIGNMENT = /(?:access[_-]?token|refresh[_-]?token|api[_-]?key|client[_-]?secret|secret|password|credential|authorization|cookie)\s*(?:=|:)\s*[^\s&#;,]+/i;
|
|
17
17
|
const URI_USERINFO = /^[a-z][a-z0-9+.-]*:\/\/[^/?#\s]*:[^@/?#\s]+@/i;
|
|
18
|
+
export const ONBOARDING_BLOCK_PATTERNS = Object.freeze([
|
|
19
|
+
{ name: 'openai_api_key', regex: /sk-[A-Za-z0-9]{20,}/g },
|
|
20
|
+
{ name: 'github_pat', regex: /ghp_[A-Za-z0-9]{20,}/g },
|
|
21
|
+
{ name: 'anthropic_api_key', regex: /sk-ant-[A-Za-z0-9_-]{20,}/g },
|
|
22
|
+
{ name: 'aws_access_key', regex: /AKIA[0-9A-Z]{16}/g },
|
|
23
|
+
{ name: 'slack_token', regex: /xox[abpsr]-[A-Za-z0-9-]{10,}/g },
|
|
24
|
+
{ name: 'private_key_block', regex: /-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/g },
|
|
25
|
+
{ name: 'password_assignment', regex: /(?:password|passwd|secret)\s*=\s*['"][^'"\s]{6,}['"]/gi },
|
|
26
|
+
{ name: 'infisical_ref', regex: /infisical:\/\/[^\s]+/gi }
|
|
27
|
+
]);
|
|
28
|
+
export const ONBOARDING_PII_PATTERNS = Object.freeze([
|
|
29
|
+
{ name: 'jp_phone', regex: /0\d{1,4}-\d{1,4}-\d{3,4}/g },
|
|
30
|
+
{ name: 'email', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g },
|
|
31
|
+
{ name: 'credit_card_like', regex: /\b(?:\d{4}[- ]?){3}\d{4}\b/g }
|
|
32
|
+
]);
|
|
33
|
+
/** Scan candidate text without exposing raw content in the finding output. */
|
|
34
|
+
export function scanOnboardingContent(content) {
|
|
35
|
+
if (typeof content !== 'string' || content.length === 0) {
|
|
36
|
+
return { block: false, review: false, findings: [] };
|
|
37
|
+
}
|
|
38
|
+
const findings = [];
|
|
39
|
+
for (const { name, regex } of ONBOARDING_BLOCK_PATTERNS) {
|
|
40
|
+
const matches = content.match(regex);
|
|
41
|
+
if (matches && matches.length > 0)
|
|
42
|
+
findings.push({ kind: 'block', name, count: matches.length });
|
|
43
|
+
}
|
|
44
|
+
for (const { name, regex } of ONBOARDING_PII_PATTERNS) {
|
|
45
|
+
const matches = content.match(regex);
|
|
46
|
+
if (matches && matches.length > 0)
|
|
47
|
+
findings.push({ kind: 'review', name, count: matches.length });
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
block: findings.some((finding) => finding.kind === 'block'),
|
|
51
|
+
review: findings.some((finding) => finding.kind === 'review'),
|
|
52
|
+
findings
|
|
53
|
+
};
|
|
54
|
+
}
|
|
18
55
|
const PERMISSION_KEYS = new Set([
|
|
19
56
|
'visibility', 'collectedBy', 'provider', 'connectionId', 'grantId', 'accountId',
|
|
20
57
|
'folderId', 'projectCode', 'roleMin', 'sensitivity', 'scope', 'scopes', 'roles',
|