empirical-sdd 0.32.0 → 0.33.0

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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,42 @@ under the alpha rules in [docs/versioning.md](docs/versioning.md).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.33.0] - 2026-09-09
12
+
13
+ ### Added
14
+
15
+ - Added a saved **Create mockups before coding?** Yes/No preference to init and
16
+ configuration, exposed as `mockupsBeforeCoding` through MCP. Existing
17
+ repositories retain enabled behavior until explicitly changed. Disabling it
18
+ skips mandatory UI mockups while retaining fidelity checks for approved designs.
19
+ - Added a terminal-only fresh-repository demo with recorded CLI, MCP, worktree,
20
+ verification, review, and integration scenarios.
21
+
22
+ ### Changed
23
+
24
+ - Setup shows proposed defaults for new repositories and actual saved values
25
+ for existing repositories, with one applicable menu and explicit Save.
26
+ - Init prefers permitted host-native selection controls and uses one text
27
+ prompt when they are unavailable, without duplicating the same question.
28
+ - Linear MCP discovery resolves actual host tools by provider and capability,
29
+ including lazy-loaded tools, and distinguishes configuration, tool exposure,
30
+ authentication, permissions, and transient failures before offering recovery.
31
+
32
+ ### Fixed
33
+
34
+ - Fixed initialization rejecting valid Linear MCP policies after discovery and
35
+ preview by routing them through the existing credential-free MCP setup path.
36
+ - Prevented missing tool-name prefixes or unavailable future write operations
37
+ from triggering an automatic Linear API-key fallback.
38
+ - Preserved an explicit mockup preference through repeated initialization and
39
+ unrelated configuration updates, while rejecting invalid values before writes.
40
+ - Limited product test discovery to `tests/` so generated demo repositories do
41
+ not accidentally enter the product suite.
42
+
43
+ ### Migration
44
+
45
+ No migration required.
46
+
11
47
  ## [0.32.0] - 2026-09-08
12
48
 
13
49
  ### Added
@@ -438,7 +474,8 @@ Published through GitHub Actions trusted publishing with npm provenance.
438
474
 
439
475
  - Prepared and released package version `0.20.2`.
440
476
 
441
- [Unreleased]: https://github.com/goempirical/empirical-sdd/compare/v0.32.0...HEAD
477
+ [Unreleased]: https://github.com/goempirical/empirical-sdd/compare/v0.33.0...HEAD
478
+ [0.33.0]: https://github.com/goempirical/empirical-sdd/compare/v0.32.0...v0.33.0
442
479
  [0.32.0]: https://github.com/goempirical/empirical-sdd/compare/v0.31.0...v0.32.0
443
480
  [0.31.0]: https://github.com/goempirical/empirical-sdd/compare/v0.30.0...v0.31.0
444
481
  [0.30.0]: https://github.com/goempirical/empirical-sdd/compare/v0.29.0...v0.30.0
package/dist/cli.js CHANGED
@@ -14853,7 +14853,7 @@ function verifyReceiptDigest(receipt) {
14853
14853
  throw new Error(`Evidence receipt ${parsed.id} has an asserted result inconsistent with its provenance.`);
14854
14854
  }
14855
14855
  }
