nerdsystem 0.0.9 → 0.0.10

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.
@@ -121,7 +121,7 @@ export async function runChecks(root, opts) {
121
121
  }
122
122
  if (opts.typecheck && problems.every((p) => p.endsWith("(warning)") || p.startsWith("Agent instruction files"))) {
123
123
  log.step("Typechecking");
124
- const code = await run("npx", ["tsc", "-b", "--pretty"], { cwd: root });
124
+ const code = await run("npx", ["tsc", "-b", "--pretty"], { cwd: root, stdoutToStderr: true });
125
125
  if (code !== 0)
126
126
  problems.push("Typecheck failed (see errors above).");
127
127
  }
@@ -144,7 +144,9 @@ export async function check(opts) {
144
144
  }
145
145
  }
146
146
  // Loaded here, not at the top: the gates import staticChecks from this file.
147
- const { runVerify } = await import("./verify.js");
147
+ const { runVerify, assertRuleHasGate } = await import("./verify.js");
148
+ if (opts.rule)
149
+ assertRuleHasGate(opts.rule);
148
150
  const out = await runVerify(root, { withBuild: !opts["no-build"], stopOnFail: false, quiet: opts.json, onlyRules: opts.rule ? [opts.rule] : undefined });
149
151
  const failed = out.results.filter((r) => r.status === "fail").length;
150
152
  if (opts.json)
@@ -65,7 +65,10 @@ async function probeAfterDeploy(deployment, url, skip) {
65
65
  return skippedProbe("--skip-probe was given");
66
66
  if (deployment.status !== "live")
67
67
  return skippedProbe(`the deployment status is ${deployment.status}, not live`);
68
- await waitForDeployment(url);
68
+ // Until the dispatcher knows the new script it answers its own 404 page, which has none of the app's
69
+ // headers or routes; probing that would report false auth failures. Wait, and say so when it never came.
70
+ if (!(await waitForDeployment(url)))
71
+ return skippedProbe("the app had not started answering within 30 seconds; open the URL to check, or run nerd deploy again to probe it");
69
72
  return probeDeployment(url);
70
73
  }
