114 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Role loading for the `/role` command.
*
* The file format stays the claude-harness format: a global layer
* (`~/.claude/roles/<role>.md`) and an optional project layer
* (`<workspace>/.claude/roles/<role>.md`). The project layer wins on conflict.
* Auto-memory is Claude Code-only; this port explicitly drops that step and
* leaves the role's "Читать на старте" section to the model.
*/
import { existsSync, readFileSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
export const DEFAULT_ROLE_NAMES = ['dev', 'admin', 'analyst', 'qa']
/** Parse `/role [name]` without owning any grammar beyond the first word. */
export function parseRole(rawInput, roleNames = DEFAULT_ROLE_NAMES) {
const input = String(rawInput ?? '').trim()
if (input === '') return { kind: 'list' }
const role = input.split(/\s+/u)[0]
if (!roleNames.includes(role)) return { kind: 'invalid', role }
return { kind: 'role', role }
}
/** Absolute paths of both layers for one role. */
export function rolePaths({ role, cwd, rolesDir = join(homedir(), '.claude', 'roles'), projectRolesDir = '' }) {
const globalPath = join(rolesDir, `${role}.md`)
const projectPath = join(projectRolesDir || join(cwd, '.claude', 'roles'), `${role}.md`)
return { globalPath, projectPath }
}
function readIfPresent(path) {
try {
return existsSync(path) ? readFileSync(path, 'utf8') : undefined
} catch {
return undefined
}
}
/** Render the list shown by a bare `/role`. */
export function listRoles({ cwd, rolesDir, projectRolesDir, roleNames = DEFAULT_ROLE_NAMES }) {
const lines = [
`Роли claude-harness; общий слой: ${rolesDir || join(homedir(), '.claude', 'roles')}`,
`Workspace: ${cwd}`,
'',
]
for (const role of roleNames) {
const { globalPath, projectPath } = rolePaths({ role, cwd, rolesDir, projectRolesDir })
const global = existsSync(globalPath) ? 'есть' : 'нет'
const project = existsSync(projectPath) ? 'есть' : 'нет'
lines.push(`- ${role}: общий — ${global}; проектный — ${project}`)
}
lines.push('', 'Вызов: /role dev | /role admin | /role analyst | /role qa')
return lines.join('\n')
}
/**
* Build the model-visible role instructions.
* @throws when the global role file is missing.
*/
export function buildRoleText({ role, cwd, rolesDir, projectRolesDir, roleNames = DEFAULT_ROLE_NAMES }) {
if (!roleNames.includes(role)) throw new Error(`Неизвестная роль: ${role}`)
const { globalPath, projectPath } = rolePaths({ role, cwd, rolesDir, projectRolesDir })
const globalText = readIfPresent(globalPath)
if (globalText === undefined) {
throw new Error(`Нет общего файла роли: ${globalPath}`)
}
const projectText = readIfPresent(projectPath)
const projectMissing = projectText === undefined
const sections = [
`[dsh-harness] Роль сессии: ${role}. Workspace: ${cwd}`,
'Новая роль заменяет предыдущую целиком: правила прежней роли больше не действуют.',
'',
`## Общий слой: ${globalPath}`,
globalText.trim(),
'',
]
if (projectMissing) {
sections.push(
`## Проектный слой: ${projectPath}`,
`Проектного файла роли ${role} нет — работаю по общему слою.`,
'',
)
} else {
sections.push(`## Проектный слой: ${projectPath}`, projectText.trim(), '')
}
sections.push(
'## Память',
'Auto-memory в DeepSeek Harness нет. Раздел «Читать на старте» выполни сам: пути читай через read,',
'команды запускай через bash и только на чтение. Строки с префиксом `brief:` в этом spike не поддерживаются —',
'пропусти их и скажи об этом одной строкой.',
'',
'## Ответ',
'Ответь ровно пятью строками:',
'- Роль: <role> — <описание из первой строки файла роли>.',
'- Процесс: <одна строка из раздела «Процесс» — что действует по умолчанию>.',
'- Прочитано: <перечень файлов обоих слоёв и файлов памяти, через запятую>.',
'- Не делать: <раздел «Не делать» одной строкой>.',
'- Ждёт решения владельца: <из памяти роли; если нет — «ничего»>.',
)
return {
text: sections.join('\n'),
globalPath,
projectPath,
projectMissing,
}
}