Function bodies 330 total
analyzeImpact function · typescript · L62-L71 (10 LOC)packages/cli/src/services/impact-service.ts
export async function analyzeImpact(
cwd: string,
id: string,
options?: { maxDepth?: number },
): Promise<ImpactAnalysis> {
if (id.startsWith("spec-")) {
return analyzeFromSpecification(cwd, id);
}
return analyzeFromRequirement(cwd, id, options);
}analyzeFromRequirement function · typescript · L73-L195 (123 LOC)packages/cli/src/services/impact-service.ts
async function analyzeFromRequirement(
cwd: string,
id: string,
options?: { maxDepth?: number },
): Promise<ImpactAnalysis> {
const allRequirements = await reqRepo.findAll(cwd);
const allSpecifications = await specRepo.findAll(cwd);
const reqMap = new Map(allRequirements.map((r) => [r.id, r]));
if (!reqMap.has(id)) {
throw new Error(`Requirement ${id} not found.`);
}
const maxDepth = options?.maxDepth ?? Infinity;
const directImpacts: ImpactEntry[] = [];
const indirectImpacts: ImpactEntry[] = [];
// BFS traversal
const visited = new Set<string>();
visited.add(id);
interface QueueItem {
targetId: string;
relation: "blocks" | "relatedTo";
depth: number;
path: string[];
fromRelatedTo: boolean;
}
const queue: QueueItem[] = [];
const sourceReq = reqMap.get(id);
if (sourceReq) {
for (const blockId of sourceReq.dependencies.blocks) {
queue.push({ targetId: blockId, relation: "blocks", depth: 1, path: [id, blockId], analyzeFromSpecification function · typescript · L197-L234 (38 LOC)packages/cli/src/services/impact-service.ts
async function analyzeFromSpecification(cwd: string, id: string): Promise<ImpactAnalysis> {
const spec = await specRepo.findById(cwd, id);
if (!spec) {
throw new Error(`Specification ${id} not found.`);
}
const allSpecifications = await specRepo.findAll(cwd);
const parentReq = await reqRepo.findById(cwd, spec.requirementId);
// Related specs for the same requirement (excluding self)
const relatedSpecifications: SpecificationRef[] = allSpecifications
.filter((s) => s.requirementId === spec.requirementId && s.id !== id)
.map((s) => ({ id: s.id, requirementId: s.requirementId, status: s.status }));
// Issues from tasks.yaml linked to this spec
const tasksForSpec = await loadTasksForSpec(cwd, id);
const relatedIssues: IssueRef[] = tasksForSpec.map((task) => ({
number: task.number,
title: task.title,
url: task.url,
status: task.status,
specificationId: id,
}));
return {
sourceId: id,
sourceType: "specification",
parentRdetectCircularDependencies function · typescript · L236-L268 (33 LOC)packages/cli/src/services/impact-service.ts
function detectCircularDependencies(startId: string, allRequirements: Requirement[]): string[][] {
const reqMap = new Map(allRequirements.map((r) => [r.id, r]));
const cycles: string[][] = [];
const visited = new Set<string>();
const inStack = new Set<string>();
const path: string[] = [];
function dfs(id: string): void {
if (inStack.has(id)) {
const cycleStart = path.indexOf(id);
cycles.push(path.slice(cycleStart).concat(id));
return;
}
if (visited.has(id)) return;
visited.add(id);
inStack.add(id);
path.push(id);
const req = reqMap.get(id);
if (req) {
for (const depId of req.dependencies.blocks) {
dfs(depId);
}
}
inStack.delete(id);
path.pop();
}
dfs(startId);
return cycles;
}dfs function · typescript · L243-L264 (22 LOC)packages/cli/src/services/impact-service.ts
function dfs(id: string): void {
if (inStack.has(id)) {
const cycleStart = path.indexOf(id);
cycles.push(path.slice(cycleStart).concat(id));
return;
}
if (visited.has(id)) return;
visited.add(id);
inStack.add(id);
path.push(id);
const req = reqMap.get(id);
if (req) {
for (const depId of req.dependencies.blocks) {
dfs(depId);
}
}
inStack.delete(id);
path.pop();
}notifyImpact function · typescript · L289-L402 (114 LOC)packages/cli/src/services/impact-service.ts
export async function notifyImpact(
cwd: string,
id: string,
options?: { dryRun?: boolean; message?: string },
): Promise<NotifyResult> {
const dryRun = options?.dryRun ?? false;
const analysis = await analyzeImpact(cwd, id);
const issuesWithContext: Array<{
number: number;
title: string;
status: string;
relation: string;
path: string;
}> = [];
if (analysis.sourceType === "specification") {
// Spec-origin: collect notification targets from relatedIssues
for (const issue of analysis.relatedIssues) {
issuesWithContext.push({
number: issue.number,
title: issue.title,
status: issue.status,
relation: "specification",
path: id,
});
}
} else {
// Requirement-origin: collect related issues from direct/indirect impacts
const impactedIds = new Set([
...analysis.directImpacts.map((e) => e.id),
...analysis.indirectImpacts.map((e) => e.id),
]);
const impactMap = new MbuildNotificationComment function · typescript · L404-L427 (24 LOC)packages/cli/src/services/impact-service.ts
function buildNotificationComment(params: {
sourceId: string;
sourceLabel: string;
title: string;
relation: string;
path: string;
customMessage: string;
}): string {
const lines = [
"**reqord Impact Notification**",
"",
`${params.sourceLabel} \`${params.sourceId}\` (${params.title}) has been changed.`,
"This issue may be affected.",
"",
`**Relation:** ${params.relation}`,
`**Path:** ${params.path}`,
];
if (params.customMessage) {
lines.push("", params.customMessage);
}
return lines.join("\n");
}Repobility · severity-and-effort ranking · https://repobility.com
loadTasksForSpec function · typescript · L51-L61 (11 LOC)packages/cli/src/services/impl-validation-service.ts
async function loadTasksForSpec(
cwd: string,
specId: string,
): Promise<
Array<{
number: number;
title: string;
url: string;
status: "open" | "closed";
priority?: string;
}>parseDesignPaths function · typescript · L80-L136 (57 LOC)packages/cli/src/services/impl-validation-service.ts
export function parseDesignPaths(designContent: string): DesignPaths {
const components: Array<{ path: string; description: string }> = [];
const tests: Array<{ path: string; type: "unit" | "integration" }> = [];
const seenPaths = new Set<string>();
function addComponent(path: string, desc: string) {
const normalized = path.replace(/^\//, "");
// Guard against path traversal (e.g. "../../../etc/passwd")
if (normalized.includes("..") || /^[a-zA-Z]:/.test(normalized)) return;
if (!seenPaths.has(normalized) && /\.tsx?$/.test(normalized)) {
seenPaths.add(normalized);
if (normalized.includes(".test.") || normalized.includes(".spec.")) {
tests.push({
path: normalized,
type: normalized.includes("integration") ? "integration" : "unit",
});
} else {
components.push({ path: normalized, description: desc });
}
}
}
// Extract paths from section headings: ### 3.1 Title (`path.ts`)
const headingPaaddComponent function · typescript · L85-L100 (16 LOC)packages/cli/src/services/impl-validation-service.ts
function addComponent(path: string, desc: string) {
const normalized = path.replace(/^\//, "");
// Guard against path traversal (e.g. "../../../etc/passwd")
if (normalized.includes("..") || /^[a-zA-Z]:/.test(normalized)) return;
if (!seenPaths.has(normalized) && /\.tsx?$/.test(normalized)) {
seenPaths.add(normalized);
if (normalized.includes(".test.") || normalized.includes(".spec.")) {
tests.push({
path: normalized,
type: normalized.includes("integration") ? "integration" : "unit",
});
} else {
components.push({ path: normalized, description: desc });
}
}
}determineOverallStatus function · typescript · L138-L161 (24 LOC)packages/cli/src/services/impl-validation-service.ts
export function determineOverallStatus(
issueCheck: IssueCheckResult,
componentCheck: ComponentCheckResult,
testCheck: TestCheckResult,
): "complete" | "partial" | "not-started" {
const issueComplete =
issueCheck.total === 0 || issueCheck.completed === issueCheck.total;
const componentComplete =
componentCheck.total === 0 ||
componentCheck.exists === componentCheck.total;
const testComplete =
testCheck.total === 0 || testCheck.exists === testCheck.total;
if (issueComplete && componentComplete && testComplete) return "complete";
// "not-started" = no files exist AND no issues completed.
// If all totals are 0, the "complete" check above catches it first.
if (
componentCheck.exists === 0 &&
testCheck.exists === 0 &&
issueCheck.completed === 0
)
return "not-started";
return "partial";
}validateImplementation function · typescript · L163-L243 (81 LOC)packages/cli/src/services/impl-validation-service.ts
export async function validateImplementation(
cwd: string,
specId: string,
): Promise<ImplValidation> {
const spec = await specRepo.findByIdOrThrow(cwd, specId);
const design = await specRepo.loadFile(cwd, specId, "design.md");
// Issue check from tasks.yaml
const issueCheck: IssueCheckResult = { total: 0, completed: 0, issues: [] };
const tasks = await loadTasksForSpec(cwd, specId);
for (const task of tasks) {
issueCheck.issues.push({
number: task.number,
title: task.title,
state: task.status,
priority: task.priority,
});
issueCheck.total++;
if (task.status === "closed") issueCheck.completed++;
}
// If design.md is missing, we cannot validate — treat as not-started
if (!design) {
return {
specId,
requirementId: spec.requirementId,
issueCheck,
componentCheck: { total: 0, exists: 0, components: [] },
testCheck: { total: 0, exists: 0, tests: [] },
overallStatus: "not-started",
vacheckImplementConsistency function · typescript · L258-L288 (31 LOC)packages/cli/src/services/impl-validation-service.ts
export async function checkImplementConsistency(
cwd: string,
reqId: string,
): Promise<ImplementConsistencyResult> {
const specs = await specRepo.findAll(cwd);
const relatedSpecs = specs.filter((s) => s.requirementId === reqId);
const warnings: ImplementConsistencyWarning[] = [];
for (const spec of relatedSpecs) {
if (spec.status !== "implemented") {
warnings.push({
type: "spec-not-implemented",
message: `${spec.id} status is ${spec.status} (not implemented)`,
details: { id: spec.id, currentStatus: spec.status },
});
}
const tasks = await loadTasksForSpec(cwd, spec.id);
for (const task of tasks) {
if (task.status !== "closed") {
warnings.push({
type: "issue-not-closed",
message: `#${task.number} (${task.title}) is ${task.status}`,
details: { id: String(task.number), currentStatus: task.status },
});
}
}
}
return { warnings };
}initProject function · typescript · L24-L82 (59 LOC)packages/cli/src/services/init-service.ts
export async function initProject(cwd: string): Promise<InitResult> {
const reqordRoot = fs.getReqordDir(cwd);
if (await fs.exists(reqordRoot)) {
return { created: [], alreadyExists: true };
}
const created: string[] = [];
const dirs = [
fs.joinPath(reqordRoot, CONTEXT_DIR, DOMAIN_DIR),
fs.joinPath(reqordRoot, REQUIREMENTS_DIR),
fs.joinPath(reqordRoot, SPECIFICATIONS_DIR),
fs.joinPath(reqordRoot, SETTINGS_DIR, TEMPLATES_DIR, ISSUE_TEMPLATES_DIR),
fs.joinPath(reqordRoot, SETTINGS_DIR, RULES_DIR),
fs.joinPath(reqordRoot, ASSETS_DIR),
fs.joinPath(reqordRoot, ISSUES_DIR),
];
for (const dir of dirs) {
await fs.mkdirp(dir);
created.push(dir);
}
// Write default templates
const templatePath = fs.joinPath(
reqordRoot,
SETTINGS_DIR,
TEMPLATES_DIR,
"requirement-description.md",
);
await fs.writeText(templatePath, DEFAULT_REQUIREMENT_DESCRIPTION_TEMPLATE);
created.push(templatePath);
// Write default rules
createMigrationPlan function · typescript · L30-L89 (60 LOC)packages/cli/src/services/migration-service.ts
export async function createMigrationPlan(cwd: string): Promise<MigrationPlanItem[]> {
const plan: MigrationPlanItem[] = [];
const reqordPath = join(cwd, REQORD_DIR);
// Requirements
const requirementsPath = join(reqordPath, REQUIREMENTS_DIR);
if (await fs.exists(requirementsPath)) {
const entries = await readdir(requirementsPath);
for (const file of entries.filter((f) => f.endsWith(".json") && f.startsWith("req-"))) {
plan.push({
source: join(requirementsPath, file),
destination: join(requirementsPath, file.replace(".json", ".yaml")),
type: "requirement",
});
}
}
// Specifications
const specificationsPath = join(reqordPath, SPECIFICATIONS_DIR);
if (await fs.exists(specificationsPath)) {
const entries = await readdir(specificationsPath);
for (const file of entries.filter((f) => f.endsWith(".json") && f.startsWith("spec-"))) {
plan.push({
source: join(specificationsPath, file),
destination:Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
convertToYaml function · typescript · L91-L99 (9 LOC)packages/cli/src/services/migration-service.ts
function convertToYaml(data: unknown): string {
return yamlDump(data, {
indent: 2,
lineWidth: 120,
noRefs: true,
sortKeys: false,
schema: JSON_SCHEMA,
});
}migrateToYaml function · typescript · L101-L162 (62 LOC)packages/cli/src/services/migration-service.ts
export async function migrateToYaml(
cwd: string,
options: MigrationOptions,
): Promise<MigrationResult> {
const reqordPath = join(cwd, REQORD_DIR);
if (!(await fs.exists(reqordPath))) {
throw new AppError(
".reqord/ directory not found. Run 'reqord init' first.",
ErrorCode.NOT_FOUND,
);
}
const plan = await createMigrationPlan(cwd);
if (options.dryRun) {
return { plan, success: [], errors: [] };
}
// Create backup directory with timestamp to allow multiple runs per day
const now = new Date();
const datePart = now.toISOString().split("T")[0];
const timePart = now.toTimeString().split(" ")[0].replace(/:/g, "-");
const backupDir = join(reqordPath, ".backup", `${datePart}_${timePart}`);
await mkdir(backupDir, { recursive: true });
const success: string[] = [];
const errors: Array<{ file: string; reason: string }> = [];
for (const item of plan) {
try {
const jsonContent = await readFile(item.source, "utf-8");
cparseReqordComment function · typescript · L16-L38 (23 LOC)packages/cli/src/services/reqord-comment.ts
export function parseReqordComment(body: string): ReqordFeedbackComment | null {
const match = COMMENT_PATTERN.exec(body);
if (!match) return null;
try {
const json = match[1].trim();
const data = JSON.parse(json);
const raw = data.linkedTo ?? {};
const result: ReqordFeedbackComment = {
linkedTo: {
requirements: Array.isArray(raw.requirements) ? raw.requirements : [],
createdRequirements: Array.isArray(raw.createdRequirements) ? raw.createdRequirements : [],
specifications: Array.isArray(raw.specifications) ? raw.specifications : [],
createdSpecifications: Array.isArray(raw.createdSpecifications) ? raw.createdSpecifications : [],
},
};
if (data.type) result.type = data.type;
if (data.severity) result.severity = data.severity;
return result;
} catch {
return null;
}
}buildReqordComment function · typescript · L40-L46 (7 LOC)packages/cli/src/services/reqord-comment.ts
export function buildReqordComment(metadata: ReqordFeedbackComment): string {
const obj: Record<string, unknown> = {};
if (metadata.type) obj.type = metadata.type;
if (metadata.severity) obj.severity = metadata.severity;
obj.linkedTo = metadata.linkedTo;
return `<!-- reqord:feedback ${JSON.stringify(obj)} -->`;
}upsertReqordComment function · typescript · L48-L60 (13 LOC)packages/cli/src/services/reqord-comment.ts
export function upsertReqordComment(
body: string,
metadata: ReqordFeedbackComment,
): string {
const comment = buildReqordComment(metadata);
if (COMMENT_PATTERN.test(body)) {
return body.replace(COMMENT_PATTERN, comment);
}
if (body.length === 0) return comment;
return `${body}\n\n${comment}`;
}buildReqApprovalPrBody function · typescript · L50-L102 (53 LOC)packages/cli/src/services/requirement-approval-handler.ts
export function buildReqApprovalPrBody(params: ReqApprovalPrBodyParams): string {
const lines: string[] = [
`## Requirement Approval Request`,
``,
`| Field | Value |`,
`|-----------|------|`,
`| ID | ${params.id} |`,
`| Title | ${params.title} |`,
`| Version | ${params.version} |`,
``,
`### Changes`,
`status: draft → approved`,
];
if (params.successCriteria && params.successCriteria.length > 0) {
lines.push(``, `### Success Criteria`);
for (const criterion of params.successCriteria) {
lines.push(`- ${criterion}`);
}
}
if (params.dependencies) {
const deps = params.dependencies;
const hasAny =
deps.blockedBy.length > 0 ||
deps.blocks.length > 0 ||
deps.relatedTo.length > 0;
if (hasAny) {
lines.push(``, `### Dependencies`);
if (deps.blockedBy.length > 0) {
lines.push(`- **blockedBy:** ${deps.blockedBy.join(", ")}`);
}
if (deps.blocks.length > 0) {
licreateRequirement function · typescript · L22-L68 (47 LOC)packages/cli/src/services/requirement-service.ts
export async function createRequirement(
cwd: string,
options: CreateOptions,
): Promise<CreateResult> {
const id = await generateNextId(cwd);
const now = new Date().toISOString();
const priority = options.priority ?? "medium";
const formatType = options.format ?? "user-story";
const format = buildFormat(formatType);
const requirement: Requirement = {
id,
version: "1.0",
title: options.title,
status: "draft",
priority,
createdAt: now,
updatedAt: now,
versionHistory: [],
files: {
description: `${REQUIREMENTS_DIR}/${id}/description.md`,
supplementary: [],
},
successCriteria: [],
format,
dependencies: {
blockedBy: [],
blocks: [],
relatedTo: [],
},
};
await reqRepo.save(cwd, requirement);
// Generate description.md from template
const template =
(await loadProjectTemplate(cwd, "requirement-description.md")) ??
DEFAULT_REQUIREMENT_DESCRIPTION_TEMPLATE;
const descriptiobuildFormat function · typescript · L70-L85 (16 LOC)packages/cli/src/services/requirement-service.ts
function buildFormat(type: FormatType): Requirement["format"] {
switch (type) {
case "user-story":
return {
type: "user-story",
userStory: { as: "", iWant: "", soThat: "" },
};
case "ears":
return {
type: "ears",
ears: { type: "event-driven", action: "" },
};
case "free-form":
return { type: "free-form" };
}
}About: code-quality intelligence by Repobility · https://repobility.com
showRequirement function · typescript · L93-L100 (8 LOC)packages/cli/src/services/requirement-service.ts
export async function showRequirement(
cwd: string,
id: string,
): Promise<ShowResult> {
const requirement = await reqRepo.findByIdOrThrow(cwd, id);
const description = await reqRepo.loadDescription(cwd, id);
return { requirement, description };
}updateRequirement function · typescript · L119-L229 (111 LOC)packages/cli/src/services/requirement-service.ts
export async function updateRequirement(
cwd: string,
id: string,
options: UpdateOptions,
): Promise<UpdateResult> {
const before = await reqRepo.findByIdOrThrow(cwd, id);
// Start with existing data
const merged: Record<string, unknown> = { ...before };
// Apply patch-file data (replace semantics for top-level fields)
if (options.patchData) {
const patch = options.patchData;
// Only allow known updatable fields
const allowedPatchFields = [
"title", "status", "priority", "successCriteria", "format",
"dependencies", "estimatedComplexity", "estimatedHours",
];
for (const key of allowedPatchFields) {
if (key in patch) {
merged[key] = patch[key];
}
}
}
// Individual flags override patch-file (explicit flags win)
if (options.title !== undefined) merged.title = options.title;
if (options.status !== undefined) merged.status = options.status;
if (options.priority !== undefined) merged.priority = options.priorideleteRequirement function · typescript · L232-L238 (7 LOC)packages/cli/src/services/requirement-service.ts
export async function deleteRequirement(
cwd: string,
id: string,
): Promise<void> {
await reqRepo.findByIdOrThrow(cwd, id);
await reqRepo.deleteById(cwd, id);
}listRequirements function · typescript · L246-L260 (15 LOC)packages/cli/src/services/requirement-service.ts
export async function listRequirements(
cwd: string,
options: ListOptions = {},
): Promise<Requirement[]> {
let requirements = await reqRepo.findAll(cwd);
if (options.status) {
requirements = requirements.filter((r) => r.status === options.status);
}
if (options.priority) {
requirements = requirements.filter((r) => r.priority === options.priority);
}
return requirements;
}extractDesignSummary function · typescript · L1-L23 (23 LOC)packages/cli/src/services/spec-approval-helpers.ts
export function extractDesignSummary(designContent: string): string {
if (!designContent) {
return "(No design summary)";
}
// Split content into sections by ## headings
const sections = designContent.split(/\n(?=##\s)/);
// Find the section that starts with "## Design Overview" or "## 設計概要" (with optional numbering)
const summarySection = sections.find(section =>
/^##\s+(?:\d+\.\s+)?(?:Design Overview|設計概要)/.test(section)
);
if (!summarySection) {
return "(No design summary)";
}
// Extract content after the heading
const contentAfterHeading = summarySection.replace(/^##\s+(?:\d+\.\s+)?(?:Design Overview|設計概要)\s*\n+/, "");
const summary = contentAfterHeading.trim();
return summary || "(No design summary)";
}extractDesignSection function · typescript · L29-L46 (18 LOC)packages/cli/src/services/spec-approval-helpers.ts
export function extractDesignSection(designContent: string, sectionName: string): string | null {
if (!designContent) return null;
const escaped = escapeRegExp(sectionName);
const sections = designContent.split(/\n(?=##\s)/);
const section = sections.find(s =>
new RegExp(`^##\\s+(?:\\d+\\.\\s+)?${escaped}`).test(s),
);
if (!section) return null;
const contentAfterHeading = section.replace(
new RegExp(`^##\\s+(?:\\d+\\.\\s+)?${escaped}\\s*\\n+`),
"",
);
const trimmed = contentAfterHeading.trim();
return trimmed || null;
}extractComponentList function · typescript · L48-L64 (17 LOC)packages/cli/src/services/spec-approval-helpers.ts
export function extractComponentList(designContent: string): string[] {
if (!designContent) return [];
// Try English first, then Japanese fallback
const section = extractDesignSection(designContent, "Component Design")
?? extractDesignSection(designContent, "コンポーネント設計");
if (!section) return [];
// Extract ### headings as component names
const components: string[] = [];
const headingRegex = /^###\s+[\d.]*\s*(.+)/gm;
let match;
while ((match = headingRegex.exec(section)) !== null) {
components.push(match[1].trim());
}
return components;
}buildSpecApprovalPrBody function · typescript · L77-L127 (51 LOC)packages/cli/src/services/spec-approval-helpers.ts
export function buildSpecApprovalPrBody(params: SpecApprovalPrBodyParams): string {
const lines: string[] = [
`## Specification Approval Request`,
``,
`| Field | Value |`,
`|-----------|------|`,
`| Specification ID | ${params.specId} |`,
`| Requirement ID | ${params.reqId} |`,
`| Requirement Title | ${params.reqTitle} |`,
`| Version | ${params.version} |`,
``,
`### Design Overview`,
params.designSummary,
``,
`### Changes`,
`status: draft → approved`,
];
if (params.successCriteria && params.successCriteria.length > 0) {
lines.push(``, `### Success Criteria`);
for (const criterion of params.successCriteria) {
lines.push(`- ${criterion}`);
}
}
if (params.components && params.components.length > 0) {
lines.push(``, `### Components`);
for (const component of params.components) {
lines.push(`- ${component}`);
}
}
if (params.testPlan) {
lines.push(``, `### Test Plan`, params.testPIf a scraper extracted this row, it came from Repobility (https://repobility.com)
createSpecification function · typescript · L23-L66 (44 LOC)packages/cli/src/services/specification-service.ts
export async function createSpecification(
cwd: string,
options: CreateSpecOptions,
): Promise<CreateSpecResult> {
const requirement = await reqRepo.findByIdOrThrow(cwd, options.requirementId);
const id = await generateNextSpecId(cwd);
const now = new Date().toISOString();
const title = options.title ?? requirement.title;
const specification: Specification = {
id,
requirementId: options.requirementId,
title,
requirementVersion: requirement.version,
version: "1.0",
status: "draft",
createdAt: now,
updatedAt: now,
versionHistory: [],
files: {
design: `${SPECIFICATIONS_DIR}/${id}/design.md`,
supplementary: [],
},
};
await specRepo.ensureSpecDir(cwd, id);
await specRepo.save(cwd, specification);
// Generate template files
const designTemplate =
(await loadProjectTemplate(cwd, "specification-design.md")) ??
DEFAULT_SPECIFICATION_DESIGN_TEMPLATE;
const replace = (template: string) =>
template
listSpecifications function · typescript · L75-L91 (17 LOC)packages/cli/src/services/specification-service.ts
export async function listSpecifications(
cwd: string,
options: ListSpecOptions = {},
): Promise<Specification[]> {
let specifications = await specRepo.findAll(cwd);
if (options.status) {
specifications = specifications.filter((s) => s.status === options.status);
}
if (options.requirementId) {
specifications = specifications.filter(
(s) => s.requirementId === options.requirementId,
);
}
return specifications;
}showSpecification function · typescript · L100-L107 (8 LOC)packages/cli/src/services/specification-service.ts
export async function showSpecification(
cwd: string,
id: string,
): Promise<ShowSpecResult> {
const specification = await specRepo.findByIdOrThrow(cwd, id);
const design = await specRepo.loadFile(cwd, id, "design.md");
return { specification, design };
}updateSpecDesign function · typescript · L120-L139 (20 LOC)packages/cli/src/services/specification-service.ts
export async function updateSpecDesign(
cwd: string,
id: string,
options: UpdateFileOptions = {},
): Promise<UpdateFileResult> {
const spec = await specRepo.findByIdOrThrow(cwd, id);
const filePath = spec.files.design;
if (options.content === undefined) {
return { filePath, updated: false };
}
await specRepo.saveFile(cwd, id, "design.md", options.content);
await specRepo.save(cwd, {
...spec,
updatedAt: new Date().toISOString(),
});
return { filePath, updated: true };
}checkSpecApprovalPrerequisites function · typescript · L148-L179 (32 LOC)packages/cli/src/services/specification-service.ts
export async function checkSpecApprovalPrerequisites(
cwd: string,
specId: string,
): Promise<PrerequisiteResult> {
const spec = await specRepo.findByIdOrThrow(cwd, specId);
const errors: string[] = [];
// 1. Status check
if (spec.status !== "draft") {
errors.push(`Specification status is not "draft" (current: ${spec.status})`);
}
// 2. Related requirement status check
const req = await reqRepo.findById(cwd, spec.requirementId);
if (!req) {
errors.push(`Related requirement ${spec.requirementId} not found`);
} else if (req.status !== "approved" && req.status !== "implemented") {
errors.push(`Related requirement ${spec.requirementId} is not approved (current: ${req.status})`);
}
// 3. design.md content check
const design = await specRepo.loadFile(cwd, specId, "design.md");
if (design == null) {
errors.push("design.md does not exist or could not be read. Create design.md and write the design content.");
} else if (design.trim().length ===hasSpecMetadataChanges function · typescript · L195-L214 (20 LOC)packages/cli/src/services/specification-service.ts
export function hasSpecMetadataChanges(before: Specification, after: Specification): boolean {
// Extract metadata fields only (file paths, not content)
const metadataBefore = {
id: before.id,
requirementId: before.requirementId,
version: before.version,
createdAt: before.createdAt,
files: before.files,
};
const metadataAfter = {
id: after.id,
requirementId: after.requirementId,
version: after.version,
createdAt: after.createdAt,
files: after.files,
};
return JSON.stringify(metadataBefore) !== JSON.stringify(metadataAfter);
}updateSpecification function · typescript · L231-L314 (84 LOC)packages/cli/src/services/specification-service.ts
export async function updateSpecification(
cwd: string,
id: string,
options: UpdateSpecOptions = {},
): Promise<UpdateSpecResult> {
const before = await specRepo.findByIdOrThrow(cwd, id);
// Apply patch data
let merged: Specification = { ...before };
if (options.patchData) {
merged = { ...merged, ...options.patchData };
}
// Update status if specified
if (options.status !== undefined) {
// Validate status transition
if (!versionService.isValidTransition(before.status, options.status)) {
const transitions = versionService.getStateTransitions();
const allowed = (transitions.get(before.status) ?? []).join(", ");
throw new Error(
`Invalid status transition: ${before.status} → ${options.status}. Allowed: ${allowed}`
);
}
merged.status = options.status;
}
// Detect content changes (for summary generation)
const hasContentChanges = hasSpecMetadataChanges(before, merged);
// Determine next version
// Version updateSpecificationStatus function · typescript · L323-L330 (8 LOC)packages/cli/src/services/specification-service.ts
export async function updateSpecificationStatus(
cwd: string,
id: string,
newStatus: Status,
): Promise<UpdateSpecStatusResult> {
// Delegate to updateSpecification
return updateSpecification(cwd, id, { status: newStatus });
}Repobility · severity-and-effort ranking · https://repobility.com
checkDesignSections function · typescript · L37-L68 (32 LOC)packages/cli/src/services/spec-validation-service.ts
function checkDesignSections(design: string): ValidationRuleResult {
const sectionPattern = /^##\s+(?:\d+\.\s+)?(.+)/gm;
const foundSections: string[] = [];
let match;
while ((match = sectionPattern.exec(design)) !== null) {
foundSections.push(match[1].trim());
}
const missing = REQUIRED_SECTIONS.filter(
(s) => {
const variants = s.split("|");
return !foundSections.some((f) => variants.some((v) => f.includes(v)));
},
);
if (missing.length === 0) {
return {
ruleId: "design-sections",
ruleName: "Section structure",
severity: "warning",
status: "pass",
};
}
return {
ruleId: "design-sections",
ruleName: "Section structure",
severity: "warning",
status: "fail",
message: `Required sections missing: ${missing.map((s) => s.split("|")[0]).join(", ")}`,
};
}checkTestStrategy function · typescript · L70-L93 (24 LOC)packages/cli/src/services/spec-validation-service.ts
function checkTestStrategy(design: string): ValidationRuleResult {
const hasTestSection = /##\s+(?:\d+\.\s+)?(?:テスト方針|Test Plan)/i.test(design);
const hasUnitTest = /ユニットテスト|unit\s*test/i.test(design);
const hasIntegrationTest = /統合テスト|integration\s*test/i.test(design);
if (hasTestSection && (hasUnitTest || hasIntegrationTest)) {
return {
ruleId: "test-strategy",
ruleName: "Test strategy description",
severity: "warning",
status: "pass",
};
}
return {
ruleId: "test-strategy",
ruleName: "Test strategy description",
severity: "warning",
status: "fail",
message: hasTestSection
? "Test Plan section does not mention unit test/integration test"
: "Test Plan section is missing",
};
}checkArchLayer function · typescript · L95-L119 (25 LOC)packages/cli/src/services/spec-validation-service.ts
function checkArchLayer(design: string, _technical: unknown): ValidationRuleResult {
// Check that design.md mentions architectural layers consistent with technical.yaml
// This is a basic check: look for layer-like patterns (Command, Service, Repository)
const layerPatterns = ["command", "service", "repository"];
const hasLayerStructure = layerPatterns.some(
(p) => design.toLowerCase().includes(p),
);
if (hasLayerStructure) {
return {
ruleId: "arch-layer",
ruleName: "Layer consistency",
severity: "warning",
status: "pass",
};
}
return {
ruleId: "arch-layer",
ruleName: "Layer consistency",
severity: "warning",
status: "fail",
message: "Design does not describe layer structure",
};
}checkArchDependency function · typescript · L121-L153 (33 LOC)packages/cli/src/services/spec-validation-service.ts
function checkArchDependency(design: string): ValidationRuleResult {
// Check for reverse dependency patterns (Service importing from Command layer)
const lines = design.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Detect service-layer importing from command-layer
if (
/services?\/.*import.*commands?\//i.test(line) ||
/from\s+['"].*commands?\//i.test(line)
) {
if (
/services?\//.test(line) &&
!/commands?\/.*services?\//i.test(line)
) {
return {
ruleId: "arch-dependency",
ruleName: "Dependency direction",
severity: "warning",
status: "fail",
message: "Reference from Service layer to Command layer detected",
location: { line: i + 1, content: line.trim() },
};
}
}
}
return {
ruleId: "arch-dependency",
ruleName: "Dependency direction",
severity: "warning",
status: "pass",
};
}checkNamingConvention function · typescript · L155-L167 (13 LOC)packages/cli/src/services/spec-validation-service.ts
function checkNamingConvention(
design: string,
_structure: unknown,
): ValidationRuleResult {
// Check for consistent naming patterns in the design
// Look for component names that follow kebab-case for files
const filePatterns = design.match(/`([^`]+\.[a-zA-Z0-9]+)`/g) ?? [];
// Check file naming: should be kebab-case
const badFileNames = filePatterns.filter((f) => {
const name = f.replace(/`/g, "").split(".")[0];
return /[A-Z]/.test(name); // PascalCase or camelCase in file names
});checkDepConflict function · typescript · L187-L231 (45 LOC)packages/cli/src/services/spec-validation-service.ts
function checkDepConflict(
specReqId: string,
allReqs: Array<{ id: string; dependencies?: { blockedBy: string[] } }>,
allSpecs: Array<{ requirementId: string; status: string }>,
): ValidationRuleResult {
const req = allReqs.find((r) => r.id === specReqId);
if (!req || !req.dependencies) {
return {
ruleId: "dep-conflict",
ruleName: "Dependency conflict",
severity: "error",
status: "pass",
};
}
const missingSpecDeps: string[] = [];
for (const depId of req.dependencies.blockedBy) {
const depReq = allReqs.find((r) => r.id === depId);
if (!depReq) continue;
// Check if the dependent requirement has a non-deprecated specification
const hasSpec = allSpecs.some(
(s) => s.requirementId === depId && s.status !== "deprecated",
);
if (!hasSpec) {
missingSpecDeps.push(depId);
}
}
if (missingSpecDeps.length > 0) {
return {
ruleId: "dep-conflict",
ruleName: "Dependency conflict",
severivalidateSpecDesign function · typescript · L233-L312 (80 LOC)packages/cli/src/services/spec-validation-service.ts
export async function validateSpecDesign(
cwd: string,
specId: string,
): Promise<ValidateSpecDesignResult> {
const spec = await specRepo.findByIdOrThrow(cwd, specId);
const design = await specRepo.loadFile(cwd, specId, "design.md");
if (!design) {
return {
validation: {
specId,
rules: [
{
ruleId: "design-exists",
ruleName: "Design document exists",
severity: "error",
status: "fail",
message: "design.md not found",
},
],
passed: 0,
warnings: 0,
errors: 1,
validatedAt: new Date().toISOString(),
},
spec,
};
}
// Load project context (optional) - errors are caught to avoid interrupting validation
let technical: unknown = null;
try {
technical = await contextRepo.loadContextFile(cwd, "technical");
} catch (err) {
console.warn(
`Warning: Failed to parse technical.yaml: ${err instanceof Error ? erbuildStatusSummary function · typescript · L82-L97 (16 LOC)packages/cli/src/services/status-service.ts
export function buildStatusSummary(
items: Array<{ status: string }>,
): StatusSummary {
const total = items.length;
const byStatus: Record<string, number> = {};
for (const item of items) {
byStatus[item.status] = (byStatus[item.status] ?? 0) + 1;
}
const implementedCount = byStatus["implemented"] ?? 0;
const implementedPercentage =
total > 0 ? Math.round((implementedCount / total) * 100) : 0;
const approvedCount = (byStatus["approved"] ?? 0) + implementedCount;
const approvedPercentage =
total > 0 ? Math.round((approvedCount / total) * 100) : 0;
return { total, byStatus, implementedPercentage, approvedPercentage };
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
buildIssueSummary function · typescript · L99-L110 (12 LOC)packages/cli/src/services/status-service.ts
export function buildIssueSummary(tasks: TaskEntry[]): IssueSummary {
let total = 0;
let closed = 0;
for (const task of tasks) {
total++;
if (task.status === "closed") closed++;
}
const open = total - closed;
const closedPercentage =
total > 0 ? Math.round((closed / total) * 100) : 0;
return { total, closed, open, closedPercentage };
}loadAllTasks function · typescript · L112-L118 (7 LOC)packages/cli/src/services/status-service.ts
async function loadAllTasks(cwd: string): Promise<TaskEntry[]> {
const tasksPath = fs.joinPath(cwd, REQORD_DIR, ISSUES_DIR, "tasks.yaml");
if (!(await fs.exists(tasksPath))) return [];
const raw = await fs.readYAML<unknown>(tasksPath);
const parsed = TasksIndexSchema.parse(raw);
return parsed.tasks;
}detectWarnings function · typescript · L125-L234 (110 LOC)packages/cli/src/services/status-service.ts
export async function detectWarnings(
cwd: string,
requirements: Requirement[],
specifications: Specification[],
): Promise<Warning[]> {
const warnings: Warning[] = [];
const feedbackIndex = await feedbackRepo.loadIndex(cwd);
const allFeedbacks = feedbackIndex.feedbacks;
for (const req of requirements) {
const relatedSpecs = specifications.filter(
(s) => s.requirementId === req.id,
);
const consistencyWarnings = checkConsistency(req, relatedSpecs);
for (const cw of consistencyWarnings) {
warnings.push({
id: req.id,
type: cw.type,
message: cw.message,
severity: "warning",
});
}
if (req.status === "deprecated") continue;
const hasSpec = specifications.some(
(s) => s.requirementId === req.id && s.status !== "deprecated",
);
if (!hasSpec && req.status !== "draft") {
warnings.push({
id: req.id,
type: "no-specification",
message: `No specification created