71
74
  function printProbe(url, probes) {
@@ -99,7 +102,7 @@ export async function deploy(opts) {
99
102
  if (!opts["skip-build"]) {
100
103
  log.step("Building with vite");
101
104
  ensureDeps(root);
102
- await runOrThrow("npx", ["vite", "build"], { cwd: root });
105
+ await runOrThrow("npx", ["vite", "build"], { cwd: root, stdoutToStderr: true });
103
106
  }
104
107
  const build = readBuildOutput(root);
105
108
  const built = await runGates(BUILD_GATES, { root, build, knownSecrets, log: (l) => log.info(l) }, { stopOnFail: true });
@@ -127,10 +130,12 @@ export async function deploy(opts) {
127
130
  }
128
131
  const { deployment } = await api.post(projectPath(nerd, "/deployments"), form);
129
132
  const { project } = await api.get(projectPath(nerd));
133
+ log.ok(`Deployed ${deployment.id} (${deployment.status}) → ${project.url}`);
134
+ if (!opts["skip-probe"] && deployment.status === "live")
135
+ log.step(`Probing ${project.url}`);
130
136
  const probes = await probeAfterDeploy(deployment, project.url, Boolean(opts["skip-probe"]));
131
137
  if (opts.json)
132
138
  return printJson({ deployment, url: project.url, migrations: mig, probes });
133
- log.ok(`Deployed ${deployment.id} (${deployment.status}) → ${project.url}`);
134
139
  printProbe(project.url, probes);
135
140
  log.info(`First time? Sign up on your app at ${project.url}, then make yourself admin in the dashboard's Auth tab: ${dashboardUrl(api.apiUrl)}/app/${nerd.org}/projects/${nerd.project}/auth`);
136
141
  }
@@ -174,4 +174,4 @@ export async function init(dir, args) {
174
174
  log.ok(`Ready. Next:\n cd ${dir}\n pnpm install\n nerd dev\n\nThen open the folder in your coding agent and ask it to build the first feature; the nerdsystem skill walks it through.\nDeploys go to ${project.url}`);
175
175
  }
176
176
  // Kept in sync with packages/sdk/package.json by the release script.
177
- const SDK_VERSION = "0.0.9";
177
+ const SDK_VERSION = "0.0.10";
@@ -28,7 +28,7 @@ export async function login(args) {
28
28
  else {
29
29
  log.info(`Open this link in your browser and approve code ${start.userCode}:\n\n ${start.verificationUrl}\n`);
30
30
  if (args.email)
31
- log.info(`An email went to ${args.email}: one click signs you in (creating your account if you are new) and approves.`);
31
+ log.info(`An email went to ${args.email}: the link signs you in (creating your account if you are new) and opens a page where you press Approve.`);
32
32
  log.info("Waiting for approval…");
33
33
  }
34
34
  }
@@ -17,6 +17,8 @@ export declare function runVerify(root: string, opts: {
17
17
  onlyRules?: string[] | undefined;
18
18
  trackAttempts?: boolean;
19
19
  }): Promise<VerifyOutput>;
20
+ /** `--rule smoke/x` would select no gate and report "All 0 gates passed"; say which prefixes a gate owns instead. */
21
+ export declare function assertRuleHasGate(rule: string): void;
20
22
  export declare function verify(opts: {
21
23
  json: boolean;
22
24
  "no-build": boolean;
@@ -35,8 +35,19 @@ export async function runVerify(root, opts) {
35
35
  }
36
36
  return { ok: results.every((r) => r.status !== "fail"), failures, stop, results };
37
37
  }
38
+ /** `--rule smoke/x` would select no gate and report "All 0 gates passed"; say which prefixes a gate owns instead. */
39
+ export function assertRuleHasGate(rule) {
40
+ const prefixes = [...new Set([...SOURCE_GATES, ...BUILD_GATES].flatMap((g) => g.rulePrefixes))];
41
+ if (prefixes.some((p) => rule.startsWith(p)))
42
+ return;
43
+ if (rule.startsWith("smoke/"))
44
+ throw new CliError(`No gate runs ${rule} on the source: the live URL is probed by nerd deploy. Deploy again to re-check it.`, 2);
45
+ throw new CliError(`No gate runs ${rule}. Rule ids start with one of: ${prefixes.join(", ")}`, 2);
46
+ }
38
47
  export async function verify(opts) {
39
48
  const root = findProjectRoot();
49
+ if (opts.rule)
50
+ assertRuleHasGate(opts.rule);
40
51
  const out = await runVerify(root, { withBuild: !opts["no-build"], stopOnFail: false, quiet: opts.json, onlyRules: opts.rule ? [opts.rule] : undefined });
41
52
  if (opts.json)
42
53
  process.stdout.write(`${JSON.stringify({ ok: out.ok, failures: out.failures, stop: out.stop, skipped: skippedGates(out.results) }, null, 2)}\n`);
@@ -73,14 +73,16 @@ export function depsRuleFor(severity) {
73
73
  }
74
74
  /** Known vulnerabilities in dependencies. Critical and high block; moderate warns. Package names come from the registry, not the repo. */
75
75
  /**
76
- * The lockfile for the project: in its root, or in an ancestor up to the repository root (the directory holding .git),
77
- * since a workspace member such as the template inside this monorepo shares the lockfile committed at the top.
76
+ * The lockfile for the project: in its root, or in an ancestor that is itself a package or workspace root
77
+ * (it holds a package.json or pnpm-workspace.yaml), searched up to the repository root (the directory holding
78
+ * .git). A workspace member such as the template inside this monorepo shares the lockfile committed at the top;
79
+ * a stray lockfile in an unrelated ancestor such as the home folder is never adopted.
78
80
  */
79
81
  function findLockfile(start) {
80
82
  let dir = start;
81
83
  for (;;) {
82
84
  const hit = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"].find((f) => existsSync(path.join(dir, f)));
83
- if (hit)
85
+ if (hit && (dir === start || existsSync(path.join(dir, "package.json")) || existsSync(path.join(dir, "pnpm-workspace.yaml"))))
84
86
  return path.join(dir, hit);
85
87
  const parent = path.dirname(dir);
86
88
  if (parent === dir || existsSync(path.join(dir, ".git")))
@@ -101,7 +103,8 @@ export const depsGate = {
101
103
  if (!lock)
102
104
  findings.push(finding("deps/lockfile-required", {}));
103
105
  if (await hasTool("osv-scanner")) {
104
- const r = await exec("osv-scanner", ["scan", "source", "--format", "json", "--lockfile", lockPath ?? path.join(ctx.root, "pnpm-lock.yaml")], ctx.root);
106
+ // Run from the lockfile's own folder with a bare name: on Windows the shell would split an absolute path with a space.
107
+ const r = await exec("osv-scanner", ["scan", "source", "--format", "json", "--lockfile", lock ?? "pnpm-lock.yaml"], lockPath ? path.dirname(lockPath) : ctx.root);
105
108
  try {
106
109
  const json = JSON.parse(r.stdout);
107
110
  for (const res of json.results ?? [])
@@ -14,8 +14,10 @@ function withTimeout(init) {
14
14
  const signal = typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(PROBE_TIMEOUT_MS) : undefined;
15
15
  return { redirect: "manual", ...(signal ? { signal } : {}), ...init };
16
16
  }
17
+ /** No gate owns the smoke/ prefix, so `nerd verify --rule smoke/...` would run nothing; the way to re-check is another deploy. */
18
+ const VERIFY = "nerd deploy (the live URL is probed after every deploy)";
17
19
  function smokeFinding(ruleId, risk, key) {
18
- return finding(ruleId, { risk, fingerprint: fingerprint(ruleId, key) });
20
+ return { ...finding(ruleId, { risk, fingerprint: fingerprint(ruleId, key) }), verify: VERIFY };
19
21
  }
20
22
  function probeResult(findings, startedAt) {
21
23
  const status = verdict(findings);
@@ -28,7 +30,7 @@ function probeResult(findings, startedAt) {
28
30
  * 404 for a script it does not know yet). Returns true as soon as it does; false after the last attempt.
29
31
  */
30
32
  export async function waitForDeployment(url, opts = {}) {
31
- const attempts = opts.attempts ?? 3;
33
+ const attempts = opts.attempts ?? 8;
32
34
  const delayMs = opts.delayMs ?? 2000;
33
35
  const fetchImpl = opts.fetchImpl ?? defaultFetch;
34
36
  for (let i = 0; i < attempts; i++) {
package/dist/util.d.ts CHANGED
@@ -10,22 +10,22 @@ export declare class CliError extends Error {
10
10
  });
11
11
  }
12
12
  /** The one sentence every unauthenticated command prints, with both ways in. */
13
- export declare const NOT_LOGGED_IN = "Not logged in. Run `nerd login --email you@example.com` (one click in the email signs you in and creates your account the first time), or set NERD_TOKEN to an API token for scripts.";
13
+ export declare const NOT_LOGGED_IN = "Not logged in. Run `nerd login --email you@example.com` (the link in the email signs you in, creating your account the first time, and opens a page where you press Approve), or set NERD_TOKEN to an API token for scripts.";
14
14
  export declare const log: {
15
15
  info: (msg: string) => void;
16
16
  step: (msg: string) => void;
17
17
  ok: (msg: string) => void;
18
18
  warn: (msg: string) => void;
19
19
  };
20
- /** Runs a command, streaming output. Resolves with the exit code. */
21
- export declare function run(cmd: string, args: string[], opts?: {
22
- cwd?: string;
23
- env?: NodeJS.ProcessEnv;
24
- }): Promise<number>;
25
- export declare function runOrThrow(cmd: string, args: string[], opts?: {
20
+ export interface RunOptions {
26
21
  cwd?: string;
27
22
  env?: NodeJS.ProcessEnv;
28
- }): Promise<void>;
23
+ /** Send the child's stdout to our stderr, so stdout stays reserved for `--json` output. */
24
+ stdoutToStderr?: boolean;
25
+ }
26
+ /** Runs a command, streaming output. Resolves with the exit code. */
27
+ export declare function run(cmd: string, args: string[], opts?: RunOptions): Promise<number>;
28
+ export declare function runOrThrow(cmd: string, args: string[], opts?: RunOptions): Promise<void>;
29
29
  export declare function printJson(value: unknown): void;
30
30
  export declare function table(rows: Record<string, unknown>[], columns: string[]): void;
31
31
  /** The dashboard that goes with an API origin (https://api.nerdsystem.com -> https://nerdsystem.com). */
package/dist/util.js CHANGED
@@ -15,7 +15,7 @@ export class CliError extends Error {
15
15
  }
16
16
  }
17
17
  /** The one sentence every unauthenticated command prints, with both ways in. */
18
- export const NOT_LOGGED_IN = "Not logged in. Run `nerd login --email you@example.com` (one click in the email signs you in and creates your account the first time), or set NERD_TOKEN to an API token for scripts.";
18
+ export const NOT_LOGGED_IN = "Not logged in. Run `nerd login --email you@example.com` (the link in the email signs you in, creating your account the first time, and opens a page where you press Approve), or set NERD_TOKEN to an API token for scripts.";
19
19
  export const log = {
20
20
  info: (msg) => console.error(`${msg}`),
21
21
  step: (msg) => console.error(`\x1b[36m›\x1b[0m ${msg}`),
@@ -25,7 +25,8 @@ export const log = {
25
25
  /** Runs a command, streaming output. Resolves with the exit code. */
26
26
  export function run(cmd, args, opts = {}) {
27
27
  return new Promise((resolve, reject) => {
28
- const child = spawn(cmd, args, { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: "inherit", shell: process.platform === "win32" });
28
+ const child = spawn(cmd, args, { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["inherit", opts.stdoutToStderr ? "pipe" : "inherit", "inherit"], shell: process.platform === "win32" });
29
+ child.stdout?.pipe(process.stderr);
29
30
  child.on("error", reject);
30
31
  child.on("exit", (code) => resolve(code ?? 1));
31
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nerdsystem",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "nerd: start, check, and deploy a web app on nerdsystem from the terminal or a coding agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,14 +36,14 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
36
36
  1. Make the change.
37
37
  2. `nerd check` (static rules, then typecheck). Fix anything it reports.
38
38
  3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
39
- 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, and prints the URL.
39
+ 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, prints the URL, and probes the live URL (headers and the sign-in routes). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
40
40
  5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
41
41
 
42
42
  Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
43
43
 
44
44
  ## Deploying from GitHub
45
45
 
46
- Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically (for accounts younger than seven days, counted from the nerdsystem account's creation, git builds are deferred: deploy with `nerd deploy` from the machine until then), and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
46
+ Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
47
47
 
48
48
  ## Where things go
49
49
 
@@ -2,7 +2,7 @@
2
2
  description: How to build, run, and deploy this app on nerdsystem. Applies to server, database, auth, and deploy work.
3
3
  alwaysApply: true
4
4
  ---
5
- <!-- generated from .claude/skills/nerdsystem/SKILL.md (f8685cd08ad1); edit the skill, then run: nerd agents sync -->
5
+ <!-- generated from .claude/skills/nerdsystem/SKILL.md (10dcd79a44bd); edit the skill, then run: nerd agents sync -->
6
6
 
7
7
  # Building on nerdsystem
8
8
 
@@ -37,14 +37,14 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
37
37
  1. Make the change.
38
38
  2. `nerd check` (static rules, then typecheck). Fix anything it reports.
39
39
  3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
40
- 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, and prints the URL.
40
+ 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, prints the URL, and probes the live URL (headers and the sign-in routes). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
41
41
  5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
42
42
 
43
43
  Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
44
44
 
45
45
  ## Deploying from GitHub
46
46
 
47
- Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically (for accounts younger than seven days, counted from the nerdsystem account's creation, git builds are deferred: deploy with `nerd deploy` from the machine until then), and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
47
+ Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
48
48
 
49
49
  ## Where things go
50
50
 
@@ -1,4 +1,4 @@
1
- <!-- generated from .claude/skills/nerdsystem/SKILL.md (f8685cd08ad1); edit the skill, then run: nerd agents sync -->
1
+ <!-- generated from .claude/skills/nerdsystem/SKILL.md (10dcd79a44bd); edit the skill, then run: nerd agents sync -->
2
2
 
3
3
  # Building on nerdsystem
4
4
 
@@ -33,14 +33,14 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
33
33
  1. Make the change.
34
34
  2. `nerd check` (static rules, then typecheck). Fix anything it reports.
35
35
  3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
36
- 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, and prints the URL.
36
+ 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, prints the URL, and probes the live URL (headers and the sign-in routes). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
37
37
  5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
38
38
 
39
39
  Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
40
40
 
41
41
  ## Deploying from GitHub
42
42
 
43
- Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically (for accounts younger than seven days, counted from the nerdsystem account's creation, git builds are deferred: deploy with `nerd deploy` from the machine until then), and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
43
+ Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
44
44
 
45
45
  ## Where things go
46
46
 
@@ -1,4 +1,4 @@
1
- <!-- generated from .claude/skills/nerdsystem/SKILL.md (f8685cd08ad1); edit the skill, then run: nerd agents sync -->
1
+ <!-- generated from .claude/skills/nerdsystem/SKILL.md (10dcd79a44bd); edit the skill, then run: nerd agents sync -->
2
2
 
3
3
  # Building on nerdsystem
4
4
 
@@ -33,14 +33,14 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
33
33
  1. Make the change.
34
34
  2. `nerd check` (static rules, then typecheck). Fix anything it reports.
35
35
  3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
36
- 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, and prints the URL.
36
+ 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, prints the URL, and probes the live URL (headers and the sign-in routes). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
37
37
  5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
38
38
 
39
39
  Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
40
40
 
41
41
  ## Deploying from GitHub
42
42
 
43
- Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically (for accounts younger than seven days, counted from the nerdsystem account's creation, git builds are deferred: deploy with `nerd deploy` from the machine until then), and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
43
+ Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
44
44
 
45
45
  ## Where things go
46
46
 
@@ -1,4 +1,4 @@
1
- <!-- generated from .claude/skills/nerdsystem/SKILL.md (f8685cd08ad1); edit the skill, then run: nerd agents sync -->
1
+ <!-- generated from .claude/skills/nerdsystem/SKILL.md (10dcd79a44bd); edit the skill, then run: nerd agents sync -->
2
2
 
3
3
  # Building on nerdsystem
4
4
 
@@ -33,14 +33,14 @@ Tell the person plainly what does not carry. Their users sign up again on the ne
33
33
  1. Make the change.
34
34
  2. `nerd check` (static rules, then typecheck). Fix anything it reports.
35
35
  3. `nerd dev` to run locally when you need to try it. The local app talks to the real auth service, so signup and login work exactly as in production.
36
- 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, and prints the URL.
36
+ 4. `nerd deploy` when it is ready. It runs the checks, builds, applies pending migrations, uploads, prints the URL, and probes the live URL (headers and the sign-in routes). With `--json` the output carries `probes`, the same shape as a check result, with `status: "skip"` and the reason when it did not run; probe findings never fail the deploy.
37
37
  5. If something is wrong after deploying: `nerd logs` shows recent output from the deployed app (`--q` to search, `--pr 12` for a preview), `nerd deployments` shows status and errors, `nerd db query "..."` inspects production data.
38
38
 
39
39
  Run `nerd check --json` when you want a machine-readable list of problems. Its summary says how many gates ran and which were skipped (`skipped` in the JSON): semgrep and gitleaks only run locally when installed; git builds always run them in the builder.
40
40
 
41
41
  ## Deploying from GitHub
42
42
 
43
- Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically (for accounts younger than seven days, counted from the nerdsystem account's creation, git builds are deferred: deploy with `nerd deploy` from the machine until then), and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
43
+ Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
44
44
 
45
45
  ## Where things go
46
46