tycono 0.3.42 → 0.3.43-beta.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/package.json +1 -1
- package/src/api/src/create-server.ts +2 -2
- package/src/api/src/engine/agent-loop.ts +3 -3
- package/src/api/src/engine/context-assembler.ts +5 -5
- package/src/api/src/engine/knowledge-gate.ts +3 -3
- package/src/api/src/engine/org-tree.ts +2 -2
- package/src/api/src/engine/role-lifecycle.ts +7 -7
- package/src/api/src/engine/tools/definitions.ts +1 -1
- package/src/api/src/routes/company.ts +3 -3
- package/src/api/src/routes/execute.ts +3 -3
- package/src/api/src/routes/operations.ts +11 -11
- package/src/api/src/routes/presets.ts +3 -3
- package/src/api/src/routes/projects.ts +4 -4
- package/src/api/src/routes/roles.ts +5 -5
- package/src/api/src/routes/skills.ts +3 -3
- package/src/api/src/routes/speech.ts +14 -14
- package/src/api/src/routes/sync.ts +1 -1
- package/src/api/src/services/activity-stream.ts +1 -1
- package/src/api/src/services/activity-tracker.ts +1 -1
- package/src/api/src/services/execution-manager.ts +2 -2
- package/src/api/src/services/git-save.ts +0 -9
- package/src/api/src/services/preset-loader.ts +5 -5
- package/src/api/src/services/scaffold.ts +23 -23
- package/src/api/src/services/session-store.ts +1 -1
- package/src/api/src/services/supervisor-heartbeat.ts +3 -3
- package/src/api/src/services/token-ledger.ts +1 -1
- package/src/api/src/services/wave-tracker.ts +5 -5
- package/templates/CLAUDE.md.tmpl +27 -27
package/package.json
CHANGED
|
@@ -185,8 +185,8 @@ export function createExpressApp(): express.Application {
|
|
|
185
185
|
let companyName: string | null = null;
|
|
186
186
|
if (initialized) {
|
|
187
187
|
try {
|
|
188
|
-
// Read company name from
|
|
189
|
-
const companyMdPath = path.join(COMPANY_ROOT, '
|
|
188
|
+
// Read company name from knowledge/company.md (user-owned data)
|
|
189
|
+
const companyMdPath = path.join(COMPANY_ROOT, 'knowledge', 'company.md');
|
|
190
190
|
const content = fs.readFileSync(companyMdPath, 'utf-8');
|
|
191
191
|
const match = content.match(/^#\s+(.+)/m);
|
|
192
192
|
if (match) companyName = match[1].trim();
|
|
@@ -655,10 +655,10 @@ export async function runAgentLoop(config: AgentConfig): Promise<AgentResult> {
|
|
|
655
655
|
'',
|
|
656
656
|
'## ④ Knowledge 업데이트 (The Loop Step 4)',
|
|
657
657
|
'다음 중 해당하는 항목을 수행하세요:',
|
|
658
|
-
'- 본인 journal 업데이트 (`roles/' + roleId + '/journal/YYYY-MM-DD.md` — 오늘 날짜 파일)',
|
|
658
|
+
'- 본인 journal 업데이트 (`knowledge/roles/' + roleId + '/journal/YYYY-MM-DD.md` — 오늘 날짜 파일)',
|
|
659
659
|
'- 구현 중 새로 발견한 패턴/아키텍처 결정이 있다면 관련 문서 업데이트',
|
|
660
|
-
' (예: architecture/web-app-ia.md, architecture/session-worktree-isolation.md 등)',
|
|
661
|
-
'- 중요한 기술 결정은
|
|
660
|
+
' (예: knowledge/architecture/web-app-ia.md, knowledge/architecture/session-worktree-isolation.md 등)',
|
|
661
|
+
'- 중요한 기술 결정은 knowledge/decisions/ 또는 knowledge/architecture/ 반영',
|
|
662
662
|
'',
|
|
663
663
|
'## ⑤ Task 상태 갱신 (The Loop Step 5)',
|
|
664
664
|
'- `projects/tycono-platform/tasks.md` (또는 관련 tasks 파일)에서 완료한 태스크 상태를 DONE으로 변경',
|
|
@@ -275,7 +275,7 @@ ${docList}
|
|
|
275
275
|
* Returns concatenated content (capped at 2000 chars per doc).
|
|
276
276
|
*/
|
|
277
277
|
function loadPresetKnowledge(companyRoot: string, presetId: string): string | null {
|
|
278
|
-
const knowledgeDir = path.join(companyRoot, '
|
|
278
|
+
const knowledgeDir = path.join(companyRoot, 'knowledge', 'presets', presetId, 'knowledge');
|
|
279
279
|
if (!fs.existsSync(knowledgeDir)) return null;
|
|
280
280
|
|
|
281
281
|
const parts: string[] = [];
|
|
@@ -309,8 +309,8 @@ function loadCompanyRules(companyRoot: string): string | null {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
// 3. Company info (
|
|
313
|
-
const companyMdPath = path.join(companyRoot, '
|
|
312
|
+
// 3. Company info (knowledge/company.md — user owned)
|
|
313
|
+
const companyMdPath = path.join(companyRoot, 'knowledge', 'company.md');
|
|
314
314
|
if (fs.existsSync(companyMdPath)) {
|
|
315
315
|
const companyInfo = fs.readFileSync(companyMdPath, 'utf-8').trim();
|
|
316
316
|
if (companyInfo) {
|
|
@@ -450,7 +450,7 @@ function loadHubSummaries(companyRoot: string, node: OrgNode): string | null {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
function loadCeoDecisions(companyRoot: string): string | null {
|
|
453
|
-
const decisionsDir = path.join(companyRoot, '
|
|
453
|
+
const decisionsDir = path.join(companyRoot, 'knowledge', 'decisions');
|
|
454
454
|
if (!fs.existsSync(decisionsDir)) return null;
|
|
455
455
|
|
|
456
456
|
const files = fs.readdirSync(decisionsDir)
|
|
@@ -834,7 +834,7 @@ function buildKnowledgeManagementSection(roleId: string): string {
|
|
|
834
834
|
const domainScope = roleId === 'cto'
|
|
835
835
|
? '`architecture/`, `knowledge/` (technical docs)'
|
|
836
836
|
: roleId === 'cbo'
|
|
837
|
-
? '`knowledge
|
|
837
|
+
? '`knowledge/` (business docs)'
|
|
838
838
|
: '`knowledge/` (your domain docs)';
|
|
839
839
|
|
|
840
840
|
return `# Knowledge Consistency Management (C-Level Responsibility)
|
|
@@ -88,7 +88,7 @@ export function extractKeywords(text: string): string[] {
|
|
|
88
88
|
export function searchRelatedDocs(companyRoot: string, keywords: string[]): RelatedDoc[] {
|
|
89
89
|
if (keywords.length === 0) return [];
|
|
90
90
|
|
|
91
|
-
const searchDirs = ['knowledge', 'architecture', 'projects'];
|
|
91
|
+
const searchDirs = ['knowledge', 'knowledge/architecture', 'knowledge/projects'];
|
|
92
92
|
const results: RelatedDoc[] = [];
|
|
93
93
|
|
|
94
94
|
for (const dir of searchDirs) {
|
|
@@ -222,7 +222,7 @@ export function postKnowledgingCheck(
|
|
|
222
222
|
|
|
223
223
|
// Categorize
|
|
224
224
|
// We can't tell new vs modified from just file list, so check if it's a knowledge/architecture doc
|
|
225
|
-
if (file.startsWith('knowledge/') || file.startsWith('architecture/') || file.startsWith('projects/')) {
|
|
225
|
+
if (file.startsWith('knowledge/') || file.startsWith('knowledge/architecture/') || file.startsWith('knowledge/projects/')) {
|
|
226
226
|
modifiedDocs.push(file);
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -257,7 +257,7 @@ export function postKnowledgingCheck(
|
|
|
257
257
|
|
|
258
258
|
/** Scan for orphan docs (not registered in Hub) and broken links */
|
|
259
259
|
export function detectDecay(companyRoot: string): DecayReport {
|
|
260
|
-
const searchDirs = ['knowledge', 'architecture'];
|
|
260
|
+
const searchDirs = ['knowledge', 'knowledge/architecture'];
|
|
261
261
|
const orphanDocs: string[] = [];
|
|
262
262
|
const staleDocs: string[] = [];
|
|
263
263
|
const brokenLinks: Array<{ file: string; link: string }> = [];
|
|
@@ -86,7 +86,7 @@ interface RawRoleYaml {
|
|
|
86
86
|
/* ─── Build ──────────────────────────────────── */
|
|
87
87
|
|
|
88
88
|
export function buildOrgTree(companyRoot: string, presetId?: string): OrgTree {
|
|
89
|
-
const rolesDir = path.join(companyRoot, 'roles');
|
|
89
|
+
const rolesDir = path.join(companyRoot, 'knowledge', 'roles');
|
|
90
90
|
const tree: OrgTree = { root: 'ceo', nodes: new Map() };
|
|
91
91
|
|
|
92
92
|
// CEO is implicit (not a role.yaml file)
|
|
@@ -108,7 +108,7 @@ export function buildOrgTree(companyRoot: string, presetId?: string): OrgTree {
|
|
|
108
108
|
|
|
109
109
|
// If preset specified, also scan preset's roles directory
|
|
110
110
|
if (presetId && presetId !== 'default') {
|
|
111
|
-
const presetRolesDir = path.join(companyRoot, '
|
|
111
|
+
const presetRolesDir = path.join(companyRoot, 'knowledge', 'presets', presetId, 'roles');
|
|
112
112
|
if (fs.existsSync(presetRolesDir)) roleDirs.push(presetRolesDir);
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -57,7 +57,7 @@ export class RoleLifecycleManager {
|
|
|
57
57
|
* Create a new Role: role.yaml + SKILL.md + profile.md + journal/
|
|
58
58
|
*/
|
|
59
59
|
async createRole(def: RoleDefinition): Promise<void> {
|
|
60
|
-
const roleDir = path.join(this.companyRoot, 'roles', def.id);
|
|
60
|
+
const roleDir = path.join(this.companyRoot, 'knowledge', 'roles', def.id);
|
|
61
61
|
const skillDir = path.join(this.companyRoot, '.claude', 'skills', def.id);
|
|
62
62
|
const journalDir = path.join(roleDir, 'journal');
|
|
63
63
|
|
|
@@ -105,7 +105,7 @@ export class RoleLifecycleManager {
|
|
|
105
105
|
* Update an existing Role's definition
|
|
106
106
|
*/
|
|
107
107
|
async updateRole(id: string, changes: Partial<RoleDefinition>): Promise<void> {
|
|
108
|
-
const yamlPath = path.join(this.companyRoot, 'roles', id, 'role.yaml');
|
|
108
|
+
const yamlPath = path.join(this.companyRoot, 'knowledge', 'roles', id, 'role.yaml');
|
|
109
109
|
if (!fs.existsSync(yamlPath)) {
|
|
110
110
|
throw new Error(`Role not found: ${id}`);
|
|
111
111
|
}
|
|
@@ -145,7 +145,7 @@ export class RoleLifecycleManager {
|
|
|
145
145
|
* Remove a Role and all its files
|
|
146
146
|
*/
|
|
147
147
|
async removeRole(id: string): Promise<void> {
|
|
148
|
-
const roleDir = path.join(this.companyRoot, 'roles', id);
|
|
148
|
+
const roleDir = path.join(this.companyRoot, 'knowledge', 'roles', id);
|
|
149
149
|
const skillDir = path.join(this.companyRoot, '.claude', 'skills', id);
|
|
150
150
|
|
|
151
151
|
if (fs.existsSync(roleDir)) {
|
|
@@ -180,7 +180,7 @@ export class RoleLifecycleManager {
|
|
|
180
180
|
validateRole(id: string): RoleValidationResult {
|
|
181
181
|
const issues: RoleValidationResult['issues'] = [];
|
|
182
182
|
|
|
183
|
-
const roleDir = path.join(this.companyRoot, 'roles', id);
|
|
183
|
+
const roleDir = path.join(this.companyRoot, 'knowledge', 'roles', id);
|
|
184
184
|
const yamlPath = path.join(roleDir, 'role.yaml');
|
|
185
185
|
const profilePath = path.join(roleDir, 'profile.md');
|
|
186
186
|
const journalDir = path.join(roleDir, 'journal');
|
|
@@ -223,7 +223,7 @@ export class RoleLifecycleManager {
|
|
|
223
223
|
*/
|
|
224
224
|
validateAll(): Map<string, RoleValidationResult> {
|
|
225
225
|
const results = new Map<string, RoleValidationResult>();
|
|
226
|
-
const rolesDir = path.join(this.companyRoot, 'roles');
|
|
226
|
+
const rolesDir = path.join(this.companyRoot, 'knowledge', 'roles');
|
|
227
227
|
|
|
228
228
|
if (!fs.existsSync(rolesDir)) return results;
|
|
229
229
|
|
|
@@ -319,7 +319,7 @@ ${def.authority.needsApproval.map((a) => `- ${a}`).join('\n')}
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
private addToRolesHub(def: RoleDefinition): void {
|
|
322
|
-
const hubPath = path.join(this.companyRoot, 'roles', 'roles.md');
|
|
322
|
+
const hubPath = path.join(this.companyRoot, 'knowledge', 'roles', 'roles.md');
|
|
323
323
|
if (!fs.existsSync(hubPath)) return;
|
|
324
324
|
|
|
325
325
|
const content = fs.readFileSync(hubPath, 'utf-8');
|
|
@@ -333,7 +333,7 @@ ${def.authority.needsApproval.map((a) => `- ${a}`).join('\n')}
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
private removeFromRolesHub(id: string): void {
|
|
336
|
-
const hubPath = path.join(this.companyRoot, 'roles', 'roles.md');
|
|
336
|
+
const hubPath = path.join(this.companyRoot, 'knowledge', 'roles', 'roles.md');
|
|
337
337
|
if (!fs.existsSync(hubPath)) return;
|
|
338
338
|
|
|
339
339
|
const content = fs.readFileSync(hubPath, 'utf-8');
|
|
@@ -10,7 +10,7 @@ export const READ_TOOLS: ToolDefinition[] = [
|
|
|
10
10
|
input_schema: {
|
|
11
11
|
type: 'object',
|
|
12
12
|
properties: {
|
|
13
|
-
path: { type: 'string', description: 'File path relative to company root (e.g., "roles/cto/role.yaml")' },
|
|
13
|
+
path: { type: 'string', description: 'File path relative to company root (e.g., "knowledge/roles/cto/role.yaml")' },
|
|
14
14
|
},
|
|
15
15
|
required: ['path'],
|
|
16
16
|
},
|
|
@@ -8,7 +8,7 @@ export const companyRouter = Router();
|
|
|
8
8
|
// GET /api/company — 회사 기본 정보
|
|
9
9
|
companyRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
10
10
|
try {
|
|
11
|
-
const companyContent = readFile('
|
|
11
|
+
const companyContent = readFile('knowledge/company.md');
|
|
12
12
|
const kv = extractBoldKeyValues(companyContent);
|
|
13
13
|
|
|
14
14
|
// blockquote에서 미션 추출
|
|
@@ -16,7 +16,7 @@ companyRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
|
16
16
|
const mission = missionMatch ? missionMatch[1].trim() : '';
|
|
17
17
|
|
|
18
18
|
// Role 목록
|
|
19
|
-
const rolesContent = readFile('roles/roles.md');
|
|
19
|
+
const rolesContent = readFile('knowledge/roles/roles.md');
|
|
20
20
|
const roleRows = parseMarkdownTable(rolesContent);
|
|
21
21
|
const roles = roleRows
|
|
22
22
|
.filter(row => (row.id ?? '').toLowerCase() !== 'ceo')
|
|
@@ -25,7 +25,7 @@ companyRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
|
25
25
|
let name = row.role ?? row.name ?? '';
|
|
26
26
|
|
|
27
27
|
// role.yaml의 name이 있으면 우선 사용 (커스텀 이름 반영)
|
|
28
|
-
const yamlPath = `roles/${id}/role.yaml`;
|
|
28
|
+
const yamlPath = `knowledge/roles/${id}/role.yaml`;
|
|
29
29
|
if (id && fileExists(yamlPath)) {
|
|
30
30
|
try {
|
|
31
31
|
const raw = YAML.parse(readFile(yamlPath)) as Record<string, unknown>;
|
|
@@ -325,7 +325,7 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
325
325
|
);
|
|
326
326
|
|
|
327
327
|
// Scan activity-streams for sessions belonging to this wave
|
|
328
|
-
const streamsDir = path.join(COMPANY_ROOT, '
|
|
328
|
+
const streamsDir = path.join(COMPANY_ROOT, '.tycono', 'activity-streams');
|
|
329
329
|
if (fs.existsSync(streamsDir)) {
|
|
330
330
|
const waveTimestamp = waveId.replace('wave-', '');
|
|
331
331
|
for (const file of fs.readdirSync(streamsDir)) {
|
|
@@ -407,7 +407,7 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
407
407
|
rolesData.push({ roleId, roleName, sessionId: sid, status, events, childSessions });
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
-
const wavesDir = path.join(COMPANY_ROOT, '
|
|
410
|
+
const wavesDir = path.join(COMPANY_ROOT, '.tycono', 'waves');
|
|
411
411
|
if (!fs.existsSync(wavesDir)) {
|
|
412
412
|
fs.mkdirSync(wavesDir, { recursive: true });
|
|
413
413
|
}
|
|
@@ -477,7 +477,7 @@ function handleSaveWave(body: Record<string, unknown>, res: ServerResponse): voi
|
|
|
477
477
|
} catch { /* non-critical */ }
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
jsonResponse(res, 200, { ok: true, path:
|
|
480
|
+
jsonResponse(res, 200, { ok: true, path: `.tycono/waves/${baseName}.json` });
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
/* ─── GET /api/waves/:waveId/stream ── */
|
|
@@ -11,12 +11,12 @@ export const operationsRouter = Router();
|
|
|
11
11
|
// --- Standups ---
|
|
12
12
|
operationsRouter.get('/standups', (_req: Request, res: Response, next: NextFunction) => {
|
|
13
13
|
try {
|
|
14
|
-
const files = listFiles('
|
|
14
|
+
const files = listFiles('.tycono/standup');
|
|
15
15
|
const standups = files
|
|
16
16
|
.filter(f => f.endsWith('.md'))
|
|
17
17
|
.map(f => {
|
|
18
18
|
const date = path.basename(f, '.md');
|
|
19
|
-
const content = readFile(
|
|
19
|
+
const content = readFile(`.tycono/standup/${f}`);
|
|
20
20
|
return { date, content };
|
|
21
21
|
})
|
|
22
22
|
.sort((a, b) => b.date.localeCompare(a.date));
|
|
@@ -30,7 +30,7 @@ operationsRouter.get('/standups', (_req: Request, res: Response, next: NextFunct
|
|
|
30
30
|
operationsRouter.get('/standups/:date', (req: Request, res: Response, next: NextFunction) => {
|
|
31
31
|
try {
|
|
32
32
|
const { date } = req.params;
|
|
33
|
-
const filePath =
|
|
33
|
+
const filePath = `.tycono/standup/${date}.md`;
|
|
34
34
|
if (!fileExists(filePath)) {
|
|
35
35
|
res.status(404).json({ error: `Standup not found: ${date}` });
|
|
36
36
|
return;
|
|
@@ -45,12 +45,12 @@ operationsRouter.get('/standups/:date', (req: Request, res: Response, next: Next
|
|
|
45
45
|
// --- Waves (JSON-only) ---
|
|
46
46
|
operationsRouter.get('/waves', (_req: Request, res: Response, next: NextFunction) => {
|
|
47
47
|
try {
|
|
48
|
-
const files = listFiles('
|
|
48
|
+
const files = listFiles('.tycono/waves', '*.json');
|
|
49
49
|
const waves = files
|
|
50
50
|
.map(f => {
|
|
51
51
|
const id = path.basename(f, '.json');
|
|
52
52
|
try {
|
|
53
|
-
const data = JSON.parse(readFile(
|
|
53
|
+
const data = JSON.parse(readFile(`.tycono/waves/${f}`));
|
|
54
54
|
const roles = data.roles ?? [];
|
|
55
55
|
const hasRunning = roles.some((r: { status?: string }) => r.status && isMessageActive(r.status as MessageStatus));
|
|
56
56
|
return {
|
|
@@ -78,7 +78,7 @@ operationsRouter.get('/waves', (_req: Request, res: Response, next: NextFunction
|
|
|
78
78
|
operationsRouter.get('/waves/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
79
79
|
try {
|
|
80
80
|
const { id } = req.params;
|
|
81
|
-
const jsonPath =
|
|
81
|
+
const jsonPath = `.tycono/waves/${id}.json`;
|
|
82
82
|
|
|
83
83
|
if (!fileExists(jsonPath)) {
|
|
84
84
|
res.status(404).json({ error: `Wave not found: ${id}` });
|
|
@@ -96,7 +96,7 @@ operationsRouter.get('/waves/:id', (req: Request, res: Response, next: NextFunct
|
|
|
96
96
|
operationsRouter.patch('/waves/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
97
97
|
try {
|
|
98
98
|
const { id } = req.params;
|
|
99
|
-
const jsonPath =
|
|
99
|
+
const jsonPath = `.tycono/waves/${id}.json`;
|
|
100
100
|
|
|
101
101
|
if (!fileExists(jsonPath)) {
|
|
102
102
|
res.status(404).json({ error: `Wave not found: ${id}` });
|
|
@@ -119,12 +119,12 @@ operationsRouter.patch('/waves/:id', (req: Request, res: Response, next: NextFun
|
|
|
119
119
|
// --- Decisions ---
|
|
120
120
|
operationsRouter.get('/decisions', (_req: Request, res: Response, next: NextFunction) => {
|
|
121
121
|
try {
|
|
122
|
-
const files = listFiles('
|
|
122
|
+
const files = listFiles('knowledge/decisions');
|
|
123
123
|
const decisions = files
|
|
124
124
|
.filter(f => f.endsWith('.md'))
|
|
125
125
|
.map(f => {
|
|
126
126
|
const id = path.basename(f, '.md');
|
|
127
|
-
const content = readFile(`
|
|
127
|
+
const content = readFile(`knowledge/decisions/${f}`);
|
|
128
128
|
const firstLine = content.split('\n').find(l => l.startsWith('# '));
|
|
129
129
|
const title = firstLine ? firstLine.replace(/^#\s+/, '') : id;
|
|
130
130
|
const kv = extractBoldKeyValues(content);
|
|
@@ -146,7 +146,7 @@ operationsRouter.put('/decisions/:id', (req: Request, res: Response, next: NextF
|
|
|
146
146
|
res.status(400).json({ error: 'content (string) is required' });
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
|
-
const filePath = `
|
|
149
|
+
const filePath = `knowledge/decisions/${id}.md`;
|
|
150
150
|
const absPath = path.resolve(COMPANY_ROOT, filePath);
|
|
151
151
|
// Ensure parent directory exists
|
|
152
152
|
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
@@ -165,7 +165,7 @@ operationsRouter.put('/decisions/:id', (req: Request, res: Response, next: NextF
|
|
|
165
165
|
operationsRouter.delete('/decisions/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
166
166
|
try {
|
|
167
167
|
const { id } = req.params;
|
|
168
|
-
const filePath = `
|
|
168
|
+
const filePath = `knowledge/decisions/${id}.md`;
|
|
169
169
|
const absPath = path.resolve(COMPANY_ROOT, filePath);
|
|
170
170
|
if (!fs.existsSync(absPath)) {
|
|
171
171
|
res.status(404).json({ error: `Decision not found: ${id}` });
|
|
@@ -62,7 +62,7 @@ presetsRouter.post('/install', (req, res) => {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Create preset directory and write preset.yaml
|
|
65
|
-
const presetDir = path.join(COMPANY_ROOT, '
|
|
65
|
+
const presetDir = path.join(COMPANY_ROOT, 'knowledge', 'presets', id);
|
|
66
66
|
fs.mkdirSync(presetDir, { recursive: true });
|
|
67
67
|
|
|
68
68
|
// Write preset.yaml
|
|
@@ -92,7 +92,7 @@ presetsRouter.post('/install', (req, res) => {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
res.json({ ok: true, id, path: `
|
|
95
|
+
res.json({ ok: true, id, path: `knowledge/presets/${id}` });
|
|
96
96
|
} catch (err) {
|
|
97
97
|
res.status(500).json({ error: `Install failed: ${err instanceof Error ? err.message : 'unknown'}` });
|
|
98
98
|
}
|
|
@@ -107,7 +107,7 @@ presetsRouter.delete('/:id', (req, res) => {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
const presetDir = path.join(COMPANY_ROOT, '
|
|
110
|
+
const presetDir = path.join(COMPANY_ROOT, 'knowledge', 'presets', id);
|
|
111
111
|
if (!fs.existsSync(presetDir)) {
|
|
112
112
|
res.status(404).json({ error: `Preset not found: ${id}` });
|
|
113
113
|
return;
|
|
@@ -7,7 +7,7 @@ export const projectsRouter = Router();
|
|
|
7
7
|
// GET /api/projects — 프로젝트 목록
|
|
8
8
|
projectsRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
9
9
|
try {
|
|
10
|
-
const content = readFile('projects/projects.md');
|
|
10
|
+
const content = readFile('knowledge/projects/projects.md');
|
|
11
11
|
const rows = parseMarkdownTable(content);
|
|
12
12
|
|
|
13
13
|
const projects = rows.map(row => {
|
|
@@ -33,7 +33,7 @@ projectsRouter.get('/:id', (req: Request, res: Response, next: NextFunction) =>
|
|
|
33
33
|
const { id } = req.params;
|
|
34
34
|
|
|
35
35
|
// 기본 정보
|
|
36
|
-
const listContent = readFile('projects/projects.md');
|
|
36
|
+
const listContent = readFile('knowledge/projects/projects.md');
|
|
37
37
|
const rows = parseMarkdownTable(listContent);
|
|
38
38
|
const projectRow = rows.find(r => {
|
|
39
39
|
const name = r.project ?? '';
|
|
@@ -56,13 +56,13 @@ projectsRouter.get('/:id', (req: Request, res: Response, next: NextFunction) =>
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
// PRD 읽기
|
|
59
|
-
const prdPath = `projects/${id}/prd.md`;
|
|
59
|
+
const prdPath = `knowledge/projects/${id}/prd.md`;
|
|
60
60
|
if (fileExists(prdPath)) {
|
|
61
61
|
project.prd = readFile(prdPath);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Tasks 읽기
|
|
65
|
-
const tasksPath = `projects/${id}/tasks.md`;
|
|
65
|
+
const tasksPath = `knowledge/projects/${id}/tasks.md`;
|
|
66
66
|
if (fileExists(tasksPath)) {
|
|
67
67
|
const tasksContent = readFile(tasksPath);
|
|
68
68
|
const taskRows = parseMarkdownTable(tasksContent);
|
|
@@ -8,7 +8,7 @@ export const rolesRouter = Router();
|
|
|
8
8
|
// GET /api/roles — Role 목록
|
|
9
9
|
rolesRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
10
10
|
try {
|
|
11
|
-
const content = readFile('roles/roles.md');
|
|
11
|
+
const content = readFile('knowledge/roles/roles.md');
|
|
12
12
|
const rows = parseMarkdownTable(content);
|
|
13
13
|
|
|
14
14
|
const roles = rows.map(row => {
|
|
@@ -16,7 +16,7 @@ rolesRouter.get('/', (_req: Request, res: Response, next: NextFunction) => {
|
|
|
16
16
|
let name = row.role ?? row.name ?? '';
|
|
17
17
|
|
|
18
18
|
// role.yaml의 name이 있으면 우선 사용 (rename 반영)
|
|
19
|
-
const yamlPath = `roles/${id}/role.yaml`;
|
|
19
|
+
const yamlPath = `knowledge/roles/${id}/role.yaml`;
|
|
20
20
|
if (id && fileExists(yamlPath)) {
|
|
21
21
|
try {
|
|
22
22
|
const raw = YAML.parse(readFile(yamlPath)) as Record<string, unknown>;
|
|
@@ -45,7 +45,7 @@ rolesRouter.get('/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
|
45
45
|
const { id } = req.params;
|
|
46
46
|
|
|
47
47
|
// 기본 정보 (roles.md 테이블에서)
|
|
48
|
-
const listContent = readFile('roles/roles.md');
|
|
48
|
+
const listContent = readFile('knowledge/roles/roles.md');
|
|
49
49
|
const rows = parseMarkdownTable(listContent);
|
|
50
50
|
const roleRow = rows.find(r => r.id === id);
|
|
51
51
|
|
|
@@ -66,7 +66,7 @@ rolesRouter.get('/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
|
66
66
|
};
|
|
67
67
|
|
|
68
68
|
// role.yaml에서 name + persona + authority + skills 읽기
|
|
69
|
-
const yamlPath = `roles/${id}/role.yaml`;
|
|
69
|
+
const yamlPath = `knowledge/roles/${id}/role.yaml`;
|
|
70
70
|
if (fileExists(yamlPath)) {
|
|
71
71
|
const raw = YAML.parse(readFile(yamlPath)) as Record<string, unknown>;
|
|
72
72
|
if (raw.name) role.name = raw.name;
|
|
@@ -100,7 +100,7 @@ rolesRouter.get('/:id', (req: Request, res: Response, next: NextFunction) => {
|
|
|
100
100
|
|
|
101
101
|
// 오늘 저널 읽기
|
|
102
102
|
const today = new Date().toISOString().slice(0, 10);
|
|
103
|
-
const journalPath = `roles/${id}/journal/${today}.md`;
|
|
103
|
+
const journalPath = `knowledge/roles/${id}/journal/${today}.md`;
|
|
104
104
|
if (fileExists(journalPath)) {
|
|
105
105
|
role.journal = readFile(journalPath).slice(0, 3000);
|
|
106
106
|
}
|
|
@@ -226,7 +226,7 @@ skillsRouter.post('/role/:roleId', (req: Request, res: Response, next: NextFunct
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// Add to role.yaml skills array
|
|
229
|
-
const yamlPath = path.join(COMPANY_ROOT, 'roles', roleId, 'role.yaml');
|
|
229
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
230
230
|
if (!fs.existsSync(yamlPath)) {
|
|
231
231
|
res.status(404).json({ error: `Role not found: ${roleId}` });
|
|
232
232
|
return;
|
|
@@ -256,7 +256,7 @@ skillsRouter.delete('/role/:roleId/:skillId', (req: Request, res: Response, next
|
|
|
256
256
|
const roleId = req.params.roleId as string;
|
|
257
257
|
const skillId = req.params.skillId as string;
|
|
258
258
|
|
|
259
|
-
const yamlPath = path.join(COMPANY_ROOT, 'roles', roleId, 'role.yaml');
|
|
259
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
260
260
|
if (!fs.existsSync(yamlPath)) {
|
|
261
261
|
res.status(404).json({ error: `Role not found: ${roleId}` });
|
|
262
262
|
return;
|
|
@@ -307,7 +307,7 @@ function installSkillFromTemplate(skillId: string): void {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
function getRoleSkills(roleId: string): string[] {
|
|
310
|
-
const yamlPath = path.join(COMPANY_ROOT, 'roles', roleId, 'role.yaml');
|
|
310
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
311
311
|
if (!fs.existsSync(yamlPath)) return [];
|
|
312
312
|
|
|
313
313
|
const raw = YAML.parse(fs.readFileSync(yamlPath, 'utf-8')) as Record<string, unknown>;
|
|
@@ -100,7 +100,7 @@ const AKB_TOOLS: ToolDefinition[] = [
|
|
|
100
100
|
type: 'object' as const,
|
|
101
101
|
properties: {
|
|
102
102
|
query: { type: 'string', description: 'Search keywords (e.g. "landing deploy", "refactoring decision", "Store Import")' },
|
|
103
|
-
path: { type: 'string', description: 'Optional subdirectory to search in (e.g. "
|
|
103
|
+
path: { type: 'string', description: 'Optional subdirectory to search in (e.g. "knowledge/decisions", "knowledge/projects", "knowledge"). Defaults to entire AKB.' },
|
|
104
104
|
},
|
|
105
105
|
required: ['query'],
|
|
106
106
|
},
|
|
@@ -111,18 +111,18 @@ const AKB_TOOLS: ToolDefinition[] = [
|
|
|
111
111
|
input_schema: {
|
|
112
112
|
type: 'object' as const,
|
|
113
113
|
properties: {
|
|
114
|
-
path: { type: 'string', description: 'File path relative to AKB root (e.g. "
|
|
114
|
+
path: { type: 'string', description: 'File path relative to AKB root (e.g. "knowledge/decisions/008-repo-structure.md", "knowledge/projects/projects.md")' },
|
|
115
115
|
},
|
|
116
116
|
required: ['path'],
|
|
117
117
|
},
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
name: 'list_files',
|
|
121
|
-
description: 'List files in a directory. Useful to discover what exists (e.g. "
|
|
121
|
+
description: 'List files in a directory. Useful to discover what exists (e.g. ".tycono/waves/", "knowledge/roles/engineer/journal/").',
|
|
122
122
|
input_schema: {
|
|
123
123
|
type: 'object' as const,
|
|
124
124
|
properties: {
|
|
125
|
-
path: { type: 'string', description: 'Directory path relative to AKB root (e.g. "
|
|
125
|
+
path: { type: 'string', description: 'Directory path relative to AKB root (e.g. ".tycono/standup", "knowledge/roles/pm/journal")' },
|
|
126
126
|
pattern: { type: 'string', description: 'Glob pattern (default: "*.md")' },
|
|
127
127
|
},
|
|
128
128
|
required: ['path'],
|
|
@@ -257,7 +257,7 @@ function buildCompanyContext(): string {
|
|
|
257
257
|
|
|
258
258
|
// 1. Company info (name, mission)
|
|
259
259
|
try {
|
|
260
|
-
const companyContent = readFile('
|
|
260
|
+
const companyContent = readFile('knowledge/company.md');
|
|
261
261
|
const companyName = companyContent.split('\n').find(l => l.startsWith('# '))?.replace(/^#\s+/, '') ?? '';
|
|
262
262
|
const missionMatch = companyContent.match(/^>\s*(.+)/m);
|
|
263
263
|
const mission = missionMatch ? missionMatch[1].trim() : '';
|
|
@@ -283,7 +283,7 @@ function buildCompanyContext(): string {
|
|
|
283
283
|
|
|
284
284
|
// 3. Active projects + current phase from tasks.md
|
|
285
285
|
try {
|
|
286
|
-
const projectsContent = readFile('projects/projects.md');
|
|
286
|
+
const projectsContent = readFile('knowledge/projects/projects.md');
|
|
287
287
|
const rows = parseMarkdownTable(projectsContent);
|
|
288
288
|
const activeProjects = rows
|
|
289
289
|
.filter(r => (r.status ?? r.상태 ?? '').toLowerCase() !== 'archived')
|
|
@@ -335,7 +335,7 @@ function buildCompanyContext(): string {
|
|
|
335
335
|
|
|
336
336
|
// 5. Recent CEO decisions (max 5)
|
|
337
337
|
try {
|
|
338
|
-
const decisionsDir = path.join(COMPANY_ROOT, '
|
|
338
|
+
const decisionsDir = path.join(COMPANY_ROOT, 'knowledge', 'decisions');
|
|
339
339
|
if (fs.existsSync(decisionsDir)) {
|
|
340
340
|
const files = fs.readdirSync(decisionsDir)
|
|
341
341
|
.filter(f => f.endsWith('.md') && f !== 'decisions.md')
|
|
@@ -370,7 +370,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
370
370
|
|
|
371
371
|
// 0. Role profile — gives the agent its identity and work context
|
|
372
372
|
try {
|
|
373
|
-
const profilePath = path.join(COMPANY_ROOT, 'roles', roleId, 'profile.md');
|
|
373
|
+
const profilePath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'profile.md');
|
|
374
374
|
if (fs.existsSync(profilePath)) {
|
|
375
375
|
const content = fs.readFileSync(profilePath, 'utf-8').trim();
|
|
376
376
|
if (content.length > 20) {
|
|
@@ -381,7 +381,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
381
381
|
|
|
382
382
|
// 1. Role's journal — latest entry only, compact summary (not full header dump)
|
|
383
383
|
try {
|
|
384
|
-
const journalDir = path.join(COMPANY_ROOT, 'roles', roleId, 'journal');
|
|
384
|
+
const journalDir = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'journal');
|
|
385
385
|
if (fs.existsSync(journalDir)) {
|
|
386
386
|
const files = fs.readdirSync(journalDir)
|
|
387
387
|
.filter(f => f.endsWith('.md'))
|
|
@@ -399,7 +399,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
399
399
|
|
|
400
400
|
// 2. Current tasks assigned to this role (from all project tasks.md files)
|
|
401
401
|
try {
|
|
402
|
-
const projectsDir = path.join(COMPANY_ROOT, 'projects');
|
|
402
|
+
const projectsDir = path.join(COMPANY_ROOT, 'knowledge', 'projects');
|
|
403
403
|
if (fs.existsSync(projectsDir)) {
|
|
404
404
|
const taskFiles = glob.sync('**/tasks.md', { cwd: projectsDir, absolute: false });
|
|
405
405
|
const roleTasks: string[] = [];
|
|
@@ -425,7 +425,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
425
425
|
|
|
426
426
|
// 3. Recent waves — only from last 7 days (stale waves cause repetitive references)
|
|
427
427
|
try {
|
|
428
|
-
const wavesDir = path.join(COMPANY_ROOT, '
|
|
428
|
+
const wavesDir = path.join(COMPANY_ROOT, '.tycono', 'waves');
|
|
429
429
|
if (fs.existsSync(wavesDir)) {
|
|
430
430
|
const tree = buildOrgTree(COMPANY_ROOT);
|
|
431
431
|
const node = tree.nodes.get(roleId);
|
|
@@ -473,7 +473,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
473
473
|
|
|
474
474
|
// 4. Recent standup (latest, this role's section)
|
|
475
475
|
try {
|
|
476
|
-
const standupDir = path.join(COMPANY_ROOT, '
|
|
476
|
+
const standupDir = path.join(COMPANY_ROOT, '.tycono', 'standup');
|
|
477
477
|
if (fs.existsSync(standupDir)) {
|
|
478
478
|
const files = fs.readdirSync(standupDir).filter(f => f.endsWith('.md')).sort().slice(-1);
|
|
479
479
|
for (const file of files) {
|
|
@@ -489,7 +489,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
489
489
|
|
|
490
490
|
// 5. Recent decisions (last 3)
|
|
491
491
|
try {
|
|
492
|
-
const decisionsDir = path.join(COMPANY_ROOT, '
|
|
492
|
+
const decisionsDir = path.join(COMPANY_ROOT, 'knowledge', 'decisions');
|
|
493
493
|
if (fs.existsSync(decisionsDir)) {
|
|
494
494
|
const files = fs.readdirSync(decisionsDir)
|
|
495
495
|
.filter(f => f.endsWith('.md') && f !== 'decisions.md')
|
|
@@ -863,7 +863,7 @@ ${companyCtx}
|
|
|
863
863
|
${roleCtx}
|
|
864
864
|
|
|
865
865
|
You have search_akb, read_file, list_files tools. AKB root: ${COMPANY_ROOT}/
|
|
866
|
-
Optionally explore 1-2 for fresh context:
|
|
866
|
+
Optionally explore 1-2 for fresh context: .tycono/waves/, knowledge/decisions/, knowledge/roles/${roleId}/journal/
|
|
867
867
|
|
|
868
868
|
RULES:
|
|
869
869
|
1. Match the tone and length from the example conversations above. 1-3 sentences MAX.
|
|
@@ -65,7 +65,7 @@ syncRouter.post('/apply', async (req: Request, res: Response, next: NextFunction
|
|
|
65
65
|
|
|
66
66
|
// Update source.upstream_version if provided
|
|
67
67
|
if (upstreamVersion) {
|
|
68
|
-
const yamlPath = path.join(COMPANY_ROOT, 'roles', roleId, 'role.yaml');
|
|
68
|
+
const yamlPath = path.join(COMPANY_ROOT, 'knowledge', 'roles', roleId, 'role.yaml');
|
|
69
69
|
const raw = YAML.parse(fs.readFileSync(yamlPath, 'utf-8')) as Record<string, unknown>;
|
|
70
70
|
if (raw.source && typeof raw.source === 'object') {
|
|
71
71
|
(raw.source as Record<string, unknown>).upstream_version = upstreamVersion;
|
|
@@ -10,7 +10,7 @@ import type { ActivityEventType, ActivityEvent } from '../../../shared/types.js'
|
|
|
10
10
|
/* ─── Constants ──────────────────────────── */
|
|
11
11
|
|
|
12
12
|
function streamsDir(): string {
|
|
13
|
-
return path.join(COMPANY_ROOT, '
|
|
13
|
+
return path.join(COMPANY_ROOT, '.tycono', 'activity-streams');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function ensureDir(): void {
|
|
@@ -4,7 +4,7 @@ import { COMPANY_ROOT } from './file-reader.js';
|
|
|
4
4
|
import type { RoleStatus } from '../../../shared/types.js';
|
|
5
5
|
|
|
6
6
|
function activityDir(): string {
|
|
7
|
-
return path.join(COMPANY_ROOT, '
|
|
7
|
+
return path.join(COMPANY_ROOT, '.tycono', 'activity');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export interface RoleActivity {
|
|
@@ -124,7 +124,7 @@ class ExecutionManager {
|
|
|
124
124
|
const session = getSession(params.sessionId);
|
|
125
125
|
if (session?.waveId) {
|
|
126
126
|
try {
|
|
127
|
-
const wavePath = path.join(COMPANY_ROOT, '
|
|
127
|
+
const wavePath = path.join(COMPANY_ROOT, '.tycono', 'waves', `${session.waveId}.json`);
|
|
128
128
|
if (fs.existsSync(wavePath)) {
|
|
129
129
|
const waveData = JSON.parse(fs.readFileSync(wavePath, 'utf-8'));
|
|
130
130
|
presetId = waveData.preset;
|
|
@@ -698,7 +698,7 @@ class ExecutionManager {
|
|
|
698
698
|
const deadSession = getSession(deadExecution.sessionId);
|
|
699
699
|
if (deadSession?.waveId) {
|
|
700
700
|
try {
|
|
701
|
-
const wp = path.join(COMPANY_ROOT, '
|
|
701
|
+
const wp = path.join(COMPANY_ROOT, '.tycono', 'waves', `${deadSession.waveId}.json`);
|
|
702
702
|
if (fs.existsSync(wp)) {
|
|
703
703
|
recoveryPresetId = JSON.parse(fs.readFileSync(wp, 'utf-8')).preset;
|
|
704
704
|
}
|
|
@@ -73,16 +73,7 @@ export interface SyncStatus {
|
|
|
73
73
|
* See: knowledge/data-persistence-architecture.md §2
|
|
74
74
|
*/
|
|
75
75
|
const SAVE_PATHS = [
|
|
76
|
-
'roles/',
|
|
77
|
-
'projects/',
|
|
78
76
|
'knowledge/',
|
|
79
|
-
'architecture/',
|
|
80
|
-
'company/',
|
|
81
|
-
'operations/standup/',
|
|
82
|
-
'operations/waves/',
|
|
83
|
-
'operations/decisions/',
|
|
84
|
-
'operations/cost/',
|
|
85
|
-
'operations/activity-streams/',
|
|
86
77
|
'.claude/skills/',
|
|
87
78
|
'.tycono/',
|
|
88
79
|
'CLAUDE.md',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* preset-loader.ts — Load presets from
|
|
2
|
+
* preset-loader.ts — Load presets from knowledge/presets/
|
|
3
3
|
*
|
|
4
|
-
* Scans
|
|
5
|
-
* - _default.yaml (auto-generated from existing roles/)
|
|
4
|
+
* Scans knowledge/presets/ for:
|
|
5
|
+
* - _default.yaml (auto-generated from existing knowledge/roles/)
|
|
6
6
|
* - {name}/preset.yaml (installed presets with roles/skills/knowledge)
|
|
7
7
|
*
|
|
8
8
|
* Returns PresetSummary[] for TUI display and full LoadedPreset for wave creation.
|
|
@@ -12,7 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import YAML from 'yaml';
|
|
13
13
|
import type { PresetDefinition, LoadedPreset, PresetSummary } from '../../../shared/types.js';
|
|
14
14
|
|
|
15
|
-
const PRESETS_DIR = '
|
|
15
|
+
const PRESETS_DIR = 'knowledge/presets';
|
|
16
16
|
const DEFAULT_PRESET_FILE = '_default.yaml';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -20,7 +20,7 @@ const DEFAULT_PRESET_FILE = '_default.yaml';
|
|
|
20
20
|
* This is generated on-the-fly — no need to persist _default.yaml.
|
|
21
21
|
*/
|
|
22
22
|
function buildDefaultPreset(companyRoot: string): LoadedPreset {
|
|
23
|
-
const rolesDir = path.join(companyRoot, 'roles');
|
|
23
|
+
const rolesDir = path.join(companyRoot, 'knowledge', 'roles');
|
|
24
24
|
const roles: string[] = [];
|
|
25
25
|
|
|
26
26
|
if (fs.existsSync(rolesDir)) {
|
|
@@ -314,12 +314,12 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
314
314
|
|
|
315
315
|
// Create directories
|
|
316
316
|
const dirs = [
|
|
317
|
-
'
|
|
318
|
-
'
|
|
319
|
-
'
|
|
320
|
-
'
|
|
321
|
-
'
|
|
322
|
-
'.claude/skills
|
|
317
|
+
'knowledge', 'knowledge/roles', 'knowledge/projects',
|
|
318
|
+
'knowledge/architecture', 'knowledge/methodologies',
|
|
319
|
+
'knowledge/decisions', 'knowledge/presets',
|
|
320
|
+
'.tycono/waves', '.tycono/sessions', '.tycono/standup',
|
|
321
|
+
'.tycono/activity-streams', '.tycono/cost', '.tycono/activity',
|
|
322
|
+
'.claude/skills', '.claude/skills/_shared', '.tycono',
|
|
323
323
|
];
|
|
324
324
|
for (const dir of dirs) {
|
|
325
325
|
fs.mkdirSync(path.join(root, dir), { recursive: true });
|
|
@@ -343,15 +343,15 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
343
343
|
created.push('.tycono/custom-rules.md');
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
// Write
|
|
346
|
+
// Write knowledge/company.md
|
|
347
347
|
const companyTmpl = loadTemplate('company.md.tmpl');
|
|
348
|
-
fs.writeFileSync(path.join(root, '
|
|
349
|
-
created.push('
|
|
348
|
+
fs.writeFileSync(path.join(root, 'knowledge', 'company.md'), renderTemplate(companyTmpl, vars));
|
|
349
|
+
created.push('knowledge/company.md');
|
|
350
350
|
|
|
351
|
-
// Write roles/roles.md
|
|
351
|
+
// Write knowledge/roles/roles.md
|
|
352
352
|
const rolesTmpl = loadTemplate('roles.md.tmpl');
|
|
353
|
-
fs.writeFileSync(path.join(root, 'roles', 'roles.md'), renderTemplate(rolesTmpl, vars));
|
|
354
|
-
created.push('roles/roles.md');
|
|
353
|
+
fs.writeFileSync(path.join(root, 'knowledge', 'roles', 'roles.md'), renderTemplate(rolesTmpl, vars));
|
|
354
|
+
created.push('knowledge/roles/roles.md');
|
|
355
355
|
|
|
356
356
|
// Write .gitignore
|
|
357
357
|
const giTmpl = loadTemplate('gitignore.tmpl');
|
|
@@ -400,14 +400,14 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
400
400
|
// Create roles with skill references
|
|
401
401
|
for (const role of roles) {
|
|
402
402
|
createRole(root, role);
|
|
403
|
-
created.push(`roles/${role.id}/`);
|
|
403
|
+
created.push(`knowledge/roles/${role.id}/`);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
406
|
|
|
407
407
|
// Hub files
|
|
408
408
|
const hubs: Record<string, string> = {
|
|
409
|
-
'projects/projects.md': `# Projects\n\nProject listing for ${config.companyName}.\n\n| Project | Status | Lead |\n|---------|--------|------|\n`,
|
|
410
|
-
'architecture/architecture.md': `# Architecture\n\nTechnical architecture for ${config.companyName}.\n`,
|
|
409
|
+
'knowledge/projects/projects.md': `# Projects\n\nProject listing for ${config.companyName}.\n\n| Project | Status | Lead |\n|---------|--------|------|\n`,
|
|
410
|
+
'knowledge/architecture/architecture.md': `# Architecture\n\nTechnical architecture for ${config.companyName}.\n`,
|
|
411
411
|
'knowledge/knowledge.md': `# Knowledge Base\n\nDomain knowledge for ${config.companyName}.\n`,
|
|
412
412
|
};
|
|
413
413
|
for (const [filePath, content] of Object.entries(hubs)) {
|
|
@@ -419,15 +419,15 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
// Methodology documents
|
|
422
|
-
const methodologiesHub = path.join(root, 'methodologies', 'methodologies.md');
|
|
422
|
+
const methodologiesHub = path.join(root, 'knowledge', 'methodologies', 'methodologies.md');
|
|
423
423
|
if (!fs.existsSync(methodologiesHub)) {
|
|
424
424
|
fs.writeFileSync(methodologiesHub, `# Methodologies\n\n> Frameworks and principles that guide how AI agents work in this organization.\n\n## Documents\n\n| Document | Description |\n|----------|-------------|\n| [agentic-knowledge-base.md](./agentic-knowledge-base.md) | AKB — the file-based knowledge protocol for AI agents |\n\n---\n\n*Managed by: All*\n`);
|
|
425
|
-
created.push('methodologies/methodologies.md');
|
|
425
|
+
created.push('knowledge/methodologies/methodologies.md');
|
|
426
426
|
}
|
|
427
|
-
const akbDoc = path.join(root, 'methodologies', 'agentic-knowledge-base.md');
|
|
427
|
+
const akbDoc = path.join(root, 'knowledge', 'methodologies', 'agentic-knowledge-base.md');
|
|
428
428
|
if (!fs.existsSync(akbDoc)) {
|
|
429
429
|
fs.writeFileSync(akbDoc, AKB_METHODOLOGY_CONTENT);
|
|
430
|
-
created.push('methodologies/agentic-knowledge-base.md');
|
|
430
|
+
created.push('knowledge/methodologies/agentic-knowledge-base.md');
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
// Set default appearances for team roles
|
|
@@ -445,14 +445,14 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
445
445
|
|
|
446
446
|
// Brownfield: note existing project path
|
|
447
447
|
if (config.existingProjectPath) {
|
|
448
|
-
const targetDir = path.join(root, 'projects', 'existing');
|
|
448
|
+
const targetDir = path.join(root, 'knowledge', 'projects', 'existing');
|
|
449
449
|
if (!fs.existsSync(targetDir)) {
|
|
450
450
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
451
451
|
fs.writeFileSync(
|
|
452
452
|
path.join(targetDir, 'prd.md'),
|
|
453
453
|
`# Existing Project\n\nImported from: ${config.existingProjectPath}\n`
|
|
454
454
|
);
|
|
455
|
-
created.push('projects/existing/');
|
|
455
|
+
created.push('knowledge/projects/existing/');
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
|
|
@@ -481,7 +481,7 @@ export function scaffold(config: ScaffoldConfig): string[] {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
function createRole(root: string, role: TeamRole): void {
|
|
484
|
-
const roleDir = path.join(root, 'roles', role.id);
|
|
484
|
+
const roleDir = path.join(root, 'knowledge', 'roles', role.id);
|
|
485
485
|
const skillDir = path.join(root, '.claude', 'skills', role.id);
|
|
486
486
|
const journalDir = path.join(roleDir, 'journal');
|
|
487
487
|
|
|
@@ -533,7 +533,7 @@ function createRole(root: string, role: TeamRole): void {
|
|
|
533
533
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skill);
|
|
534
534
|
|
|
535
535
|
// Append to roles.md
|
|
536
|
-
const rolesHubPath = path.join(root, 'roles', 'roles.md');
|
|
536
|
+
const rolesHubPath = path.join(root, 'knowledge', 'roles', 'roles.md');
|
|
537
537
|
if (fs.existsSync(rolesHubPath)) {
|
|
538
538
|
const hubContent = fs.readFileSync(rolesHubPath, 'utf-8');
|
|
539
539
|
const row = `| ${role.name} | ${role.id} | ${role.level} | ${role.reportsTo} | Active |`;
|
|
@@ -65,7 +65,7 @@ export interface Session {
|
|
|
65
65
|
/* ─── Session directory ─────────────────── */
|
|
66
66
|
|
|
67
67
|
function sessionsDir(): string {
|
|
68
|
-
return path.join(COMPANY_ROOT, '
|
|
68
|
+
return path.join(COMPANY_ROOT, '.tycono', 'sessions');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function ensureDir(): void {
|
|
@@ -123,7 +123,7 @@ class SupervisorHeartbeat {
|
|
|
123
123
|
*/
|
|
124
124
|
private saveWaveFile(waveId: string, directive: string, preset?: string): void {
|
|
125
125
|
try {
|
|
126
|
-
const wavesDir = path.join(COMPANY_ROOT, '
|
|
126
|
+
const wavesDir = path.join(COMPANY_ROOT, '.tycono', 'waves');
|
|
127
127
|
if (!fs.existsSync(wavesDir)) fs.mkdirSync(wavesDir, { recursive: true });
|
|
128
128
|
const wavePath = path.join(wavesDir, `${waveId}.json`);
|
|
129
129
|
if (!fs.existsSync(wavePath)) {
|
|
@@ -185,7 +185,7 @@ class SupervisorHeartbeat {
|
|
|
185
185
|
let originalDirective = '';
|
|
186
186
|
let originalPreset: string | undefined;
|
|
187
187
|
try {
|
|
188
|
-
const waveFile = path.join(COMPANY_ROOT, '
|
|
188
|
+
const waveFile = path.join(COMPANY_ROOT, '.tycono', 'waves', `${waveId}.json`);
|
|
189
189
|
if (fs.existsSync(waveFile)) {
|
|
190
190
|
const waveData = JSON.parse(fs.readFileSync(waveFile, 'utf-8'));
|
|
191
191
|
originalDirective = waveData.directive ?? '';
|
|
@@ -820,7 +820,7 @@ ${state.continuous ? `## Continuous Improvement Mode (ON)
|
|
|
820
820
|
}
|
|
821
821
|
}
|
|
822
822
|
|
|
823
|
-
// Auto-save the completed wave to
|
|
823
|
+
// Auto-save the completed wave to .tycono/waves/
|
|
824
824
|
try {
|
|
825
825
|
const result = saveCompletedWave(state.waveId, state.directive);
|
|
826
826
|
if (result.ok) {
|
|
@@ -37,7 +37,7 @@ export class TokenLedger {
|
|
|
37
37
|
private filePath: string;
|
|
38
38
|
|
|
39
39
|
constructor(companyRoot: string) {
|
|
40
|
-
const dir = path.join(companyRoot, '
|
|
40
|
+
const dir = path.join(companyRoot, '.tycono', 'cost');
|
|
41
41
|
if (!fs.existsSync(dir)) {
|
|
42
42
|
fs.mkdirSync(dir, { recursive: true });
|
|
43
43
|
}
|
|
@@ -13,7 +13,7 @@ import { type WaveRoleStatus, eventTypeToMessageStatus } from '../../../shared/t
|
|
|
13
13
|
/* ─── Find wave file ──────────────────────── */
|
|
14
14
|
|
|
15
15
|
export function findWaveFile(waveId: string): string | null {
|
|
16
|
-
const wavesDir = path.join(COMPANY_ROOT, '
|
|
16
|
+
const wavesDir = path.join(COMPANY_ROOT, '.tycono', 'waves');
|
|
17
17
|
if (!fs.existsSync(wavesDir)) return null;
|
|
18
18
|
|
|
19
19
|
const direct = path.join(wavesDir, `${waveId}.json`);
|
|
@@ -159,7 +159,7 @@ export function updateFollowUpInWave(waveId: string, sessionId: string, roleId:
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/* ─── Save completed wave to
|
|
162
|
+
/* ─── Save completed wave to .tycono/waves/ ── */
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
165
|
* Auto-save a completed wave to disk.
|
|
@@ -178,7 +178,7 @@ export function saveCompletedWave(waveId: string, directive: string): { ok: bool
|
|
|
178
178
|
// Scan activity-streams for ALL sessions belonging to this wave.
|
|
179
179
|
// Wave sessions share a traceId chain: CEO → C-Level → subordinates.
|
|
180
180
|
// We find the CEO session (waveId timestamp embedded in its ID), then follow dispatch:start events.
|
|
181
|
-
const streamsDir = path.join(COMPANY_ROOT, '
|
|
181
|
+
const streamsDir = path.join(COMPANY_ROOT, '.tycono', 'activity-streams');
|
|
182
182
|
if (fs.existsSync(streamsDir)) {
|
|
183
183
|
// Find all activity stream files and check if they belong to this wave
|
|
184
184
|
const waveTimestamp = waveId.replace('wave-', '');
|
|
@@ -260,7 +260,7 @@ export function saveCompletedWave(waveId: string, directive: string): { ok: bool
|
|
|
260
260
|
rolesData.push({ roleId, roleName, sessionId: sid, status, events, childSessions });
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
const wavesDir = path.join(COMPANY_ROOT, '
|
|
263
|
+
const wavesDir = path.join(COMPANY_ROOT, '.tycono', 'waves');
|
|
264
264
|
if (!fs.existsSync(wavesDir)) {
|
|
265
265
|
fs.mkdirSync(wavesDir, { recursive: true });
|
|
266
266
|
}
|
|
@@ -325,7 +325,7 @@ export function saveCompletedWave(waveId: string, directive: string): { ok: bool
|
|
|
325
325
|
if (existingPreset) waveJson.preset = existingPreset;
|
|
326
326
|
fs.writeFileSync(jsonPath, JSON.stringify(waveJson, null, 2), 'utf-8');
|
|
327
327
|
|
|
328
|
-
const relativePath =
|
|
328
|
+
const relativePath = `.tycono/waves/${baseName}.json`;
|
|
329
329
|
console.log(`[WaveTracker] Wave saved: ${relativePath} (${rolesData.length} roles)`);
|
|
330
330
|
|
|
331
331
|
// Earn coins for wave completion (non-critical)
|
package/templates/CLAUDE.md.tmpl
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
| Task | Read First | Role |
|
|
10
10
|
|------|-----------|------|
|
|
11
|
-
| Product planning | `projects/` | PM |
|
|
12
|
-
| Technical design | `architecture/` | CTO |
|
|
13
|
-
| Implementation | `projects/*/tasks.md` | Engineer |
|
|
14
|
-
| UI/UX Design | `projects/*/design/` | Designer |
|
|
15
|
-
| Testing/QA | `projects/*/tasks.md` | QA |
|
|
16
|
-
| Operations |
|
|
17
|
-
| Business/Revenue | `company
|
|
11
|
+
| Product planning | `knowledge/projects/` | PM |
|
|
12
|
+
| Technical design | `knowledge/architecture/` | CTO |
|
|
13
|
+
| Implementation | `knowledge/projects/*/tasks.md` | Engineer |
|
|
14
|
+
| UI/UX Design | `knowledge/projects/*/design/` | Designer |
|
|
15
|
+
| Testing/QA | `knowledge/projects/*/tasks.md` | QA |
|
|
16
|
+
| Operations | `.tycono/` | PM |
|
|
17
|
+
| Business/Revenue | `knowledge/company.md` | CBO |
|
|
18
18
|
| Domain knowledge | `knowledge/` | CBO |
|
|
19
19
|
|
|
20
20
|
---
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
> **AKB** = A file-based knowledge system where AI uses **search (Grep/Glob)** to find and **contextual links** to navigate
|
|
25
25
|
>
|
|
26
|
-
> Full reference: `methodologies/agentic-knowledge-base.md`
|
|
26
|
+
> Full reference: `knowledge/methodologies/agentic-knowledge-base.md`
|
|
27
27
|
|
|
28
28
|
| Layer | Role | AI Usage |
|
|
29
29
|
|-------|------|----------|
|
|
@@ -203,30 +203,30 @@ After completing any task, check:
|
|
|
203
203
|
| +-- preferences.json <- UI preferences (auto-generated)
|
|
204
204
|
| +-- custom-rules.md <- Company custom rules (user owned)
|
|
205
205
|
| +-- rules-version <- Current CLAUDE.md version
|
|
206
|
-
+-- company/
|
|
207
|
-
| +-- company.md <- Mission, vision, company info
|
|
208
|
-
+-- roles/
|
|
209
|
-
| +-- roles.md <- Role listing (Hub)
|
|
210
|
-
| +-- {role-id}/
|
|
211
|
-
| +-- role.yaml <- Role definition
|
|
212
|
-
| +-- profile.md <- Role profile
|
|
213
|
-
| +-- journal/ <- Work journal
|
|
214
|
-
+-- projects/
|
|
215
|
-
| +-- projects.md <- Project listing (Hub)
|
|
216
|
-
+-- architecture/
|
|
217
|
-
| +-- architecture.md <- Technical architecture (Hub)
|
|
218
|
-
+-- operations/
|
|
219
|
-
| +-- standup/ <- Daily standups
|
|
220
206
|
| +-- waves/ <- Wave execution logs
|
|
221
|
-
| +-- decisions/ <- Decision log
|
|
222
|
-
| +-- activity-streams/ <- SSE activity event logs
|
|
223
207
|
| +-- sessions/ <- Session state
|
|
208
|
+
| +-- standup/ <- Daily standups
|
|
209
|
+
| +-- activity-streams/ <- SSE activity event logs
|
|
224
210
|
| +-- cost/ <- Token usage ledger
|
|
211
|
+
| +-- activity/ <- Activity logs
|
|
225
212
|
+-- knowledge/
|
|
226
213
|
| +-- knowledge.md <- Domain knowledge (Hub)
|
|
227
|
-
+--
|
|
228
|
-
| +--
|
|
229
|
-
| +--
|
|
214
|
+
| +-- company.md <- Mission, vision, company info
|
|
215
|
+
| +-- roles/
|
|
216
|
+
| | +-- roles.md <- Role listing (Hub)
|
|
217
|
+
| | +-- {role-id}/
|
|
218
|
+
| | +-- role.yaml <- Role definition
|
|
219
|
+
| | +-- profile.md <- Role profile
|
|
220
|
+
| | +-- journal/ <- Work journal
|
|
221
|
+
| +-- projects/
|
|
222
|
+
| | +-- projects.md <- Project listing (Hub)
|
|
223
|
+
| +-- architecture/
|
|
224
|
+
| | +-- architecture.md <- Technical architecture (Hub)
|
|
225
|
+
| +-- decisions/ <- Decision log
|
|
226
|
+
| +-- presets/ <- Preset configurations
|
|
227
|
+
| +-- methodologies/
|
|
228
|
+
| +-- methodologies.md <- Methodology listing (Hub)
|
|
229
|
+
| +-- agentic-knowledge-base.md <- AKB protocol reference
|
|
230
230
|
+-- .claude/skills/
|
|
231
231
|
+-- _shared/ <- Shared skill plugins
|
|
232
232
|
+-- {role-id}/SKILL.md <- Role-specific skill guides
|