npx tsx state/lib/cockpit-layout.ts .md file to compare - side-by-side diff against cockpit-layout
cockpit-layout
What it does for you
Lays out your workspace so opening it shows your business, not technical clutter.
What it produces
A recent result, so you can see the kind of work it returns.
loading…
How to get it
These run inside the Snappy workspace. Want this working in your business? I set skills like this up with you, in one focused week.
For developers how this skill is built, graded, and how it runs
at a glance- the short version
what's inside - the parts that make up a skill 3/4 present
A skill is just a few plain-text files. Only the main one is required. The rest are optional, added as the work needs them. This is what the skill is made of; how it runs is just below.
state/skills/cockpit-layout/SKILL.md
present
state/lib/cockpit-layout.ts
present
state/bin/cockpit-layout/
not present
state/skills/cockpit-layout/AGENTS.md
present
how it runs - the shared frame every skill uses 2/5 present
Every skill runs the same way. One part does the work, a separate part checks it, and a short loader hands the AI exactly what it needs for the job. Anything this skill doesn't use shows a one-line note saying why, on purpose, not by accident.
This skill doesn't fix its own gaps yet.
state/log/evals.ndjson - The bundle layout command is npx tsx state/lib/cockpit-layout.ts. It creates operator folders/symlinks and _about.md files without moving canonical state/ data.
- Before claiming a snappy-shell page redesign is done, run npx tsx state/lint/codable-drift.ts from snappy-os and trace the page's actual dataClient method plus as: Type.self decode target. Fix the Codable the screen really uses, not just the first lint row.
- Pre-mount Swift screens can call only bootstrap endpoints: /healthz, /state, /bundle/status, /bundle/mount, /bundle/eject, /bundle-meta, and /events. Add a bootstrap endpoint server-side if richer data is needed before mount.
- Multi-rail snappy-os pages need an outer vertical ScrollView with a leading VStack; otherwise lower rails clip behind the pinned footer.
- For Swift investigation, use swiftlens first: swift_get_symbols_overview, symbol definition/references, diagnostics, validation. Read whole Swift files only after semantic tools point to a tight range.
- Parallel-agent workflow must use explicit pathspecs. Never git add -A, git add ., git commit -a, or broad git stash -u. For commits, prefer git commit -m "..." -- <paths>.
- +4 more in AGENTS.md →
what it has learned - fixes written back in over time sample
When a run hits something this skill didn't handle, the fix gets written back into the skill so it doesn't happen again. FIXED means it was corrected on the spot. LOGGED means it's queued for a bigger rewrite. Either way, the skill gets a little better and never makes the same mistake twice.
- Loading feedback rows…
how the work flows- step by step
SKILL.md- the skill, written out in plain English
cockpit-layout - make the bundle look like the OS
When you open a snappy-os bundle in Finder, what you see should BE the sidebar. Not the engine room. The sidebar items collapse into 6 natural families per state/log/agents-md-feedback.log cockpit-recon-2026-04-26:
- catalog/ - what the bundle CAN do (skills, apis, clients, agents, loaders)
- ledger/ - what HAS happened (one evals.ndjson, six lenses)
- memory/ - what's REMEMBERED (journal, daily, observations, asks)
- inbox/ - incoming signals + queued work (observations, feedback, regen, meetings)
- activity/ - what's HAPPENING (dispatches, commands, chain)
- artifacts/ - what was PRODUCED (drafts, gallery)
- meta/ - bundle ABOUT itself (env-vars, gateway, architecture, home)
This skill is idempotent - it can run repeatedly. Existing canonical files under state/ are left untouched; the operator surface is built from symlinks and one tiny generated _about.md per folder explaining what that folder is for.
Steps
npx tsx state/lib/cockpit-layout.ts
That command:
- Walks the canonical
state/tree - Creates the 7 top-level operator folders
- For each folder, drops symlinks pointing at the canonical files/dirs
- Writes a small
_about.mdat the top of each folder describing what
it contains and which sidebar items it powers
- Logs an eval row with
mode: shapebecause the auditor walks the
resulting tree and asserts every sidebar item has a path
Eval
shape. Auditor reads Route.swift-equivalent list (hardcoded in the lib), checks each section has either a folder or a symlink at the bundle root. Score 1.0 when every section resolves; partial credit per unresolved.
Why this is the bundle thesis
The thesis: a snappy-os bundle is the unit of distribution. The Swift shell renders whatever the bundle contains. Today the bundle's filesystem looks like a Node project; the operator sees node_modules/, package-lock.json, scattered markdown. After this skill runs, the bundle LOOKS like the OS in Finder. Same data, different surface.
AGENTS.md- what the AI loads when this skill comes up
cockpit-layout - loader
Full reference: state/skills/cockpit-layout/SKILL.md. The skill makes a bundle look like the OS in Finder; this loader also carries the cockpit layout guardrails that have caused regressions.
Critical Rules
- The bundle layout command is
npx tsx state/lib/cockpit-layout.ts. It creates operator folders/symlinks and_about.mdfiles without moving canonicalstate/data. - Before claiming a snappy-shell page redesign is done, run
npx tsx state/lint/codable-drift.tsfrom snappy-os and trace the page's actualdataClientmethod plusas: Type.selfdecode target. Fix the Codable the screen really uses, not just the first lint row. - Pre-mount Swift screens can call only bootstrap endpoints:
/healthz,/state,/bundle/status,/bundle/mount,/bundle/eject,/bundle-meta, and/events. Add a bootstrap endpoint server-side if richer data is needed before mount. - Multi-rail snappy-os pages need an outer vertical
ScrollViewwith a leadingVStack; otherwise lower rails clip behind the pinned footer. - For Swift investigation, use swiftlens first:
swift_get_symbols_overview, symbol definition/references, diagnostics, validation. Read whole Swift files only after semantic tools point to a tight range. - Parallel-agent workflow must use explicit pathspecs. Never
git add -A,git add .,git commit -a, or broadgit stash -u. For commits, prefergit commit -m "..." -- <paths>. - After a pull/rebase in a shared worktree, re-read or diff your files before editing/committing; stash-pop and rebase can silently restore older content.
- New Swift files require
xcodegenafter the file is written and beforexcodebuild. - Concurrent
xcodebuildruns need unique-derivedDataPath /tmp/snappy-shell-build-<name>. - Regen-drain/headless consumers must never run git write operations and must append a writeback line even when no edit is needed. Consumer timeout failures are harness/budget issues, not a reason to add loader prose.
Commands
| action | command |
|---|---|
| build operator layout | npx tsx state/lib/cockpit-layout.ts |
| Codable drift gate | npx tsx state/lint/codable-drift.ts |
| shell deploy | cd /Users/robertboulos/projects/snappy-shell && bash scripts/deploy.sh |
| Swift build truth | xcodebuild -derivedDataPath /tmp/snappy-shell-build-<name> ... |
| safe commit shape | git commit -m "message" -- <paths> |
| eval log | state/log/evals.ndjson (skill: cockpit-layout) |
Self-Test
An agent reading this should:
- [ ] Know the layout skill is
state/lib/cockpit-layout.ts. - [ ] Run Codable drift and trace actual screen decode paths before page claims.
- [ ] Respect the pre-mount endpoint allowlist.
- [ ] Add ScrollView for multi-rail shell pages.
- [ ] Use swiftlens for Swift navigation.
- [ ] Use pathspec-only git operations in shared worktrees.
- [ ] Keep regen consumers out of git writes and require writeback proof.
Found a gap? Edit this file. <!-- footer-injection-point -->
api.ts- the code it can call
#!/usr/bin/env npx tsx
/**
* cockpit-layout.ts — build the operator-facing top-level folder layout
* for a snappy-os bundle.
*
* Reads `state/cockpit-routes.json` (canonical sidebar manifest) and
* creates 7 top-level folders (catalog/, ledger/, memory/, inbox/,
* activity/, artifacts/, meta/). Inside each folder, drops a symlink
* pointing at the canonical state/-rooted source for every route
* declared in that family. Writes a small `_about.md` per folder.
*
* Idempotent: existing symlinks are replaced with the current target;
* existing _about.md files are overwritten with the freshly-generated
* version.
*
* Phase 1 (today): symlinks only — original state/ tree untouched.
* Phase 3 (later): when state/ moves to _engine/state/, this file gets
* a one-line update and re-runs to point at the new root.
*
* Run:
* npx tsx state/lib/cockpit-layout.ts
*/
import { readFileSync, writeFileSync, mkdirSync, symlinkSync, existsSync, lstatSync, unlinkSync, statSync, readdirSync } from "fs";
import { join, dirname, relative, resolve, basename } from "path";
import { fileURLToPath } from "url";
import { appendNdjsonQueued } from "./log.ts";
const HERE = dirname(fileURLToPath(import.meta.url));
const BUNDLE = resolve(HERE, "..", ".."); // state/lib → bundle root
type Route = {
id: string;
label: string;
source: string;
kind: "file" | "dir" | "glob" | "view";
filter?: string;
view?: string;
alias_of?: string;
phase2?: string;
};
type Family = {
id: string;
title: string;
routes: Route[];
};
type Manifest = {
version: number;
description: string;
families: Family[];
engine_room_paths: string[];
};
const manifest: Manifest = JSON.parse(
readFileSync(join(BUNDLE, "state/cockpit-routes.json"), "utf-8")
);
/** Replace existing symlink/file at path with a symlink pointing at target. */
function relink(linkPath: string, target: string): void {
if (existsSync(linkPath) || isBrokenLink(linkPath)) {
unlinkSync(linkPath);
}
symlinkSync(target, linkPath);
}
function isBrokenLink(p: string): boolean {
try { return lstatSync(p).isSymbolicLink(); }
catch { return false; }
}
const reportLines: string[] = [];
let routesResolved = 0;
let routesMissing = 0;
let symlinksCreated = 0;
let viewsGenerated = 0;
for (const family of manifest.families) {
const familyDir = join(BUNDLE, family.id);
mkdirSync(familyDir, { recursive: true });
// _about.md — what is this folder, what sidebar items it powers.
const aboutPath = join(familyDir, "_about.md");
const aboutBody = generateAbout(family);
writeFileSync(aboutPath, aboutBody);
for (const route of family.routes) {
const srcAbs = join(BUNDLE, route.source);
// For "view" kind routes, write a small _<id>.md explainer
// pointing at the source data file. The real rendering lives in
// the Swift app or downstream readers; this is for filesystem
// browsing.
if (route.kind === "view") {
const viewPath = join(familyDir, `${route.id}.md`);
writeFileSync(viewPath, generateViewExplainer(route));
viewsGenerated++;
if (existsSync(srcAbs)) {
routesResolved++;
reportLines.push(`✓ ${family.id}/${route.id}.md → view of ${route.source}`);
} else {
routesMissing++;
reportLines.push(`✗ ${family.id}/${route.id}.md → MISSING ${route.source}`);
}
continue;
}
// alias_of — skip linking, just record
if (route.alias_of) {
reportLines.push(`↩ ${family.id}/${route.id} alias of ${route.alias_of}`);
continue;
}
// file/dir/glob — drop a symlink (or directory of symlinks for glob)
const linkPath = join(familyDir, route.id);
if (route.kind === "glob" && route.filter) {
// Build a directory full of links to matching files/dirs under source.
mkdirSync(linkPath, { recursive: true });
const matches = expandGlob(srcAbs, route.filter);
for (const m of matches) {
const entry = join(linkPath, basename(m));
const rel = relative(linkPath, m);
relink(entry, rel);
symlinksCreated++;
}
if (matches.length > 0) {
routesResolved++;
reportLines.push(`✓ ${family.id}/${route.id}/ ← ${matches.length} matches of ${route.source}/${route.filter}`);
} else {
routesMissing++;
reportLines.push(`✗ ${family.id}/${route.id}/ ← 0 matches of ${route.source}/${route.filter}`);
}
continue;
}
// file or dir — single symlink
if (!existsSync(srcAbs)) {
// Phase-2 routes (krisp) may not exist yet; mark as pending.
const tag = route.phase2 ? `pending (phase 2: ${route.phase2})` : "MISSING";
routesMissing++;
reportLines.push(`✗ ${family.id}/${route.id} → ${tag}: ${route.source}`);
continue;
}
const rel = relative(familyDir, srcAbs);
relink(linkPath, rel);
symlinksCreated++;
routesResolved++;
reportLines.push(`✓ ${family.id}/${route.id} → ${route.source}`);
}
}
// Top-level _about.md — explains the layout itself
const layoutAbout = `# snappy-os bundle layout
This bundle's filesystem mirrors the snappy-shell sidebar. Open any of
these folders to see the operator surface; the engine room lives under
\`state/\` (and will move to \`_engine/state/\` in a future restructure).
${manifest.families.map(f => `- **${f.id}/** — ${f.title}`).join("\n")}
The canonical source for sidebar items is \`state/cockpit-routes.json\`.
Re-run \`npx tsx state/lib/cockpit-layout.ts\` after editing the manifest
to rebuild the symlinks.
Last built: ${new Date().toISOString()}
Routes resolved: ${routesResolved}, missing: ${routesMissing}, symlinks: ${symlinksCreated}, views: ${viewsGenerated}
`;
writeFileSync(join(BUNDLE, "BUNDLE.md"), layoutAbout);
// Eval log — shape audit
const evalRow = {
ts: new Date().toISOString(),
skill: "cockpit-layout",
verb: "build",
ok: routesMissing === 0,
score: routesMissing === 0 ? 1.0 : Math.max(0, 1 - routesMissing / (routesResolved + routesMissing)),
routes_resolved: routesResolved,
routes_missing: routesMissing,
symlinks_created: symlinksCreated,
views_generated: viewsGenerated,
writer_id: "state/lib/cockpit-layout.ts",
actor_session_id: process.env.CLAUDE_SESSION_ID ?? `cockpit-layout-${Date.now()}`,
auditor_session_id: `cockpit-layout-shape-${Date.now()}`,
};
const evalsPath = join(BUNDLE, "state/log/evals.ndjson");
// P0 #2 (2026-05-11): per-path queue. Top-level await is OK here — this
// file is a CLI script with await elsewhere.
await appendNdjsonQueued(evalsPath, evalRow);
console.log(reportLines.join("\n"));
console.log(`\nlayout: ${routesResolved} routes resolved, ${routesMissing} missing, ${symlinksCreated} symlinks, ${viewsGenerated} views`);
console.log(`eval row appended → state/log/evals.ndjson (score=${evalRow.score.toFixed(2)})`);
process.exit(routesMissing === 0 ? 0 : 1);
// =============================================================
function generateAbout(family: Family): string {
const lines: string[] = [];
lines.push(`# ${family.id}/ — ${family.title}`);
lines.push("");
lines.push(`This folder is part of the snappy-os bundle's operator surface — the`);
lines.push(`top-level layout that mirrors the snappy-shell sidebar. Routes here:`);
lines.push("");
for (const r of family.routes) {
const target = r.kind === "view" ? `view of ${r.source}` : r.source;
lines.push(`- **${r.id}** — ${r.label} → \`${target}\`${r.alias_of ? ` (alias of ${r.alias_of})` : ""}`);
}
lines.push("");
lines.push(`Generated by \`state/lib/cockpit-layout.ts\` from`);
lines.push(`\`state/cockpit-routes.json\`. Edit the manifest, re-run, refresh.`);
lines.push("");
lines.push(`Last built: ${new Date().toISOString()}`);
return lines.join("\n");
}
function generateViewExplainer(route: Route): string {
return [
`# ${route.label} — view`,
"",
`This route is a **derived view** of \`${route.source}\`.`,
`The view shape is \`${route.view ?? "unknown"}\`.`,
"",
`Open \`${route.source}\` to see the underlying data.`,
`The snappy-shell screen for this route renders the view; the file`,
`you're reading is a filesystem-side breadcrumb so the bundle's`,
`layout reflects the sidebar.`,
"",
`Generated by \`state/lib/cockpit-layout.ts\`.`,
].join("\n");
}
function expandGlob(srcDir: string, pattern: string): string[] {
// Two patterns supported by cockpit-routes.json today:
// "client-*" → files/dirs in srcDir whose name starts with client-
// "*/AGENTS.md" → AGENTS.md inside each subdir of srcDir
if (!existsSync(srcDir)) return [];
if (pattern.startsWith("*/")) {
const tail = pattern.slice(2);
const out: string[] = [];
for (const e of readdirSync(srcDir, { withFileTypes: true })) {
if (!e.isDirectory()) continue;
const candidate = join(srcDir, e.name, tail);
if (existsSync(candidate)) out.push(candidate);
}
return out;
}
if (pattern.endsWith("*")) {
const prefix = pattern.slice(0, -1);
return readdirSync(srcDir)
.filter(n => n.startsWith(prefix))
.map(n => join(srcDir, n));
}
if (pattern.startsWith("*")) {
const suffix = pattern.slice(1);
return readdirSync(srcDir)
.filter(n => n.endsWith(suffix))
.map(n => join(srcDir, n));
}
return [];
}
scripts- helper scripts it can run
prose-only skill - 1 inline code block live in SKILL.md above (no state/bin/ sidecar yet).
how we check it- the checks, plus the last 2 runs
| timestamp | verb | score | primary_issue | artifact |
|---|---|---|---|---|
| 2026-06-30 14:24Z | - | 1.00 | - | - |
| 2026-06-30 08:35Z | - | 0.55 | - | - |