@corelayer-ai/cli 0.4.5 → 0.5.1

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
  }
@@ -60,7 +60,7 @@ SUBCOMMANDS:
60
60
  get <id|slug> Show details for one issue
61
61
  summary Summarize issue health for a group
62
62
  diff Show issues new or changed since a timestamp
63
- close <id> Close an issue (write operation)
63
+ close <id|slug> Close an issue (write operation)
64
64
  bulk-close Close many issues in one request (write operation)
65
65
  reopen <id> Reopen a closed issue (write operation)
66
66
  delete <id> Delete an issue (write operation)
@@ -90,7 +90,7 @@ EXAMPLES:
90
90
  corelayer issues diff --group g_123 --since 2026-06-01T00:00:00Z
91
91
  corelayer issues diff --group g_123 --since 24hours
92
92
  corelayer issues diff --group g_123 --duration 2hours --limit 50
93
- corelayer issues close i_456 --feedback "fixed in deploy 2026-04-07"
93
+ corelayer issues close corelayer-prod-engine-20260602-1 --feedback "fixed in deploy 2026-04-07"
94
94
  corelayer issues bulk-close --group g_123 --last-seen-before-or-on 14days --feedback "Not seen in two weeks"
95
95
  corelayer issues delete i_456 --yes
96
96
  `);
@@ -183,14 +183,18 @@ export async function runIssues(args, ctx) {
183
183
  return;
184
184
  }
185
185
  if (sub === "close") {
186
- const issueId = commandArgs[0];
187
- if (!issueId) {
188
- fail('Usage: corelayer issues close <issueId> [--feedback "..."]');
186
+ const issueIdentifier = commandArgs[0];
187
+ if (!issueIdentifier) {
188
+ fail('Usage: corelayer issues close <issueId|slug> [--feedback "..."]');
189
189
  }
190
190
  const feedback = readFlag(args, "--feedback");
191
- await client.closeIssue(issueId, feedback);
191
+ const issue = await client.getIssue(issueIdentifier);
192
+ if (!issue.id) {
193
+ fail("Could not resolve issue identifier to an issue ID.");
194
+ }
195
+ await client.closeIssue(issue.id, feedback);
192
196
  if (!ctx.quiet) {
193
- process.stdout.write(`Issue ${issueId} closed.\n`);
197
+ process.stdout.write(`Issue ${issue.slug || issue.id} closed.\n`);
194
198
  }
195
199
  return;
196
200
  }
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corelayer-ai/cli",
3
- "version": "0.4.5",
3
+ "version": "0.5.1",
4
4
  "description": "Corelayer CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "dependencies": {},
22
22
  "devDependencies": {
23
23
  "@types/node": "^20.17.57",
24
- "tsx": "^4.19.4",
24
+ "tsx": "^4.22.4",
25
25
  "typescript": "^5.8.3"
26
26
  }
27
27
  }
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
 
@@ -54,10 +56,22 @@ corelayer issues diff --group <groupId> --duration 30m --json
54
56
 
55
57
  Returns issues that are new or changed since the given timestamp. Useful after PR merges/deploys to identify regressions or newly surfaced issues.
56
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
+
57
71
  ### Triage issues
58
72
 
59
73
  ```bash
60
- corelayer issues close <issueId> --feedback "Fixed in PR #123"
74
+ corelayer issues close <issueId|slug> --feedback "Fixed in PR #123"
61
75
  corelayer issues bulk-close --group <groupId> --last-seen-before-or-on 14days --feedback "Not seen in two weeks"
62
76
  corelayer issues reopen <issueId>
63
77
  corelayer issues delete <issueId> --yes
@@ -115,7 +129,7 @@ Returns issues that are new or updated since the given point in time. Useful for
115
129
 
116
130
  Use `--json` for machine-readable output suitable for agent automation.
117
131
 
118
- ### `corelayer issues close <issueId> [--feedback "..."]`
132
+ ### `corelayer issues close <issueId|slug> [--feedback "..."]`
119
133
 
120
134
  Close an issue. Optionally provide feedback (e.g., link to the fix).
121
135
 
@@ -131,6 +145,10 @@ Reopen a previously closed issue.
131
145
 
132
146
  Soft-delete an issue. Prompts for confirmation unless `--yes` is passed.
133
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
+
134
152
  ### `corelayer groups list`
135
153
 
136
154
  List all groups in the organization.
@@ -165,6 +183,16 @@ corelayer issues list --severity critical
165
183
  corelayer issues get <issueId|slug> --json
166
184
  ```
167
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
+
168
196
  ### Scripting and automation
169
197
 
170
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`).