mirror of
https://github.com/davidkaya/aryx.git
synced 2026-11-15 22:52:04 +01:00
feat: expand repo customization discovery
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
dc69f8bf04
commit
36b37e8915
6 files changed
+420
-44
No files matched your search
@@ -1,7 +1,13 @@
|
||||
export type ProjectInstructionApplicationMode = 'always' | 'file' | 'task' | 'manual';
|
||||
|
||||
export interface ProjectInstructionFile {
|
||||
id: string;
|
||||
sourcePath: string;
|
||||
content: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
applyTo?: string;
|
||||
applicationMode: ProjectInstructionApplicationMode;
|
||||
}
|
||||
|
||||
export interface ProjectAgentProfileMcpServerConfig {
|
||||
@@ -111,8 +117,34 @@ export function listEnabledProjectAgentProfiles(
|
||||
export function resolveProjectInstructionsContent(
|
||||
state?: Partial<ProjectCustomizationState>,
|
||||
): string | undefined {
|
||||
const content = normalizeProjectCustomizationState(state).instructions
|
||||
.map((instruction) => instruction.content)
|
||||
const instructions = normalizeProjectCustomizationState(state).instructions;
|
||||
const contentBlocks = instructions
|
||||
.filter((instruction) => instruction.applicationMode === 'always')
|
||||
.map((instruction) => instruction.content);
|
||||
|
||||
const fileScopedInstructions = instructions.filter((instruction) => instruction.applicationMode === 'file');
|
||||
if (fileScopedInstructions.length > 0) {
|
||||
contentBlocks.push(
|
||||
formatProjectInstructionSection(
|
||||
'Repository file-scoped instructions:',
|
||||
'Apply each instruction only when working on files whose relative workspace path matches the listed glob.',
|
||||
fileScopedInstructions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const taskScopedInstructions = instructions.filter((instruction) => instruction.applicationMode === 'task');
|
||||
if (taskScopedInstructions.length > 0) {
|
||||
contentBlocks.push(
|
||||
formatProjectInstructionSection(
|
||||
'Repository task-scoped instructions:',
|
||||
'Apply each instruction only when the current task matches its description.',
|
||||
taskScopedInstructions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const content = contentBlocks
|
||||
.filter((value) => value.length > 0)
|
||||
.join('\n\n')
|
||||
.trim();
|
||||
@@ -139,11 +171,35 @@ export function setProjectAgentProfileEnabled(
|
||||
}
|
||||
|
||||
function normalizeProjectInstructionFile(file: ProjectInstructionFile): ProjectInstructionFile {
|
||||
return {
|
||||
const normalizedSourcePath = normalizePathLikeString(file.sourcePath);
|
||||
const normalizedName = normalizeOptionalString(file.name);
|
||||
const normalizedDescription = normalizeOptionalString(file.description);
|
||||
const normalizedApplyTo = normalizeOptionalString(file.applyTo);
|
||||
const normalizedInstruction: ProjectInstructionFile = {
|
||||
id: file.id.trim(),
|
||||
sourcePath: normalizePathLikeString(file.sourcePath),
|
||||
sourcePath: normalizedSourcePath,
|
||||
content: file.content.trim(),
|
||||
applicationMode: normalizeInstructionApplicationMode(
|
||||
file.applicationMode,
|
||||
normalizedSourcePath,
|
||||
normalizedApplyTo,
|
||||
normalizedDescription,
|
||||
),
|
||||
};
|
||||
|
||||
if (normalizedName) {
|
||||
normalizedInstruction.name = normalizedName;
|
||||
}
|
||||
|
||||
if (normalizedDescription) {
|
||||
normalizedInstruction.description = normalizedDescription;
|
||||
}
|
||||
|
||||
if (normalizedApplyTo) {
|
||||
normalizedInstruction.applyTo = normalizedApplyTo;
|
||||
}
|
||||
|
||||
return normalizedInstruction;
|
||||
}
|
||||
|
||||
function normalizeProjectAgentProfile(profile: ProjectAgentProfile): ProjectAgentProfile {
|
||||
@@ -216,6 +272,38 @@ function compareProjectFiles(
|
||||
return left.sourcePath.localeCompare(right.sourcePath) || left.id.localeCompare(right.id);
|
||||
}
|
||||
|
||||
function formatProjectInstructionSection(
|
||||
title: string,
|
||||
guidance: string,
|
||||
instructions: ReadonlyArray<ProjectInstructionFile>,
|
||||
): string {
|
||||
return [`${title}\n${guidance}`, ...instructions.map(formatProjectInstructionEntry)]
|
||||
.filter((block) => block.length > 0)
|
||||
.join('\n\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function formatProjectInstructionEntry(instruction: ProjectInstructionFile): string {
|
||||
const lines = [`Source: ${instruction.sourcePath}`];
|
||||
|
||||
if (instruction.name) {
|
||||
lines.push(`Name: ${instruction.name}`);
|
||||
}
|
||||
|
||||
if (instruction.description) {
|
||||
lines.push(`Description: ${instruction.description}`);
|
||||
}
|
||||
|
||||
if (instruction.applyTo) {
|
||||
lines.push(`ApplyTo: ${instruction.applyTo}`);
|
||||
}
|
||||
|
||||
lines.push('Instructions:');
|
||||
lines.push(instruction.content);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function normalizeOptionalMcpServers(
|
||||
value?: Record<string, ProjectAgentProfileMcpServerConfig>,
|
||||
): Record<string, ProjectAgentProfileMcpServerConfig> | undefined {
|
||||
@@ -242,6 +330,19 @@ function normalizeOptionalString(value?: string): string | undefined {
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeInstructionApplicationMode(
|
||||
value: ProjectInstructionApplicationMode | undefined,
|
||||
sourcePath: string,
|
||||
applyTo?: string,
|
||||
description?: string,
|
||||
): ProjectInstructionApplicationMode {
|
||||
if (value === 'always' || value === 'file' || value === 'task' || value === 'manual') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return inferInstructionApplicationMode(sourcePath, applyTo, description);
|
||||
}
|
||||
|
||||
function normalizeOptionalStringArray(values?: ReadonlyArray<string>): string[] | undefined {
|
||||
if (!values) {
|
||||
return undefined;
|
||||
@@ -254,6 +355,43 @@ function normalizePathLikeString(value: string): string {
|
||||
return value.trim().replaceAll('/', '\\');
|
||||
}
|
||||
|
||||
function inferInstructionApplicationMode(
|
||||
sourcePath: string,
|
||||
applyTo?: string,
|
||||
description?: string,
|
||||
): ProjectInstructionApplicationMode {
|
||||
if (isAlwaysOnInstructionSource(sourcePath) || isMatchAllInstructionGlob(applyTo)) {
|
||||
return 'always';
|
||||
}
|
||||
|
||||
if (applyTo) {
|
||||
return 'file';
|
||||
}
|
||||
|
||||
if (description) {
|
||||
return 'task';
|
||||
}
|
||||
|
||||
return 'manual';
|
||||
}
|
||||
|
||||
function isAlwaysOnInstructionSource(sourcePath: string): boolean {
|
||||
const normalizedSourcePath = normalizePathLikeString(sourcePath).toLowerCase();
|
||||
return normalizedSourcePath === '.github\\copilot-instructions.md'
|
||||
|| normalizedSourcePath === 'agents.md'
|
||||
|| normalizedSourcePath === 'claude.md'
|
||||
|| normalizedSourcePath === '.claude\\claude.md';
|
||||
}
|
||||
|
||||
function isMatchAllInstructionGlob(value?: string): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedValue = value.trim().replaceAll('\\', '/');
|
||||
return normalizedValue === '**' || normalizedValue === '**/*';
|
||||
}
|
||||
|
||||
function normalizeYamlValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(normalizeYamlValue);
|
||||
|
||||
Reference in new issue
Block a user