@corelayer-ai/cli 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, copyFileSync, unlinkSync, rmdirSync, } from "node:fs";
1
+ import { existsSync, cpSync, rmSync } from "node:fs";
2
2
  import { join, dirname } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { fileURLToPath } from "node:url";
@@ -7,11 +7,11 @@ const SKILL_NAME = "corelayer";
7
7
  function getSkillTargetDir() {
8
8
  return join(homedir(), ".claude", "skills", SKILL_NAME);
9
9
  }
10
- function getSkillSourcePath() {
10
+ function getSkillSourceDir() {
11
11
  const thisFile = fileURLToPath(import.meta.url);
12
12
  const distDir = dirname(dirname(thisFile));
13
13
  const packageDir = dirname(distDir);
14
- return join(packageDir, "skill", "SKILL.md");
14
+ return join(packageDir, "skill");
15
15
  }
16
16
  function printInstallHelp() {
17
17
  process.stdout
@@ -44,16 +44,15 @@ export async function runInstallSkill(args, ctx) {
44
44
  printInstallHelp();
45
45
  return;
46
46
  }
47
- const source = getSkillSourcePath();
48
- if (!existsSync(source)) {
49
- fail("Skill file not found. The CLI package may be incomplete.");
47
+ const sourceDir = getSkillSourceDir();
48
+ if (!existsSync(join(sourceDir, "SKILL.md"))) {
49
+ fail("Skill files not found. The CLI package may be incomplete.");
50
50
  }
51
51
  const targetDir = getSkillTargetDir();
52
- const targetFile = join(targetDir, "SKILL.md");
53
- mkdirSync(targetDir, { recursive: true });
54
- copyFileSync(source, targetFile);
52
+ rmSync(targetDir, { recursive: true, force: true });
53
+ cpSync(sourceDir, targetDir, { recursive: true });
55
54
  if (!ctx.quiet) {
56
- process.stdout.write(`Corelayer skill installed to ${targetFile}\n`);
55
+ process.stdout.write(`Corelayer skill installed to ${targetDir}\n`);
57
56
  }
58
57
  }
59
58
  export async function runUninstallSkill(args, ctx) {
@@ -62,17 +61,10 @@ export async function runUninstallSkill(args, ctx) {
62
61
  return;
63
62
  }
64
63
  const targetDir = getSkillTargetDir();
65
- const targetFile = join(targetDir, "SKILL.md");
66
- if (!existsSync(targetFile)) {
64
+ if (!existsSync(targetDir)) {
67
65
  fail("Corelayer skill is not installed.");
68
66
  }
69
- unlinkSync(targetFile);
70
- try {
71
- rmdirSync(targetDir);
72
- }
73
- catch {
74
- // directory not empty or already removed
75
- }
67
+ rmSync(targetDir, { recursive: true, force: true });
76
68
  if (!ctx.quiet) {
77
69
  process.stdout.write("Corelayer skill uninstalled.\n");
78
70
  }
@@ -1,7 +1,7 @@
1
- import { createInterface } from "node:readline/promises";
2
1
  import { stdin as input, stdout as output } from "node:process";
2
+ import { createInterface } from "node:readline/promises";
3
3
  import { CorelayerClient } from "../lib/api-client.js";
4
- import { readConfig, resolveToken, resolveApiUrl } from "../lib/config.js";
4
+ import { readConfig, resolveApiUrl, resolveToken } from "../lib/config.js";
5
5
  import { fail, formatDate, printJson, printTable } from "../lib/output.js";
6
6
  function readFlag(args, flag) {
7
7
  const index = args.indexOf(flag);
@@ -27,7 +27,9 @@ function removeFlags(args) {
27
27
  arg === "--page" ||
28
28
  arg === "--feedback" ||
29
29
  arg === "--last-seen-before" ||
30
- arg === "--last-seen-before-or-on") {
30
+ arg === "--last-seen-before-or-on" ||
31
+ arg === "--since" ||
32
+ arg === "--duration") {
31
33
  skipNext = true;
32
34
  continue;
33
35
  }
@@ -55,8 +57,9 @@ USAGE:
55
57
 
56
58
  SUBCOMMANDS:
57
59
  list List issues in a group
58
- get <id> Show details for one issue
60
+ get <id|slug> Show details for one issue
59
61
  summary Summarize issue health for a group
62
+ diff Show issues new or changed since a timestamp
60
63
  close <id> Close an issue (write operation)
61
64
  bulk-close Close many issues in one request (write operation)
62
65
  reopen <id> Reopen a closed issue (write operation)
@@ -66,25 +69,49 @@ OPTIONS:
66
69
  --group <id> Group ID (or set default-group via 'corelayer config set')
67
70
  --status <status> Filter by status (list)
68
71
  --severity <sev> Filter by severity (list)
69
- --limit <n> Limit results (list)
70
- --page <n> Page number (list)
72
+ --limit <n> Limit results (list, diff)
73
+ --page <n> Page number (list, diff)
71
74
  --feedback <text> Feedback message (close)
72
75
  --last-seen-before <date|preset>
73
76
  Filter bulk-close to issues seen before a cutoff
74
77
  --last-seen-before-or-on <date|preset>
75
78
  Filter bulk-close to issues seen on or before a cutoff
79
+ --since <timestamp|preset>
80
+ Diff: ISO 8601 timestamp or preset (1hour, 24hours, 7days, 14days, 30days)
81
+ --duration <duration>
82
+ Diff: human-friendly duration (e.g. 2hours, 4hours, 30m, 1d)
76
83
  --yes Skip confirmation prompt (delete)
77
84
  --json Output as JSON
78
85
  -h, --help Print help
79
86
 
80
87
  EXAMPLES:
81
88
  corelayer issues list --group g_123 --status Open
82
- corelayer issues get i_456 --json
89
+ corelayer issues get corelayer-prod-engine-20260602-1 --json
90
+ corelayer issues diff --group g_123 --since 2026-06-01T00:00:00Z
91
+ corelayer issues diff --group g_123 --since 24hours
92
+ corelayer issues diff --group g_123 --duration 2hours --limit 50
83
93
  corelayer issues close i_456 --feedback "fixed in deploy 2026-04-07"
84
94
  corelayer issues bulk-close --group g_123 --last-seen-before-or-on 14days --feedback "Not seen in two weeks"
85
95
  corelayer issues delete i_456 --yes
86
96
  `);
87
97
  }
98
+ function parseDuration(value) {
99
+ const match = value
100
+ .trim()
101
+ .toLowerCase()
102
+ .match(/^(\d+(?:\.\d+)?)\s*(m|min|minutes?|h|hr|hours?|d|days?)$/);
103
+ if (!match)
104
+ return null;
105
+ const n = parseFloat(match[1]);
106
+ const unit = match[2];
107
+ if (unit.startsWith("m"))
108
+ return Math.round(n * 60 * 1000);
109
+ if (unit.startsWith("h"))
110
+ return Math.round(n * 60 * 60 * 1000);
111
+ if (unit.startsWith("d"))
112
+ return Math.round(n * 24 * 60 * 60 * 1000);
113
+ return null;
114
+ }
88
115
  export async function runIssues(args, ctx) {
89
116
  if (args.includes("--help") || args.includes("-h")) {
90
117
  printHelp();
@@ -114,7 +141,7 @@ export async function runIssues(args, ctx) {
114
141
  printJson(result);
115
142
  return;
116
143
  }
117
- const rows = result.issues.map(issue => [
144
+ const rows = result.issues.map((issue) => [
118
145
  issue.id,
119
146
  issue.slug || "-",
120
147
  issue.severity || "-",
@@ -129,11 +156,11 @@ export async function runIssues(args, ctx) {
129
156
  return;
130
157
  }
131
158
  if (sub === "get") {
132
- const issueId = commandArgs[0];
133
- if (!issueId) {
134
- fail("Usage: corelayer issues get <issueId>");
159
+ const issueIdentifier = commandArgs[0];
160
+ if (!issueIdentifier) {
161
+ fail("Usage: corelayer issues get <issueId|slug>");
135
162
  }
136
- const issue = await client.getIssue(issueId);
163
+ const issue = await client.getIssue(issueIdentifier);
137
164
  if (ctx.json) {
138
165
  printJson(issue);
139
166
  return;
@@ -239,5 +266,50 @@ export async function runIssues(args, ctx) {
239
266
  printJson(summary);
240
267
  return;
241
268
  }
242
- fail("Usage: corelayer issues <list|get|close|bulk-close|reopen|delete|summary> [options]");
269
+ if (sub === "diff") {
270
+ const groupId = readFlag(args, "--group") || config.defaults?.group;
271
+ if (!groupId) {
272
+ fail("groupId is required. Use --group <groupId> or set default-group via corelayer config set default-group <groupId>");
273
+ }
274
+ const sinceFlag = readFlag(args, "--since");
275
+ const durationFlag = readFlag(args, "--duration");
276
+ if (!sinceFlag && !durationFlag) {
277
+ fail("One of --since <timestamp|preset> or --duration <duration> is required.\n" +
278
+ "Examples: --since 2026-06-01T00:00:00Z --since 24hours --duration 2hours");
279
+ }
280
+ let sinceValue;
281
+ if (durationFlag) {
282
+ const ms = parseDuration(durationFlag);
283
+ if (ms === null) {
284
+ fail(`Invalid --duration value: "${durationFlag}". Examples: 30m, 2hours, 4h, 1d, 7days`);
285
+ }
286
+ sinceValue = new Date(Date.now() - ms).toISOString();
287
+ }
288
+ else {
289
+ sinceValue = sinceFlag;
290
+ }
291
+ const result = await client.diffIssues(groupId, sinceValue, {
292
+ limit: readFlag(args, "--limit"),
293
+ page: readFlag(args, "--page"),
294
+ });
295
+ if (ctx.json) {
296
+ printJson(result);
297
+ return;
298
+ }
299
+ process.stdout.write(`DIFF SINCE ${result.since}\n`);
300
+ const rows = result.issues.map((issue) => [
301
+ issue.id,
302
+ issue.slug || "-",
303
+ issue.severity || "-",
304
+ issue.status || "-",
305
+ issue.title || "-",
306
+ formatDate(issue.last_seen_at || issue.lastSeenAt),
307
+ ]);
308
+ printTable(["ID", "SLUG", "SEVERITY", "STATUS", "TITLE", "LAST SEEN"], rows);
309
+ if (!ctx.quiet) {
310
+ process.stdout.write(`${result.total} issue${result.total === 1 ? "" : "s"} changed since ${result.since}\n`);
311
+ }
312
+ return;
313
+ }
314
+ fail("Usage: corelayer issues <list|get|close|bulk-close|reopen|delete|summary|diff> [options]");
243
315
  }
@@ -0,0 +1,137 @@
1
+ import { execSync } from "node:child_process";
2
+ import { CorelayerClient } from "../lib/api-client.js";
3
+ import { resolveToken, resolveApiUrl } from "../lib/config.js";
4
+ import { fail, printJson } from "../lib/output.js";
5
+ const MAX_DIFF_CHARS = 12_000;
6
+ function printHelp() {
7
+ process.stdout.write(`corelayer preflight - Check a change against org memory before you push
8
+
9
+ Surfaces failure modes and things to double-check that your org has hit before for
10
+ changes like this one. Run inside a git repo with no arguments and it derives the PR
11
+ context from your branch; override any piece with the flags below.
12
+
13
+ USAGE:
14
+ corelayer preflight [options]
15
+
16
+ OPTIONS:
17
+ --title <text> PR title (default: latest commit subject / branch name)
18
+ --description <text> PR description (default: commits since base + changed files)
19
+ --repo <name> Repository name (default: parsed from the git remote)
20
+ --base <ref> Base branch to diff against (default: origin/HEAD, else main)
21
+ --diff Include the (truncated) code diff in what's sent
22
+ --json Output as JSON
23
+ -h, --help Print help
24
+
25
+ EXAMPLES:
26
+ corelayer preflight
27
+ corelayer preflight --diff
28
+ corelayer preflight --description "Add Redis cache to the payments worker" --repo payments-api
29
+ `);
30
+ }
31
+ function readFlag(args, flag) {
32
+ const index = args.indexOf(flag);
33
+ if (index === -1)
34
+ return undefined;
35
+ return args[index + 1];
36
+ }
37
+ function git(args) {
38
+ try {
39
+ return execSync(`git ${args}`, {
40
+ encoding: "utf8",
41
+ stdio: ["ignore", "pipe", "ignore"],
42
+ }).trim();
43
+ }
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ function detectRepo() {
49
+ const url = git("config --get remote.origin.url");
50
+ if (!url)
51
+ return undefined;
52
+ const match = url.replace(/\.git$/, "").match(/([^/:]+)$/);
53
+ return match ? match[1] : undefined;
54
+ }
55
+ function detectBase() {
56
+ const head = git("rev-parse --abbrev-ref origin/HEAD"); // e.g. origin/main
57
+ if (head && head.startsWith("origin/"))
58
+ return head.slice("origin/".length);
59
+ for (const candidate of ["main", "master"]) {
60
+ if (git(`rev-parse --verify ${candidate}`))
61
+ return candidate;
62
+ }
63
+ return "main";
64
+ }
65
+ function severityColor(sev, noColor) {
66
+ const label = (sev || "n/a").toUpperCase();
67
+ if (noColor)
68
+ return label;
69
+ const code = sev === "critical" || sev === "high"
70
+ ? "31" // red
71
+ : sev === "medium"
72
+ ? "33" // yellow
73
+ : "90"; // grey
74
+ return `\x1b[${code}m${label}\x1b[0m`;
75
+ }
76
+ function printChecks(checks, noColor) {
77
+ checks.forEach((check, i) => {
78
+ process.stdout.write(`${i + 1}. ${check.advice}\n`);
79
+ const seen = check.occurrenceCount > 1 ? ` · seen ${check.occurrenceCount}×` : "";
80
+ const where = check.scope === "resource" ? check.anchorLabel : check.scope;
81
+ process.stdout.write(` ${severityColor(check.severity, noColor)} · "${check.title}" · ${where}${seen}\n`);
82
+ });
83
+ }
84
+ export async function runPreflight(args, ctx) {
85
+ if (args.includes("--help") || args.includes("-h")) {
86
+ printHelp();
87
+ return;
88
+ }
89
+ const base = readFlag(args, "--base") || detectBase();
90
+ const commits = git(`log ${base}..HEAD --pretty=format:%s%n%b`) || "";
91
+ const files = git(`diff --name-only ${base}...HEAD`) || "";
92
+ const branch = git("rev-parse --abbrev-ref HEAD");
93
+ const repo = readFlag(args, "--repo") || detectRepo();
94
+ const title = readFlag(args, "--title") || commits.split("\n")[0]?.trim() || branch;
95
+ let description = readFlag(args, "--description");
96
+ if (!description) {
97
+ const parts = [];
98
+ if (commits.trim())
99
+ parts.push(commits.trim());
100
+ if (files.trim())
101
+ parts.push(`Changed files:\n${files.trim()}`);
102
+ description = parts.join("\n\n");
103
+ }
104
+ if (args.includes("--diff")) {
105
+ const diff = git(`diff ${base}...HEAD`);
106
+ if (diff)
107
+ description += `\n\nDiff:\n${diff.slice(0, MAX_DIFF_CHARS)}`;
108
+ }
109
+ if (!description.trim()) {
110
+ fail(`No PR context found. Run inside a git repo with commits ahead of "${base}", or pass --description.`);
111
+ }
112
+ const token = resolveToken();
113
+ if (!token) {
114
+ fail("Not authenticated. Set CORELAYER_API_KEY or run: corelayer login");
115
+ }
116
+ const apiUrl = resolveApiUrl(ctx.apiUrlOverride);
117
+ const client = new CorelayerClient(apiUrl, token);
118
+ const result = await client.preflight({
119
+ description,
120
+ title: title || undefined,
121
+ repo,
122
+ });
123
+ if (ctx.json) {
124
+ printJson(result);
125
+ return;
126
+ }
127
+ if (result.checks.length === 0) {
128
+ if (!ctx.quiet) {
129
+ process.stdout.write("✓ Preflight: nothing in org memory to double-check for this change.\n");
130
+ }
131
+ return;
132
+ }
133
+ if (!ctx.quiet) {
134
+ process.stdout.write(`Preflight — ${result.checks.length} thing(s) to double-check before you push:\n\n`);
135
+ }
136
+ printChecks(result.checks, ctx.noColor);
137
+ }
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { runIssues } from "./commands/issues.js";
8
8
  import { runInstallSkill, runUninstallSkill, } from "./commands/install-skill.js";
9
9
  import { runLogin } from "./commands/login.js";
10
10
  import { runLogout } from "./commands/logout.js";
11
+ import { runPreflight } from "./commands/preflight.js";
11
12
  const require = createRequire(import.meta.url);
12
13
  const { version } = require("../package.json");
13
14
  function printHelp() {
@@ -23,6 +24,7 @@ COMMANDS:
23
24
  api-keys Create Corelayer API keys
24
25
  groups List and inspect groups
25
26
  issues List, inspect, and manage issues
27
+ preflight Check a change against org memory before you push
26
28
  integrations List integrations for a group
27
29
  config Read and write local CLI config
28
30
  install-skill Install the Corelayer agent skill
@@ -119,6 +121,9 @@ async function main() {
119
121
  case "integrations":
120
122
  await runIntegrations(rest, ctx);
121
123
  return;
124
+ case "preflight":
125
+ await runPreflight(rest, ctx);
126
+ return;
122
127
  case "config":
123
128
  await runConfig(rest, ctx);
124
129
  return;
@@ -64,6 +64,9 @@ export class CorelayerClient {
64
64
  async getIssue(issueId) {
65
65
  return this.request("GET", `/api/v1/issues/${issueId}`);
66
66
  }
67
+ async preflight(params) {
68
+ return this.request("POST", "/api/v1/preflight", params);
69
+ }
67
70
  async closeIssue(issueId, feedback) {
68
71
  await this.request("PATCH", `/api/v1/issues/${issueId}`, {
69
72
  status: "Closed",
@@ -94,6 +97,15 @@ export class CorelayerClient {
94
97
  async deleteIssue(issueId) {
95
98
  await this.request("DELETE", `/api/v1/issues/${issueId}`);
96
99
  }
100
+ async diffIssues(groupId, since, options = {}) {
101
+ const params = new URLSearchParams({ groupId, since });
102
+ for (const [key, value] of Object.entries(options)) {
103
+ if (value !== undefined && value !== null && value !== "") {
104
+ params.set(key, String(value));
105
+ }
106
+ }
107
+ return this.request("GET", `/api/v1/issues/diff?${params.toString()}`);
108
+ }
97
109
  async getIssueSummary(groupId) {
98
110
  const params = new URLSearchParams({ groupId });
99
111
  return this.request("GET", `/api/v1/issues/summary?${params.toString()}`);
@@ -130,13 +142,13 @@ export class CorelayerClient {
130
142
  const suffix = params.toString() ? `?${params.toString()}` : "";
131
143
  const raw = await this.request("GET", `/api/v1/integrations/accounts${suffix}`);
132
144
  return {
133
- accounts: (raw.accounts ?? []).map(account => ({
145
+ accounts: (raw.accounts ?? []).map((account) => ({
134
146
  id: account.id,
135
147
  group_id: account.group_id,
136
148
  provider_code: account.provider_code,
137
149
  is_active: account.is_active,
138
150
  created_at: account.created_at,
139
- resources: (account.resources ?? []).map(resource => ({
151
+ resources: (account.resources ?? []).map((resource) => ({
140
152
  id: resource.id,
141
153
  resource_type: resource.resource_type,
142
154
  external_id: resource.external_id,
@@ -144,7 +156,7 @@ export class CorelayerClient {
144
156
  is_active: resource.is_active,
145
157
  })),
146
158
  })),
147
- providers: (raw.providers ?? []).map(provider => ({
159
+ providers: (raw.providers ?? []).map((provider) => ({
148
160
  provider_code: provider.provider_code,
149
161
  display_name: provider.display_name,
150
162
  })),
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@corelayer-ai/cli",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Corelayer CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
- "corelayer": "./dist/index.js"
7
+ "corelayer": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
package/skill/SKILL.md CHANGED
@@ -5,7 +5,9 @@ description: >
5
5
  error analysis, root cause analysis, issue triage, Corelayer, or wants to
6
6
  check/manage/close/reopen production issues. Also use when the user says
7
7
  "check prod", "what's broken", "any errors", "issue summary", or references
8
- the corelayer CLI.
8
+ the corelayer CLI. Also use before opening or pushing a pull request, or when
9
+ the user asks what could break, what to watch out for, or for a preflight /
10
+ pre-merge check on a change.
9
11
  user-invocable: true
10
12
  ---
11
13
 
@@ -37,12 +39,35 @@ corelayer issues list --status open
37
39
  ### Inspect a specific issue
38
40
 
39
41
  ```bash
40
- corelayer issues get <issueId>
41
- corelayer issues get <issueId> --json
42
+ corelayer issues get <issueId|slug>
43
+ corelayer issues get <issueId|slug> --json
42
44
  ```
43
45
 
44
46
  Returns: title, status, severity, event count, what happened, root cause, and next steps.
45
47
 
48
+ ### Diff issues since a timestamp (post-deploy triage)
49
+
50
+ ```bash
51
+ corelayer issues diff --group <groupId> --since 2026-06-01T00:00:00Z
52
+ corelayer issues diff --group <groupId> --since 24hours
53
+ corelayer issues diff --group <groupId> --duration 2hours --limit 50
54
+ corelayer issues diff --group <groupId> --duration 30m --json
55
+ ```
56
+
57
+ Returns issues that are new or changed since the given timestamp. Useful after PR merges/deploys to identify regressions or newly surfaced issues.
58
+
59
+ ### Preflight a change before you push
60
+
61
+ ```bash
62
+ corelayer preflight # derives PR context from the current git branch
63
+ corelayer preflight --diff # include the code diff in the check
64
+ corelayer preflight --json
65
+ ```
66
+
67
+ Returns failure modes and things to double-check that the org has hit before for a change
68
+ like this; empty means memory has nothing to flag. See [references/preflight.md](references/preflight.md)
69
+ for flags, how to read the output, and the `corelayer.preflight` MCP tool.
70
+
46
71
  ### Triage issues
47
72
 
48
73
  ```bash
@@ -84,7 +109,7 @@ Setting a default group avoids repeating `--group` on every command.
84
109
  List issues for a group. Requires `--group` or a configured default group.
85
110
  Filterable by status (`open`, `closed`) and severity (`critical`, `high`, `medium`, `low`).
86
111
 
87
- ### `corelayer issues get <issueId>`
112
+ ### `corelayer issues get <issueId|slug>`
88
113
 
89
114
  Get full details for an issue including AI-generated analysis: what happened, root cause, and next steps.
90
115
 
@@ -92,6 +117,18 @@ Get full details for an issue including AI-generated analysis: what happened, ro
92
117
 
93
118
  Returns a JSON severity breakdown for the group.
94
119
 
120
+ ### `corelayer issues diff --group <id> (--since <timestamp|preset> | --duration <duration>) [--limit <n>] [--page <n>]`
121
+
122
+ Returns issues that are new or updated since the given point in time. Useful for post-deploy triage — run after a PR merge to see what regressions or new errors have surfaced.
123
+
124
+ `--since` accepts an ISO 8601 timestamp (`2026-06-01T00:00:00Z`) or a preset: `1hour`, `24hours`, `7days`, `14days`, `30days`.
125
+
126
+ `--duration` accepts human-friendly durations: `30m`, `2hours`, `4h`, `1d`, `7days`. The duration is subtracted from the current time to compute the since-timestamp.
127
+
128
+ `--limit` and `--page` paginate results. The API defaults to 20 results and caps `--limit` at 100.
129
+
130
+ Use `--json` for machine-readable output suitable for agent automation.
131
+
95
132
  ### `corelayer issues close <issueId> [--feedback "..."]`
96
133
 
97
134
  Close an issue. Optionally provide feedback (e.g., link to the fix).
@@ -108,6 +145,10 @@ Reopen a previously closed issue.
108
145
 
109
146
  Soft-delete an issue. Prompts for confirmation unless `--yes` is passed.
110
147
 
148
+ ### `corelayer preflight [--title <t>] [--description <d>] [--repo <name>] [--base <ref>] [--diff]`
149
+
150
+ Check a pending change against the org's accumulated memory before opening a PR. With no flags it derives repo/title/description from the current git branch (commits + changed files since the base branch); any flag overrides its piece, and `--diff` includes the patch. Returns grounded, second-person reminders for this change, or nothing when no relevant lesson exists. Full guidance in [references/preflight.md](references/preflight.md).
151
+
111
152
  ### `corelayer groups list`
112
153
 
113
154
  List all groups in the organization.
@@ -139,9 +180,19 @@ Clear stored credentials.
139
180
  ```bash
140
181
  corelayer issues summary
141
182
  corelayer issues list --severity critical
142
- corelayer issues get <id> --json
183
+ corelayer issues get <issueId|slug> --json
143
184
  ```
144
185
 
186
+ ### Before you open a pull request
187
+
188
+ ```bash
189
+ corelayer preflight
190
+ ```
191
+
192
+ Run it once the change is ready, before pushing. Surface any returned checks to the user as
193
+ reminders to double-check (they're advisory, not blockers); if it returns nothing, say memory
194
+ had nothing to flag. See [references/preflight.md](references/preflight.md).
195
+
145
196
  ### Scripting and automation
146
197
 
147
198
  Use `--json` and `--quiet` flags for machine-readable output:
@@ -0,0 +1,71 @@
1
+ # Preflight — check a change against org memory before you push
2
+
3
+ Preflight asks Corelayer's organizational brain: *"for a change like this, what has
4
+ bitten this org before?"* It returns specific, second-person reminders grounded in past
5
+ incidents, facts, decisions, and standing preferences for the services the change touches —
6
+ e.g. an env-var flag that's broken prod deploys before, a packaging step a service has missed,
7
+ a config that needs to be set in every environment. It returns nothing when no relevant lesson
8
+ exists, so an empty result is a meaningful "all clear from memory."
9
+
10
+ Run it after the work is done and before opening the PR, as a final review pass.
11
+
12
+ ## CLI
13
+
14
+ Run inside the git repo with no arguments — it derives the PR context from the branch:
15
+
16
+ ```bash
17
+ corelayer preflight
18
+ ```
19
+
20
+ - **repo** ← parsed from the git remote (`origin`)
21
+ - **title** ← the latest commit subject (falls back to the branch name)
22
+ - **description** ← commit messages since the base branch + the list of changed files
23
+ - **base** ← `origin/HEAD` (else `main`/`master`)
24
+
25
+ Override any piece, or include the actual diff for a richer signal:
26
+
27
+ ```bash
28
+ corelayer preflight --diff
29
+ corelayer preflight --base develop
30
+ corelayer preflight --title "Add Redis cache" --description "Caches payment lookups in the worker" --repo payments-api
31
+ corelayer preflight --json # machine-readable, for automation
32
+ ```
33
+
34
+ If there are no commits ahead of the base branch (nothing to describe), pass `--description`
35
+ explicitly.
36
+
37
+ ## Reading the output
38
+
39
+ Each check is one reminder plus its grounding:
40
+
41
+ ```
42
+ 1. Double-check the prod env-var flags — deploys here have failed before when they were missing.
43
+ CRITICAL · "Missing/Incorrect Environment Variables in Deployments" · payments-api · seen 30×
44
+ ```
45
+
46
+ - **severity** — `critical | high | medium | low` of the underlying lesson.
47
+ - **"title"** — the lesson the reminder is grounded in (cite-and-verify; the wording is generated, the lesson is real).
48
+ - **service / scope** — the service the lesson is anchored to, or `system` / `cross-cutting` for org-wide lessons.
49
+ - **seen N×** — how many times the org has hit this; higher = more established.
50
+
51
+ `--json` also returns `consideredCount` (lessons in the org's catalog) and `pulledCount`
52
+ (lessons the judge read in full for this change).
53
+
54
+ ## How to act on it
55
+
56
+ - Treat the checks as **reminders, not blockers** — preflight is advisory and fails open
57
+ (it never blocks a push). Surface them to the user and let them decide.
58
+ - An **empty result is normal and useful** — it means nothing in memory maps to this change.
59
+ Don't manufacture concerns; say memory had nothing to flag.
60
+ - Higher `seen N×` and `critical`/`high` severity deserve the most attention.
61
+
62
+ ## MCP equivalent
63
+
64
+ If the Corelayer MCP server is connected, the same capability is the `corelayer.preflight`
65
+ tool (read-only), taking `{ description, title?, repo? }`. Use it when you already have the
66
+ PR text in context and don't need the CLI's git gathering.
67
+
68
+ ## Notes
69
+
70
+ - Scoped to your organization via the API key — it only sees your org's memory.
71
+ - Read-only; requires authentication (`corelayer login` or `CORELAYER_API_KEY`).