← back to kicchann__reqord

Function bodies 330 total

All specs Real LLM only Function bodies
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",
    parentR
detectCircularDependencies 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 M
buildNotificationComment 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 headingPa
addComponent 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",
      va
checkImplementConsistency 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");
      c
parseReqordComment 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) {
        li
createRequirement 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 descriptio
buildFormat 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.priori
deleteRequirement 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.testP
If 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",
      severi
validateSpecDesign 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 ? er
buildStatusSummary 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
‹ prevpage 3 / 7next ›