tycono 0.1.61 → 0.1.62
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/routes/setup.ts +5 -1
- package/src/api/src/routes/speech.ts +94 -50
- package/src/api/src/services/git-save.ts +15 -1
- package/src/web/dist/assets/{index-CdkeNB5B.css → index-C7IEX_o_.css} +1 -1
- package/src/web/dist/assets/index-MzayY_Qt.js +101 -0
- package/src/web/dist/assets/{preview-app-C0OnJLc1.js → preview-app-CPEP4rUN.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
|
@@ -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 });
|
|
@@ -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,25 @@ 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 (READ THIS CAREFULLY) ═══
|
|
667
|
+
${companyCtx}
|
|
668
|
+
${roleCtx}
|
|
669
|
+
═══ END CONTEXT ═══
|
|
670
|
+
|
|
671
|
+
GROUNDING (CRITICAL — READ BEFORE RESPONDING):
|
|
672
|
+
Everything above between ═══ markers is REAL data from your company's knowledge base.
|
|
673
|
+
You MUST base your conversation on this context. Mention specific projects, tasks, decisions, or work items BY NAME.
|
|
674
|
+
NEVER invent technologies, tools, migrations, file names, or projects that are NOT in the context above.
|
|
675
|
+
If the Tech Stack says "TypeScript + React + Node.js", do NOT mention Python, tc.py, or any language migration.
|
|
676
|
+
If you have assigned tasks listed, reference them. If you have journal entries, reference your recent work.
|
|
677
|
+
If you cannot find something specific to say from the context, say [SILENT] instead of making things up.
|
|
629
678
|
|
|
630
|
-
|
|
631
|
-
You have been given real company knowledge above under "COMPANY CONTEXT" and "YOUR KNOWLEDGE". This is from AKB files, journal, CEO waves, standups, and decisions.
|
|
632
|
-
You MUST reference this real context in your conversations — mention specific projects, decisions, tasks, or events by name.
|
|
633
|
-
Do NOT generate generic workplace chatter. Every message should show you're aware of what's actually happening in the company.
|
|
634
|
-
If your knowledge section mentions a specific decision or wave, reference it naturally (e.g. "after the test minimization decision..." or "CEO's wave about side panel...").
|
|
635
|
-
NEVER invent or assume technologies, tools, migrations, or projects NOT mentioned in the context above. If the Tech Stack says "TypeScript + React + Node.js", do NOT talk about Python, tc.py, or any language migration. Only discuss what is explicitly in your provided context.
|
|
679
|
+
${roleStyle}
|
|
636
680
|
|
|
637
681
|
CONVERSATION RULES:
|
|
638
682
|
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)) {
|