14856
- var SCHEMA_VERSION = 5, POLICY_SCHEMA_VERSION = 2, MANIFEST_SCHEMA_VERSION = 2, RECEIPT_SCHEMA_VERSION = 1, PRODUCT_VERSION = "0.32.0", workflowSchema, executionModeSchema, riskFloorSchema, completionLevelSchema, phaseSchema, workflowStatusSchema, criterionSchema, evidenceKindSchema, qaCheckKindSchema, qaAttemptOutcomeSchema, commandPolicySchema, evidencePolicySchema, projectPolicySchema, impactManifestSchema, authorizationSchema, COMPLETION_ORDER, receiptProvenanceSchema, receiptBaseSchema, executedReceiptSchema, artifactRecordSchema, qaCommandRecordSchema, qaResultRecordSchema, qaAttemptSchema, qaReceiptSchema, collectedReceiptSchema, evidenceReceiptSchema;
14856
+ var SCHEMA_VERSION = 5, POLICY_SCHEMA_VERSION = 2, MANIFEST_SCHEMA_VERSION = 2, RECEIPT_SCHEMA_VERSION = 1, PRODUCT_VERSION = "0.33.0", workflowSchema, executionModeSchema, riskFloorSchema, completionLevelSchema, phaseSchema, workflowStatusSchema, criterionSchema, evidenceKindSchema, qaCheckKindSchema, qaAttemptOutcomeSchema, commandPolicySchema, evidencePolicySchema, projectPolicySchema, impactManifestSchema, authorizationSchema, COMPLETION_ORDER, receiptProvenanceSchema, receiptBaseSchema, executedReceiptSchema, artifactRecordSchema, qaCommandRecordSchema, qaResultRecordSchema, qaAttemptSchema, qaReceiptSchema, collectedReceiptSchema, evidenceReceiptSchema;
14857
14857
  var init_protocol = __esm(() => {
14858
14858
  init_zod();
14859
14859
  workflowSchema = exports_external.enum(["fast", "complex"]);
@@ -25762,6 +25762,9 @@ function isRecord(value) {
25762
25762
  function normalizeConfig(config2) {
25763
25763
  assertSupportedSchema(config2);
25764
25764
  const value = config2;
25765
+ if (value.mockupsBeforeCoding !== undefined && typeof value.mockupsBeforeCoding !== "boolean") {
25766
+ throw new EmpiricalError("INVALID_CONFIG", "mockupsBeforeCoding must be a boolean");
25767
+ }
25765
25768
  const isolation = isRecord(value.isolation) ? value.isolation : {};
25766
25769
  const decisions = isRecord(value.decisions) ? value.decisions : {};
25767
25770
  const interaction = isRecord(value.interaction) ? value.interaction : {};
@@ -25788,6 +25791,7 @@ function normalizeConfig(config2) {
25788
25791
  ...config2,
25789
25792
  schemaVersion: SCHEMA_VERSION,
25790
25793
  profile: normalizeProfile(config2.profile),
25794
+ mockupsBeforeCoding: value.mockupsBeforeCoding === undefined ? true : value.mockupsBeforeCoding,
25791
25795
  evidence: {
25792
25796
  required: typeof evidence.required === "boolean" ? evidence.required : true,
25793
25797
  browserForUi: typeof evidence.browserForUi === "boolean" ? evidence.browserForUi : true,
@@ -25944,6 +25948,52 @@ first and private \`empirical __internal\` fallbacks only when MCP is unavailabl
25944
25948
  If the config is missing, invalid, or incomplete, do not initialize implicitly;
25945
25949
  tell the user to invoke \`empirical-init\` explicitly.
25946
25950
  ${END}`;
25951
+ var LINEAR_MCP_DISCOVERY_GUIDANCE = `### Discover the host's Linear connection
25952
+
25953
+ Discover by capability, not by a guessed tool prefix. Inspect the actual host
25954
+ registry/tool metadata (including provider/server identity, descriptions and input
25955
+ schemas). Use host tool search or lazy loading when exposed; search for Linear
25956
+ and the needed operation, not just one namespace string. In code-mode hosts,
25957
+ inspect the available tool catalog (for example ALL_TOOLS) before invoking its
25958
+ actual callable name. Names such as mcp__linear__list_teams,
25959
+ mcp__plugin_linear__list_teams, or linear.list_teams are examples, not a whitelist.
25960
+ A failed prefix filter is not evidence that Linear is absent. Do not substitute
25961
+ an unrelated tool merely because its name or description contains "Linear";
25962
+ confirm provider provenance and a compatible input schema.
25963
+
25964
+ Keep three facts separate: configured server, exposed callable tools, and a
25965
+ successful authenticated request. An enabled OAuth entry in a host's MCP list
25966
+ proves configuration, not that this session loaded its tools or can authenticate.
25967
+ When the user selected Linear discovery, verify access with a bounded read-only
25968
+ team-list request using the smallest supported limit. Never probe with a write.
25969
+ Only claim authenticated access after a successful provider response. Classify
25970
+ 401/login failures, 403/permission failures, transport/rate-limit failures, and
25971
+ missing tool exposure separately; none means "Linear is not installed".
25972
+
25973
+ For setup, discover only the needed read capabilities first: list teams, list
25974
+ projects and list issue statuses. Do not require save_issue, save_comment, or
25975
+ other future sync operations to begin read-only catalog discovery. Resolve later
25976
+ capabilities when required and report the exact missing operation. A partial
25977
+ capability set never authorizes changing authentication mode or pretending sync
25978
+ is available.
25979
+
25980
+ Treat canonical linear.* names returned by the Empirical bridge as operation
25981
+ identifiers, not literal host tool names. Map each to the discovered actual
25982
+ callable tool and its documented schema. Preserve operation semantics, target,
25983
+ authorized fields, bounded pagination and normalized result validation. Do not
25984
+ invent an argument translation, drop a required field, or replace an unavailable
25985
+ write with another operation; report the precise capability/schema gap instead.
25986
+
25987
+ Before reporting missing tools, exhaust the exposed registry and any available
25988
+ host search/lazy-load path. If the user shows an enabled OAuth server but tools
25989
+ remain unexposed, say "Linear is configured, but this session does not expose
25990
+ <needed capability>" and give host-specific exposure/reload recovery. Do not ask
25991
+ them to add an already configured server or log in again without an authentication
25992
+ failure. Do not infer a need for an API key from missing names, partial tools,
25993
+ permission errors or transient failures. Keep the chosen MCP/OAuth route; offer
25994
+ host-file credentials only after actual discovery/recovery checks and an explicit
25995
+ user choice to use that alternative. Never inspect private host OAuth stores.
25996
+ `;
25947
25997
  var INIT_SKILL_BODY = `# Empirical Init
25948
25998
 
25949
25999
  Use this skill only when the user explicitly asks to initialize, set up, or
@@ -25956,9 +26006,10 @@ Present setup as a wizard inside the current agent conversation. On each setup
25956
26006
  turn, show \`Empirical init — Step N of 6: <name>\` and a compact progress line
25957
26007
  using these stable stages, in order:
25958
26008
 
25959
- 1. **Setup** — inspect the repository and choose Apply recommended settings,
25960
- Keep current settings, Customize, Configure tracker, or Cancel as applicable.
25961
- 2. **Preferences** — verification, parallel work, decisions, and question detail.
26009
+ 1. **Setup** — inspect the repository, explain the proposed or saved settings,
26010
+ and offer only the actions applicable to that configuration.
26011
+ 2. **Preferences** — verification, mockups before coding, parallel work, decisions,
26012
+ and question detail.
25962
26013
  3. **Review bot** — resolve the optional bot choice once or keep its saved value.
25963
26014
  4. **Tracking** — keep the saved choice or complete the existing tracker flow.
25964
26015
  5. **Confirm** — show all effective settings; offer Save, Edit a section, or Cancel.
@@ -25968,13 +26019,22 @@ Mark each stage \`[done]\`, \`[current]\`, \`[pending]\`, or \`[blocked]\`.
25968
26019
  For example, while choosing tracking:
25969
26020
  \`[done] Setup > [done] Preferences > [done] Review bot > [current] Tracking > [pending] Confirm > [pending] Apply\`.
25970
26021
  Before Apply, \`[done]\` means a decision is resolved, not that settings were
25971
- saved. Keep stage numbers stable when skipping or revisiting decisions. Use a
25972
- host-native choice widget when available, with the same numbered text fallback;
25973
- do not invent an interactive widget or require a browser or terminal screen.
26022
+ saved. Keep stage numbers stable when skipping or revisiting decisions. Present each pending question exactly once. Prefer a callable host-native choice
26023
+ tool permitted in the current mode: Claude Code's AskUserQuestion, Codex's
26024
+ request_user_input when available, or the equivalent exposed by another agent.
26025
+ Follow that tool's schema and option limits; do not switch host mode to unlock it.
26026
+ Native tools supply keyboard/arrow selection where supported by the host; skill
26027
+ text cannot create keyboard controls. Put choices in the native tool only, with
26028
+ one step heading/progress summary; do not also print a numbered menu. If no
26029
+ permitted choice tool is available, end the turn with one numbered text question.
26030
+ Do not render the same step, summary or question in both commentary and final.
26031
+ After asking, wait for the answer without echoing the prompt. Do not invent an
26032
+ interactive widget or require a browser or terminal screen.
25974
26033
 
25975
26034
  Show only the current section's detail and ask one material question at a time.
25976
- Apply recommended settings and Keep current settings resolve unchanged preference
25977
- steps without redundant questions; they never bypass the one-time bot choice,
26035
+ Use recommended settings and Keep current settings resolve unchanged preference
26036
+ steps without redundant questions; they never bypass an unresolved mockup choice,
26037
+ the one-time bot choice,
25978
26038
  an unconfigured tracker choice, provider authorization, or final Save. On repair,
25979
26039
  mark saved bot and tracker choices kept without offering them again. A user who
25980
26040
  already answered a question in the request need not answer it again.
@@ -26010,16 +26070,48 @@ These are presentation stages, not feature workflow phases or new durable state.
26010
26070
  Determine whether this is first setup or a repair. Preserve existing
26011
26071
  configuration values and durable workflow history unless the user explicitly
26012
26072
  changes a value.
26013
- 2. At Setup, show a compact overview and offer Apply recommended settings (or
26014
- Keep current settings), Customize, Configure tracker, and Cancel. At Confirm,
26073
+ 2. At Setup, choose the menu from inspected repository configuration, never from
26074
+ whether Empirical or its skills are globally installed:
26075
+ - Fresh repository (no saved configuration): say "No settings saved in this
26076
+ repository yet". Show the proposed defaults: verification required, browser
26077
+ and screenshot evidence for UI changes, isolated code review, ask before
26078
+ creating worktrees, automatic Git base, worktree path ../{repo}-{feature},
26079
+ branch pattern {type}/{feature}, Complex decision records required, and
26080
+ concise questions. Propose mockups before coding for UI changes, but resolve
26081
+ the explicit Yes/No choice in Preferences. Label reviewer bot and tracking
26082
+ as "Not chosen yet".
26083
+ Offer **Use recommended settings**, **Customize**, and **Cancel** only.
26084
+ Explain Use recommended settings as "Continue with the defaults shown;
26085
+ nothing is saved until Save". Never offer Keep current settings here.
26086
+ - Valid saved configuration: show its actual Verification, Parallel work,
26087
+ Mockups before coding, Decisions, Interaction, Review bot and Tracker values, labeled "Saved in
26088
+ this repository". Offer **Keep current settings**, **Customize**,
26089
+ **Configure tracker**, and **Cancel**. Explain Keep as retaining the values
26090
+ shown; never show recommended and current as competing primary choices.
26091
+ - Invalid or incomplete configuration: explain what is missing or invalid,
26092
+ preserve readable values in the draft, and resolve the missing choices;
26093
+ do not claim a complete saved setup or silently replace it with defaults.
26094
+ Keep this overview compact and put all action choices in the single native
26095
+ tool or text fallback described above. At Confirm,
26015
26096
  before any mutation, render the complete effective Empirical setup summary for
26016
- Verification, Pull-request review, Parallel work, Decisions, Interaction,
26017
- and Tracker. For question detail in Preferences, offer
26097
+ Verification, Mockups before coding, Pull-request review, Parallel work,
26098
+ Decisions, Interaction, and Tracker. In Preferences ask **Create mockups before
26099
+ coding?** with **Yes (Recommended)** — approve a mockup before coding UI changes,
26100
+ and **No** — start UI implementation without mandatory mockups. Resolve this
26101
+ once on fresh setup even after Use recommended settings, unless already answered.
26102
+ Save the boolean as \`mockupsBeforeCoding\` through \`empirical_init\` or
26103
+ \`empirical_configure\` (private CLI: \`--mockups on/off\`). Show Yes/No in
26104
+ Confirm. Keep an existing value, including No, on repeat setup and unrelated
26105
+ configuration; revisit only on Customize/Edit or an explicit change request.
26106
+ Older configurations without the field retain Yes. This applies to UI-feature
26107
+ mockups, not terminal/backend work; browser/screenshot verification remains
26108
+ independent. Previously approved mockups still require fidelity verification.
26109
+ For question detail in Preferences, offer
26018
26110
  \`concise\` questions (recommended for new setup) or \`detailed\` questions;
26019
26111
  concise means ask only a question whose answer changes scope, architecture,
26020
26112
  authorization, or a safety gate. When tracker setup is unconfigured, Tracker
26021
26113
  MUST present \`Track work by type\` as the recommended default and
26022
- \`No tracking\` as the explicit alternative. Applying recommended
26114
+ \`No tracking\` as the explicit alternative. Using recommended
26023
26115
  settings or customizing MUST NOT bypass that choice. On repair with an
26024
26116
  explicitly disabled or configured tracker, Preserve current tracker is the
26025
26117
  default and MUST make no provider request. On Customize, visit Preferences,
@@ -26054,6 +26146,7 @@ These are presentation stages, not feature workflow phases or new durable state.
26054
26146
  3. \`Track work by type\` selects Linear, GitHub Projects, or Jira and starts with
26055
26147
  OAuth. First check for callable authenticated Linear MCP tools and prefer
26056
26148
  the sibling MCP bridge when available, including during legacy policy repair.
26149
+ Follow the shared host-discovery procedure below before any unavailable claim.
26057
26150
  Preserve target, states, ticket rules, visibility and enforcement; remove
26058
26151
  credentialEnv only in the previewed Policy v2 MCP migration. The installed \`empirical mcp\` server supplies an in-memory client for
26059
26152
  Linear's official remote MCP OAuth endpoint when no embedding resolver is
@@ -26076,20 +26169,21 @@ These are presentation stages, not feature workflow phases or new durable state.
26076
26169
  Codex, verify that \`linear\` is enabled with OAuth, and resume tracker setup.
26077
26170
  Explain that this enables direct Linear MCP tools but does not authorize
26078
26171
  Empirical tracker mirroring. Do not read or reuse Codex's private OAuth
26079
- credential store. Whenever callable \`linear.list_teams\`, \`linear.list_projects\`,
26080
- \`linear.list_issue_statuses\`, \`linear.list_issues\`, \`linear.get_issue\`,
26081
- \`linear.save_issue\`, \`linear.list_comments\`, and \`linear.save_comment\`
26082
- tools are available, use real Linear MCP-backed tracking: collect a bounded
26083
- catalog, call \`empirical_tracker_linear_mcp_discover\`, preview and save a
26172
+ credential store. Resolve the actual callable names for the read capabilities
26173
+ using the shared host-discovery procedure. Collect a bounded catalog, call
26174
+ \`empirical_tracker_linear_mcp_discover\`, preview and save a
26084
26175
  Linear Policy v2 with \`connection: "linear-mcp"\` and no \`credentialEnv\`,
26085
26176
  then call \`empirical_tracker_linear_mcp_prepare\`. Execute only the exact
26086
- returned \`linear.*\` tool and arguments, exhaust bounded pagination when
26177
+ returned \`linear.*\` operation through its discovered host tool and compatible
26178
+ arguments, exhaust bounded pagination when
26087
26179
  the result shape requires \`complete: true\`, normalize only its documented safe
26088
26180
  IDs/keys/URLs/names/state/comment fields, submit them with
26089
26181
  \`empirical_tracker_linear_mcp_accept\`, and repeat prepare/tool/accept until
26090
26182
  tracker health is \`synced\`. Never claim that login alone synchronized
26091
- Empirical. If those Linear tools are unavailable, present the secure
26092
- alternative and say exactly
26183
+ Empirical. A discovery or tool-exposure failure alone does not authorize
26184
+ credential fallback. Only after the shared discovery/recovery checks and the
26185
+ user explicitly chooses host-file credentials, present the secure alternative
26186
+ and say exactly
26093
26187
  \`Never paste credentials into chat\`, show the expanded
26094
26188
  host-only file path (\`\${XDG_CONFIG_HOME:-$HOME/.config}/empirical/secrets.env\`
26095
26189
  on POSIX or \`%APPDATA%\\Empirical\\secrets.env\` on Windows), and name only
@@ -26134,7 +26228,9 @@ These are presentation stages, not feature workflow phases or new durable state.
26134
26228
  or archive, and do not create or select feature workflow state.
26135
26229
 
26136
26230
  Never scan other repositories, overwrite unmanaged files, follow unsafe paths,
26137
- or claim automatic activation for a repository without valid completed config.`;
26231
+ or claim automatic activation for a repository without valid completed config.
26232
+
26233
+ ${LINEAR_MCP_DISCOVERY_GUIDANCE}`;
26138
26234
  var LOCAL_AUTOMATIC_SKILL_BODY = `# Empirical
26139
26235
 
26140
26236
  Automatically route, track, resume, and complete Empirical work in this
@@ -26202,7 +26298,8 @@ the user does not need to mention Empirical or choose a profile.
26202
26298
  require an exact choice; Empirical never guesses.
26203
26299
  After each local workflow mutation is durably committed, inspect the tracker
26204
26300
  policy. Before a Linear credential operation, check authenticated sibling
26205
- Linear MCP tools. Prefer that connection even when a legacy API-key policy
26301
+ Linear MCP tools using the shared host-discovery procedure below. Prefer
26302
+ that connection even when a legacy API-key policy
26206
26303
  exists: call the MCP prepare operation to obtain its migration candidate,
26207
26304
  discover and preview through MCP, then configure the candidate preserving
26208
26305
  target, states, ticket rules and enforcement. Policy v1 maps to manual
@@ -26211,7 +26308,8 @@ the user does not need to mention Empirical or choose a profile.
26211
26308
  empirical_integrations; package upgrades alone do not rewrite repository files.
26212
26309
  For Linear Policy v2 with \`connection: "linear-mcp"\`, call
26213
26310
  \`empirical_tracker_linear_mcp_prepare\` with the exact feature id, execute
26214
- only its returned \`linear.*\` tool/arguments, submit the bounded normalized
26311
+ only its returned \`linear.*\` operation via the discovered equivalent host
26312
+ tool/schema, submit the bounded normalized
26215
26313
  result through \`empirical_tracker_linear_mcp_accept\`, and repeat until
26216
26314
  synced. For issue results, copy attachment URLs from Linear get_issue into
26217
26315
  attachmentUrls. For reconciliation, paginate the entire bounded team/project
@@ -26230,7 +26328,8 @@ the user does not need to mention Empirical or choose a profile.
26230
26328
  Tracker operations are granular MCP tools, not additional skills or user
26231
26329
  commands. OAuth authorization is out-of-band through negotiated URL mode.
26232
26330
  Raw credentials are never chat text or tool arguments/results. If OAuth is
26233
- unavailable and authenticated Linear MCP access has been checked, tell the human \`Never paste credentials into chat\` and pause
26331
+ unavailable, host discovery/recovery has been checked, and the user explicitly
26332
+ chooses host-file credentials, tell the human \`Never paste credentials into chat\` and pause
26234
26333
  for direct host-file configuration at
26235
26334
  \`\${XDG_CONFIG_HOME:-$HOME/.config}/empirical/secrets.env\` on POSIX or
26236
26335
  \`%APPDATA%\\Empirical\\secrets.env\` on Windows.
@@ -26258,7 +26357,10 @@ the user does not need to mention Empirical or choose a profile.
26258
26357
  isolated boundary on that exact remote base/head diff; request changes keep
26259
26358
  the PR draft and a new head requires a new packet. Report the exact highest
26260
26359
  completion level. Stop only at Done, Blocked, or Awaiting Human.
26261
- 9. When a phase asks for a mockup, build the directions it names, then run
26360
+ 9. The saved \`mockupsBeforeCoding\` preference controls new required UI mockups.
26361
+ Honor No without asking again; change it only on an explicit user request via
26362
+ \`empirical_configure\`. Existing approved designs retain fidelity checks.
26363
+ When a phase asks for a mockup, build the directions it names, then run
26262
26364
  \`empirical mockups\` and give the user the address it prints. That command is
26263
26365
  public on purpose: a person clicks through and chooses. Record their choice,
26264
26366
  and anything the mockup revealed that the criteria do not cover, before the
@@ -26268,7 +26370,9 @@ the user does not need to mention Empirical or choose a profile.
26268
26370
  another runtime requires explicit approval of its exact target, cwd, and argv.
26269
26371
 
26270
26372
  Do not invent state, weaken acceptance criteria, expose credentials, or persist
26271
- private chain-of-thought. Files under .empirical/ are the durable source of truth.`;
26373
+ private chain-of-thought. Files under .empirical/ are the durable source of truth.
26374
+
26375
+ ${LINEAR_MCP_DISCOVERY_GUIDANCE}`;
26272
26376
  function skillContent(name, description, body, explicitOnly = false) {
26273
26377
  const invocationMetadata = explicitOnly ? `disable-model-invocation: true
26274
26378
  ` : "";
@@ -42762,6 +42866,8 @@ async function evaluateMockupGate(input) {
42762
42866
  if (input.phase === MOCKUP_GATE_PHASE) {
42763
42867
  if (!interfaceSurface)
42764
42868
  return empty(false);
42869
+ if (input.mockupsBeforeCoding === false)
42870
+ return empty(true);
42765
42871
  const path = mockupApprovalPath(input.feature);
42766
42872
  const entry = featureMockupEntryPath(input.feature);
42767
42873
  const requiredPaths = [entry, path];
@@ -45742,7 +45848,7 @@ class EmpiricalProject {
45742
45848
  static async initialize(root = process.cwd(), options = {}) {
45743
45849
  const absoluteRoot = resolve16(root);
45744
45850
  const trackerChange = options.tracker ? parseTrackerSetupChange(options.tracker) : undefined;
45745
- if (trackerChange?.mode === "apply") {
45851
+ if (trackerChange?.mode === "apply" && !isLinearMcpPolicy(trackerChange.policy)) {
45746
45852
  await previewTrackerPolicy(trackerChange.policy, withTrackerRepositoryRoot2(options.trackerDependencies ?? {}, absoluteRoot));
45747
45853
  }
45748
45854
  await mkdir9(absoluteRoot, { recursive: true });
@@ -45936,6 +46042,7 @@ class EmpiricalProject {
45936
46042
  const current = await this.store.loadConfig();
45937
46043
  const configured = await this.store.configure({
45938
46044
  ...current,
46045
+ mockupsBeforeCoding: input.mockupsBeforeCoding === undefined ? current.mockupsBeforeCoding : input.mockupsBeforeCoding,
45939
46046
  evidence: { ...current.evidence, ...input.evidence },
45940
46047
  isolation: { ...current.isolation, ...input.isolation },
45941
46048
  decisions: { ...current.decisions, ...input.decisions },
@@ -47853,6 +47960,7 @@ class EmpiricalProject {
47853
47960
  feature,
47854
47961
  phase: state.phase,
47855
47962
  criteria,
47963
+ mockupsBeforeCoding: (await store.loadConfig()).mockupsBeforeCoding,
47856
47964
  read: async (path) => {
47857
47965
  try {
47858
47966
  const absolute = join20(store.root, path);
@@ -47940,6 +48048,7 @@ function defaultConfig(profile, legacySource, options = {}) {
47940
48048
  schemaVersion: SCHEMA_VERSION,
47941
48049
  profile,
47942
48050
  maxRepairAttempts: 2,
48051
+ mockupsBeforeCoding: options.mockupsBeforeCoding === undefined ? true : options.mockupsBeforeCoding,
47943
48052
  evidence: {
47944
48053
  required: options.evidence?.required ?? true,
47945
48054
  browserForUi: options.evidence?.browserForUi ?? true,
@@ -47972,9 +48081,10 @@ function initializationConfiguration(options) {
47972
48081
  const decisions = options.decisions && Object.keys(options.decisions).length > 0 ? options.decisions : undefined;
47973
48082
  const interaction = options.interaction && Object.keys(options.interaction).length > 0 ? options.interaction : undefined;
47974
48083
  const review = options.review && Object.keys(options.review).length > 0 ? options.review : undefined;
47975
- if (!evidence && !isolation && !decisions && !interaction && !review && options.setupComplete === undefined)
48084
+ if (!evidence && !isolation && !decisions && !interaction && !review && options.setupComplete === undefined && options.mockupsBeforeCoding === undefined)
47976
48085
  return null;
47977
48086
  return {
48087
+ ...options.mockupsBeforeCoding !== undefined ? { mockupsBeforeCoding: options.mockupsBeforeCoding } : {},
47978
48088
  ...evidence ? { evidence } : {},
47979
48089
  ...isolation ? { isolation } : {},
47980
48090
  ...decisions ? { decisions } : {},
@@ -51255,6 +51365,7 @@ var reviewSubmissionSchema = exports_external.object({
51255
51365
  reviewerInvocationId: exports_external.string().min(1).max(200)
51256
51366
  }).strict();
51257
51367
  var configurationSchema = {
51368
+ mockupsBeforeCoding: exports_external.boolean().optional(),
51258
51369
  evidenceRequired: exports_external.boolean().optional(),
51259
51370
  browserForUi: exports_external.boolean().optional(),
51260
51371
  screenshotForUi: exports_external.boolean().optional(),
@@ -51361,7 +51472,7 @@ function createMcpServer(defaultRoot = mcpDefaultRoot(), options = {}) {
51361
51472
  description: operationSummary("init"),
51362
51473
  inputSchema: { root: exports_external.string().optional(), profile: profileSchema.optional(), tracker: trackerSetupSchema.optional(), ...configurationSchema },
51363
51474
  annotations: operationAnnotations("init")
51364
- }, async ({ root, profile, tracker, evidenceRequired, browserForUi, screenshotForUi, codeReview, reviewMode, reviewerTokenEnv, isolation, base, worktreePath, branchPattern, decisions, questions }) => toolResult(async () => {
51475
+ }, async ({ root, profile, tracker, evidenceRequired, browserForUi, screenshotForUi, codeReview, reviewMode, reviewerTokenEnv, isolation, base, worktreePath, branchPattern, decisions, questions, mockupsBeforeCoding }) => toolResult(async () => {
51365
51476
  const initialized = await EmpiricalProject.initialize(root ?? defaultRoot, {
51366
51477
  ...profile ? { profile } : {},
51367
51478
  evidence: {
@@ -51376,6 +51487,7 @@ function createMcpServer(defaultRoot = mcpDefaultRoot(), options = {}) {
51376
51487
  ...worktreePath ? { worktreePath } : {},
51377
51488
  ...branchPattern ? { branchPattern } : {}
51378
51489
  },
51490
+ ...mockupsBeforeCoding !== undefined ? { mockupsBeforeCoding } : {},
51379
51491
  decisions: { ...decisions ? { complexRecords: decisions } : {} },
51380
51492
  interaction: { ...questions ? { questions } : {} },
51381
51493
  review: {
@@ -51417,7 +51529,7 @@ function createMcpServer(defaultRoot = mcpDefaultRoot(), options = {}) {
51417
51529
  description: operationSummary("configure"),
51418
51530
  inputSchema: { root: exports_external.string().optional(), policy: exports_external.unknown().optional(), tracker: trackerSetupSchema.optional(), ...configurationSchema },
51419
51531
  annotations: operationAnnotations("configure")
51420
- }, async ({ root, policy, tracker, evidenceRequired, browserForUi, screenshotForUi, codeReview, reviewMode, reviewerTokenEnv, isolation, base, worktreePath, branchPattern, decisions, questions }) => toolResult(async () => {
51532
+ }, async ({ root, policy, tracker, evidenceRequired, browserForUi, screenshotForUi, codeReview, reviewMode, reviewerTokenEnv, isolation, base, worktreePath, branchPattern, decisions, questions, mockupsBeforeCoding }) => toolResult(async () => {
51421
51533
  const project = await EmpiricalProject.open(root ?? defaultRoot, { selection: "selected" });
51422
51534
  const configurationSupplied = [
51423
51535
  evidenceRequired,
@@ -51431,7 +51543,8 @@ function createMcpServer(defaultRoot = mcpDefaultRoot(), options = {}) {
51431
51543
  worktreePath,
51432
51544
  branchPattern,
51433
51545
  decisions,
51434
- questions
51546
+ questions,
51547
+ mockupsBeforeCoding
51435
51548
  ].some((value) => value !== undefined);
51436
51549
  if (policy !== undefined && (tracker !== undefined || configurationSupplied)) {
51437
51550
  throw new EmpiricalError("INVALID_CONFIG", "Configure project policy, project settings, and tracker setup in separate exact requests");
@@ -51453,6 +51566,7 @@ function createMcpServer(defaultRoot = mcpDefaultRoot(), options = {}) {
51453
51566
  ...worktreePath ? { worktreePath } : {},
51454
51567
  ...branchPattern ? { branchPattern } : {}
51455
51568
  },
51569
+ ...mockupsBeforeCoding !== undefined ? { mockupsBeforeCoding } : {},
51456
51570
  decisions: { ...decisions ? { complexRecords: decisions } : {} },
51457
51571
  interaction: { ...questions ? { questions } : {} },
51458
51572
  review: {
@@ -52651,6 +52765,7 @@ function characterWidth(character) {
52651
52765
  // src/setup.ts
52652
52766
  function recommendedSetupSettings() {
52653
52767
  return {
52768
+ mockupsBeforeCoding: true,
52654
52769
  evidence: {
52655
52770
  required: true,
52656
52771
  browserForUi: true,
@@ -52673,6 +52788,7 @@ function recommendedSetupSettings() {
52673
52788
  }
52674
52789
  function setupSettingsFromConfig(config2) {
52675
52790
  return {
52791
+ mockupsBeforeCoding: config2.mockupsBeforeCoding,
52676
52792
  evidence: { ...config2.evidence },
52677
52793
  isolation: { ...config2.isolation },
52678
52794
  decisions: { ...config2.decisions },
@@ -52682,6 +52798,7 @@ function setupSettingsFromConfig(config2) {
52682
52798
  }
52683
52799
  function setupConfigurationInput(settings) {
52684
52800
  return {
52801
+ mockupsBeforeCoding: settings.mockupsBeforeCoding,
52685
52802
  evidence: { ...settings.evidence },
52686
52803
  isolation: { ...settings.isolation },
52687
52804
  decisions: { ...settings.decisions },
@@ -52691,6 +52808,9 @@ function setupConfigurationInput(settings) {
52691
52808
  };
52692
52809
  }
52693
52810
  function validateSetupSettings(settings) {
52811
+ if (typeof settings.mockupsBeforeCoding !== "boolean") {
52812
+ throw new EmpiricalError("INVALID_CONFIG", "mockupsBeforeCoding must be a boolean");
52813
+ }
52694
52814
  if (settings.isolation.mode !== "ask" && settings.isolation.mode !== "off") {
52695
52815
  throw new EmpiricalError("INVALID_CONFIG", "Isolation must be ask or off");
52696
52816
  }
@@ -52736,6 +52856,7 @@ function renderSetupSummary(settings, options = { current: false }) {
52736
52856
  "Questions: concise · only material blockers",
52737
52857
  `Verification: ${verification}`,
52738
52858
  `Worktrees: ${settings.isolation.mode} · base ${base} · ${settings.isolation.branchPattern}`,
52859
+ `Mockups before coding: ${settings.mockupsBeforeCoding ? "Yes (UI changes)" : "No"}`,
52739
52860
  `Decisions: ${settings.decisions.complexRecords}`,
52740
52861
  `Review: ${settings.review.mode} · ${settings.review.mode === "bot" ? `credential name ${settings.review.reviewerTokenEnv}` : "fallback · no second credential"} · ${state}`,
52741
52862
  reviewSetupGuidance(settings.review),
@@ -52763,6 +52884,8 @@ function renderSetupSummary(settings, options = { current: false }) {
52763
52884
  `│ Path: ${settings.isolation.worktreePath}`,
52764
52885
  `│ Branch: ${settings.isolation.branchPattern}`,
52765
52886
  "│",
52887
+ `│ Mockups before coding: ${settings.mockupsBeforeCoding ? "Yes — approve UI mockups before coding" : "No — skip required mockups"}`,
52888
+ "│",
52766
52889
  "│ Decisions",
52767
52890
  `│ ${marker2(settings.decisions.complexRecords === "required")} ${settings.decisions.complexRecords === "required" ? "Require reviewable decision records for Complex work" : "Do not require Complex decision records"}`,
52768
52891
  "│",
@@ -53615,6 +53738,9 @@ ${renderSetupSummary(settings, {
53615
53738
  const firstChoice = await askEnumDefault(prompt, `Choice [${primary}]: `, primary, new Set([primary, current ? "k" : "a", "customize", "c", "tracker", "t", "cancel", "x", "q"]));
53616
53739
  if (["cancel", "x", "q"].includes(firstChoice))
53617
53740
  throw setupCancelled();
53741
+ if (!current && !["customize", "c"].includes(firstChoice)) {
53742
+ settings.mockupsBeforeCoding = await askMockupPreference(prompt, settings.mockupsBeforeCoding);
53743
+ }
53618
53744
  if (firstChoice === primary || firstChoice === (current ? "k" : "a")) {
53619
53745
  if (currentTrackerSetup.mode !== "unconfigured") {
53620
53746
  return { configuration: setupConfigurationInput(settings), tracker: { mode: "preserve" } };
@@ -53929,6 +54055,7 @@ async function customizeSetup(prompt, current) {
53929
54055
  } else {
53930
54056
  console.log("Worktree base and templates are inactive and will keep their stored values.");
53931
54057
  }
54058
+ const mockupsBeforeCoding = await askMockupPreference(prompt, current.mockupsBeforeCoding);
53932
54059
  console.log(`
53933
54060
  ◆ Decisions`);
53934
54061
  const complexRecords = await askEnumDefault(prompt, `Complex decision records [${current.decisions.complexRecords}] (required/off): `, current.decisions.complexRecords, new Set(["required", "off"]));
@@ -53938,11 +54065,15 @@ async function customizeSetup(prompt, current) {
53938
54065
  return {
53939
54066
  evidence: { required: required2, browserForUi, screenshotForUi, codeReview },
53940
54067
  isolation: { mode, baseBranch, worktreePath, branchPattern },
54068
+ mockupsBeforeCoding,
53941
54069
  decisions: { complexRecords },
53942
54070
  interaction: { questions },
53943
54071
  review: { mode: reviewMode, reviewerTokenEnv }
53944
54072
  };
53945
54073
  }
54074
+ async function askMockupPreference(prompt, current) {
54075
+ return await askEnumDefault(prompt, `Create mockups before coding? [${current ? "yes" : "no"}] (yes/no, UI changes): `, current ? "yes" : "no", new Set(["yes", "no"])) === "yes";
54076
+ }
53946
54077
  async function askOnOff(prompt, label, current) {
53947
54078
  const fallback = current ? "on" : "off";
53948
54079
  return await askEnumDefault(prompt, `${label} [${fallback}] (on/off): `, fallback, new Set(["on", "off"])) === "on";
@@ -54318,6 +54449,7 @@ function parseGlobals(argv) {
54318
54449
  return { args, root, json: json2 };
54319
54450
  }
54320
54451
  function readConfigurationFlags(args) {
54452
+ const mockupsBeforeCoding = takeOnOffOption(args, "--mockups");
54321
54453
  const evidenceRequired = takeOnOffOption(args, "--evidence");
54322
54454
  const browserForUi = takeOnOffOption(args, "--ui-browser");
54323
54455
  const screenshotForUi = takeOnOffOption(args, "--ui-screenshot");
@@ -54349,10 +54481,11 @@ function readConfigurationFlags(args) {
54349
54481
  const questions = takeOption(args, "--questions");
54350
54482
  if (questions && questions !== "concise" && questions !== "detailed")
54351
54483
  throw new EmpiricalError("INVALID_CONFIG", "--questions must be concise or detailed");
54352
- const explicit = [evidenceRequired, browserForUi, screenshotForUi, codeReview].some((value) => value !== undefined) || Boolean(reviewMode || reviewerTokenEnv || mode || baseBranch || worktreePath || branchPattern || complexRecords || questions);
54484
+ const explicit = [mockupsBeforeCoding, evidenceRequired, browserForUi, screenshotForUi, codeReview].some((value) => value !== undefined) || Boolean(reviewMode || reviewerTokenEnv || mode || baseBranch || worktreePath || branchPattern || complexRecords || questions);
54353
54485
  return {
54354
54486
  explicit,
54355
54487
  input: {
54488
+ ...mockupsBeforeCoding !== undefined ? { mockupsBeforeCoding } : {},
54356
54489
  ...evidenceRequired !== undefined || browserForUi !== undefined || screenshotForUi !== undefined || codeReview !== undefined ? { evidence: {
54357
54490
  ...evidenceRequired !== undefined ? { required: evidenceRequired } : {},
54358
54491
  ...browserForUi !== undefined ? { browserForUi } : {},