fix template, remove okr, use casan.*

This commit is contained in:
thanhnv
2026-07-18 16:45:31 +07:00
parent 0dfd1742d3
commit 13fae3e6c3
249 changed files with 4881 additions and 5702 deletions
@@ -16,6 +16,8 @@ export interface GoalProject {
domain: string;
domain_root: string;
context_roots: string[];
manifest?: string;
shell_root?: string;
}
export interface GoalProjectCreateInput {
@@ -109,6 +111,7 @@ const ORCHESTRATOR_CLI = join(HARNESS_BIN, 'goal-orchestrator.py');
const PATCH_EXECUTOR_CLI = join(HARNESS_BIN, 'goal-patch-executor.py');
const RBAC_CLI = join(HARNESS_BIN, 'rbac-check.py');
const PROJECT_REGISTRY = join(APP_ROOT, 'packages', 'casan-harness', 'level5', 'project-registry.json');
const PROJECT_SCAFFOLDER = join(APP_ROOT, 'packages', 'casan-devkit', 'project-scaffold.py');
function parseJson<T>(value: string): T | null {
try {
@@ -260,10 +263,10 @@ export class GoalsService {
if (actor.role !== 'org-admin') throw new ForbiddenException('GOAL_PROJECT_CREATE_DENIED');
const projectId = String(input.projectId ?? '').trim();
const domain = String(input.domain ?? '').trim();
if (!/^[A-Za-z][A-Za-z0-9._-]{2,63}$/.test(projectId)) {
if (!/^[a-z][a-z0-9-]{1,62}$/.test(projectId)) {
throw new BadRequestException('GOAL_PROJECT_ID_INVALID');
}
if (domain.length < 3 || domain.length > 100) {
if (!/^[\p{L}\p{N}][\p{L}\p{N} .&()'_-]{1,99}$/u.test(domain)) {
throw new BadRequestException('GOAL_PROJECT_DOMAIN_INVALID');
}
const lockPath = `${PROJECT_REGISTRY}.lock`;
@@ -291,13 +294,83 @@ export class GoalsService {
absoluteRoot = join(canonicalProjects, projectId);
mkdirSync(absoluteRoot, { mode: 0o750 });
createdRoot = true;
const relativeRoot = join('apps', 'projects', projectId);
const entry = { project_id: projectId, domain, domain_root: relativeRoot, context_roots: [relativeRoot], harness_package: 'fpt-casan-sdd-harness', harness_version: '1.0.0', status: 'active' };
execFileSync('python3', [PROJECT_SCAFFOLDER,
'--target', absoluteRoot,
'--project', projectId,
'--name', domain,
'--template', 'nestjs-react',
'--with-harness',
], {
cwd: APP_ROOT,
env: process.env,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 60_000,
});
const relativeRoot = join('apps', 'projects', projectId).replaceAll('\\', '/');
const embeddedAppRoot = `${relativeRoot}/apps/${projectId}`;
const adapterManifestPath = `${relativeRoot}/casan.workspace.manifest.json`;
const adapterManifest = {
schema_version: 1,
project_id: projectId,
display_name: domain,
domain_root: `${embeddedAppRoot}/domain`,
requirements: `${embeddedAppRoot}/domain/input/requirement.md`,
architecture: `${embeddedAppRoot}/domain/input/architecture.md`,
quality_profile: `${relativeRoot}/config/casan/quality-profiles/enterprise-web-v1.json`,
feature: { id: `001-${projectId}-app`, module_id: 'MOD-01', slug: `${projectId}-core`, title: `${domain} Core` },
source_roots: [`${embeddedAppRoot}/backend`, `${embeddedAppRoot}/frontend`],
commands: {
build: [['npm', '--prefix', relativeRoot, 'run', 'build']],
test: [['npm', '--prefix', relativeRoot, 'test']],
},
verification: [
{
path_prefix: `${embeddedAppRoot}/backend/`,
commands: [
['npm', '--prefix', relativeRoot, 'run', 'build', '-w', `@${projectId}/backend`],
['npm', '--prefix', relativeRoot, 'test', '-w', `@${projectId}/backend`],
],
},
{
path_prefix: `${embeddedAppRoot}/frontend/`,
commands: [
['npm', '--prefix', relativeRoot, 'run', 'build', '-w', `@${projectId}/frontend`],
['npm', '--prefix', relativeRoot, 'test', '-w', `@${projectId}/frontend`],
],
},
],
artifacts_root: `${relativeRoot}/docs/output`,
implementation_evidence: [
`${embeddedAppRoot}/backend/src/main.ts`,
`${embeddedAppRoot}/backend/test/health.test.ts`,
`${embeddedAppRoot}/frontend/src/App.tsx`,
`${embeddedAppRoot}/frontend/src/__tests__/App.test.tsx`,
],
tech_stack: 'NestJS 10, React 18, Vite 5, Tailwind CSS 3, strict TypeScript',
};
const adapterAbsolute = join(APP_ROOT, adapterManifestPath);
const adapterTemporary = `${adapterAbsolute}.${process.pid}.${randomUUID()}.tmp`;
writeFileSync(adapterTemporary, `${JSON.stringify(adapterManifest, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
renameSync(adapterTemporary, adapterAbsolute);
const entry = {
project_id: projectId,
domain,
domain_root: `${embeddedAppRoot}/domain`,
manifest: adapterManifestPath,
context_roots: [relativeRoot],
shell_root: relativeRoot,
harness_package: 'fpt-casan-sdd-harness',
harness_version: '1.0.0',
status: 'active',
};
const updated = { ...registry, projects: [...registry.projects, entry] };
const temporary = `${PROJECT_REGISTRY}.${process.pid}.${randomUUID()}.tmp`;
writeFileSync(temporary, `${JSON.stringify(updated, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
renameSync(temporary, PROJECT_REGISTRY);
return { project_id: projectId, domain, domain_root: relativeRoot, context_roots: [relativeRoot] };
return { project_id: projectId, domain, domain_root: entry.domain_root, manifest: adapterManifestPath, shell_root: relativeRoot, context_roots: [relativeRoot] };
} catch (error) {
if (createdRoot && absoluteRoot) rmSync(absoluteRoot, { recursive: true, force: true });
if (error instanceof HttpException) throw error;
@@ -379,7 +452,14 @@ export class GoalsService {
}
return relative;
});
return { project_id: projectId, domain: String(entry.domain ?? projectId), domain_root: domainRoot, context_roots: contextRoots };
return {
project_id: projectId,
domain: String(entry.domain ?? projectId),
domain_root: domainRoot,
context_roots: contextRoots,
manifest: entry.manifest ? String(entry.manifest) : undefined,
shell_root: entry.shell_root ? String(entry.shell_root) : undefined,
};
});
}