tycono 0.1.61 → 0.1.63
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/engine/llm-adapter.ts +1 -1
- package/src/api/src/routes/setup.ts +5 -1
- package/src/api/src/routes/speech.ts +99 -50
- package/src/api/src/services/git-save.ts +15 -1
- package/src/web/dist/assets/index-Btwm9L8u.js +101 -0
- package/src/web/dist/assets/{index-CdkeNB5B.css → index-C7IEX_o_.css} +1 -1
- package/src/web/dist/assets/{preview-app-C0OnJLc1.js → preview-app-Qc37pwGk.js} +1 -1
- package/src/web/dist/index.html +2 -2
- package/src/web/dist/assets/index-Bx4F8eya.js +0 -101
package/package.json
CHANGED
|
@@ -247,7 +247,7 @@ export class ClaudeCliProvider implements LLMProvider {
|
|
|
247
247
|
'-p',
|
|
248
248
|
'--system-prompt', systemPrompt,
|
|
249
249
|
'--model', this.model,
|
|
250
|
-
'--max-turns', useTools ? '
|
|
250
|
+
'--max-turns', useTools ? '50' : '1',
|
|
251
251
|
'--output-format', 'text',
|
|
252
252
|
...(useTools ? [
|
|
253
253
|
'--tools', 'Read,Grep,Glob',
|
|
@@ -12,6 +12,7 @@ import os from 'node:os';
|
|
|
12
12
|
import { scaffold, getAvailableTeams, loadTeam } from '../services/scaffold.js';
|
|
13
13
|
import type { ScaffoldConfig } from '../services/scaffold.js';
|
|
14
14
|
import { importKnowledge } from '../services/knowledge-importer.js';
|
|
15
|
+
import { gitInit } from '../services/git-save.js';
|
|
15
16
|
import { AnthropicProvider, type LLMProvider } from '../engine/llm-adapter.js';
|
|
16
17
|
import { jobManager } from '../services/job-manager.js';
|
|
17
18
|
import { applyConfig, readConfig, writeConfig } from '../services/company-config.js';
|
|
@@ -136,7 +137,10 @@ setupRouter.post('/scaffold', (req, res) => {
|
|
|
136
137
|
}
|
|
137
138
|
jobManager.refreshRunner();
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
// Auto git init (graceful — skip if git not installed)
|
|
141
|
+
const gitResult = gitInit(projectRoot);
|
|
142
|
+
|
|
143
|
+
res.json({ ok: true, companyName, projectRoot, created, git: gitResult });
|
|
140
144
|
} catch (err) {
|
|
141
145
|
const message = err instanceof Error ? err.message : 'Scaffold failed';
|
|
142
146
|
res.status(500).json({ error: message });
|
|
@@ -26,7 +26,7 @@ export const speechRouter = Router();
|
|
|
26
26
|
* AKB Tools — Let chat roles explore company knowledge
|
|
27
27
|
* ══════════════════════════════════════════════════ */
|
|
28
28
|
|
|
29
|
-
const MAX_TOOL_ROUNDS =
|
|
29
|
+
const MAX_TOOL_ROUNDS = 50;
|
|
30
30
|
const MAX_FILE_CHARS = 1500; // truncate large files
|
|
31
31
|
|
|
32
32
|
const AKB_TOOLS: ToolDefinition[] = [
|
|
@@ -295,12 +295,24 @@ function buildCompanyContext(): string {
|
|
|
295
295
|
|
|
296
296
|
/**
|
|
297
297
|
* Build role-specific AKB context by pre-fetching relevant knowledge server-side.
|
|
298
|
-
*
|
|
298
|
+
* This is the PRIMARY source of grounding for chat — must be rich enough that
|
|
299
|
+
* agents don't need to use tools (Haiku won't proactively search).
|
|
299
300
|
*/
|
|
300
301
|
function buildRoleContext(roleId: string): string {
|
|
301
302
|
const parts: string[] = [];
|
|
302
303
|
|
|
303
|
-
//
|
|
304
|
+
// 0. Role profile — gives the agent its identity and work context
|
|
305
|
+
try {
|
|
306
|
+
const profilePath = path.join(COMPANY_ROOT, 'roles', roleId, 'profile.md');
|
|
307
|
+
if (fs.existsSync(profilePath)) {
|
|
308
|
+
const content = fs.readFileSync(profilePath, 'utf-8').trim();
|
|
309
|
+
if (content.length > 20) {
|
|
310
|
+
parts.push(`[Your Profile]\n${content.slice(0, 600)}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch { /* no profile */ }
|
|
314
|
+
|
|
315
|
+
// 1. Role's journal — latest entry with meaningful content (headers as summary)
|
|
304
316
|
try {
|
|
305
317
|
const journalDir = path.join(COMPANY_ROOT, 'roles', roleId, 'journal');
|
|
306
318
|
if (fs.existsSync(journalDir)) {
|
|
@@ -310,49 +322,81 @@ function buildRoleContext(roleId: string): string {
|
|
|
310
322
|
.slice(-2);
|
|
311
323
|
for (const file of files) {
|
|
312
324
|
const content = fs.readFileSync(path.join(journalDir, file), 'utf-8');
|
|
313
|
-
// Extract
|
|
325
|
+
// Extract all ### headers as work summary + first paragraph of each
|
|
326
|
+
const sections = content.match(/###\s+.+/g) ?? [];
|
|
314
327
|
const title = content.match(/^#\s+(.+)/m)?.[1] ?? file;
|
|
315
|
-
const
|
|
316
|
-
|
|
328
|
+
const summary = sections.length > 0
|
|
329
|
+
? sections.map(s => ` ${s.replace(/^###\s+/, '- ')}`).join('\n')
|
|
330
|
+
: content.split('\n').slice(1).join('\n').trim().slice(0, 300);
|
|
331
|
+
parts.push(`[Your Work Log: ${file}] ${title}\n${summary}`);
|
|
317
332
|
}
|
|
318
333
|
}
|
|
319
334
|
} catch { /* no journal */ }
|
|
320
335
|
|
|
321
|
-
// 2.
|
|
336
|
+
// 2. Current tasks assigned to this role (from all project tasks.md files)
|
|
337
|
+
try {
|
|
338
|
+
const projectsDir = path.join(COMPANY_ROOT, 'projects');
|
|
339
|
+
if (fs.existsSync(projectsDir)) {
|
|
340
|
+
const taskFiles = glob.sync('**/tasks.md', { cwd: projectsDir, absolute: false });
|
|
341
|
+
const roleTasks: string[] = [];
|
|
342
|
+
for (const tf of taskFiles.slice(0, 3)) {
|
|
343
|
+
const content = fs.readFileSync(path.join(projectsDir, tf), 'utf-8');
|
|
344
|
+
const rows = parseMarkdownTable(content);
|
|
345
|
+
const myTasks = rows.filter(r => {
|
|
346
|
+
const role = (r.role ?? r.Role ?? '').toLowerCase();
|
|
347
|
+
return role.includes(roleId);
|
|
348
|
+
});
|
|
349
|
+
for (const t of myTasks.slice(0, 5)) {
|
|
350
|
+
const id = t.id ?? t.ID ?? '';
|
|
351
|
+
const task = t.task ?? t.Task ?? t.title ?? '';
|
|
352
|
+
const status = t.status ?? t.Status ?? '';
|
|
353
|
+
if (task) roleTasks.push(`- ${id}: ${task} [${status}]`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (roleTasks.length > 0) {
|
|
357
|
+
parts.push(`[Your Assigned Tasks]\n${roleTasks.join('\n')}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch { /* no tasks */ }
|
|
361
|
+
|
|
362
|
+
// 3. Recent waves (broadened matching: roleId, role name, level keywords)
|
|
322
363
|
try {
|
|
323
364
|
const wavesDir = path.join(COMPANY_ROOT, 'operations', 'waves');
|
|
324
365
|
if (fs.existsSync(wavesDir)) {
|
|
366
|
+
// Also get role name for matching
|
|
367
|
+
const tree = buildOrgTree(COMPANY_ROOT);
|
|
368
|
+
const node = tree.nodes.get(roleId);
|
|
369
|
+
const roleName = node?.name?.toLowerCase() ?? '';
|
|
370
|
+
const roleLevel = node?.level?.toLowerCase() ?? '';
|
|
371
|
+
|
|
325
372
|
const waveFiles = fs.readdirSync(wavesDir)
|
|
326
373
|
.filter(f => f.endsWith('.md'))
|
|
327
374
|
.sort()
|
|
328
|
-
.slice(-10);
|
|
375
|
+
.slice(-10);
|
|
329
376
|
const relevant: string[] = [];
|
|
330
377
|
for (const file of waveFiles.reverse()) {
|
|
331
|
-
if (relevant.length >=
|
|
378
|
+
if (relevant.length >= 2) break;
|
|
332
379
|
const content = fs.readFileSync(path.join(wavesDir, file), 'utf-8');
|
|
333
380
|
const lower = content.toLowerCase();
|
|
334
|
-
if (lower.includes(roleId) || lower.includes('all roles') || lower.includes('전체')
|
|
381
|
+
if (lower.includes(roleId) || lower.includes('all roles') || lower.includes('전체')
|
|
382
|
+
|| (roleName && lower.includes(roleName))
|
|
383
|
+
|| (roleLevel && lower.includes(roleLevel))) {
|
|
335
384
|
const title = content.match(/^#\s+(.+)/m)?.[1] ?? file;
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
relevant.push(`[Wave: ${file}] ${title}\n${snippet.slice(0, 300)}`);
|
|
385
|
+
const snippet = content.split('\n').slice(1, 8).join('\n').trim();
|
|
386
|
+
relevant.push(`[CEO Wave: ${file}] ${title}\n${snippet.slice(0, 400)}`);
|
|
339
387
|
}
|
|
340
388
|
}
|
|
341
389
|
if (relevant.length > 0) parts.push(...relevant);
|
|
342
390
|
}
|
|
343
391
|
} catch { /* no waves */ }
|
|
344
392
|
|
|
345
|
-
//
|
|
393
|
+
// 4. Recent standup (latest, this role's section)
|
|
346
394
|
try {
|
|
347
395
|
const standupDir = path.join(COMPANY_ROOT, 'operations', 'standup');
|
|
348
396
|
if (fs.existsSync(standupDir)) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
.sort()
|
|
352
|
-
.slice(-1);
|
|
353
|
-
for (const file of standupFiles) {
|
|
397
|
+
const files = fs.readdirSync(standupDir).filter(f => f.endsWith('.md')).sort().slice(-1);
|
|
398
|
+
for (const file of files) {
|
|
354
399
|
const content = fs.readFileSync(path.join(standupDir, file), 'utf-8');
|
|
355
|
-
// Try to extract this role's section from standup
|
|
356
400
|
const rolePattern = new RegExp(`(## .*${roleId}.*|### .*${roleId}.*)([\\s\\S]*?)(?=\\n## |\\n### |$)`, 'i');
|
|
357
401
|
const match = content.match(rolePattern);
|
|
358
402
|
if (match) {
|
|
@@ -362,7 +406,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
362
406
|
}
|
|
363
407
|
} catch { /* no standups */ }
|
|
364
408
|
|
|
365
|
-
//
|
|
409
|
+
// 5. Recent decisions (last 3)
|
|
366
410
|
try {
|
|
367
411
|
const decisionsDir = path.join(COMPANY_ROOT, 'operations', 'decisions');
|
|
368
412
|
if (fs.existsSync(decisionsDir)) {
|
|
@@ -374,8 +418,7 @@ function buildRoleContext(roleId: string): string {
|
|
|
374
418
|
for (const file of files) {
|
|
375
419
|
const content = fs.readFileSync(path.join(decisionsDir, file), 'utf-8');
|
|
376
420
|
const title = content.match(/^#\s+(.+)/m)?.[1] ?? file;
|
|
377
|
-
|
|
378
|
-
decisions.push(`- ${title}${summary ? ': ' + summary.split('\n').slice(1).join(' ').trim().slice(0, 150) : ''}`);
|
|
421
|
+
decisions.push(`- ${title}`);
|
|
379
422
|
}
|
|
380
423
|
if (decisions.length > 0) {
|
|
381
424
|
parts.push(`[Recent Decisions]\n${decisions.join('\n')}`);
|
|
@@ -383,28 +426,25 @@ function buildRoleContext(roleId: string): string {
|
|
|
383
426
|
}
|
|
384
427
|
} catch { /* no decisions */ }
|
|
385
428
|
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (active.length > 0) {
|
|
400
|
-
parts.push(`[Active Tech Debt]\n${active.join('\n')}`);
|
|
401
|
-
}
|
|
429
|
+
// 6. Architecture highlights (always include — not just fallback)
|
|
430
|
+
try {
|
|
431
|
+
const techDebtPath = path.join(COMPANY_ROOT, 'architecture', 'tech-debt.md');
|
|
432
|
+
if (fs.existsSync(techDebtPath)) {
|
|
433
|
+
const tdContent = fs.readFileSync(techDebtPath, 'utf-8');
|
|
434
|
+
const rows = parseMarkdownTable(tdContent);
|
|
435
|
+
const active = rows
|
|
436
|
+
.filter(r => !(r.status ?? '').toLowerCase().includes('fixed') && !(r.status ?? '').toLowerCase().includes('done'))
|
|
437
|
+
.slice(0, 3)
|
|
438
|
+
.map(r => `- ${r.id ?? ''}: ${r.title ?? r.issue ?? ''} (${r.status ?? ''})`)
|
|
439
|
+
.filter(s => s.length > 10);
|
|
440
|
+
if (active.length > 0) {
|
|
441
|
+
parts.push(`[Active Tech Issues]\n${active.join('\n')}`);
|
|
402
442
|
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
443
|
+
}
|
|
444
|
+
} catch { /* no tech-debt */ }
|
|
405
445
|
|
|
406
446
|
return parts.length > 0
|
|
407
|
-
? `\n\nYOUR KNOWLEDGE (real AKB context — reference this in conversation):\n${parts.join('\n\n')}`
|
|
447
|
+
? `\n\nYOUR KNOWLEDGE (real AKB context — you MUST reference this in conversation):\n${parts.join('\n\n')}`
|
|
408
448
|
: '';
|
|
409
449
|
}
|
|
410
450
|
|
|
@@ -618,21 +658,30 @@ speechRouter.post('/chat', async (req: Request, res: Response, next: NextFunctio
|
|
|
618
658
|
Persona: ${persona}
|
|
619
659
|
${workCtx}
|
|
620
660
|
${levelCtx}
|
|
621
|
-
${companyCtx}
|
|
622
|
-
${roleCtx}
|
|
623
661
|
|
|
624
662
|
You are in the #${channelId} chat channel.${topicCtx}
|
|
625
663
|
Members: ${memberList}
|
|
626
664
|
${relContext}
|
|
627
665
|
|
|
628
|
-
|
|
666
|
+
═══ COMPANY & ROLE CONTEXT (pre-fetched from AKB) ═══
|
|
667
|
+
${companyCtx}
|
|
668
|
+
${roleCtx}
|
|
669
|
+
═══ END CONTEXT ═══
|
|
670
|
+
|
|
671
|
+
AKB ACCESS (USE BEFORE RESPONDING):
|
|
672
|
+
You have Read, Grep, Glob tools. The company AKB root is: ${COMPANY_ROOT}/
|
|
673
|
+
⛔ BEFORE writing your chat message:
|
|
674
|
+
1. Read ${COMPANY_ROOT}/CLAUDE.md (check Task Routing table for your role's paths)
|
|
675
|
+
2. Follow Hub-First Principle: read the relevant Hub, then explore journals, tasks, decisions as needed
|
|
676
|
+
3. Write your 1-3 sentence response grounded in what you found
|
|
629
677
|
|
|
630
678
|
GROUNDING (CRITICAL):
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
If
|
|
635
|
-
|
|
679
|
+
Base your response ONLY on the pre-fetched context above AND data you read via tools.
|
|
680
|
+
Reference specific projects, tasks, decisions, journal entries BY NAME.
|
|
681
|
+
NEVER invent technologies, tools, file names, or projects not found in AKB files.
|
|
682
|
+
If you cannot find anything specific to say, respond with [SILENT].
|
|
683
|
+
|
|
684
|
+
${roleStyle}
|
|
636
685
|
|
|
637
686
|
CONVERSATION RULES:
|
|
638
687
|
1. Stay deeply in character — your expertise, vocabulary, and concerns should be DISTINCT from other roles.
|
|
@@ -151,6 +151,16 @@ function runOrThrow(cmd: string, cwd: string): string {
|
|
|
151
151
|
return execSync(cmd, { cwd, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/** Check if git binary is available */
|
|
155
|
+
function isGitAvailable(): boolean {
|
|
156
|
+
try {
|
|
157
|
+
execSync('git --version', { timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
154
164
|
/** Check if directory is a git repository */
|
|
155
165
|
function isGitRepo(root: string): boolean {
|
|
156
166
|
return run('git rev-parse --is-inside-work-tree', root) === 'true';
|
|
@@ -161,7 +171,11 @@ function isGitRepo(root: string): boolean {
|
|
|
161
171
|
* @param root - AKB repository root (COMPANY_ROOT)
|
|
162
172
|
* @param repo - Repository type ('akb' or 'code'), default 'akb'
|
|
163
173
|
*/
|
|
164
|
-
export function gitInit(root: string, repo: RepoType = 'akb'): { ok: boolean; message: string } {
|
|
174
|
+
export function gitInit(root: string, repo: RepoType = 'akb'): { ok: boolean; message: string; noGitBinary?: boolean } {
|
|
175
|
+
if (!isGitAvailable()) {
|
|
176
|
+
return { ok: false, message: 'git is not installed', noGitBinary: true };
|
|
177
|
+
}
|
|
178
|
+
|
|
165
179
|
const repoRoot = resolveRepoRoot(root, repo);
|
|
166
180
|
|
|
167
181
|
if (isGitRepo(repoRoot)) {
|