@merchantduo/code 0.1.0-beta.0 → 0.2.0-beta.1

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.
Files changed (135) hide show
  1. package/README.md +132 -99
  2. package/dist/app/runtime.d.ts +30 -0
  3. package/dist/app/runtime.js +56 -0
  4. package/dist/app/status.d.ts +3 -0
  5. package/dist/app/status.js +9 -0
  6. package/dist/app/system-prompt.d.ts +2 -0
  7. package/dist/{skills.js → app/system-prompt.js} +3 -32
  8. package/dist/cli/arguments.d.ts +10 -0
  9. package/dist/cli/arguments.js +34 -0
  10. package/dist/cli/commands/agent.d.ts +1 -0
  11. package/dist/cli/commands/agent.js +33 -0
  12. package/dist/cli/commands/doctor.d.ts +3 -0
  13. package/dist/cli/commands/doctor.js +14 -0
  14. package/dist/cli/commands/init.d.ts +9 -0
  15. package/dist/cli/commands/init.js +68 -0
  16. package/dist/cli/commands/provision.d.ts +6 -0
  17. package/dist/cli/commands/provision.js +35 -0
  18. package/dist/cli/main.js +31 -0
  19. package/dist/config/index.d.ts +11 -0
  20. package/dist/config/index.js +15 -0
  21. package/dist/config/loader.d.ts +10 -0
  22. package/dist/config/loader.js +61 -0
  23. package/dist/config/paths.d.ts +1 -0
  24. package/dist/config/paths.js +7 -0
  25. package/dist/config/project-store.d.ts +2 -0
  26. package/dist/config/project-store.js +12 -0
  27. package/dist/config/schema.d.ts +210 -0
  28. package/dist/config/schema.js +36 -0
  29. package/dist/config/trust-store.d.ts +3 -0
  30. package/dist/config/trust-store.js +29 -0
  31. package/dist/environments/adapter.d.ts +5 -0
  32. package/dist/environments/adapters/local.d.ts +12 -0
  33. package/dist/environments/adapters/local.js +14 -0
  34. package/dist/environments/adapters/ssh.d.ts +11 -0
  35. package/dist/environments/adapters/ssh.js +24 -0
  36. package/dist/environments/adapters/warden.d.ts +13 -0
  37. package/dist/environments/adapters/warden.js +24 -0
  38. package/dist/environments/backend.d.ts +13 -0
  39. package/dist/environments/backend.js +73 -0
  40. package/dist/environments/executor.d.ts +23 -0
  41. package/dist/environments/factory.d.ts +3 -0
  42. package/dist/environments/factory.js +13 -0
  43. package/dist/environments/model.d.ts +28 -0
  44. package/dist/environments/model.js +3 -0
  45. package/dist/environments/path-policy.d.ts +1 -0
  46. package/dist/environments/path-policy.js +8 -0
  47. package/dist/{mage2gen.d.ts → features/mage2gen/service.d.ts} +0 -1
  48. package/dist/{mage2gen.js → features/mage2gen/service.js} +8 -3
  49. package/dist/integrations/pi/context.d.ts +2 -0
  50. package/dist/integrations/pi/context.js +46 -0
  51. package/dist/integrations/pi/mage2gen.d.ts +2 -0
  52. package/dist/integrations/pi/mage2gen.js +19 -0
  53. package/dist/integrations/pi/permissions.d.ts +6 -0
  54. package/dist/integrations/pi/permissions.js +36 -0
  55. package/dist/integrations/pi/session.d.ts +12 -0
  56. package/dist/integrations/pi/session.js +9 -0
  57. package/dist/integrations/pi/testing.d.ts +2 -0
  58. package/dist/integrations/pi/testing.js +88 -0
  59. package/dist/integrations/pi/theme.d.ts +2 -0
  60. package/dist/integrations/pi/theme.js +6 -0
  61. package/dist/integrations/pi/workflows.d.ts +2 -0
  62. package/dist/integrations/pi/workflows.js +26 -0
  63. package/dist/integrations/pi/workspace.d.ts +5 -0
  64. package/dist/integrations/pi/workspace.js +33 -0
  65. package/dist/magento/deploy-mode.d.ts +2 -0
  66. package/dist/magento/deploy-mode.js +10 -0
  67. package/dist/magento/index.d.ts +5 -0
  68. package/dist/magento/index.js +4 -0
  69. package/dist/magento/inspector.d.ts +9 -0
  70. package/dist/magento/inspector.js +56 -0
  71. package/dist/magento/model.d.ts +25 -0
  72. package/dist/magento/prompt.d.ts +2 -0
  73. package/dist/magento/prompt.js +3 -0
  74. package/dist/magento/themes.d.ts +3 -0
  75. package/dist/magento/themes.js +46 -0
  76. package/dist/provision/advisor.d.ts +12 -0
  77. package/dist/provision/advisor.js +28 -0
  78. package/dist/provision/discovery.d.ts +12 -0
  79. package/dist/provision/discovery.js +61 -0
  80. package/dist/provision/interview.d.ts +9 -0
  81. package/dist/provision/interview.js +29 -0
  82. package/dist/provision/model.d.ts +82 -0
  83. package/dist/provision/model.js +5 -0
  84. package/dist/provision/planner.d.ts +4 -0
  85. package/dist/provision/planner.js +51 -0
  86. package/dist/provision/runner.d.ts +35 -0
  87. package/dist/provision/runner.js +98 -0
  88. package/dist/shared/package-paths.d.ts +3 -0
  89. package/dist/shared/package-paths.js +10 -0
  90. package/dist/testing/discovery.d.ts +10 -0
  91. package/dist/testing/discovery.js +68 -0
  92. package/dist/testing/host.d.ts +29 -0
  93. package/dist/testing/host.js +50 -0
  94. package/dist/testing/index.d.ts +5 -0
  95. package/dist/testing/index.js +5 -0
  96. package/dist/testing/magerun.d.ts +8 -0
  97. package/dist/testing/magerun.js +73 -0
  98. package/dist/testing/model.d.ts +18 -0
  99. package/dist/testing/url.d.ts +4 -0
  100. package/dist/testing/url.js +26 -0
  101. package/dist/workflows/builtins.d.ts +2 -0
  102. package/dist/workflows/builtins.js +38 -0
  103. package/dist/workflows/change-tracker.d.ts +9 -0
  104. package/dist/workflows/change-tracker.js +38 -0
  105. package/dist/workflows/definition.d.ts +7 -0
  106. package/dist/workflows/index.d.ts +4 -0
  107. package/dist/workflows/index.js +3 -0
  108. package/dist/workflows/runner.d.ts +7 -0
  109. package/dist/workflows/runner.js +28 -0
  110. package/package.json +32 -9
  111. package/skills/magento-testing/SKILL.md +10 -0
  112. package/dist/backend.d.ts +0 -24
  113. package/dist/backend.js +0 -103
  114. package/dist/cli.js +0 -89
  115. package/dist/config.d.ts +0 -127
  116. package/dist/config.js +0 -170
  117. package/dist/config.test.js +0 -51
  118. package/dist/constants.d.ts +0 -20
  119. package/dist/constants.js +0 -15
  120. package/dist/mage2gen.test.js +0 -6
  121. package/dist/magento.d.ts +0 -40
  122. package/dist/magento.js +0 -119
  123. package/dist/magento.test.js +0 -31
  124. package/dist/skills.d.ts +0 -11
  125. package/dist/skills.test.js +0 -34
  126. package/dist/workflow.d.ts +0 -20
  127. package/dist/workflow.js +0 -67
  128. package/dist/workflow.test.js +0 -17
  129. package/extensions/merchantduo.js +0 -311
  130. /package/dist/{cli.d.ts → cli/main.d.ts} +0 -0
  131. /package/dist/{config.test.d.ts → environments/adapter.js} +0 -0
  132. /package/dist/{mage2gen.test.d.ts → environments/executor.js} +0 -0
  133. /package/dist/{magento.test.d.ts → magento/model.js} +0 -0
  134. /package/dist/{skills.test.d.ts → testing/model.js} +0 -0
  135. /package/dist/{workflow.test.d.ts → workflows/definition.js} +0 -0
