import { type ScaffoldInputs } from "./index.js"; /** The Neon `projects` table the inventory rows live in. */ export declare const INVENTORY_TABLE = "projects"; /** The conflict key — the `projects/{slug}.md` filename the cron keys on. */ export declare const INVENTORY_CONFLICT_KEY = "slug"; /** * The projects holding `ecosystem_role = 'ecosystem'`. Kerry ruled and CLOSED * this set at 8 on 2026-08-02. Mirrored here from the registry's * `projects_ecosystem_set_closed_chk` so this CLI can assert it never scaffolds * an unlisted member; the database is the enforcement point, this is the local * copy. **Both must move together** — see `ecosystemRoleFor` for why a stale * copy here silently DEMOTES a real `ecosystem` row rather than erroring. * * An `ecosystem` project's subject matter IS the other projects — it has no * purpose outside the portfolio. Everything else, including the shared * capability engines Kerry explicitly ruled on (`z2w-ai-engine`, * `crawl-engine`, `backup-engine`, `dashboard-engine`), is `domain`. * * **`z2w-templates` added 2026-09-08 as the 9th, and that is an APPLICATION of * Kerry's rule rather than a reopening of his ruling.** Its registry row (id * 104) was not created until 2026-08-29, so it could not have been classified * when he closed the set three weeks earlier — it was the only one of 100 rows * left `NULL`. It holds the canonical AGENTS/Resources bodies that every other * project is scaffolded FROM, which is the criterion above stated almost * literally, and its authoring sibling `cursor-project-templates` was already * in the set. Reversible in one `UPDATE` plus this line if Kerry disagrees. */ export declare const ECOSYSTEM_PROJECTS: readonly string[]; /** * The `ecosystem_role` a given slug must hold, derived from the closed set. * * This is deliberately a LOOKUP and not the constant `"domain"`, because * `buildUpsert` emits `ON CONFLICT (slug) DO UPDATE SET ecosystem_role = * EXCLUDED.ecosystem_role`. A flat `"domain"` would be correct for every new * project and silently WRONG on any re-registration of one of the 8 — it would * demote a real `ecosystem` row, and the database CHECK could not catch it, * because `domain` is a legal value for every slug. The constraint guards * against wrongly ADDING a 9th member; only this lookup guards against wrongly * REMOVING one. */ export declare function ecosystemRoleFor(slug: string): string; /** * Columns this CLI may SEED on a brand-new row but must never OVERWRITE on an * existing one — excluded from the UPSERT's `DO UPDATE SET` list. * * This closes a hole in the principle stated at the top of `buildProjectRow`: * *"manually-curated columns are intentionally omitted so the UPSERT's DO UPDATE * never clobbers Kerry's hand-entered values."* That was enforced by leaving a * column out of the row entirely — which works for columns the CLI never knows * (`technology`, `licensing`, `notes`), but NOT for `project_name`, which the * CLI both derives and Kerry curates. * * Measured 2026-08-03 against all 96 registry rows: a re-registration would have * rewritten **43 of them (45%)**, and most of those names could not be derived * from a slug by any algorithm — `Femperium Web Scraping / Lead Gen`, * `Financial | Investing`, `Static Sites For Small Businesses`, * `Docker for Z2W Multi-Lingual`, `Ente Photos & Locker`, * `Grantor (off-WP rebuild)`. Those are editorial, not mechanical. A slug-derived * guess is the right SEED for a new project and the wrong answer for every * curated one, so the fix is about WHEN the column is written, not how well it * is computed. * * `audit-engine` reported this on 2026-07-31 as two bad rows * (`org-hq.project_name` = "Org Hq", plus a `videomigrator-engine.github_url`). * Both rows were repaired by hand before this session; the EMITTER was not, so * the next re-registration would have reintroduced one of them and 42 others. */ export declare const INSERT_ONLY_COLUMNS: readonly string[]; export interface InventoryConfig { /** Postgres connection string (Neon pooled "Connection String"). */ databaseUrl: string; } /** * Resolve the Neon inventory config from the environment. Returns null when no * Z2W_INVENTORY_DATABASE_URL is set — the signal that the Neon write is disabled * for this caller (every CLI user who isn't Kerry). Mirrors * `resolveAirtableConfig`'s no-op gate exactly. */ export declare function resolveInventoryConfig(env?: NodeJS.ProcessEnv): InventoryConfig | null; /** A single column's value. The `projects` columns we write are text or boolean. */ export type ProjectRowValue = string | boolean; /** A row's CLI-owned columns, keyed by their snake_case Neon column name. */ export type ProjectRow = Record; export interface BuildProjectRowOptions { /** * Whether the freshly-written scaffold was confirmed to carry the * coordination block (see `scaffoldCarriesCoordinationBlock`). Same semantics * as the Airtable mapper: a boolean gates the `agent_coordination = Registered` * assertion ("verify, don't assert"); `undefined` (could not verify — dry-run * or a direct unit-test call) falls back to the `participateInBulletin` flag. */ coordinationVerified?: boolean; } /** * Pure mapping from ScaffoldInputs to the Neon `projects` row. Mirrors the * decisions in src/airtable.ts `buildProjectFields`, mapped to the FINAL 23-column * snake_case schema (column audit executed 2026-06-22). Only columns the CLI can * auto-fill at scaffold time are written — manually-curated columns (technology, * licensing, notes, research_url, …) are intentionally omitted so the UPSERT's * DO UPDATE never clobbers Kerry's hand-entered values. * * Difference from the Airtable mapper: the scaffold type IS written here (the Neon * column is plain text and the CLI knows the value, so it earns its place per the * column-audit principle "a column earns its place only if the CLI can auto-fill * it"). The Airtable mapper omitted it only because that base's Project Type is a * single-select whose option names could 422 on first use. `skill_vault_aware` is * a real boolean here (Neon column type), not the Airtable "Yes" string. * * 🛑 **`project_type` is SUPERSEDED as of 2026-08-02 and this mapper no longer * writes it.** It had been carrying three questions in one column: an audience * label a human typed into Airtable years ago, a stack slug THIS function * emitted, and 20 NULLs — so `GROUP BY project_type` returned a clean-looking * result that meant nothing. This CLI was the source of the second vocabulary, * which makes the split ours to perform. Kerry ruled the field "out of date, * needs a review and/or should be replaced" (2026-07-30) and ratified the * replacement on 2026-08-02: `stack` (how it is built — this enum) and * `audience` (who it is for — internal | client | personal). * * `audience` is deliberately NOT written here. The CLI cannot know it: every * value is a judgement about who the project serves, and a scaffolder guessing * `internal` for every new repo would recreate exactly the unvalidated * categorisation Kerry just retired. It stays a human/agent field. * * `ecosystem_role` IS written, and the contrast with `audience` is the whole * argument for it. It looks like the same kind of judgement call, but it is not * one, because **Kerry CLOSED the ecosystem set at 8 on 2026-08-02** (his list * matched `audit-engine`'s three proposals exactly; all 9 abstains cleared to * `domain`; the shared-capability class — `z2w-ai-engine`, `crawl-engine`, * `backup-engine`, `dashboard-engine` — upheld as `domain`). A closed set means * every row outside it is `domain` **by construction**, so a scaffolder writing * `domain` is not guessing — it is applying the only value a new row can hold. * A 9th `ecosystem` project is a REOPENING that needs Kerry, not a * classification, which is exactly why this is safe to automate and `audience` * is not. * * Enforced in the database rather than trusted here: the registry carries * `projects_ecosystem_set_closed_chk`, which rejects `ecosystem` on any slug * outside the 8. If this constant ever drifts to `ecosystem`, the write fails * loudly instead of quietly seating a 9th member. (Adopted from `audit-engine`'s * 2026-08-02 ACTION — *"the set being CLOSED is the part to encode"* — whose own * `assert_ecosystem_set_is_closed()` guards its Python; the CHECK is the durable * version that binds all three registry writers.) */ export declare function buildProjectRow(inputs: ScaffoldInputs, opts?: BuildProjectRowOptions): ProjectRow; /** * A well-formed inventory slug: lowercase letters, digits, and hyphens only. * This mirrors the coordination bulletin's `projects/{slug}.md` filename * convention (the slug IS the filename), and rejects the historical CSV-imported * junk the 2026-07-05 registry data-hygiene sweep found — e.g. `"Skill Vault"` * (space + capitals) or `"Website - VimeoBunny"`. The CLI's input-side * `project_slug` rule is stricter still (`^[a-z][a-z0-9-]{2,49}$`); this is the * looser floor the inventory row must clear before it is written. */ export declare const INVENTORY_SLUG_RE: RegExp; /** * A well-formed GitHub URL: exactly `https://github.com/{owner}/{repo}` with no * trailing slash or path. Rejects the malformed values the hygiene sweep found — * a missing scheme (`github.com/owner/repo`, `owner/repo`) or a trailing slash * (`.../website-aharon/`). Owner/repo allow the GitHub-legal set `[A-Za-z0-9_.-]`. */ export declare const INVENTORY_GITHUB_URL_RE: RegExp; /** * Validate the CLI-owned fields of a would-be inventory row. Returns a list of * human-readable problems (empty = valid). Called before every write so a * malformed `slug` / `github_url` can never be UPSERTed into the registry — the * prevention half of the 2026-07-05 `z2w-agent-coordination` data-hygiene ask. */ export declare function validateProjectRow(row: ProjectRow): string[]; export interface UpsertStatement { /** The parameterized SQL (column names are code-controlled — injection-safe). */ sql: string; /** Positional parameter values, in $1..$n order. */ params: ProjectRowValue[]; /** The columns written, in order — for a secret-free dry-run preview. */ columns: string[]; } /** * Build the INSERT … ON CONFLICT (slug) DO UPDATE statement from a row. Column * names come from `buildProjectRow` (our own code, never user input), so string * interpolation of column names is injection-safe; all VALUES are parameterized. * The DO UPDATE set list excludes `slug` (the conflict key) and touches only the * columns present in `row`, so columns the CLI doesn't manage are never altered. * It also excludes `INSERT_ONLY_COLUMNS` — seeded on a new row, never overwritten * on an existing one, so a re-registration cannot clobber a curated value. * `updated_at` is left to the table's auto-touch trigger. */ export declare function buildUpsert(row: ProjectRow): UpsertStatement; export type NeonOutcome = "upserted" | "dry-run" | "skipped" | "failed"; export interface RegisterNeonResult { outcome: NeonOutcome; /** The row that was (or would be) written. */ row: ProjectRow; /** The UPSERT SQL (column names only — never contains the connection string). */ sql?: string; /** The created/updated row id, when outcome === "upserted". */ recordId?: string | number; /** Human-readable, secret-free line for the CLI to surface. */ message: string; /** * Result of the on-disk coordination-block check, when it ran. `undefined` * when verification was not attempted (dry-run / no targetPath / non-participant). */ coordinationVerified?: boolean; } /** Query seam: run parameterized SQL, resolve to the RETURNING rows. */ export type QueryFn = (sql: string, params: ProjectRowValue[]) => Promise>>; export interface RegisterNeonOptions { env?: NodeJS.ProcessEnv; /** Build the statement but do not execute — for `init --dry-run`. */ dryRun?: boolean; /** * Override config explicitly (tests). When provided (including `null`), * bypasses env resolution. `null` forces the "skipped" path. */ config?: InventoryConfig | null; /** Test/override seam for the DB client. Defaults to the Neon HTTP driver. */ queryImpl?: QueryFn; /** * Absolute path to the freshly-written scaffold. When provided (and not a * dry-run) for a participating project, the coordination block is grep-verified * on disk before `agent_coordination = Registered` is asserted. */ targetPath?: string; /** Test seam for the filesystem read used by coordination verification. */ readFileImpl?: (path: string) => Promise; } /** * UPSERT the inventory row for a freshly-scaffolded project into Neon. * Warn-and-proceed: always resolves (never rejects); the CLI surfaces `message` * and continues. * * - No Z2W_INVENTORY_DATABASE_URL → outcome "skipped" (silent for normal users). * - opts.dryRun → outcome "dry-run" (prints the would-be SQL). * - success → outcome "upserted" (+ recordId). * - query error → outcome "failed" (+ a diagnostic; row NOT written). */ export declare function registerProjectRowNeon(inputs: ScaffoldInputs, opts?: RegisterNeonOptions): Promise;