CtrlK
BlogDocsLog inGet started
Tessl Logo
PluginPublic

tessl/factory-setup

Set up, understand, and change a Tessl factory on a repository: pick automations that fit how you work, such as shipping tickets as pull requests, answering reviews and fixing checks on those pull requests, code review, and scheduled jobs; prepare the repository for agents; write issues for the factory to build; then see what the factory has done, find out why a run failed or never started, and change or extend it.

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

findings.mjsskills/prepare-repo/scripts/sources/

// Open Tessl findings for the project this directory is linked to.

import { clip, failed, run, runJson, skipped } from '../lib/run.mjs';

const PAGE_SIZE = 100;
const TEXT_CHARS = 600;

/** The linked project id from `tessl project repair --json`, or a reason. */
export async function linkedProject(root) {
  // Without flags, `project repair` only diagnoses the link; it changes nothing.
  const result = await run('tessl', ['project', 'repair', '--json'], {
    cwd: root,
  });
  if (result.missing) return { reason: 'tessl is not installed' };
  let parsed;
  try {
    parsed = JSON.parse(result.stdout);
  } catch {
    return {
      reason: `could not read the project link: ${result.error ?? 'invalid JSON'}`,
    };
  }
  const id = parsed.linkedProject?.id;
  if (!id)
    return {
      reason:
        parsed.message ?? 'this directory is not linked to a Tessl project',
    };
  return { id, name: parsed.linkedProject.name ?? null };
}

function summarize(finding) {
  const a = finding.attributes ?? {};
  const evidence = (a.observations ?? []).flatMap((o) => o.evidence ?? []);
  return {
    id: finding.id,
    title: a.title,
    analysis: clip(a.analysis, TEXT_CHARS),
    recommendation: clip(a.recommendation, TEXT_CHARS),
    evidence: evidence.slice(0, 10),
    createdAt: a.createdAt,
  };
}

/** Open findings, newest first, up to `maxFindings`, reading every page. */
export async function collectFindings(ctx) {
  const project = await linkedProject(ctx.root);
  if (!project.id) return skipped(project.reason);
  const items = [];
  let after = null;
  let more = true;
  while (more && items.length < ctx.limits.maxFindings) {
    const args = [
      'finding',
      'list',
      '--project',
      project.id,
      '--state',
      'open',
      '--json',
      '--limit',
      String(Math.min(PAGE_SIZE, ctx.limits.maxFindings)),
    ];
    if (after) args.push('--after', after);
    const page = await runJson('tessl', args, { cwd: ctx.root });
    if (!page.ok) return failed(`tessl finding list failed: ${page.error}`);
    items.push(...(page.data.data ?? []).map(summarize));
    const next = page.data.links?.next;
    after = next ? new URL(next).searchParams.get('page[after]') : null;
    more = Boolean(after);
  }
  return {
    status: 'ok',
    project,
    truncated: more || items.length > ctx.limits.maxFindings,
    items: items.slice(0, ctx.limits.maxFindings),
  };
}

README.md

tile.json