redline status

What is installed here, how hard it bites, what an administrator still owes you, and whether the standards have moved on — from the checkout alone, with no credential.

What this is

Answers "what is Redline doing in this repository?" from the checkout alone. What is installed, which rung the gate sits at, what an administrator still owes you, and whether the standards this CLI carries have moved on from the ones the repository was rendered against.

How to onboard it

Nothing to install, and nothing to authorise. It reads .redline.json and the rendered artifacts in the working tree — it contacts no host and needs no credential, which is what makes it the right first command on a repository you have not seen before. On a repository that was never onboarded it says so and names the next command rather than failing at an API call it should not have attempted.

How to use it

terminal
$ npx redlinegate@latest status                        # what is installed here
$ npx redlinegate@latest status --json                 # the same, for a wrapper that has to act on it

The flags that change behaviour materially:

  • --jsonThe whole report as a stable object — onboarded, profile, stacks, host, rung, vendors, capabilities, integrations, pendingAdmin, standardsVersion against currentStandards, drifted, and the two timestamps. Shaped so a script can branch on `drifted` or on a non-empty `pendingAdmin` without parsing prose.

Expected output

Three or four lines on a healthy repository, naming the profile, the rung and the standards version it was rendered from. Two things it will not do: it will not report a repository healthy on the strength of a check it could not run, and it will not claim a host setting it never read — everything here comes from the working tree, so a ruleset edited by hand in the GitHub UI is invisible to it by design. That is what `redline verify` is for, and status says so rather than implying it covered it.

How to edit it

cli/commands/status.ts, reading cli/config/redline-json.ts for the recorded state and cli/render/manifest.ts for the standards version to compare against. The drift comparison is a version comparison, not a content diff: a repository that re-rendered from the same version is current even if a human has since edited an artifact, which is the case `redline verify` catches.

Run npm test and npm run typecheck before pushing: the command surface is unit-tested against a fake host client, so a behaviour change shows up as a failing assertion rather than as a surprise on someone's repository.

The full file

cli/commands/status.ts · 139 lines · 4.7 KB
import { capabilityName, readConfig } from '../config/redline-json.ts';
import { standardsVersion } from './sync.ts';
import { resolveProfile } from '../render/profile.ts';
import { loadManifest } from '../render/manifest.ts';
import { TOOL_PROBES } from '../detect/existing.ts';
import type { Rung } from '../enforce/ladder.ts';

// Where this repository stands, on one screen and with no credential.
//
// Everything here was already knowable and none of it was answerable without
// reading `.redline.json` by hand or running `verify` and interpreting it. The
// question this exists for is the one asked before touching anything: what is
// installed, how hard does it bite, what is still waiting on somebody else, and
// is any of it out of date.

export interface StatusReport {
  readonly onboarded: boolean;
  readonly profile: string;
  readonly stacks: readonly string[];
  readonly host: string;
  readonly rung: Rung;
  readonly vendors: readonly string[];
  readonly capabilities: readonly string[];
  readonly integrations: readonly string[];
  /** Capabilities an administrator still has to grant. */
  readonly pendingAdmin: readonly string[];
  readonly standardsVersion: string;
  readonly currentStandards: string;
  /** True when the repository is rendered from an older standards version. */
  readonly drifted: boolean;
  readonly onboardedAt: string;
  readonly lastRunAt: string;
}

export function status(cwd: string, root: string): StatusReport {
  const config = readConfig(cwd);
  const current = standardsVersion(root);

  if (config === null) {
    return {
      onboarded: false,
      profile: '',
      stacks: [],
      host: '',
      rung: 'observe',
      vendors: [],
      capabilities: [],
      integrations: [],
      pendingAdmin: [],
      standardsVersion: '',
      currentStandards: current,
      drifted: false,
      onboardedAt: '',
      lastRunAt: '',
    };
  }

  // A profile the manifest no longer defines must not crash the one command
  // whose job is to report the state of a repository — that repository is
  // exactly the one somebody needs to look at.
  let stacks: string[] = [];
  try {
    stacks = resolveProfile(loadManifest(root), config.profile).stacks;
  } catch {
    stacks = [];
  }

  const selected = Object.entries(config.capabilities)
    .filter(([, on]) => on)
    .map(([key]) => capabilityName(key));

  return {
    onboarded: true,
    profile: config.profile,
    stacks,
    host: config.host,
    rung: config.rung,
    vendors: config.vendors,
    capabilities: selected,
    integrations: config.integrations,
    pendingAdmin: config.pendingAdmin,
    standardsVersion: config.standardsVersion,
    currentStandards: current,
    drifted: config.standardsVersion !== current,
    onboardedAt: config.onboardedAt,
    lastRunAt: config.lastRunAt,
  };
}

/** The report as lines, in the order they answer the question. */
export function formatStatus(report: StatusReport): string[] {
  if (!report.onboarded) {
    return [
      'not onboarded — no .redline.json here',
      `the standards in this CLI are at ${report.currentStandards}`,
      'start with: redline init --dry-run',
    ];
  }

  const lines = [
    `profile      ${report.profile}${report.stacks.length > 0 ? ` (${report.stacks.join(', ')})` : ''}`,
    `host         ${report.host}`,
    `rung         ${report.rung} — ${RUNG_MEANING[report.rung]}`,
    `assistants   ${report.vendors.join(', ')}`,
    `installed    ${report.capabilities.length > 0 ? report.capabilities.join(', ') : 'nothing'}`,
  ];

  if (report.integrations.length > 0) {
    const labels = report.integrations.map(
      (id) => TOOL_PROBES.find((probe) => probe.id === id)?.label ?? id
    );
    lines.push(`alongside    ${labels.join(', ')}`);
  }

  lines.push(
    `standards    ${report.standardsVersion}${
      report.drifted ? ` — behind ${report.currentStandards}, a sync will raise it` : ' — current'
    }`
  );

  if (report.pendingAdmin.length > 0) {
    // Named as waiting on a person rather than as a failure: nobody here can
    // clear it, and reporting it as broken sends them looking for a bug.
    lines.push(`waiting on   an administrator, for: ${report.pendingAdmin.join(', ')}`);
  }

  lines.push(`onboarded    ${report.onboardedAt.slice(0, 10)}, last run ${report.lastRunAt.slice(0, 10)}`);
  return lines;
}

// Record<Rung, …> rather than a lookup with a fallback: a rung added to the
// ladder has to be given a meaning here, and the build says so.
const RUNG_MEANING: Record<Rung, string> = {
  observe: 'comments only, the check is always green',
  warn: 'comments and labels, still never blocks a merge',
  'block-blocker': 'a BLOCKER finding stops the merge',
  'block-high': 'BLOCKER and HIGH both stop the merge',
};