package/README.md CHANGED
@@ -1,91 +1,78 @@
1
1
  # MerchantDuo
2
2
 
3
- MerchantDuo is a Magento-native coding companion for [Pi](https://github.com/earendil-works/pi). It gives an agent the right Magento context and safely routes its work to a local project, a Warden service, or an SSH environment—without forking Pi or storing credentials.
3
+ MerchantDuo is a Pi coding agent for Magento teams that works from the store's real environment, Magento version, deployment mode, and theme context.
4
4
 
5
- Built for Magento Open Source and Adobe Commerce 2.4.x projects. This is an early public beta.
5
+ ## Main features
6
6
 
7
- ## Features
7
+ - Magento-aware coding sessions
8
+ - Local, Warden, and SSH environments
9
+ - Developer-store provisioning
10
+ - Magento 2.4, Luma, Hyva, Mage2Gen, and testing skills
11
+ - Magento workflows and focused test tools
12
+ - Version, edition, deployment-mode, and theme detection
13
+ - Remote-write and operational safety controls
8
14
 
9
- - Detects Magento edition, version, PHP version, deploy mode, cache types, and enabled modules.
10
- - Keeps a terminal session pinned to one environment: local, Warden, or SSH.
11
- - Uses project-scoped configuration with strict validation, environment-variable secrets, and one-time trust per config revision.
12
- - Runs Warden commands through the selected service, including `php-debug` when requested.
13
- - Makes SSH environments cautious by default: remote profiles are read-only unless explicitly made writable, and remote commands require confirmation.
14
- - Tracks Magento-related edits and exposes safe, explicit cache, maintenance, compilation, setup, and static-content workflows through Pi.
15
- - Adds a configurable, read-only Magento knowledge MCP connection when available.
16
- - Detects Magento-registered themes and lets a session scope work to all themes, any relevant theme, or one selected theme with `/theme`.
17
- - Ships Magento 2.4, Luma, Mage2Gen, and official Hyva skill sources; selected Magento/theme guidance is injected only when applicable.
18
- - Ships with a placeholder OpenAI-compatible provider configuration that you can replace before production use.
15
+ ## Details
19
16
 
20
- ## Requirements
17
+ MerchantDuo gives Magento developers a coding-agent workspace that starts with the project context they need. It runs on Pi, inspects the selected Magento environment, keeps theme scope visible, and exposes Magento-specific tools for the work that usually slows an agency team down.
21
18
 
22
- - Node.js 22.19 or newer
23
- - Bash
24
- - A Magento project for Magento-aware features
25
- - Optional: [Warden](https://docs.warden.dev/) or SSH for those environment types
19
+ It is built for Magento 2.4 development, not as a generic chat wrapper with a few shell commands attached. A session knows whether it is working locally, in Warden, or through SSH. It can identify the Magento edition, version, PHP version, deployment mode, enabled module count, cache types, and real registered themes before it suggests framework-specific work.
26
20
 
27
- ## Install
21
+ MerchantDuo is currently beta software for development teams.
28
22
 
29
- Run it without a global install:
23
+ ## What agencies get
30
24
 
31
- ```sh
32
- npx merchantduo init
33
- ```
34
-
35
- Or install the beta globally:
36
-
37
- ```sh
38
- npm install --global merchantduo
39
- merchantduo init
40
- ```
41
-
42
- `merchantduo init` creates `.merchantduo.yaml` in the current project.
25
+ - A Pi coding-agent package with Magento context from the start of each session.
26
+ - Local, Warden, and SSH environments selected per project or command.
27
+ - A guided `provision` command for preparing a local or Warden developer store.
28
+ - Native Magento 2.4, Luma, Hyva, Mage2Gen, and testing skills.
29
+ - Magento-aware file, search, edit, module-generation, workflow, HTTP, browser, and PHP-console tools.
30
+ - Theme discovery based on registered themes in `vendor` and `app/design`, including parent chains and Luma or Hyva classification.
31
+ - Deployment-mode guidance that changes for developer, default, and production installations.
32
+ - Guardrails around remote writes, Magento operations, credentials, and browser access.
43
33
 
44
- ## Quick start
34
+ The result is less time re-explaining a store to an agent and fewer generic recommendations that do not fit Magento.
45
35
 
46
- 1. Initialise configuration from your Magento project root.
36
+ ## Magento context before the first change
47
37
 
48
- ```sh
49
- merchantduo init
50
- ```
38
+ At session startup, MerchantDuo creates a Magento snapshot for the selected environment. It reads `composer.lock` and `bin/magento --version` when available, then reports disagreements instead of silently choosing one. The snapshot includes:
51
39
 
52
- 2. Optionally set a token for the built-in read-only Magento knowledge MCP.
40
+ - Magento Open Source or Adobe Commerce edition and version
41
+ - PHP version and deployment mode
42
+ - Enabled module count and cache types
43
+ - Registered themes, inheritance chains, and Luma or Hyva classification
44
+ - Available frontend and admin URLs, marked unverified until explicitly tested
45
+ - Whether the selected environment is writable
53
46
 
54
- ```sh
55
- export MERCHANTDUO_MCP_TOKEN="..."
56
- ```
47
+ Theme discovery only scans real Magento theme roots: package roots under `vendor` and themes under `app/design`. It does not search arbitrary vendor fixtures. Use `/theme` to select `all`, `any`, or a detected theme code. `all` keeps every detected theme in scope, while `any` tells the agent to identify the relevant theme before it edits theme-specific code.
57
48
 
58
- 3. Review the generated configuration, then approve it once. MerchantDuo records trust by absolute path and content digest, so a changed file needs to be reviewed again.
49
+ ## Curated Magento knowledge
59
50
 
60
- ```sh
61
- MERCHANTDUO_TRUST_CONFIG=1 merchantduo doctor
62
- ```
51
+ MerchantDuo ships focused Pi skills instead of relying on broad, generic instructions:
63
52
 
64
- 4. Start Pi in your chosen environment.
53
+ - `magento-24x` guides Magento 2.4 work toward service contracts, dependency injection, declarative schema and data patches, layout XML, and the right deployment-mode workflow.
54
+ - `luma` covers Magento fallback, layout XML, RequireJS, UI components, Knockout, LESS, and static assets.
55
+ - `hyva` directs work toward Hyva templates, Alpine, Tailwind, and Hyva UI components. The package also vendors the relevant Hyva skills.
56
+ - `mage2gen` makes vendored Mage2Gen the standard starting point for new modules.
57
+ - `magento-testing` keeps testing explicit, targeted, and unauthenticated.
65
58
 
66
- ```sh
67
- merchantduo --env default "Explain this Magento module"
68
- merchantduo --env warden "Find the layout handle for checkout"
69
- ```
59
+ The current package provides this curated knowledge locally through Pi skills. `merchantduo doctor` reports knowledge-service status and currently shows that no external knowledge connection is configured. That prevents the agent from presenting an unavailable MCP source as project knowledge.
70
60
 
71
- ## Configuration
61
+ ## Environments that match agency work
72
62
 
73
- MerchantDuo merges optional global defaults from `~/.merchantduo/config.yaml` with the project file `.merchantduo.yaml`. Project environments replace global environments with the same name. Unknown keys and missing interpolated variables are rejected.
63
+ Define the environments your team actually uses in `.merchantduo.yaml`, then select one at launch with `--env`.
74
64
 
75
65
  ```yaml
76
- version: 1
77
- defaultEnvironment: default
66
+ defaultEnvironment: local
78
67
  activeTheme: all
79
-
80
- automation:
81
- syntaxChecks: auto
82
- cacheClean: auto
83
-
68
+ testing:
69
+ frontendUrl: https://shop.example.test/
70
+ adminUrl: https://shop.example.test/admin/
71
+ allowInsecureTls: true
84
72
  environments:
85
- default:
73
+ local:
86
74
  type: local
87
75
  root: .
88
-
89
76
  warden:
90
77
  type: warden
91
78
  projectRoot: .
@@ -94,7 +81,6 @@ environments:
94
81
  targets:
95
82
  default: php-fpm
96
83
  debug: php-debug
97
-
98
84
  stage:
99
85
  type: ssh
100
86
  host: shop-stage
@@ -102,62 +88,109 @@ environments:
102
88
  writable: false
103
89
  ```
104
90
 
105
- Environment selection is fixed for a running session and follows this order:
91
+ Local work uses the Magento checkout directly. Warden work routes file and Magento commands through the selected service. SSH starts read-only unless you set `writable: true`, and writable remote file or shell work requires confirmation.
106
92
 
107
- 1. `--env <name>`
108
- 2. `MERCHANTDUO_ENV`
109
- 3. `defaultEnvironment`
110
- 4. An implicit local environment rooted at the current directory
93
+ For SSH user names, ports, and identities, use an OpenSSH alias rather than extra YAML keys:
111
94
 
112
- `activeTheme` is project-scoped. Its default, `all`, means changes should cover every detected registered theme. `any` tells the agent to infer the relevant theme from the request and code. A concrete Magento theme code limits theme-specific work to that theme and its inheritance chain. The `/theme` command displays a selector when called without arguments, or accepts `/theme all`, `/theme any`, or a detected theme code. Invalid values do not change the file.
95
+ ```sshconfig
96
+ Host shop-stage
97
+ HostName stage.example.com
98
+ User deploy
99
+ Port 2222
100
+ IdentityFile ~/.ssh/id_ed25519
101
+ ```
113
102
 
114
- ## Commands
103
+ Use the alias as `host: shop-stage` in `.merchantduo.yaml`.
115
104
 
116
- ```sh
117
- # Launch Pi with MerchantDuo
118
- merchantduo [--env <name>] [--config <path>] [Pi options or prompt]
105
+ `merchantduo init` offers the local checkout first. When the project `.env` contains Warden routing markers, it also offers a Warden environment. It writes only the environments you accept and then prints the remote SSH example instead of adding a fake remote entry. Local development certificates are accepted by default; set `testing.allowInsecureTls: false` when the selected URLs have publicly trusted certificates.
119
106
 
120
- # Create .merchantduo.yaml
121
- merchantduo init
107
+ ## Provision a developer store
122
108
 
123
- # Check Magento and environment connectivity
124
- merchantduo doctor [--env <name>]
109
+ Run provisioning from an empty directory or an existing Magento project:
110
+
111
+ ```sh
112
+ merchantduo provision
113
+ merchantduo provision --env warden --source https://git.example.test/store.git --database ./backup.sql.gz --yes
125
114
  ```
126
115
 
127
- `merchantduo doctor` includes detected registered themes, their inheritance chains/classes, and the active theme scope. Discovery reads registrations only from installed vendor package roots and `app/design` theme roots, excluding framework test fixtures. Pi’s footer shows the package/Magento version, environment and write policy, and `Theme: <selection> (/theme to switch)`. SSH is always confirmation-gated; a non-writable SSH profile is additionally read-only.
116
+ Provisioning supports local and Warden environments. It inspects available services, Magento source, database dumps, and existing web-server routing. The interactive flow asks only for choices it cannot resolve, prints an ordered plan, and asks once before it changes anything.
128
117
 
129
- ## Skills and Mage2Gen
118
+ With `--yes`, every decision must already be explicit or unambiguous. The command fails rather than guessing a source, database, web server, or environment.
130
119
 
131
- All shipped skills remain available for explicit use. On session start and after context compaction, MerchantDuo automatically supplies the best Magento instruction for the detected version (currently the `2.4` prefix) plus Luma or Hyva guidance only when a concrete selected theme has that class. `all` and `any` deliberately suppress automatic theme guidance. Theme changes are coalesced and take effect on the next prompt.
120
+ Sources can be an existing checkout, Git URL, local archive, or HTTPS archive. Databases can be the current database, a local SQL or compressed SQL dump, or an HTTPS URL. Downloaded archives are staged below `MERCHANTDUO_HOME/provisions`, checked for path traversal, and removed after extraction.
132
121
 
133
- Every session also receives MerchantDuo's baseline implementation rules: inspect relevant code and inheritance before edits, use Magento-native extension points, follow the selected theme scope, and validate proportionally. A direct user request to run an operational Magento action invokes the workflow immediately; its single MerchantDuo confirmation dialog is authoritative, so the agent does not ask for an additional conversational confirmation. New module requests must use the vendored Mage2Gen scaffolding first, unless it cannot represent the request and the agent states why.
122
+ Input precedence is command flags, then a discovered Magento checkout or the only SQL dump in the project root, then the interactive interview.
134
123
 
135
- The official Hyva skill repository is vendored at `vendor/hyva-ai-tools/skills`. Mage2Gen's `magento-2.4` source is vendored at `vendor/mage2gen`; use the `mage2gen_generate_module` tool rather than locating the source from the target project. It runs the package-absolute Mage2Gen Python library against the local/Warden project workspace, writes only to `app/code`, and supports an optional frontend route/controller starter. Python 3 is required; Mage2Gen's setup has no runtime dependencies beyond Python/setuptools. The tool refuses existing destinations and presents one write confirmation. SSH generation is intentionally unsupported because the local package source is not installed on the remote host.
124
+ For Warden, MerchantDuo runs Warden lifecycle and database commands on the host, then runs Composer and Magento commands in the configured PHP service. Local provisioning requires PHP, Composer, the MySQL client, and an active nginx or Apache service. If no matching virtual host exists, the approved plan can add a minimal `pub` vhost and an `/etc/hosts` entry for the selected local domain. It does not replace an existing matching vhost.
136
125
 
137
- Useful environment variables:
126
+ If `MERCHANTDUO_LLM_TOKEN` is set, provisioning can ask a narrow OpenAI-compatible advisor to choose from its already generated action plan. Its built-in endpoint and model are mock wiring, not a hosted AI service. Invalid advisor output falls back to the deterministic plan. MerchantDuo never sends the token, database credentials, or `app/etc/env.php` contents to the advisor.
138
127
 
139
- | Variable | Purpose |
140
- | ----------------------- | ------------------------------------------------------------- |
141
- | `MERCHANTDUO_HOME` | Overrides the default state directory, `~/.merchantduo`. |
142
- | `MERCHANTDUO_CONFIG` | Selects a configuration file. |
143
- | `MERCHANTDUO_ENV` | Selects the session environment. |
144
- | `MERCHANTDUO_API_KEY` | Enables the configured MerchantDuo provider. |
145
- | `MERCHANTDUO_MCP_TOKEN` | Optionally supplies the token for the built-in knowledge MCP. |
128
+ ## Tools that respect Magento boundaries
146
129
 
147
- ## Safety model and post-change workflows
130
+ MerchantDuo uses direct Pi extensions for context, permissions, workspace work, themes, Mage2Gen, workflows, and testing. The package manifest is the source of truth for both extensions and skills.
131
+
132
+ | Capability | What it does |
133
+ | --- | --- |
134
+ | Workspace tools | Read, search, write, and edit inside the selected environment. |
135
+ | `mage2gen_generate_module` | Previews or generates a module under the selected local or Warden project's `app/code`. It refuses existing destinations and does not run over SSH. |
136
+ | `magento_workflow` | Previews Magento operations such as cache cleaning, setup upgrade, DI compilation, static-content deployment, reindexing, and tests. Use `execute: true` to request the single in-tool confirmation. |
137
+ | `magento_http_test` | Runs an explicit unauthenticated GET or HEAD request against a discovered Magento URL. |
138
+ | `magento_browser_test` | Captures a screenshot or DOM dump with an isolated Chromium profile. Artifacts are stored under `MERCHANTDUO_HOME/artifacts/<project-id>/`. |
139
+ | `magento_php_repl` | Runs one PHP snippet through a one-shot `n98-magerun2` console. |
140
+ | `/navigate-frontend` and `/navigate-adminhtml` | Open a discovered same-origin frontend or admin URL without handling login credentials. |
148
141
 
149
- MerchantDuo does not read `app/etc/env.php` or inject credentials into agent context. Keep secrets in environment variables. It never automatically runs cache flushes, maintenance changes, static-content deployment, compilation, setup upgrades, indexing, tests, or remote mutations. Use the `magento_workflow` tool for command previews and explicit confirmation; it is the only route for Magento operational commands. Its actions include `cache-clean`, `cache-flush`, `maintenance-enable`, `maintenance-disable`, `setup-upgrade`, `compile`, `static-content-deploy`, `reindex`, and `tests`.
142
+ No lifecycle hook runs tests, cache actions, deployments, or browser commands because a session started or a file changed. Those operations remain explicit. Testing and navigation are direct user-request boundaries, so they do not ask for the MerchantDuo confirmation.
150
143
 
151
- MerchantDuo reads `bin/magento deploy:mode:show` into the snapshot and uses that detected mode when recommending post-change work. It first inspects the changed module/files and chooses the smallest Magento-native workflow: ordinary PHP or template edits generally require only a relevant targeted cache clean, if any. Module registration, schema, declarative configuration, or dependency changes use this guidance:
144
+ ## Magento workflows without blind deployment commands
152
145
 
153
- | Mode | Recommended workflow |
146
+ MerchantDuo tracks changed files and recommends the smallest relevant operation. It does not assume every edit needs a full Magento deployment.
147
+
148
+ | Detected mode | Typical module-change sequence |
154
149
  | --- | --- |
155
- | Developer | `setup:upgrade`, then targeted cache clean. Do not normally compile DI or deploy static content. |
156
- | Default | `setup:upgrade`, then targeted cache clean. Compile or static deploy only when the task specifically requires it. |
157
- | Production | maintenance enable `setup:upgrade` DI compile static-content deploy targeted cache clean maintenance disable. |
150
+ | Developer | `setup:upgrade`, then clean relevant cache types. |
151
+ | Default | `setup:upgrade`, then clean relevant cache types. Compile DI or deploy static content only when the change requires it. |
152
+ | Production | Enable maintenance mode, run `setup:upgrade`, compile DI, deploy static content, clean relevant cache types, then disable maintenance mode. |
153
+
154
+ If the deployment mode cannot be determined, MerchantDuo reports that fact instead of inventing a sequence.
155
+
156
+ ## Install and start
157
+
158
+ MerchantDuo requires Node.js 22.19 or later, Bash, Python 3, curl, and Chromium or Chrome. Warden environments require Warden. SSH environments require OpenSSH. A remote PHP console needs `n98-magerun2` on that environment.
159
+
160
+ Linux navigation also needs `xdg-open`. Browser discovery accepts `chromium-browser`, `chromium`, `google-chrome`, and `google-chrome-stable`.
161
+
162
+ Install the package through your team's npm registry, then create the project configuration:
163
+
164
+ ```sh
165
+ merchantduo init
166
+ merchantduo doctor --env local
167
+ merchantduo --env local
168
+ ```
169
+
170
+ `merchantduo init` creates `.merchantduo.yaml`. Review it before the first agent session. MerchantDuo requires an explicit one-time trust decision for a project configuration. Set `MERCHANTDUO_TRUST_CONFIG=1` only after reviewing that file.
171
+
172
+ Testing URLs can be configured at the top level or per environment. Configured URLs take precedence over discovery. Local discovery reads Magento base URLs and the admin URI. Warden reads `TRAEFIK_DOMAIN` and `TRAEFIK_SUBDOMAIN` from the project `.env`. MerchantDuo does not contact discovered URLs until an explicit testing request.
173
+
174
+ For SSH, MerchantDuo uses configured URLs or reads Magento remotely when URLs are absent. HTTP tests, browser tests, navigation, temporary browser profiles, and test artifacts always run on and belong to the host, including for Warden. MerchantDuo finds and invokes the host `curl` and Chrome/Chromium, never their container copies. Local TLS exceptions apply consistently to both curl and the browser. The PHP REPL remains Magento-side because it executes application code and therefore needs Warden's PHP service, project files, configuration, and database. When local `n98-magerun2` is unavailable, MerchantDuo looks in `vendor/bin`, `bin`, and `PATH`, then can use a verified official PHAR cache under `MERCHANTDUO_HOME/tools/n98-magerun2` without modifying the Magento project. Failure to obtain that cache does not stop the session.
175
+
176
+ ## Safety model
158
177
 
159
- If mode detection fails or returns an unknown value, MerchantDuo reports that rather than guessing a sequence. Recommendations do not mutate Magento. When execution is directly requested, the agent invokes `magento_workflow` with `execute: true` and relies on its single confirmation dialog; it does not ask for a separate conversational confirmation.
178
+ Magento projects carry more operational risk than ordinary application repositories. MerchantDuo keeps the boundaries simple:
160
179
 
161
- ## Status
180
+ - It does not read `app/etc/env.php`, credentials, or tokens for agent context.
181
+ - It treats discovered URLs as unverified until you run an explicit HTTP or browser test.
182
+ - It performs no background cache, deployment, browser, HTTP, PHP-console, or workflow action.
183
+ - Module generation and Magento workflows preview first. Execution requires `execute: true` and one harness confirmation.
184
+ - Read-only SSH blocks write, edit, and shell operations. Writable SSH asks for confirmation before those operations.
185
+ - SSH testing uses the publicly reachable URL and never creates a tunnel.
186
+
187
+ ## Development
188
+
189
+ ```sh
190
+ pnpm install --frozen-lockfile
191
+ pnpm check
192
+ pnpm test
193
+ pnpm pack --dry-run
194
+ ```
162
195
 
163
- MerchantDuo is beta software. Validate it against a non-production project before relying on it for operational Magento work.
196
+ For the extension layout, provisioning boundary, and session lifecycle, see [the architecture notes](docs/architecture.md).
@@ -0,0 +1,30 @@
1
+ import { type MerchantConfig } from "#config/index";
2
+ import { EnvironmentBackend } from "#environments/backend";
3
+ import type { Environment } from "#environments/model";
4
+ import { type MagentoInspector, type MagentoSnapshot } from "#magento/index";
5
+ import { ChangeTracker } from "#workflows/change-tracker";
6
+ import type { TestingSnapshot } from "#testing/model";
7
+ export type SessionState = {
8
+ config: MerchantConfig;
9
+ configPath?: string;
10
+ selected: {
11
+ name: string;
12
+ environment: Environment;
13
+ };
14
+ backend: EnvironmentBackend;
15
+ magento: MagentoSnapshot;
16
+ testing?: TestingSnapshot;
17
+ };
18
+ export declare class MerchantDuoRuntime {
19
+ #private;
20
+ readonly inspector: MagentoInspector;
21
+ readonly injected: Set<string>;
22
+ readonly changes: ChangeTracker;
23
+ constructor(inspector?: MagentoInspector);
24
+ boot(cwd: string): Promise<SessionState>;
25
+ resetInjection(): void;
26
+ injectionKey(skillId: string, theme: string): string;
27
+ changeTheme(cwd: string, activeTheme: string): Promise<SessionState>;
28
+ writable(state: SessionState): boolean;
29
+ setTesting(snapshot: TestingSnapshot): void;
30
+ }
@@ -0,0 +1,56 @@
1
+ import { loadConfig, selectEnvironment, setProjectTheme, } from "#config/index";
2
+ import { EnvironmentBackend } from "#environments/backend";
3
+ import { environmentWritable } from "#environments/model";
4
+ import { DefaultMagentoInspector, } from "#magento/index";
5
+ import { ChangeTracker } from "#workflows/change-tracker";
6
+ export class MerchantDuoRuntime {
7
+ inspector;
8
+ #state;
9
+ #injectionGeneration = 0;
10
+ injected = new Set();
11
+ changes = new ChangeTracker();
12
+ constructor(inspector = new DefaultMagentoInspector()) {
13
+ this.inspector = inspector;
14
+ }
15
+ async boot(cwd) {
16
+ if (this.#state)
17
+ return this.#state;
18
+ const loaded = await loadConfig(cwd, {
19
+ config: process.env.MERCHANTDUO_CONFIG_PATH || undefined,
20
+ });
21
+ const selected = selectEnvironment(loaded.config, cwd, process.env.MERCHANTDUO_SELECTED_ENV);
22
+ const backend = new EnvironmentBackend(selected.environment, cwd);
23
+ this.#state = {
24
+ ...loaded,
25
+ selected,
26
+ backend,
27
+ magento: await this.inspector.inspect(backend),
28
+ };
29
+ return this.#state;
30
+ }
31
+ resetInjection() {
32
+ this.#injectionGeneration += 1;
33
+ this.injected.clear();
34
+ }
35
+ injectionKey(skillId, theme) {
36
+ return `${this.#injectionGeneration}:${skillId}:${theme}`;
37
+ }
38
+ async changeTheme(cwd, activeTheme) {
39
+ const state = await this.boot(cwd);
40
+ if (!state.configPath)
41
+ throw new Error("No project .merchantduo.yaml is loaded; run merchantduo init first");
42
+ await setProjectTheme(state.configPath, activeTheme);
43
+ const reloaded = await loadConfig(cwd, { config: state.configPath });
44
+ state.config = reloaded.config;
45
+ this.resetInjection();
46
+ return state;
47
+ }
48
+ writable(state) {
49
+ return environmentWritable(state.selected.environment);
50
+ }
51
+ setTesting(snapshot) {
52
+ if (!this.#state)
53
+ throw new Error("Session has not started");
54
+ this.#state.testing = snapshot;
55
+ }
56
+ }
@@ -0,0 +1,3 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { SessionState } from "#app/runtime";
3
+ export declare function refreshStatus(ctx: ExtensionContext, state: SessionState): void;
@@ -0,0 +1,9 @@
1
+ export function refreshStatus(ctx, state) {
2
+ const environment = state.selected.environment;
3
+ const writePolicy = environment.type === "ssh"
4
+ ? environment.writable
5
+ ? "write + approve"
6
+ : "read-only + approve"
7
+ : "write + approve";
8
+ ctx.ui.setStatus("merchantduo", `MerchantDuo ${process.env.MERCHANTDUO_PACKAGE_VERSION ?? "?"} | Magento ${state.magento.version ?? "?"} | ${state.selected.name}/${environment.type} | ${writePolicy} | Theme: ${state.config.activeTheme} | URLs: ${state.testing?.frontendUrl ?? "unavailable"} (unverified)`);
9
+ }
@@ -0,0 +1,2 @@
1
+ export declare function mage2genRoot(): string;
2
+ export declare function merchantDuoSystemPrompt(mage2genPath?: string): string;
@@ -1,36 +1,7 @@
1
- import { readFile } from "node:fs/promises";
2
- import { dirname, resolve } from "node:path";
3
- const skillFile = {
4
- "magento-24x": "skills/magento-2.4/SKILL.md",
5
- luma: "skills/luma/SKILL.md",
6
- hyva: "skills/hyva/SKILL.md",
7
- mage2gen: "skills/mage2gen/SKILL.md",
8
- };
9
- export function skillRoot() {
10
- return resolve(dirname(new URL(import.meta.url).pathname), "..");
11
- }
1
+ import { packagePath } from "#shared/package-paths";
12
2
  export function mage2genRoot() {
13
- return resolve(skillRoot(), "vendor/mage2gen");
14
- }
15
- export function selectMagentoSkill(version) {
16
- return version?.startsWith("2.4") ? "magento-24x" : undefined;
17
- }
18
- export function selectedThemeSkill(activeTheme, themes) {
19
- const theme = themes.find((item) => item.code === activeTheme);
20
- return theme?.kind === "luma" ? "luma" : theme?.kind === "hyva" ? "hyva" : undefined;
21
- }
22
- export function automaticSkills(version, activeTheme, themes) {
23
- const magento = selectMagentoSkill(version);
24
- const theme = selectedThemeSkill(activeTheme, themes);
25
- return [magento, theme].filter((value) => Boolean(value));
26
- }
27
- export async function readSkill(name) {
28
- return readFile(resolve(skillRoot(), skillFile[name]), "utf8");
29
- }
30
- export function skillPaths() {
31
- return Object.fromEntries(Object.entries(skillFile).map(([name, file]) => [name, resolve(skillRoot(), file)]));
3
+ return packagePath("vendor/mage2gen");
32
4
  }
33
- /** MerchantDuo's stable session rules; environment-specific facts remain in snapshotPrompt. */
34
5
  export function merchantDuoSystemPrompt(mage2genPath = mage2genRoot()) {
35
6
  return `You are MerchantDuo, a senior Magento 2.4 architect and implementation partner. Work directly in the selected MerchantDuo environment and theme scope. Before changing code, inspect the relevant module, theme inheritance chain, configuration, and established local conventions. Make focused, production-quality changes; do not preserve obsolete compatibility paths or add speculative abstractions.
36
7
 
@@ -40,5 +11,5 @@ For every request to create a new Magento module, call the \`mage2gen_generate_m
40
11
 
41
12
  After every change, inspect the changed module and files and choose the smallest applicable post-change workflow from Magento conventions; never apply a full sequence blindly. Ordinary PHP or template edits normally need only the relevant targeted cache clean, if any. Module registration, schema, declarative configuration, or dependency changes trigger mode-aware guidance from the detected snapshot mode: developer recommends \`setup:upgrade\` then targeted cache clean and does not normally compile DI or deploy static content; default recommends \`setup:upgrade\` then targeted cache clean, with compile or static deploy only when the task specifically requires them; production recommends maintenance enable, \`setup:upgrade\`, DI compile, static-content deploy, targeted cache clean, then maintenance disable. If the mode is unknown, report that and do not infer a deployment sequence.
42
13
 
43
- For cache clean/flush, maintenance enable/disable, static content deployment, compilation, setup upgrade, indexing, tests, or deployment: when the user directly requests execution, invoke \`magento_workflow\` with \`execute: true\` immediately. Use only this workflow tool for operational Magento commands. Do not ask the user for a separate conversational confirmation or ask them to repeat a magic phrase: the harness confirmation dialog is the sole authoritative confirmation. When the user has not asked to execute, explain the required operation and offer a preview instead. Never inspect app/etc/env.php or credentials.`;
14
+ For cache clean/flush, maintenance enable/disable, static content deployment, compilation, setup upgrade, indexing, tests, or deployment: when the user directly requests execution, invoke \`magento_workflow\` with \`execute: true\` immediately. Use only this workflow tool for operational Magento commands. Do not ask the user for a separate conversational confirmation: the harness dialog is the sole authoritative confirmation. When execution was not requested, explain the operation and offer a preview. Never inspect app/etc/env.php or credentials.`;
44
15
  }
@@ -0,0 +1,10 @@
1
+ export type CliArguments = {
2
+ command?: "init" | "doctor" | "provision";
3
+ environment?: string;
4
+ configPath?: string;
5
+ source?: string;
6
+ database?: string;
7
+ yes: boolean;
8
+ piArguments: string[];
9
+ };
10
+ export declare function parseArguments(args: string[]): CliArguments;
@@ -0,0 +1,34 @@
1
+ export function parseArguments(args) {
2
+ const command = args[0] === "init" || args[0] === "doctor" || args[0] === "provision" ? args[0] : undefined;
3
+ const input = command ? args.slice(1) : args;
4
+ const piArguments = [];
5
+ let environment;
6
+ let configPath;
7
+ let source;
8
+ let database;
9
+ let yes = false;
10
+ for (let index = 0; index < input.length; index += 1) {
11
+ const value = input[index];
12
+ if (value === "--env" || value === "--config" || value === "--source" || value === "--database") {
13
+ const argument = input[index + 1];
14
+ if (!argument)
15
+ throw new Error(`${value} requires a value`);
16
+ if (value === "--env")
17
+ environment = argument;
18
+ else if (value === "--config")
19
+ configPath = argument;
20
+ else if (value === "--source")
21
+ source = argument;
22
+ else
23
+ database = argument;
24
+ index += 1;
25
+ }
26
+ else if (value === "--yes") {
27
+ yes = true;
28
+ }
29
+ else {
30
+ piArguments.push(value);
31
+ }
32
+ }
33
+ return { command, environment, configPath, source, database, yes, piArguments };
34
+ }
@@ -0,0 +1 @@
1
+ export declare function launchAgent(piArguments: string[], selectedEnvironment: string, configPath?: string): Promise<number>;
@@ -0,0 +1,33 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { merchantHome } from "#config/paths";
6
+ import { packagePath } from "#shared/package-paths";
7
+ export async function launchAgent(piArguments, selectedEnvironment, configPath) {
8
+ const state = merchantHome();
9
+ await mkdir(state, { recursive: true, mode: 0o700 });
10
+ await mkdir(resolve(state, "pi"), { recursive: true, mode: 0o700 });
11
+ const piEntry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
12
+ const piCli = resolve(dirname(piEntry), "cli.js");
13
+ const packageJson = JSON.parse(await readFile(packagePath("package.json"), "utf8"));
14
+ // Use Pi's package loader, so package.json owns both extensions and skills.
15
+ await writeFile(resolve(state, "pi", "settings.json"), `${JSON.stringify({ packages: [packagePath()] }, null, 2)}\n`, { mode: 0o600 });
16
+ return new Promise((resolveExit, reject) => {
17
+ const child = spawn(process.execPath, [
18
+ piCli,
19
+ ...piArguments,
20
+ ], {
21
+ stdio: "inherit",
22
+ env: {
23
+ ...process.env,
24
+ PI_CODING_AGENT_DIR: resolve(state, "pi"),
25
+ MERCHANTDUO_SELECTED_ENV: selectedEnvironment,
26
+ MERCHANTDUO_CONFIG_PATH: configPath ?? "",
27
+ MERCHANTDUO_PACKAGE_VERSION: packageJson.version,
28
+ },
29
+ });
30
+ child.on("error", reject);
31
+ child.on("exit", (code) => resolveExit(code ?? 1));
32
+ });
33
+ }
@@ -0,0 +1,3 @@
1
+ import type { MerchantConfig } from "#config/schema";
2
+ import type { Environment } from "#environments/model";
3
+ export declare function doctor(cwd: string, name: string, environment: Environment, config: MerchantConfig): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { EnvironmentBackend } from "#environments/backend";
2
+ import { environmentWritable } from "#environments/model";
3
+ import { DefaultMagentoInspector } from "#magento/inspector";
4
+ export async function doctor(cwd, name, environment, config) {
5
+ const magento = await new DefaultMagentoInspector().inspect(new EnvironmentBackend(environment, cwd));
6
+ console.log(JSON.stringify({
7
+ environment: name,
8
+ type: environment.type,
9
+ writable: environmentWritable(environment),
10
+ magento,
11
+ activeTheme: config.activeTheme,
12
+ knowledge: { connected: false },
13
+ }, null, 2));
14
+ }
@@ -0,0 +1,9 @@
1
+ export interface InitInterview {
2
+ confirm(question: string): Promise<boolean>;
3
+ }
4
+ export declare class TerminalInitInterview implements InitInterview {
5
+ confirm(question: string): Promise<boolean>;
6
+ }
7
+ export declare function hasWardenEnvironment(cwd: string): Promise<boolean>;
8
+ export declare function initProject(cwd: string, interview?: InitInterview, output?: (message: string) => void): Promise<void>;
9
+ export declare const remoteEnvironmentExample = "\nTo add a remote environment later, add this under environments:\n\n stage:\n type: ssh\n host: shop-stage\n root: /var/www/html\n writable: false\n\nConfigure its user, port, and identity through ~/.ssh/config:\n\n Host shop-stage\n HostName stage.example.com\n User deploy\n Port 22\n IdentityFile ~/.ssh/id_ed25519";