feat(coding): add durable project identity core
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
import type {
|
||||
CodingProjectAgent,
|
||||
CodingProjectConfig,
|
||||
ProjectIdentityChoice,
|
||||
} from '../../shared/coding-project-contracts';
|
||||
import type {
|
||||
ConversationModelState,
|
||||
@@ -47,6 +48,24 @@ export interface CreateCodingProjectAgentInput {
|
||||
pinned?: boolean;
|
||||
}
|
||||
|
||||
const CANONICAL_PROJECT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
||||
|
||||
export function isCanonicalCodingProjectId(value: unknown): value is string {
|
||||
return typeof value === 'string' && CANONICAL_PROJECT_ID_PATTERN.test(value);
|
||||
}
|
||||
|
||||
export function normalizeProjectIdentityChoice(value: unknown): ProjectIdentityChoice {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error('Project identity choice is invalid');
|
||||
}
|
||||
const record = value as { kind?: unknown; projectId?: unknown };
|
||||
if (record.kind === 'create') return { kind: 'create' };
|
||||
if (record.kind === 'bind' && isCanonicalCodingProjectId(record.projectId)) {
|
||||
return { kind: 'bind', projectId: record.projectId };
|
||||
}
|
||||
throw new Error('Project identity choice is invalid');
|
||||
}
|
||||
|
||||
const AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
||||
const AVATAR_ID_PATTERN = /^avatar-(0[1-9]|1[0-6])$/;
|
||||
const THINKING_LEVELS = new Set<ConversationThinkingLevel>([
|
||||
@@ -182,6 +201,9 @@ export function normalizeCodingProjectConfigV2(value: unknown): CodingProjectCon
|
||||
const record = value as Partial<CodingProjectConfigV2>;
|
||||
if (record.schemaVersion !== 2) throw new Error('Unsupported coding project config schema');
|
||||
if (!isProjectType(record.projectType)) throw new Error('Invalid project type');
|
||||
if (record.projectId !== undefined && !isCanonicalCodingProjectId(record.projectId)) {
|
||||
throw new Error('Project identity is invalid');
|
||||
}
|
||||
if (typeof record.initialized !== 'boolean') throw new Error('Project initialized state is invalid');
|
||||
if (record.knowledgeDirectory !== 'knowledge') throw new Error('Project knowledge directory is invalid');
|
||||
if (!Array.isArray(record.agents)) throw new Error('Project Agents must be an array');
|
||||
@@ -199,6 +221,7 @@ export function normalizeCodingProjectConfigV2(value: unknown): CodingProjectCon
|
||||
return {
|
||||
schemaVersion: 2,
|
||||
projectType: record.projectType,
|
||||
...(record.projectId !== undefined ? { projectId: record.projectId } : {}),
|
||||
initialized: record.initialized === true,
|
||||
agents,
|
||||
knowledgeDirectory: 'knowledge',
|
||||
@@ -211,10 +234,15 @@ export function normalizeCodingProjectConfigV2(value: unknown): CodingProjectCon
|
||||
export function createCodingProjectConfigV2(
|
||||
now = new Date().toISOString(),
|
||||
projectType: ProjectType = 'custom',
|
||||
projectId?: string,
|
||||
): CodingProjectConfigV2 {
|
||||
if (projectId !== undefined && !isCanonicalCodingProjectId(projectId)) {
|
||||
throw new Error('Project identity is invalid');
|
||||
}
|
||||
return {
|
||||
schemaVersion: 2,
|
||||
projectType,
|
||||
...(projectId !== undefined ? { projectId } : {}),
|
||||
initialized: false,
|
||||
agents: [],
|
||||
knowledgeDirectory: 'knowledge',
|
||||
@@ -252,6 +280,7 @@ export async function createCodingProjectMetadata(
|
||||
projectPath: string,
|
||||
options: {
|
||||
projectType?: ProjectType;
|
||||
projectId?: string;
|
||||
now?: string;
|
||||
writer?: JsonFileWriter;
|
||||
} = {},
|
||||
@@ -262,7 +291,7 @@ export async function createCodingProjectMetadata(
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||
}
|
||||
const config = createCodingProjectConfigV2(options.now, options.projectType);
|
||||
const config = createCodingProjectConfigV2(options.now, options.projectType, options.projectId);
|
||||
await Promise.all([
|
||||
mkdir(path.join(projectPath, '.niancode'), { recursive: true }),
|
||||
mkdir(path.join(projectPath, 'knowledge'), { recursive: true }),
|
||||
|
||||
Reference in New Issue
Block a user