Skip to content

feat: scaffold identity (E1) - #73

Merged
theDakshJaitly merged 1 commit into
mainfrom
feat/e1-scaffold-identity
Jun 9, 2026
Merged

feat: scaffold identity (E1)#73
theDakshJaitly merged 1 commit into
mainfrom
feat/e1-scaffold-identity

Conversation

@theDakshJaitly

Copy link
Copy Markdown
Collaborator

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 v4
  • scaffold_name — defaults to the project directory basename
  • origin / upstream — nullable, reserved for later

Behaviour

  • Generated at mex setup, unconditionally (survives the no-tool-selected path, which previously skipped writing config.json).
  • Silent migration: existing scaffolds missing scaffold_id get one on the next CLI invocation, via a new loadConfig() 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.
  • New getScaffoldIdentity() and ScaffoldIdentity exported from mex-agent. Additive / minor per COMPATIBILITY.md.

Design notes

  • All config writers now route through a shared mergeIntoConfig helper so independent keys never clobber.
  • scaffold_id is a random UUID, never derived from path, repo, or git identity.
  • Identity writes are best-effort: a failed write is swallowed and never changes a command's exit code.

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 setup is 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.

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.
Copilot AI review requested due to automatic review settings June 9, 2026 00:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ScaffoldIdentity to the config/type model and export getScaffoldIdentity() on the public API.
  • Mint/persist identity via new config helpers (mergeIntoConfig, ensureScaffoldIdentity, saveScaffoldIdentity) and trigger migration through a CLI loadConfig() 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 thread src/config.ts
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 thread src/config.ts
Comment on lines +276 to +279
export function ensureScaffoldIdentity(scaffoldRoot: string, projectRoot: string): ScaffoldIdentity {
const existing = loadScaffoldIdentity(loadPersistedConfig(scaffoldRoot));
if (existing) return existing;

Comment thread src/types.ts
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 thread CHANGELOG.md
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
theDakshJaitly merged commit e607a55 into main Jun 9, 2026
3 checks passed
@theDakshJaitly
theDakshJaitly deleted the feat/e1-scaffold-identity branch June 9, 2026 06:31
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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants