← back to kicchann__reqord

Function bodies 330 total

All specs Real LLM only Function bodies
findByIdOrThrow function · typescript · L44-L50 (7 LOC)
packages/cli/src/repositories/specification.ts
export async function findByIdOrThrow(cwd: string, id: string): Promise<Specification> {
  const specification = await findById(cwd, id);
  if (!specification) {
    throw new Error(`Specification ${id} not found.`);
  }
  return specification;
}
findById function · typescript · L52-L66 (15 LOC)
packages/cli/src/repositories/specification.ts
export async function findById(cwd: string, id: string): Promise<Specification | null> {
  const specsDir = getSpecificationsDir(cwd);
  const yamlPath = fs.joinPath(specsDir, `${id}.yaml`);

  if (!(await fs.exists(yamlPath))) {
    return null;
  }

  const raw = await fs.readYAML<unknown>(yamlPath);
  const result = SpecificationSchema.safeParse(raw);
  if (!result.success) {
    throw new Error(`Validation error for specification ${id}:\n${formatZodError(result.error)}`);
  }
  return result.data;
}
findAll function · typescript · L68-L83 (16 LOC)
packages/cli/src/repositories/specification.ts
export async function findAll(cwd: string): Promise<Specification[]> {
  const specsDir = getSpecificationsDir(cwd);
  const files = await fs.readdirFiles(specsDir, (name) =>
    /^spec-\d{6}\.yaml$/.test(name),
  );

  const specifications: Specification[] = [];
  for (const file of files.sort()) {
    const raw = await fs.readYAML<unknown>(fs.joinPath(specsDir, file));
    const result = SpecificationSchema.safeParse(raw);
    if (result.success) {
      specifications.push(result.data);
    }
  }
  return specifications;
}
deleteById function · typescript · L85-L92 (8 LOC)
packages/cli/src/repositories/specification.ts
export async function deleteById(cwd: string, id: string): Promise<void> {
  const specsDir = getSpecificationsDir(cwd);
  const yamlPath = fs.joinPath(specsDir, `${id}.yaml`);
  const specDir = fs.joinPath(specsDir, id);

  await fs.remove(yamlPath);
  await fs.remove(specDir);
}
startApproval function · typescript · L38-L105 (68 LOC)
packages/cli/src/services/approval-service.ts
export async function startApproval(
  cwd: string,
  target: ApprovalTarget,
  handler: ApprovalHandler,
  options?: ApprovalOptions,
): Promise<ApprovalResult> {
  // 1. Precondition check
  if (target.status !== "draft") {
    throw new Error(
      `Cannot start approval: ${target.id} status is "${target.status}", expected "draft".`
    );
  }

  const branchName = buildBranchName(target);

  // 2. Dry-run mode
  if (options?.dryRun) {
    const prTitle = handler.buildPrTitle(target);
    console.log(`[dry-run] Create branch: ${branchName}`);
    console.log(`[dry-run] Status change: draft → approved`);
    console.log(`[dry-run] Create PR: ${prTitle}`);
    return { branchName, prNumber: 0, prUrl: "" };
  }

  // 3. Re-validate
  await handler.revalidate(cwd, target);

  // 4. Save original branch
  const originalBranch = await gitRepo.getCurrentBranch(cwd);

  try {
    // 5. Create and switch to approval branch
    await gitRepo.createBranch(cwd, branchName);
    await gitRepo.c
initContext function · typescript · L14-L59 (46 LOC)
packages/cli/src/services/context-service.ts
export async function initContext(
  cwd: string,
  options: InitContextOptions,
): Promise<ProjectContext> {
  if (await contextRepo.contextExists(cwd)) {
    throw new Error("context.yaml already exists.");
  }

  const now = new Date().toISOString();
  const context: ProjectContext = {
    id: options.id,
    name: options.name,
    version: "0.1.0",
    language: options.language ?? "ja",
    createdAt: now,
    updatedAt: now,
    files: {
      product: { path: `${CONTEXT_DIR}/product.yaml`, format: "yaml" },
      technical: { structured: `${CONTEXT_DIR}/technical.yaml` },
      structure: { structured: `${CONTEXT_DIR}/structure.yaml` },
      domain: [],
    },
  };

  await contextRepo.save(cwd, context);

  // Generate template files (skip if they already exist)
  const contextDir = fs.getReqordDir(cwd, CONTEXT_DIR);

  const templateFiles: Array<[string, unknown]> = [
    ["product.yaml", { name: options.name, vision: "", goals: [], targetUsers: [] }],
    ["technical.yaml",
showContext function · typescript · L72-L104 (33 LOC)
packages/cli/src/services/context-service.ts
export async function showContext(cwd: string): Promise<ShowContextResult> {
  const context = await contextRepo.load(cwd);
  if (!context) {
    throw new Error(CONTEXT_NOT_FOUND);
  }

  const contextDir = fs.getReqordDir(cwd, CONTEXT_DIR);

  async function resolveFileStatus(fileRef: unknown): Promise<{ exists: boolean; content?: unknown }> {
    const fullPath = fs.getReqordDir(cwd, resolveFilePath(fileRef));
    const fileExists = await fs.exists(fullPath);
    const content = fileExists ? await safeReadYAML(fullPath) : undefined;
    return { exists: fileExists, content };
  }

  const product = await resolveFileStatus(context.files.product);
  const technical = await resolveFileStatus(context.files.technical);
  const structure = await resolveFileStatus(context.files.structure);

  const domainDir = fs.joinPath(contextDir, DOMAIN_DIR);
  const domainFiles = await fs.readdirFiles(domainDir);

  return {
    context,
    productExists: product.exists,
    technicalExists: technica
Repobility · severity-and-effort ranking · https://repobility.com
updateContext function · typescript · L120-L156 (37 LOC)
packages/cli/src/services/context-service.ts
export async function updateContext(
  cwd: string,
  options: UpdateContextOptions,
): Promise<UpdateContextResult> {
  const before = await contextRepo.load(cwd);
  if (!before) {
    throw new Error(CONTEXT_NOT_FOUND);
  }

  const after: ProjectContext = {
    ...before,
    updatedAt: new Date().toISOString(),
  };

  if (options.name !== undefined) after.name = options.name;
  if (options.version !== undefined) after.version = options.version;

  await contextRepo.save(cwd, after);

  const updatedFiles: string[] = [];

  const filePatches: Array<[contextRepo.ContextFileType, Record<string, unknown> | undefined]> = [
    ["product", options.productPatch],
    ["technical", options.technicalPatch],
    ["structure", options.structurePatch],
  ];

  for (const [fileType, patch] of filePatches) {
    if (patch) {
      const existing = await contextRepo.loadContextFile(cwd, fileType) as Record<string, unknown> | null;
      await contextRepo.saveContextFile(cwd, fileType, { ...exist
safeReadYAML function · typescript · L158-L164 (7 LOC)
packages/cli/src/services/context-service.ts
async function safeReadYAML(path: string): Promise<unknown> {
  try {
    return await fs.readYAML(path);
  } catch {
    return null;
  }
}
resolveFilePath function · typescript · L166-L176 (11 LOC)
packages/cli/src/services/context-service.ts
export function resolveFilePath(
  fileRef: unknown,
): string {
  if (typeof fileRef === "string") return fileRef;
  if (typeof fileRef === "object" && fileRef !== null) {
    const obj = fileRef as Record<string, unknown>;
    if (typeof obj.path === "string") return obj.path;
    if (typeof obj.structured === "string") return obj.structured;
  }
  return "";
}
determineCoverage function · typescript · L28-L37 (10 LOC)
packages/cli/src/services/coverage-service.ts
export function determineCoverage(
  specs: Array<{ status: Status }>,
): CoverageStatus {
  if (specs.length === 0) return "not-covered";
  const hasApprovedOrImplemented = specs.some(
    (s) => s.status === "approved" || s.status === "implemented",
  );
  if (hasApprovedOrImplemented) return "covered";
  return "partial";
}
buildCoverageReport function · typescript · L39-L74 (36 LOC)
packages/cli/src/services/coverage-service.ts
export function buildCoverageReport(
  requirements: Requirement[],
  specifications: Specification[],
): CoverageReport {
  const activeReqs = requirements.filter(
    (r) => r.status !== "deprecated",
  );

  const coverages: RequirementCoverage[] = activeReqs.map((req) => {
    const relatedSpecs = specifications
      .filter(
        (s) => s.requirementId === req.id && s.status !== "deprecated",
      )
      .map((s) => ({ id: s.id, status: s.status as Status }));

    return {
      requirementId: req.id,
      title: req.title,
      status: determineCoverage(relatedSpecs),
      specifications: relatedSpecs,
    };
  });

  const summary = {
    covered: coverages.filter((c) => c.status === "covered").length,
    partial: coverages.filter((c) => c.status === "partial").length,
    notCovered: coverages.filter((c) => c.status === "not-covered").length,
    total: coverages.length,
  };

  return {
    requirements: coverages,
    summary,
    analyzedAt: new Date().toISOString
analyzeRequirementCoverage function · typescript · L76-L90 (15 LOC)
packages/cli/src/services/coverage-service.ts
export async function analyzeRequirementCoverage(
  cwd: string,
  requirementId?: string,
): Promise<CoverageReport> {
  let requirements: Requirement[];
  if (requirementId) {
    const req = await reqRepo.findByIdOrThrow(cwd, requirementId);
    requirements = [req];
  } else {
    requirements = await reqRepo.findAll(cwd);
  }

  const specifications = await specRepo.findAll(cwd);
  return buildCoverageReport(requirements, specifications);
}
buildPrBody function · typescript · L33-L71 (39 LOC)
packages/cli/src/services/draft-reversion-service.ts
function buildPrBody(
  id: string,
  title: string,
  version: string,
  previousStatus: string,
  impactedRequirements: string[],
  entityType: "requirement" | "specification",
): string {
  const isSpec = entityType === "specification";
  const entityLabel = isSpec ? "Specification" : "Requirement";
  const heading = isSpec ? "Specification Reversion to Draft" : "Requirement Reversion to Draft";
  const impactLabel = isSpec
    ? "The following requirements depend on this specification's parent requirement:"
    : "The following requirements depend on this requirement:";
  const noImpactLabel = "None (no impact on other requirements)";

  const impactSection =
    impactedRequirements.length > 0
      ? impactedRequirements.map((rid) => `- ${rid}`).join("\n")
      : noImpactLabel;

  return `## ${heading}

| Field | Value |
|-----------|------|
| ID | ${id} |
| Title | ${title} |
| Version | ${version} |
| Previous Status | ${previousStatus} |

### Impact Analysis
${impactLabel}
${
analyzeSpecImpact function · typescript · L99-L109 (11 LOC)
packages/cli/src/services/draft-reversion-service.ts
async function analyzeSpecImpact(
  cwd: string,
  impact: ImpactAnalysis,
): Promise<string[]> {
  // For specifications, analyze impact via the parent requirement
  if (impact.parentRequirement) {
    const parentImpact = await analyzeImpact(cwd, impact.parentRequirement.id);
    return extractImpactedRequirements(parentImpact);
  }
  return [];
}
All rows scored by the Repobility analyzer (https://repobility.com)
revertToDraft function · typescript · L111-L188 (78 LOC)
packages/cli/src/services/draft-reversion-service.ts
export async function revertToDraft(
  cwd: string,
  id: string,
  options?: DraftReversionOptions,
): Promise<DraftReversionResult> {
  // 1. Load entity and check precondition
  const entity = await loadEntity(cwd, id);
  if (entity.status === "draft") {
    throw new Error(`Cannot revert to draft: ${id} is already in draft status.`);
  }

  const previousStatus = entity.status;
  const { type: entityType, dir } = getEntityInfo(id);

  // 2. Analyze impact
  const impact = await analyzeImpact(cwd, id);
  const impactedRequirements = entityType === "specification"
    ? await analyzeSpecImpact(cwd, impact)
    : extractImpactedRequirements(impact);

  // 3. Dry-run mode
  if (options?.dryRun) {
    return { previousStatus, impactedRequirements };
  }

  // 4. Save original branch
  const originalBranch = await gitRepo.getCurrentBranch(cwd);
  const branchName = buildBranchName(id);
  const filePath = `${REQORD_DIR}/${dir}/${id}.yaml`;

  try {
    // 5. Create and switch to reversion
listFeedbacks function · typescript · L40-L55 (16 LOC)
packages/cli/src/services/feedback-service.ts
export async function listFeedbacks(
  cwd: string,
  options: ListFeedbacksOptions = {},
): Promise<FeedbackEntry[]> {
  const index = await feedbackRepo.loadIndex(cwd);
  let feedbacks = index.feedbacks;

  if (options.state && options.state !== "all") {
    feedbacks = feedbacks.filter((f) => f.status === options.state);
  }
  if (options.type) {
    feedbacks = feedbacks.filter((f) => f.type === options.type);
  }

  return feedbacks;
}
showFeedback function · typescript · L57-L72 (16 LOC)
packages/cli/src/services/feedback-service.ts
export async function showFeedback(
  cwd: string,
  issueNumber: number,
): Promise<ShowFeedbackResult> {
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = index.feedbacks.find((f) => f.githubIssue === issueNumber);

  if (!feedback) {
    throw new Error(
      `Feedback for issue #${issueNumber} not found in index.yaml. Run 'reqord feedback sync' first.`,
    );
  }

  const issue = await githubClient.getIssue(issueNumber);
  return { feedback, issue };
}
linkToRequirement function · typescript · L74-L93 (20 LOC)
packages/cli/src/services/feedback-service.ts
export async function linkToRequirement(
  cwd: string,
  options: LinkToRequirementOptions,
): Promise<void> {
  // Verify requirement exists
  await reqRepo.findByIdOrThrow(cwd, options.requirementId);

  // Update index.yaml
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = getOrCreateFeedback(index, options.issueNumber, options);

  if (!feedback.linkedTo.requirements.includes(options.requirementId)) {
    feedback.linkedTo.requirements.push(options.requirementId);
  }

  await feedbackRepo.saveIndex(cwd, index);

  // Update GitHub Issue body with HTML comment
  await updateGitHubIssueBody(options.issueNumber, feedback);
}
linkWithNewRequirement function · typescript · L95-L126 (32 LOC)
packages/cli/src/services/feedback-service.ts
export async function linkWithNewRequirement(
  cwd: string,
  options: LinkWithNewRequirementOptions,
): Promise<string> {
  // Get GitHub Issue for title
  const issue = await githubClient.getIssue(options.issueNumber);

  // Create new Requirement
  const result = await createRequirement(cwd, {
    title: `[Feedback #${options.issueNumber}] ${issue.title}`,
    priority: "medium",
  });

  const newId = result.requirement.id;

  // Add origin info
  const requirement = result.requirement;
  requirement.origin = { feedbackIssue: options.issueNumber };
  await reqRepo.save(cwd, requirement);

  // Update index.yaml
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = getOrCreateFeedback(index, options.issueNumber, options);
  feedback.linkedTo.createdRequirements.push(newId);

  await feedbackRepo.saveIndex(cwd, index);

  // Update GitHub Issue body with HTML comment
  await updateGitHubIssueBody(options.issueNumber, feedback);

  return newId;
}
linkToSpecification function · typescript · L128-L147 (20 LOC)
packages/cli/src/services/feedback-service.ts
export async function linkToSpecification(
  cwd: string,
  options: LinkToSpecificationOptions,
): Promise<void> {
  // Verify specification exists
  await specRepo.findByIdOrThrow(cwd, options.specificationId);

  // Update index.yaml
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = getOrCreateFeedback(index, options.issueNumber, options);

  if (!feedback.linkedTo.specifications.includes(options.specificationId)) {
    feedback.linkedTo.specifications.push(options.specificationId);
  }

  await feedbackRepo.saveIndex(cwd, index);

  // Update GitHub Issue body with HTML comment
  await updateGitHubIssueBody(options.issueNumber, feedback);
}
closeFeedback function · typescript · L149-L165 (17 LOC)
packages/cli/src/services/feedback-service.ts
export async function closeFeedback(
  cwd: string,
  issueNumber: number,
): Promise<void> {
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = index.feedbacks.find((f) => f.githubIssue === issueNumber);

  if (!feedback) {
    throw new Error(`Feedback for issue #${issueNumber} not found`);
  }

  feedback.status = "closed";
  await feedbackRepo.saveIndex(cwd, index);

  const summary = buildImpactSummary(feedback);
  await githubClient.closeIssue(issueNumber, summary);
}
updateGitHubIssueBody function · typescript · L167-L181 (15 LOC)
packages/cli/src/services/feedback-service.ts
async function updateGitHubIssueBody(
  issueNumber: number,
  feedback: FeedbackEntry,
): Promise<void> {
  const issue = await githubClient.getIssue(issueNumber);
  const currentBody = issue.body ?? "";
  const newBody = upsertReqordComment(currentBody, {
    type: feedback.type,
    severity: feedback.severity,
    linkedTo: feedback.linkedTo,
  });
  if (newBody !== currentBody) {
    await githubClient.updateIssueBody(issueNumber, newBody);
  }
}
Repobility · code-quality intelligence · https://repobility.com
createEmptyFeedbackEntry function · typescript · L183-L195 (13 LOC)
packages/cli/src/services/feedback-service.ts
function createEmptyFeedbackEntry(issueNumber: number): FeedbackEntry {
  return {
    githubIssue: issueNumber,
    linkedTo: {
      requirements: [],
      createdRequirements: [],
      specifications: [],
      createdSpecifications: [],
    },
    syncedAt: new Date().toISOString(),
    status: "open",
  };
}
getOrCreateFeedback function · typescript · L197-L210 (14 LOC)
packages/cli/src/services/feedback-service.ts
function getOrCreateFeedback(
  index: { feedbacks: FeedbackEntry[] },
  issueNumber: number,
  options: { type?: FeedbackType; severity?: FeedbackSeverity },
): FeedbackEntry {
  let feedback = index.feedbacks.find((f) => f.githubIssue === issueNumber);
  if (!feedback) {
    feedback = createEmptyFeedbackEntry(issueNumber);
    index.feedbacks.push(feedback);
  }
  if (options.type) feedback.type = options.type;
  if (options.severity) feedback.severity = options.severity;
  return feedback;
}
buildImpactSummary function · typescript · L212-L228 (17 LOC)
packages/cli/src/services/feedback-service.ts
function buildImpactSummary(feedback: FeedbackEntry): string {
  const lines = ["**Feedback closed - Impact summary:**", ""];

  const sections: Array<{ label: string; ids: string[] }> = [
    { label: "Linked Requirements", ids: feedback.linkedTo.requirements },
    { label: "Created Requirements", ids: feedback.linkedTo.createdRequirements },
    { label: "Linked Specifications", ids: feedback.linkedTo.specifications },
    { label: "Created Specifications", ids: feedback.linkedTo.createdSpecifications },
  ];
  for (const { label, ids } of sections) {
    if (ids.length > 0) {
      lines.push(`- ${label}: ${ids.join(", ")}`);
    }
  }

  return lines.join("\n");
}
feedbackTypeToLabel function · typescript · L240-L249 (10 LOC)
packages/cli/src/services/feedback-service.ts
function feedbackTypeToLabel(type: FeedbackType): string {
  const map: Record<FeedbackType, string> = {
    "requirement-gap": "requirement-gap (missing requirement)",
    "spec-mismatch": "spec-mismatch (spec vs implementation mismatch)",
    "bug": "implementation-bug (implementation bug)",
    "improvement": "improvement (improvement proposal)",
    "security": "security (security issue)",
  };
  return map[type] ?? type;
}
severityToLabel function · typescript · L251-L259 (9 LOC)
packages/cli/src/services/feedback-service.ts
function severityToLabel(severity: FeedbackSeverity): string {
  const map: Record<FeedbackSeverity, string> = {
    critical: "critical (affects all users)",
    high: "high (affects many users)",
    medium: "medium (affects some users)",
    low: "low (minor issue)",
  };
  return map[severity] ?? severity;
}
buildFeedbackIssueBody function · typescript · L261-L299 (39 LOC)
packages/cli/src/services/feedback-service.ts
function buildFeedbackIssueBody(options: CreateFeedbackOptions): string {
  const lines: string[] = [];

  lines.push("### What happened? / What did you notice?");
  lines.push("");
  lines.push(options.description);
  lines.push("");

  lines.push("### Feedback type");
  lines.push("");
  const typeLabel = options.type
    ? feedbackTypeToLabel(options.type)
    : "unknown/unclassified";
  lines.push(typeLabel);
  lines.push("");

  if (options.relatedReq) {
    lines.push("### Related requirement (Requirement)");
    lines.push("");
    lines.push(options.relatedReq);
    lines.push("");
  }

  if (options.relatedSpec) {
    lines.push("### Related specification (Specification)");
    lines.push("");
    lines.push(options.relatedSpec);
    lines.push("");
  }

  if (options.severity) {
    lines.push("### Severity");
    lines.push("");
    lines.push(severityToLabel(options.severity));
    lines.push("");
  }

  return lines.join("\n");
}
createFeedbackIssue function · typescript · L301-L335 (35 LOC)
packages/cli/src/services/feedback-service.ts
export async function createFeedbackIssue(
  cwd: string,
  options: CreateFeedbackOptions,
): Promise<number> {
  const body = buildFeedbackIssueBody(options);

  const title = options.title.startsWith("[Feedback]")
    ? options.title
    : `[Feedback] ${options.title}`;

  const result = await githubClient.createIssue({
    title,
    body,
    labels: ["feedback", "reqord-generated", ...(options.type ? [options.type] : [])],
  });

  const index = await feedbackRepo.loadIndex(cwd);
  const newEntry: FeedbackEntry = {
    githubIssue: result.number,
    type: options.type,
    severity: options.severity,
    linkedTo: {
      requirements: [],
      createdRequirements: [],
      specifications: [],
      createdSpecifications: [],
    },
    syncedAt: new Date().toISOString(),
    status: "open",
  };
  index.feedbacks.push(newEntry);
  await feedbackRepo.saveIndex(cwd, index);

  return result.number;
}
resolveFeedback function · typescript · L343-L393 (51 LOC)
packages/cli/src/services/feedback-service.ts
export async function resolveFeedback(
  cwd: string,
  options: ResolveFeedbackOptions,
): Promise<void> {
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = index.feedbacks.find(
    (f) => f.githubIssue === options.issueNumber,
  );

  if (!feedback) {
    throw new Error(
      `Feedback for issue #${options.issueNumber} not found in index.yaml`,
    );
  }

  const isReq = options.artifactId.startsWith("req-");
  const isSpec = options.artifactId.startsWith("spec-");

  if (!isReq && !isSpec) {
    throw new Error(
      `Invalid artifact ID: ${options.artifactId}. Must start with "req-" or "spec-"`,
    );
  }

  // Verify artifact is linked (check both linked and created lists)
  const linkedList = isReq
    ? feedback.linkedTo.requirements
    : feedback.linkedTo.specifications;
  const createdList = isReq
    ? feedback.linkedTo.createdRequirements
    : feedback.linkedTo.createdSpecifications;

  if (!linkedList.includes(options.artifactId) && !createdList.
Repobility analyzer · published findings · https://repobility.com
checkRemainingFlags function · typescript · L402-L437 (36 LOC)
packages/cli/src/services/feedback-service.ts
export function checkRemainingFlags(
  feedback: FeedbackEntry,
): RemainingFlag[] {
  const remaining: RemainingFlag[] = [];
  const resolvedReqs = new Set(feedback.linkedTo.resolved?.requirements ?? []);
  const resolvedSpecs = new Set(feedback.linkedTo.resolved?.specifications ?? []);

  const allReqs = [
    ...feedback.linkedTo.requirements,
    ...(feedback.linkedTo.createdRequirements ?? []),
  ];
  const allSpecs = [
    ...feedback.linkedTo.specifications,
    ...(feedback.linkedTo.createdSpecifications ?? []),
  ];

  for (const reqId of allReqs) {
    if (!resolvedReqs.has(reqId)) {
      remaining.push({
        artifactId: reqId,
        issueNumber: feedback.githubIssue,
        severity: feedback.severity ?? "medium",
      });
    }
  }
  for (const specId of allSpecs) {
    if (!resolvedSpecs.has(specId)) {
      remaining.push({
        artifactId: specId,
        issueNumber: feedback.githubIssue,
        severity: feedback.severity ?? "medium",
      });
    }
  }
 
unlinkFromRequirement function · typescript · L450-L482 (33 LOC)
packages/cli/src/services/feedback-service.ts
export async function unlinkFromRequirement(
  cwd: string,
  options: UnlinkFromRequirementOptions,
): Promise<void> {
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = index.feedbacks.find((f) => f.githubIssue === options.issueNumber);

  if (!feedback) {
    throw new Error(`Feedback for issue #${options.issueNumber} not found in index.yaml`);
  }

  // Remove from linkedTo.requirements
  const reqIndex = feedback.linkedTo.requirements.indexOf(options.requirementId);
  if (reqIndex === -1) {
    throw new Error(
      `${options.requirementId} is not linked to feedback #${options.issueNumber}`,
    );
  }
  feedback.linkedTo.requirements.splice(reqIndex, 1);

  // Also remove from resolved to avoid stale resolved entries on re-link
  if (feedback.linkedTo.resolved?.requirements) {
    const resolvedIdx = feedback.linkedTo.resolved.requirements.indexOf(options.requirementId);
    if (resolvedIdx !== -1) {
      feedback.linkedTo.resolved.requirements.splice(resolv
unlinkFromSpecification function · typescript · L484-L516 (33 LOC)
packages/cli/src/services/feedback-service.ts
export async function unlinkFromSpecification(
  cwd: string,
  options: UnlinkFromSpecificationOptions,
): Promise<void> {
  const index = await feedbackRepo.loadIndex(cwd);
  const feedback = index.feedbacks.find((f) => f.githubIssue === options.issueNumber);

  if (!feedback) {
    throw new Error(`Feedback for issue #${options.issueNumber} not found in index.yaml`);
  }

  // Remove from linkedTo.specifications
  const specIndex = feedback.linkedTo.specifications.indexOf(options.specificationId);
  if (specIndex === -1) {
    throw new Error(
      `${options.specificationId} is not linked to feedback #${options.issueNumber}`,
    );
  }
  feedback.linkedTo.specifications.splice(specIndex, 1);

  // Also remove from resolved to avoid stale resolved entries on re-link
  if (feedback.linkedTo.resolved?.specifications) {
    const resolvedIdx = feedback.linkedTo.resolved.specifications.indexOf(options.specificationId);
    if (resolvedIdx !== -1) {
      feedback.linkedTo.resolved.spe
syncFromGitHub function · typescript · L8-L29 (22 LOC)
packages/cli/src/services/feedback-sync-service.ts
export async function syncFromGitHub(cwd: string): Promise<number> {
  const issues = await githubClient.listFeedbackIssues();
  const index = await feedbackRepo.loadIndex(cwd);
  let updatedCount = 0;

  for (const issue of issues) {
    const fromGitHub = parseGitHubIssue(issue);
    const existingIdx = index.feedbacks.findIndex(
      (f) => f.githubIssue === issue.number,
    );

    if (existingIdx >= 0) {
      index.feedbacks[existingIdx] = mergeFeedback(index.feedbacks[existingIdx], fromGitHub);
    } else {
      index.feedbacks.push(fromGitHub);
    }
    updatedCount++;
  }

  await feedbackRepo.saveIndex(cwd, index);
  return updatedCount;
}
mergeFeedback function · typescript · L32-L45 (14 LOC)
packages/cli/src/services/feedback-sync-service.ts
export function mergeFeedback(
  existing: FeedbackEntry,
  fromGitHub: FeedbackEntry,
): FeedbackEntry {
  return {
    githubIssue: existing.githubIssue,
    title: fromGitHub.title,
    type: existing.type ?? fromGitHub.type,
    severity: existing.severity ?? fromGitHub.severity,
    linkedTo: existing.linkedTo,
    syncedAt: fromGitHub.syncedAt,
    status: fromGitHub.status,
  };
}
syncToGitHub function · typescript · L47-L64 (18 LOC)
packages/cli/src/services/feedback-sync-service.ts
export async function syncToGitHub(cwd: string): Promise<number> {
  const index = await feedbackRepo.loadIndex(cwd);
  let count = 0;
  for (const feedback of index.feedbacks) {
    const issue = await githubClient.getIssue(feedback.githubIssue);
    const metadata = {
      type: feedback.type,
      severity: feedback.severity,
      linkedTo: feedback.linkedTo,
    };
    const newBody = upsertReqordComment(issue.body ?? "", metadata);
    if (newBody !== issue.body) {
      await githubClient.updateIssueBody(feedback.githubIssue, newBody);
      count++;
    }
  }
  return count;
}
parseGitHubIssue function · typescript · L66-L83 (18 LOC)
packages/cli/src/services/feedback-sync-service.ts
export function parseGitHubIssue(issue: GitHubIssue): FeedbackEntry {
  const comment = parseReqordComment(issue.body ?? "");

  return {
    githubIssue: issue.number,
    title: issue.title,
    type: comment?.type,
    severity: comment?.severity,
    linkedTo: comment?.linkedTo ?? {
      requirements: [],
      createdRequirements: [],
      specifications: [],
      createdSpecifications: [],
    },
    syncedAt: new Date().toISOString(),
    status: issue.state === "closed" ? "closed" : "open",
  };
}
normalizeIssue function · typescript · L30-L39 (10 LOC)
packages/cli/src/services/github-client.ts
function normalizeIssue(raw: GitHubIssueRaw): GitHubIssue {
  return {
    number: raw.number,
    title: raw.title,
    state: raw.state === "CLOSED" ? "closed" : "open",
    labels: raw.labels.map((l) => l.name),
    createdAt: raw.createdAt,
    body: raw.body,
  };
}
Repobility · severity-and-effort ranking · https://repobility.com
runGh function · typescript · L41-L63 (23 LOC)
packages/cli/src/services/github-client.ts
function runGh(args: string[], options?: { stdin?: string }): Promise<string> {
  return new Promise((resolve, reject) => {
    const proc = spawn("gh", args);
    let stdout = "";
    let stderr = "";
    proc.stdout.on("data", (chunk: Buffer) => { stdout += chunk.toString(); });
    proc.stderr.on("data", (chunk: Buffer) => { stderr += chunk.toString(); });

    if (options?.stdin !== undefined) {
      proc.stdin.write(options.stdin);
      proc.stdin.end();
    }

    proc.on("close", (code) => {
      if (code !== 0) {
        reject(new Error(`gh ${args[0]} ${args[1] ?? ""} failed (code ${code}): ${stderr}`.trim()));
        return;
      }
      resolve(stdout);
    });
    proc.on("error", reject);
  });
}
listFeedbackIssues function · typescript · L65-L74 (10 LOC)
packages/cli/src/services/github-client.ts
export async function listFeedbackIssues(): Promise<GitHubIssue[]> {
  const stdout = await runGh([
    "issue", "list",
    "--label", "feedback",
    "--json", "number,title,state,labels,createdAt,body",
    "--limit", "1000",
  ]);
  const raw: GitHubIssueRaw[] = JSON.parse(stdout);
  return raw.map(normalizeIssue);
}
listIssuesByLabel function · typescript · L76-L89 (14 LOC)
packages/cli/src/services/github-client.ts
export async function listIssuesByLabel(
  labels: string[],
  state: "open" | "closed" | "all" = "all",
): Promise<GitHubIssue[]> {
  const args = ["issue", "list"];
  for (const label of labels) {
    args.push("--label", label);
  }
  args.push("--state", state, "--json", "number,title,state,labels,createdAt,body", "--limit", "1000");

  const stdout = await runGh(args);
  const raw: GitHubIssueRaw[] = JSON.parse(stdout);
  return raw.map(normalizeIssue);
}
getIssue function · typescript · L91-L98 (8 LOC)
packages/cli/src/services/github-client.ts
export async function getIssue(issueNumber: number): Promise<GitHubIssue> {
  const stdout = await runGh([
    "issue", "view", String(issueNumber),
    "--json", "number,title,state,labels,createdAt,body",
  ]);
  const raw: GitHubIssueRaw = JSON.parse(stdout);
  return normalizeIssue(raw);
}
getIssueDetail function · typescript · L100-L111 (12 LOC)
packages/cli/src/services/github-client.ts
export async function getIssueDetail(issueNumber: number): Promise<GitHubIssueDetail> {
  const stdout = await runGh([
    "issue", "view", String(issueNumber),
    "--json", "number,title,state,labels,createdAt,body,updatedAt,closedAt",
  ]);
  const raw = JSON.parse(stdout);
  return {
    ...normalizeIssue(raw),
    updatedAt: raw.updatedAt,
    closedAt: raw.closedAt ?? null,
  };
}
updateIssueBody function · typescript · L113-L118 (6 LOC)
packages/cli/src/services/github-client.ts
export async function updateIssueBody(
  issueNumber: number,
  newBody: string,
): Promise<void> {
  await runGh(["issue", "edit", String(issueNumber), "--body-file", "-"], { stdin: newBody });
}
closeIssue function · typescript · L120-L129 (10 LOC)
packages/cli/src/services/github-client.ts
export async function closeIssue(
  issueNumber: number,
  comment?: string,
): Promise<void> {
  const args = ["issue", "close", String(issueNumber)];
  if (comment) {
    args.push("--comment", comment);
  }
  await runGh(args);
}
listAllIssues function · typescript · L142-L156 (15 LOC)
packages/cli/src/services/github-client.ts
export async function listAllIssues(
  state: "open" | "closed" | "all" = "all",
  limit: number = 500,
): Promise<GitHubIssue[]> {
  const args = [
    "issue", "list",
    "--state", state,
    "--json", "number,title,state,labels,createdAt,body",
    "--limit", String(limit),
  ];

  const stdout = await runGh(args);
  const raw: GitHubIssueRaw[] = JSON.parse(stdout);
  return raw.map(normalizeIssue);
}
All rows scored by the Repobility analyzer (https://repobility.com)
createIssue function · typescript · L163-L188 (26 LOC)
packages/cli/src/services/github-client.ts
export async function createIssue(
  options: CreateIssueOptions,
): Promise<CreatedIssue> {
  const args = [
    "issue",
    "create",
    "--title",
    options.title,
    "--label",
    options.labels.join(","),
    "--body-file",
    "-",
  ];

  const stdout = await runGh(args, { stdin: options.body });

  // gh issue create outputs the URL like: https://github.com/owner/repo/issues/123
  const match = stdout.trim().match(/https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
  if (!match) {
    throw new Error(`Failed to extract issue URL from gh output: ${stdout}`);
  }
  return {
    number: parseInt(match[1], 10),
    url: match[0],
  };
}
loadTasksForSpec function · typescript · L42-L50 (9 LOC)
packages/cli/src/services/impact-service.ts
async function loadTasksForSpec(cwd: string, specificationId: 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.filter((t) => t.linkedTo.specifications.includes(specificationId));
}
loadAllTasks function · typescript · L52-L60 (9 LOC)
packages/cli/src/services/impact-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;
}
‹ prevpage 2 / 7next ›