feat: scaffold identity (E1) - #73
Merged
Merged
Conversation
Add scaffold_id (UUID v4), scaffold_name, and nullable origin/upstream to .mex/config.json. Generated at `mex setup` and silently backfilled for existing scaffolds on the next CLI invocation via a loadConfig() wrapper, keeping findConfig() a pure read. Expose getScaffoldIdentity() and the ScaffoldIdentity type on the public API (additive/minor per COMPATIBILITY.md). Route all config writers through a shared mergeIntoConfig helper so independent keys never clobber. Also remove stale dev-repo pattern files; patterns/ stays empty here.
There was a problem hiding this comment.
Pull request overview
This PR introduces a stable per-scaffold identity (UUID v4) to support upcoming telemetry grouping, ensuring identities are minted during mex setup and silently backfilled for existing scaffolds when CLI commands load config.
Changes:
- Add
ScaffoldIdentityto the config/type model and exportgetScaffoldIdentity()on the public API. - Mint/persist identity via new config helpers (
mergeIntoConfig,ensureScaffoldIdentity,saveScaffoldIdentity) and trigger migration through a CLIloadConfig()wrapper. - Add unit + CLI-level tests for minting, idempotency, preservation, and migration; remove stale dev-repo pattern files.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/public-api.test.ts | Verifies getScaffoldIdentity and ScaffoldIdentity are exported and have the documented shape. |
| test/config.test.ts | Adds unit coverage for identity minting, persistence, idempotency, key preservation, and migration behavior. |
| test/cli.test.ts | Adds an end-to-end CLI migration test that backfills scaffold_id on config load. |
| src/types.ts | Introduces ScaffoldIdentity and attaches it to MexConfig. |
| src/setup/index.ts | Ensures identity is minted during setup regardless of tool selection. |
| src/index.ts | Exposes getScaffoldIdentity + ScaffoldIdentity on the public package surface. |
| src/config.ts | Implements identity load/mint/persist logic and shared config merging helper. |
| src/cli.ts | Centralizes “load config + backfill identity” behavior via loadConfig() and uses it across commands. |
| patterns/readme-refresh.md | Removes a stale dev-repo pattern file. |
| patterns/INDEX.md | Removes entries for deleted pattern files. |
| patterns/cli-option-parsing-tests.md | Removes a stale dev-repo pattern file. |
| CHANGELOG.md | Documents the new scaffold identity feature in Unreleased notes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+297
to
+301
| export function getScaffoldIdentity(config: MexConfig): ScaffoldIdentity { | ||
| if (config.identity) return config.identity; | ||
| const identity = ensureScaffoldIdentity(config.scaffoldRoot, config.projectRoot); | ||
| config.identity = identity; | ||
| return identity; |
Comment on lines
+276
to
+279
| export function ensureScaffoldIdentity(scaffoldRoot: string, projectRoot: string): ScaffoldIdentity { | ||
| const existing = loadScaffoldIdentity(loadPersistedConfig(scaffoldRoot)); | ||
| if (existing) return existing; | ||
|
|
Comment on lines
+50
to
+54
| /** | ||
| * Stable identity for a mex scaffold. Persisted in `.mex/config.json` and used | ||
| * as the grouping key for anonymous telemetry (one scaffold = one project). | ||
| * `scaffold_id` is a random UUID v4 — never derived from path, repo, or git. | ||
| */ |
Comment on lines
7
to
9
| ### Added | ||
| - **Scaffold identity** — `.mex/config.json` now carries a stable `scaffold_id` (UUID v4), `scaffold_name`, and nullable `origin`/`upstream`. Generated at `mex setup` and silently backfilled for existing scaffolds on the next CLI invocation. New `getScaffoldIdentity()` export on the public API. | ||
| - **broken-link drift checker** — flags Markdown links in scaffold files whose local target file does not exist. |
theDakshJaitly
added a commit
that referenced
this pull request
Jun 9, 2026
* feat: opt-in feedback command and invite - `mex feedback` opens a hosted form (FEEDBACK_FORM_URL) in the browser so users can opt in to maintainer user-research calls. The CLI never collects, prompts for, or transmits an email — its job ends at opening the URL. - A quiet, dismissible one-line invite appears after a successful `check`/`sync` (to stderr, so it never corrupts `--json`) and in the bare-`mex` TUI. It's TTY-gated, shown a few times then stops on its own, and `mex feedback` or `mex config set feedback off` dismisses it for good (`on` re-enables). - Kept fully separate from telemetry: the feedback module does not import the telemetry module (or vice versa), and no feedback value enters the telemetry payload. Also folds in the Copilot review nits from the E1 PR (#73): - ensureScaffoldIdentity/getScaffoldIdentity now backfill an empty scaffold_name without regenerating the id. - Reword "`.mex/config.json`" to "the scaffold's config.json" in the ScaffoldIdentity JSDoc and CHANGELOG (the context/ layout stores it at the repo root). * fix(feedback): address review on #75 - The check invite now fires only on a clean check (after the error-exit), not right before `process.exit(1)` on a drift-failing check — matches "after a successful check". - The TUI invite now respects the same gating as the CLI nudge: it uses shouldShowInvite() (so the INVITE_MAX_SHOWS cap and dismissal both apply) and records one show per TUI session via a useEffect. Extracted a recordInviteShown() helper so the count is bumped consistently from both surfaces without printing.
Merged
thekorsen
added a commit
to thekorsen/mex
that referenced
this pull request
Aug 4, 2026
…pstream Two checkouts of one repo reported the same scaffold_id: `.mex/config.json` is committed and carries a single UUID, and ensureScaffoldIdentity short-circuits on an existing id (src/config.ts:290-305). Ten developers therefore reported one scaffold identity under ten machine ids, and a worktree at `…/repo-feature-x` still reported `scaffold_name: "repo"`, frozen at first mint. scaffold_id's meaning is UNCHANGED — that is a hard constraint from the ticket, since existing telemetry cohorts key on it. "One scaffold = one project" stays correct. This is additive: a per-checkout identity alongside it. getCheckoutIdentity derives, and never persists: checkout_id = sha256(git rev-parse --absolute-git-dir)[0:32] checkout_name = basename(projectRoot) `--absolute-git-dir` is unique per working tree (verified: main `/repo/.git`, worktree `/main/.git/worktrees/<n>`, submodule `/main/.git/modules/sub`). Deriving rather than storing means it is untracked BY CONSTRUCTION: no new file, no .gitignore rule (that is #12, another lane), no new env var, no migration. A moved checkout gets a new id, by design — it is a different working context. Memoized onto config.checkout, mirroring getScaffoldIdentity. origin/upstream are REMOVED from ScaffoldIdentity. Introduced by e607a55 "feat: scaffold identity (E1) (mex-memory#73)", they were loaded, persisted and null-defaulted, but no writer ever set them non-null and no reader ever consumed them; COMPATIBILITY.md never mentioned them. The only plausible writer was the git remote URL, which is unsafe in a COMMITTED file because remote URLs can embed credentials — and src/telemetry/index.ts:75-81 plus test/telemetry.test.ts:160-166 already firewall these fields from telemetry. Cross-checkout identity is already scaffold_id, so no remote-derived identity is wanted. Pre-existing `"origin": null` keys on disk survive untouched via MexPersistedConfig's index signature and mergeIntoConfig's shallow merge; no migration strips them, deliberately. Neither helper is added to src/index.ts — the public API surface is unchanged. findConfig's exported signature is untouched (six other lanes call it), as is the existsSync(".git") at :95 that correctly makes a worktree its own project root, and findScaffoldRoot. docs/omp-integration/identity-model.md writes down the full four-layer model (machine / scaffold / checkout / contributor) that the ticket asks for before team features ship. contributor is deliberately NOT new state — git owns it via user.email. buildPayload stays at 6 keys; checkout_id is not sent anywhere, which would be a separate decision. Resolves the AGENT-ONBOARDING.md §4.3 item "origin/upstream … purpose unknown": they were reserved but never wired. The parent owns that doc, so the promotion is reported rather than applied here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
E1 from the telemetry handoff: give every mex scaffold a stable identity — the grouping key the upcoming telemetry layer needs (N machine-ids on one scaffold_id = a team of N).
Adds four fields to
.mex/config.json:scaffold_id— random UUID v4scaffold_name— defaults to the project directory basenameorigin/upstream— nullable, reserved for laterBehaviour
mex setup, unconditionally (survives the no-tool-selected path, which previously skipped writing config.json).scaffold_idget one on the next CLI invocation, via a newloadConfig()wrapper in the command layer. No prompt, no migrate command, cheap.findConfig()stays a pure read — the migration write is confined to the CLI wrapper, so the public API gains no disk side-effect.getScaffoldIdentity()andScaffoldIdentityexported frommex-agent. Additive / minor per COMPATIBILITY.md.Design notes
mergeIntoConfighelper so independent keys never clobber.scaffold_idis a random UUID, never derived from path, repo, or git identity.Tests
191 passing, typecheck clean. New coverage: mint / idempotency / key-preservation / path-independence / write-failure-swallowed in
config.test.ts, the public-API export shape, and an end-to-end migration test through the built binary.Known gap:
mex setupis interactive with no test harness, so the setup-generation path is unit-tested (on the function setup calls) rather than end-to-end; the migration path is end-to-end tested.Also
Removes two stale dev-repo pattern files;
patterns/is kept empty in this repo.