feat: add filesystem resource layout and docs query tool; replace NEW_FEAT with FEAT
This commit is contained in:
111
src/tools/resources.ts
Normal file
111
src/tools/resources.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { YonexusError } from '../models/errors';
|
||||
import type { DocsScope, DocsTopic } from '../models/types';
|
||||
import { slug } from '../utils/id';
|
||||
|
||||
const TOPICS: DocsTopic[] = ['docs', 'notes', 'knowledge', 'rules', 'lessons', 'workflows'];
|
||||
|
||||
function ensureDirs(base: string, dirs: string[]): void {
|
||||
for (const d of dirs) fs.mkdirSync(path.join(base, d), { recursive: true });
|
||||
}
|
||||
|
||||
export class ResourceLayout {
|
||||
constructor(private readonly rootDir: string) {}
|
||||
|
||||
get organizationsRoot(): string {
|
||||
return path.join(this.rootDir, 'organizations');
|
||||
}
|
||||
|
||||
orgPath(orgName: string): string {
|
||||
return path.join(this.organizationsRoot, slug(orgName));
|
||||
}
|
||||
|
||||
teamPath(orgName: string, teamName: string): string {
|
||||
return path.join(this.orgPath(orgName), 'teams', slug(teamName));
|
||||
}
|
||||
|
||||
agentPath(orgName: string, teamName: string, agentId: string): string {
|
||||
return path.join(this.teamPath(orgName, teamName), 'agents', slug(agentId));
|
||||
}
|
||||
|
||||
ensureOrganization(orgName: string): void {
|
||||
const root = this.orgPath(orgName);
|
||||
ensureDirs(root, ['teams', ...TOPICS]);
|
||||
}
|
||||
|
||||
ensureTeam(orgName: string, teamName: string): void {
|
||||
const root = this.teamPath(orgName, teamName);
|
||||
ensureDirs(root, ['agents', ...TOPICS]);
|
||||
}
|
||||
|
||||
ensureAgent(orgName: string, teamName: string, agentId: string): void {
|
||||
const root = this.agentPath(orgName, teamName, agentId);
|
||||
ensureDirs(root, TOPICS);
|
||||
}
|
||||
|
||||
private readTopicFiles(topicRoot: string, keyword: string): string[] {
|
||||
if (!fs.existsSync(topicRoot)) return [];
|
||||
let re: RegExp;
|
||||
try {
|
||||
re = new RegExp(keyword);
|
||||
} catch {
|
||||
throw new YonexusError('VALIDATION_ERROR', 'invalid_regex', { keyword });
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(topicRoot, { withFileTypes: true });
|
||||
return entries
|
||||
.filter((e) => e.isFile() && re.test(e.name))
|
||||
.map((e) => path.join(topicRoot, e.name));
|
||||
}
|
||||
|
||||
getDocs(scope: DocsScope, topic: DocsTopic, keyword: string): string {
|
||||
const groups: Record<'ORG' | 'DEPT' | 'TEAM' | 'AGENT', string[]> = {
|
||||
ORG: [],
|
||||
DEPT: [],
|
||||
TEAM: [],
|
||||
AGENT: []
|
||||
};
|
||||
|
||||
const orgsRoot = this.organizationsRoot;
|
||||
if (!fs.existsSync(orgsRoot)) {
|
||||
return '----ORG\n----DEPT\n----TEAM\n----AGENT';
|
||||
}
|
||||
|
||||
const orgs = fs.readdirSync(orgsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
||||
|
||||
for (const orgName of orgs) {
|
||||
const orgPath = path.join(orgsRoot, orgName);
|
||||
if (scope === 'organization') {
|
||||
groups.ORG.push(...this.readTopicFiles(path.join(orgPath, topic), keyword));
|
||||
}
|
||||
|
||||
const teamsRoot = path.join(orgPath, 'teams');
|
||||
if (!fs.existsSync(teamsRoot)) continue;
|
||||
|
||||
const teams = fs.readdirSync(teamsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
||||
for (const teamName of teams) {
|
||||
const teamPath = path.join(teamsRoot, teamName);
|
||||
if (scope === 'team') {
|
||||
groups.TEAM.push(...this.readTopicFiles(path.join(teamPath, topic), keyword));
|
||||
}
|
||||
|
||||
const agentsRoot = path.join(teamPath, 'agents');
|
||||
if (!fs.existsSync(agentsRoot)) continue;
|
||||
|
||||
const agents = fs.readdirSync(agentsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
||||
for (const agentId of agents) {
|
||||
if (scope === 'agent') {
|
||||
groups.AGENT.push(...this.readTopicFiles(path.join(agentsRoot, agentId, topic), keyword));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// department folders are not defined in this layout; reserved empty group for compatible output.
|
||||
const printGroup = (k: 'ORG' | 'DEPT' | 'TEAM' | 'AGENT'): string =>
|
||||
[`----${k}`, ...groups[k]].join('\n');
|
||||
|
||||
return [printGroup('ORG'), printGroup('DEPT'), printGroup('TEAM'), printGroup('AGENT')].join('\n');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user