@open-mercato/cli 0.7.1-develop.7193.1.910a5b0a1e → 0.7.1-develop.7194.1.ab4fc81f82

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 (169) hide show
  1. package/.turbo/turbo-build.log +6 -2
  2. package/dist/agentic/github-copilot/copilot-instructions.md.template +37 -0
  3. package/dist/agentic/github-copilot/instructions/entity-guard.instructions.md +27 -0
  4. package/dist/agentic/github-copilot/instructions/generated-guard.instructions.md +10 -0
  5. package/dist/agentic/github-copilot/mcp.json.example +13 -0
  6. package/dist/agentic/guides/module-facts.json +11527 -283
  7. package/dist/agentic/guides/module-facts.v2.json +11527 -283
  8. package/dist/agentic/guides/modules/agent_orchestrator/acl-features.md +31 -0
  9. package/dist/agentic/guides/modules/agent_orchestrator/active-extension-bindings.md +12 -0
  10. package/dist/agentic/guides/modules/agent_orchestrator/ai-agents.md +12 -0
  11. package/dist/agentic/guides/modules/agent_orchestrator/ai-tools-mcp-capabilities.md +13 -0
  12. package/dist/agentic/guides/modules/agent_orchestrator/backend-pages.md +28 -0
  13. package/dist/agentic/guides/modules/agent_orchestrator/cli-commands.md +14 -0
  14. package/dist/agentic/guides/modules/agent_orchestrator/contribution-resolutions.md +49 -0
  15. package/dist/agentic/guides/modules/agent_orchestrator/di-registrations-rich.md +42 -0
  16. package/dist/agentic/guides/modules/agent_orchestrator/di-service-tokens.md +24 -0
  17. package/dist/agentic/guides/modules/agent_orchestrator/domain-commands.md +28 -0
  18. package/dist/agentic/guides/modules/agent_orchestrator/encryption.md +20 -0
  19. package/dist/agentic/guides/modules/agent_orchestrator/entities.md +31 -0
  20. package/dist/agentic/guides/modules/agent_orchestrator/events.md +38 -0
  21. package/dist/agentic/guides/modules/agent_orchestrator/exact-override-targets.md +122 -0
  22. package/dist/agentic/guides/modules/agent_orchestrator/host-extension-points.md +10 -0
  23. package/dist/agentic/guides/modules/agent_orchestrator/index.md +31 -0
  24. package/dist/agentic/guides/modules/agent_orchestrator/owned-contract-module-metadata.md +11 -0
  25. package/dist/agentic/guides/modules/agent_orchestrator/setup.md +11 -0
  26. package/dist/agentic/guides/modules/agent_orchestrator/umes-contributions.md +49 -0
  27. package/dist/agentic/guides/modules/agent_orchestrator/umes-hosts.md +61 -0
  28. package/dist/agentic/guides/modules/agent_orchestrator/workers.md +14 -0
  29. package/dist/agentic/guides/modules/ai_assistant/di-registrations-rich.md +3 -2
  30. package/dist/agentic/guides/modules/ai_assistant/di-service-tokens.md +2 -1
  31. package/dist/agentic/guides/modules/ai_assistant/exact-override-targets.md +3 -2
  32. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  33. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  34. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  35. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  36. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  37. package/dist/agentic/guides/modules/auth/index.md +1 -1
  38. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  39. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  40. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  41. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  42. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  43. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  44. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  45. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  46. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  47. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  48. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  49. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  50. package/dist/agentic/guides/modules/configs/index.md +1 -1
  51. package/dist/agentic/guides/modules/content/index.md +1 -1
  52. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  53. package/dist/agentic/guides/modules/customer_accounts/cli-commands.md +11 -0
  54. package/dist/agentic/guides/modules/customer_accounts/exact-override-targets.md +5 -4
  55. package/dist/agentic/guides/modules/customer_accounts/index.md +3 -2
  56. package/dist/agentic/guides/modules/customer_accounts/setup.md +1 -1
  57. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
  58. package/dist/agentic/guides/modules/customers/exact-override-targets.md +1 -0
  59. package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +3 -0
  60. package/dist/agentic/guides/modules/customers/index.md +2 -2
  61. package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
  62. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  63. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  64. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  65. package/dist/agentic/guides/modules/devices/index.md +1 -1
  66. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  67. package/dist/agentic/guides/modules/directory/index.md +1 -1
  68. package/dist/agentic/guides/modules/documents/index.md +1 -1
  69. package/dist/agentic/guides/modules/entities/index.md +1 -1
  70. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  71. package/dist/agentic/guides/modules/events/index.md +1 -1
  72. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  73. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  74. package/dist/agentic/guides/modules/generators/index.md +1 -1
  75. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  76. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  77. package/dist/agentic/guides/modules/messages/index.md +1 -1
  78. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  79. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  80. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  81. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  82. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  83. package/dist/agentic/guides/modules/planner/index.md +1 -1
  84. package/dist/agentic/guides/modules/portal/index.md +1 -1
  85. package/dist/agentic/guides/modules/progress/index.md +1 -1
  86. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  87. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  88. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  89. package/dist/agentic/guides/modules/resources/index.md +1 -1
  90. package/dist/agentic/guides/modules/sales/index.md +1 -1
  91. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  92. package/dist/agentic/guides/modules/search/index.md +1 -1
  93. package/dist/agentic/guides/modules/security/index.md +1 -1
  94. package/dist/agentic/guides/modules/seeds/cli-commands.md +14 -0
  95. package/dist/agentic/guides/modules/seeds/exact-override-targets.md +14 -0
  96. package/dist/agentic/guides/modules/seeds/index.md +14 -0
  97. package/dist/agentic/guides/modules/seeds/owned-contract-module-metadata.md +11 -0
  98. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  99. package/dist/agentic/guides/modules/sso/index.md +1 -1
  100. package/dist/agentic/guides/modules/staff/index.md +1 -1
  101. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  102. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  103. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  104. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  105. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  106. package/dist/agentic/guides/modules/translations/index.md +1 -1
  107. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  108. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  109. package/dist/agentic/guides/modules/wms/index.md +1 -1
  110. package/dist/agentic/guides/modules/workflows/acl-features.md +11 -1
  111. package/dist/agentic/guides/modules/workflows/active-extension-bindings.md +4 -1
  112. package/dist/agentic/guides/modules/workflows/ai-tools-mcp-capabilities.md +17 -0
  113. package/dist/agentic/guides/modules/workflows/backend-pages.md +3 -0
  114. package/dist/agentic/guides/modules/workflows/contribution-resolutions.md +24 -0
  115. package/dist/agentic/guides/modules/workflows/di-registrations-rich.md +11 -7
  116. package/dist/agentic/guides/modules/workflows/di-service-tokens.md +11 -7
  117. package/dist/agentic/guides/modules/workflows/domain-commands.md +11 -0
  118. package/dist/agentic/guides/modules/workflows/entities.md +2 -0
  119. package/dist/agentic/guides/modules/workflows/events.md +14 -8
  120. package/dist/agentic/guides/modules/workflows/exact-override-targets.md +45 -10
  121. package/dist/agentic/guides/modules/workflows/frontend-pages.md +2 -0
  122. package/dist/agentic/guides/modules/workflows/incoming-installed-contributions.md +17 -0
  123. package/dist/agentic/guides/modules/workflows/index.md +15 -12
  124. package/dist/agentic/guides/modules/workflows/notifications.md +2 -0
  125. package/dist/agentic/guides/modules/workflows/setup.md +1 -1
  126. package/dist/agentic/guides/modules/workflows/umes-contributions.md +24 -0
  127. package/dist/agentic/guides/modules/workflows/umes-hosts.md +14 -6
  128. package/dist/agentic/guides/modules/workflows/workers.md +4 -1
  129. package/dist/agentic/guides/reference-module-facts.json +2 -2
  130. package/dist/agentic/guides/reference-modules/example/index.md +1 -1
  131. package/dist/agentic/guides/upstream/AGENTS.md +3 -1
  132. package/dist/agentic/guides/upstream/manifest.json +2 -2
  133. package/dist/agentic/shared/ai/harness/design-system-inventory.json +118 -106
  134. package/dist/agentic/shared/ai/harness/source-link-inventory.json +8 -8
  135. package/dist/lib/agentic-init.js +4 -0
  136. package/dist/lib/agentic-init.js.map +2 -2
  137. package/dist/lib/agentic-setup.js +19 -5
  138. package/dist/lib/agentic-setup.js.map +2 -2
  139. package/dist/lib/generators/extensions/agent-files.js +913 -0
  140. package/dist/lib/generators/extensions/agent-files.js.map +7 -0
  141. package/dist/lib/generators/extensions/index.js +2 -0
  142. package/dist/lib/generators/extensions/index.js.map +2 -2
  143. package/dist/lib/generators/index.js +3 -1
  144. package/dist/lib/generators/index.js.map +2 -2
  145. package/dist/lib/generators/module-registry.js +40 -11
  146. package/dist/lib/generators/module-registry.js.map +2 -2
  147. package/dist/lib/generators/route-collisions.js +35 -0
  148. package/dist/lib/generators/route-collisions.js.map +7 -0
  149. package/dist/lib/generators/web-research-adapters.js +95 -0
  150. package/dist/lib/generators/web-research-adapters.js.map +7 -0
  151. package/dist/mercato.js +5 -2
  152. package/dist/mercato.js.map +2 -2
  153. package/package.json +6 -6
  154. package/src/__tests__/agent-files.test.ts +199 -0
  155. package/src/__tests__/mercato.test.ts +14 -0
  156. package/src/lib/__tests__/agentic-init.test.ts +11 -0
  157. package/src/lib/__tests__/agentic-setup.ownership.test.ts +5 -0
  158. package/src/lib/agentic-init.ts +4 -0
  159. package/src/lib/agentic-setup.ts +30 -5
  160. package/src/lib/generators/__tests__/agent-files-extension.test.ts +477 -0
  161. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +25 -8
  162. package/src/lib/generators/__tests__/route-collisions.test.ts +155 -0
  163. package/src/lib/generators/extensions/agent-files.ts +1332 -0
  164. package/src/lib/generators/extensions/index.ts +2 -0
  165. package/src/lib/generators/index.ts +1 -0
  166. package/src/lib/generators/module-registry.ts +66 -17
  167. package/src/lib/generators/route-collisions.ts +57 -0
  168. package/src/lib/generators/web-research-adapters.ts +133 -0
  169. package/src/mercato.ts +4 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.7.1-develop.7193.1.910a5b0a1e",
3
+ "version": "0.7.1-develop.7194.1.ab4fc81f82",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,9 +60,9 @@
60
60
  "@mikro-orm/decorators": "^7.1.14",
61
61
  "@mikro-orm/migrations": "^7.1.14",
62
62
  "@mikro-orm/postgresql": "^7.1.14",
63
- "@open-mercato/queue": "0.7.1-develop.7193.1.910a5b0a1e",
64
- "@open-mercato/shared": "0.7.1-develop.7193.1.910a5b0a1e",
65
- "@open-mercato/telemetry": "0.7.1-develop.7193.1.910a5b0a1e",
63
+ "@open-mercato/queue": "0.7.1-develop.7194.1.ab4fc81f82",
64
+ "@open-mercato/shared": "0.7.1-develop.7194.1.ab4fc81f82",
65
+ "@open-mercato/telemetry": "0.7.1-develop.7194.1.ab4fc81f82",
66
66
  "cross-spawn": "^7.0.6",
67
67
  "pg": "8.23.0",
68
68
  "semver": "^7.8.5",
@@ -73,10 +73,10 @@
73
73
  "typescript-js": "npm:typescript@6.0.3"
74
74
  },
75
75
  "peerDependencies": {
76
- "@open-mercato/shared": "0.7.1-develop.7193.1.910a5b0a1e"
76
+ "@open-mercato/shared": "0.7.1-develop.7194.1.ab4fc81f82"
77
77
  },
78
78
  "devDependencies": {
79
- "@open-mercato/shared": "0.7.1-develop.7193.1.910a5b0a1e",
79
+ "@open-mercato/shared": "0.7.1-develop.7194.1.ab4fc81f82",
80
80
  "@types/jest": "^30.0.0",
81
81
  "jest": "^30.4.2",
82
82
  "ts-jest": "^29.4.12"
@@ -0,0 +1,199 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import { createAgentFilesExtension } from '../lib/generators/extensions/agent-files'
5
+ import type { ModuleScanContext } from '../lib/generators/extension'
6
+
7
+ /**
8
+ * Build a throwaway repo root carrying the `docker/opencode` + `packages`
9
+ * sentinels the extension uses to locate the repo, plus a module `agents/` tree
10
+ * under the app base. Returns the paths the extension's fs side effect targets.
11
+ */
12
+ function makeRepo(): {
13
+ root: string
14
+ appBase: string
15
+ pkgBase: string
16
+ dockerAgentsDir: string
17
+ manifestPath: string
18
+ } {
19
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-files-repo-'))
20
+ fs.mkdirSync(path.join(root, 'docker', 'opencode'), { recursive: true })
21
+ fs.mkdirSync(path.join(root, 'packages'), { recursive: true })
22
+ const appBase = path.join(root, 'apps', 'mercato', 'src', 'modules', 'agent_examples')
23
+ const pkgBase = path.join(root, 'packages', 'core', 'src', 'modules', '__none__')
24
+ fs.mkdirSync(appBase, { recursive: true })
25
+ fs.mkdirSync(pkgBase, { recursive: true })
26
+ return {
27
+ root,
28
+ appBase,
29
+ pkgBase,
30
+ dockerAgentsDir: path.join(root, 'docker', 'opencode', 'agents'),
31
+ manifestPath: path.join(
32
+ root,
33
+ 'packages',
34
+ 'enterprise',
35
+ 'src',
36
+ 'modules',
37
+ 'agent_orchestrator',
38
+ 'generated',
39
+ 'file-agents.generated.ts',
40
+ ),
41
+ }
42
+ }
43
+
44
+ function writeAgent(
45
+ agentDir: string,
46
+ files: { claude: string; outcome: string },
47
+ ): void {
48
+ fs.mkdirSync(agentDir, { recursive: true })
49
+ fs.writeFileSync(path.join(agentDir, 'AGENT.md'), files.claude, 'utf8')
50
+ fs.writeFileSync(path.join(agentDir, 'OUTCOME.md'), files.outcome, 'utf8')
51
+ }
52
+
53
+ const PRIMARY_CLAUDE = [
54
+ '---',
55
+ 'id: deals.health_check',
56
+ 'label: Deal health check',
57
+ 'description: Assess a deal and propose the next stage.',
58
+ 'subAgents: [deals.activity_scan]',
59
+ '---',
60
+ 'You assess deal health.',
61
+ ].join('\n')
62
+
63
+ const PRIMARY_OUTCOME = [
64
+ '---',
65
+ 'kind: proposal',
66
+ '---',
67
+ '```json',
68
+ JSON.stringify({
69
+ type: 'object',
70
+ required: ['rationale'],
71
+ properties: { rationale: { type: 'string', minLength: 1 } },
72
+ }),
73
+ '```',
74
+ ].join('\n')
75
+
76
+ const SUB_CLAUDE = [
77
+ '---',
78
+ 'id: deals.activity_scan',
79
+ 'label: Activity scan',
80
+ 'description: Scan recent deal activity.',
81
+ '---',
82
+ 'You scan activity.',
83
+ ].join('\n')
84
+
85
+ const SUB_OUTCOME = [
86
+ '---',
87
+ 'kind: research',
88
+ '---',
89
+ '```json',
90
+ JSON.stringify({
91
+ type: 'object',
92
+ required: ['momentum'],
93
+ properties: { momentum: { type: 'string', minLength: 1 } },
94
+ }),
95
+ '```',
96
+ ].join('\n')
97
+
98
+ function makeCtx(repo: ReturnType<typeof makeRepo>, moduleId: string): ModuleScanContext {
99
+ return {
100
+ moduleId,
101
+ roots: { appBase: repo.appBase, pkgBase: repo.pkgBase },
102
+ imps: {} as ModuleScanContext['imps'],
103
+ importIdRef: { value: 0 },
104
+ sharedImports: [],
105
+ resolveModuleFile: (() => null) as unknown as ModuleScanContext['resolveModuleFile'],
106
+ resolveFirstModuleFile: (() => null) as unknown as ModuleScanContext['resolveFirstModuleFile'],
107
+ processStandaloneConfig: () => null,
108
+ sanitizeGeneratedModuleSpecifier: (importPath: string) => importPath,
109
+ }
110
+ }
111
+
112
+ describe('agent-files generator (Phase 4 sub-agents)', () => {
113
+ const created: string[] = []
114
+ afterAll(() => {
115
+ for (const root of created) fs.rmSync(root, { recursive: true, force: true })
116
+ })
117
+
118
+ it('emits a subagent .md, wires the primary task permission, and nests the manifest descriptor', () => {
119
+ const repo = makeRepo()
120
+ created.push(repo.root)
121
+ const agentDir = path.join(repo.appBase, 'agents', 'deals_health_check')
122
+ writeAgent(agentDir, { claude: PRIMARY_CLAUDE, outcome: PRIMARY_OUTCOME })
123
+ writeAgent(path.join(agentDir, 'sub-agents', 'activity_scan'), {
124
+ claude: SUB_CLAUDE,
125
+ outcome: SUB_OUTCOME,
126
+ })
127
+
128
+ const extension = createAgentFilesExtension()
129
+ extension.scanModule(makeCtx(repo, 'agent_examples'))
130
+ extension.generateOutput()
131
+
132
+ const primaryMd = fs.readFileSync(
133
+ path.join(repo.dockerAgentsDir, 'deals_health_check.md'),
134
+ 'utf8',
135
+ )
136
+ const subMd = fs.readFileSync(path.join(repo.dockerAgentsDir, 'deals_activity_scan.md'), 'utf8')
137
+
138
+ // Sub-agent file is mode: subagent, read-only, and may NOT delegate (task deny).
139
+ expect(subMd).toContain('mode: subagent')
140
+ expect(subMd).toContain('write: deny')
141
+ expect(subMd).toContain('task: deny')
142
+ expect(subMd).not.toContain('"task": true')
143
+
144
+ // Primary allows the task tool and whitelists ONLY its sub-agent's name.
145
+ expect(primaryMd).toContain('mode: primary')
146
+ // Delegation runs through the orchestrator's own MCP tool, never OpenCode's
147
+ // native `task`: the server-side path is depth-capped, read-only and takes
148
+ // its scope from the run context rather than from model-supplied input. So
149
+ // `task` stays denied on the primary agent too, and the generated allowlist
150
+ // names the delegate tool instead.
151
+ expect(primaryMd).toContain('"open-mercato_agent_orchestrator_delegate_agent": true')
152
+ expect(primaryMd).toContain('task: deny')
153
+ expect(primaryMd).not.toContain('"task": true')
154
+ expect(primaryMd).toContain('## Sub-agents')
155
+
156
+ // Manifest carries the sub-agent as a nested descriptor.
157
+ const manifest = fs.readFileSync(repo.manifestPath, 'utf8')
158
+ expect(manifest).toContain('deals.health_check')
159
+ expect(manifest).toContain('subAgentDescriptors')
160
+ expect(manifest).toContain('deals.activity_scan')
161
+ })
162
+
163
+ it('fails generation when a sub-agent is proposal (only the primary proposes)', () => {
164
+ const repo = makeRepo()
165
+ created.push(repo.root)
166
+ const agentDir = path.join(repo.appBase, 'agents', 'deals_health_check')
167
+ writeAgent(agentDir, { claude: PRIMARY_CLAUDE, outcome: PRIMARY_OUTCOME })
168
+ writeAgent(path.join(agentDir, 'sub-agents', 'bad'), {
169
+ claude: SUB_CLAUDE,
170
+ // proposal sub-agent — must be rejected
171
+ outcome: PRIMARY_OUTCOME,
172
+ })
173
+
174
+ const extension = createAgentFilesExtension()
175
+ expect(() => extension.scanModule(makeCtx(repo, 'agent_examples'))).toThrow(/research/i)
176
+ })
177
+
178
+ it('fails generation when a sub-agent declares its own subAgents (depth cap = 1)', () => {
179
+ const repo = makeRepo()
180
+ created.push(repo.root)
181
+ const agentDir = path.join(repo.appBase, 'agents', 'deals_health_check')
182
+ writeAgent(agentDir, { claude: PRIMARY_CLAUDE, outcome: PRIMARY_OUTCOME })
183
+ writeAgent(path.join(agentDir, 'sub-agents', 'nested'), {
184
+ claude: [
185
+ '---',
186
+ 'id: deals.nested',
187
+ 'label: Nested',
188
+ 'description: Nested sub-agent.',
189
+ 'subAgents: [deals.deeper]',
190
+ '---',
191
+ 'body',
192
+ ].join('\n'),
193
+ outcome: SUB_OUTCOME,
194
+ })
195
+
196
+ const extension = createAgentFilesExtension()
197
+ expect(() => extension.scanModule(makeCtx(repo, 'agent_examples'))).toThrow(/depth cap/i)
198
+ })
199
+ })
@@ -359,6 +359,7 @@ describe('init command failure output', () => {
359
359
  generateModuleEntities: jest.fn().mockResolvedValue(undefined),
360
360
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
361
361
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
362
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
362
363
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
363
364
  }))
364
365
  jest.doMock('../lib/resolver', () => ({
@@ -405,6 +406,7 @@ describe('init command failure output', () => {
405
406
  generateModuleEntities: jest.fn().mockResolvedValue(undefined),
406
407
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
407
408
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
409
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
408
410
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
409
411
  }))
410
412
  jest.doMock('../lib/resolver', () => ({
@@ -454,6 +456,7 @@ describe('init command failure output', () => {
454
456
  generateModuleEntities: jest.fn().mockResolvedValue(undefined),
455
457
  generateModuleDi: jest.fn().mockResolvedValue(undefined),
456
458
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
459
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
457
460
  generateOpenApi: jest.fn().mockResolvedValue(undefined),
458
461
  }))
459
462
  jest.doMock('../lib/db', () => ({
@@ -554,6 +557,7 @@ describe('generate post-step structural invalidation', () => {
554
557
  const generateModuleEntities = jest.fn().mockResolvedValue(undefined)
555
558
  const generateModuleDi = jest.fn().mockResolvedValue(undefined)
556
559
  const generateModulePackageSources = jest.fn().mockResolvedValue(undefined)
560
+ const generateWebResearchAdapters = jest.fn().mockResolvedValue(undefined)
557
561
  const generateOpenApi = jest.fn().mockResolvedValue(undefined)
558
562
  const invalidate = jest.fn().mockResolvedValue({
559
563
  cacheEntriesDeleted: 2,
@@ -568,6 +572,7 @@ describe('generate post-step structural invalidation', () => {
568
572
  generateModuleEntities,
569
573
  generateModuleDi,
570
574
  generateModulePackageSources,
575
+ generateWebResearchAdapters,
571
576
  generateOpenApi,
572
577
  }))
573
578
  jest.doMock('../lib/resolver', () => ({
@@ -610,6 +615,7 @@ describe('generate post-step structural invalidation', () => {
610
615
  generateModuleDi: jest.fn().mockResolvedValue(unchangedResult),
611
616
  generateModulePackageSources: jest.fn().mockResolvedValue(unchangedResult),
612
617
  generateOpenApi: jest.fn().mockResolvedValue(unchangedResult),
618
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(unchangedResult),
613
619
  }
614
620
  const invalidate = jest.fn()
615
621
 
@@ -649,6 +655,7 @@ describe('generate post-step structural invalidation', () => {
649
655
  const generateModuleEntities = jest.fn().mockResolvedValue(undefined)
650
656
  const generateModuleDi = jest.fn().mockResolvedValue(undefined)
651
657
  const generateModulePackageSources = jest.fn().mockResolvedValue(undefined)
658
+ const generateWebResearchAdapters = jest.fn().mockResolvedValue(undefined)
652
659
  const generateOpenApi = jest.fn().mockResolvedValue(undefined)
653
660
  const invalidate = jest.fn().mockRejectedValue(new Error('cache maintenance unavailable'))
654
661
 
@@ -658,6 +665,7 @@ describe('generate post-step structural invalidation', () => {
658
665
  generateModuleEntities,
659
666
  generateModuleDi,
660
667
  generateModulePackageSources,
668
+ generateWebResearchAdapters,
661
669
  generateOpenApi,
662
670
  }))
663
671
  jest.doMock('../lib/resolver', () => ({
@@ -695,6 +703,7 @@ describe('generate post-step structural invalidation', () => {
695
703
  generateModuleDi: generate,
696
704
  generateModulePackageSources: generate,
697
705
  generateOpenApi: generate,
706
+ generateWebResearchAdapters: generate,
698
707
  }))
699
708
  jest.doMock('../lib/resolver', () => ({
700
709
  createResolver: () => ({
@@ -800,6 +809,7 @@ describe('server dev managed process exits', () => {
800
809
  })
801
810
  jest.doMock('../lib/generators', () => ({
802
811
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
812
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
803
813
  }))
804
814
  jest.doMock('../lib/resolver', () => ({
805
815
  resolveEnvironment: () => ({
@@ -841,6 +851,7 @@ describe('server dev managed process exits', () => {
841
851
  })
842
852
  jest.doMock('../lib/generators', () => ({
843
853
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
854
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
844
855
  }))
845
856
  jest.doMock('../lib/resolver', () => ({
846
857
  resolveEnvironment: () => ({
@@ -1209,6 +1220,7 @@ describe('server dev managed process exits', () => {
1209
1220
  })
1210
1221
  jest.doMock('../lib/generators', () => ({
1211
1222
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
1223
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
1212
1224
  }))
1213
1225
  jest.doMock('../lib/resolver', () => ({
1214
1226
  resolveEnvironment: () => ({
@@ -1363,6 +1375,7 @@ describe('server dev managed process exits', () => {
1363
1375
  })
1364
1376
  jest.doMock('../lib/generators', () => ({
1365
1377
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
1378
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
1366
1379
  }))
1367
1380
  jest.doMock('../lib/resolver', () => ({
1368
1381
  resolveEnvironment: () => ({
@@ -1643,6 +1656,7 @@ describe('server start managed process exits', () => {
1643
1656
  }))
1644
1657
  jest.doMock('../lib/generators', () => ({
1645
1658
  generateModulePackageSources: jest.fn().mockResolvedValue(undefined),
1659
+ generateWebResearchAdapters: jest.fn().mockResolvedValue(undefined),
1646
1660
  }))
1647
1661
  jest.doMock('../lib/resolver', () => ({
1648
1662
  resolveEnvironment: () => ({
@@ -94,6 +94,13 @@ describe('resolveRelevantAgenticFiles', () => {
94
94
  ])
95
95
  })
96
96
 
97
+ it('resolves github-copilot existing files', () => {
98
+ expect(loadActualModule().resolveRelevantAgenticFiles('github-copilot')).toEqual([
99
+ '.github/copilot-instructions.md',
100
+ '.vscode/mcp.json.example',
101
+ ])
102
+ })
103
+
97
104
  it('falls back to the full known file list when no tool is provided', () => {
98
105
  expect(loadActualModule().resolveRelevantAgenticFiles()).toEqual([
99
106
  'CLAUDE.md',
@@ -101,6 +108,8 @@ describe('resolveRelevantAgenticFiles', () => {
101
108
  '.mcp.json.example',
102
109
  '.codex/mcp.json.example',
103
110
  '.cursor/hooks.json',
111
+ '.github/copilot-instructions.md',
112
+ '.vscode/mcp.json.example',
104
113
  ])
105
114
  })
106
115
 
@@ -111,6 +120,8 @@ describe('resolveRelevantAgenticFiles', () => {
111
120
  '.mcp.json.example',
112
121
  '.codex/mcp.json.example',
113
122
  '.cursor/hooks.json',
123
+ '.github/copilot-instructions.md',
124
+ '.vscode/mcp.json.example',
114
125
  ])
115
126
  })
116
127
  })
@@ -397,9 +397,14 @@ describe('runAgenticSetup ownership modes', () => {
397
397
  expect(existsSync(join(appDir, '.claude', 'settings.json'))).toBe(true)
398
398
  expect(existsSync(join(appDir, '.codex', 'mcp.json.example'))).toBe(true)
399
399
  expect(existsSync(join(appDir, '.cursor', 'hooks.json'))).toBe(false)
400
+ expect(existsSync(join(appDir, '.github', 'copilot-instructions.md'))).toBe(false)
400
401
  const tiers = JSON.parse(readFileSync(join(appDir, '.ai', 'skills', 'tiers.json'), 'utf8')) as {
401
402
  agents?: { ignore?: string[] }
402
403
  }
404
+ // Every SKILL-MANAGED tool the app did not select is ignored, so the skills
405
+ // installer skips its links. GitHub Copilot is deliberately absent: it reads
406
+ // `.github/` instruction files rather than a skills directory, and the
407
+ // installer rejects its id as an unknown agent.
403
408
  expect(tiers.agents?.ignore).toEqual(['cursor'])
404
409
  })
405
410
  })
@@ -21,6 +21,10 @@ const TOOL_EXISTING_FILES: Record<string, string[]> = {
21
21
  'cursor': [
22
22
  '.cursor/hooks.json',
23
23
  ],
24
+ 'github-copilot': [
25
+ '.github/copilot-instructions.md',
26
+ '.vscode/mcp.json.example',
27
+ ],
24
28
  }
25
29
 
26
30
  function parseArgs(args: string[]): AgenticInitOptions {
@@ -811,18 +811,39 @@ function generateCursor(config: AgenticConfig): void {
811
811
  // which scripts/install-skills.sh populates.
812
812
  }
813
813
 
814
+ function generateGithubCopilot(config: AgenticConfig): void {
815
+ const { targetDir } = config
816
+ const srcDir = join(AGENTIC_DIR, 'github-copilot')
817
+
818
+ writeTemplate(srcDir, 'copilot-instructions.md.template', join(targetDir, '.github', 'copilot-instructions.md'), config)
819
+ copyFile(srcDir, 'instructions/entity-guard.instructions.md', join(targetDir, '.github', 'instructions', 'entity-guard.instructions.md'))
820
+ copyFile(srcDir, 'instructions/generated-guard.instructions.md', join(targetDir, '.github', 'instructions', 'generated-guard.instructions.md'))
821
+ copyFile(srcDir, 'mcp.json.example', join(targetDir, '.vscode', 'mcp.json.example'))
822
+
823
+ // No .github/skills directory: Copilot reads the canonical .agents/skills/,
824
+ // which scripts/install-skills.sh populates.
825
+ }
814
826
  // ─── Wizard ──────────────────────────────────────────────────────────────
815
827
 
816
828
  const TOOLS = [
817
829
  { key: '1', label: 'Claude Code (Anthropic)', id: 'claude-code' },
818
830
  { key: '2', label: 'Codex (OpenAI)', id: 'codex' },
819
831
  { key: '3', label: 'Cursor (Anysphere)', id: 'cursor' },
820
- { key: '4', label: 'Multiple tools (select individually)', id: 'multiple' },
821
- { key: '5', label: 'Skip — set up manually later', id: 'skip' },
832
+ { key: '4', label: 'GitHub Copilot (GitHub)', id: 'github-copilot' },
833
+ { key: '5', label: 'Multiple tools (select individually)', id: 'multiple' },
834
+ { key: '6', label: 'Skip — set up manually later', id: 'skip' },
822
835
  ] as const
823
836
 
824
837
  const SELECTABLE = TOOLS.filter((t) => t.id !== 'multiple' && t.id !== 'skip')
825
838
 
839
+ /**
840
+ * The agents whose SKILL directories `scripts/install-skills.mjs` manages.
841
+ * GitHub Copilot is selectable here but reads instruction files under
842
+ * `.github/`, not a skills directory, so the installer does not know that id —
843
+ * naming it in `agents.ignore` aborts the install with "unknown agent".
844
+ */
845
+ const SKILL_MANAGED_AGENT_IDS = ['claude-code', 'codex', 'cursor']
846
+
826
847
  function persistedAgentSelection(targetDir: string): string[] | null {
827
848
  const selectableIds = SELECTABLE.map((tool) => tool.id)
828
849
  const tiersPath = join(targetDir, '.ai', 'skills', 'tiers.json')
@@ -865,9 +886,9 @@ async function promptSelection(ask: AskFn): Promise<string[]> {
865
886
 
866
887
  const answer = (await ask(' Enter number(s) separated by comma [1]: ')).trim() || '1'
867
888
 
868
- if (answer === '5') return ['skip']
889
+ if (answer === '6') return ['skip']
869
890
 
870
- if (answer === '4') {
891
+ if (answer === '5') {
871
892
  const selected: string[] = []
872
893
  for (const tool of SELECTABLE) {
873
894
  const yn = await ask(` Include ${tool.label}? [y/N]: `)
@@ -962,6 +983,9 @@ export async function runAgenticSetup(
962
983
  if (selectedIds.includes('cursor')) {
963
984
  console.log(' ✓ Cursor — .cursor/rules/, .cursor/hooks/, .cursor/mcp.json.example')
964
985
  }
986
+ if (selectedIds.includes('github-copilot')) {
987
+ console.log(' ✓ GitHub Copilot — .github/copilot-instructions.md, .github/instructions/, .vscode/mcp.json.example')
988
+ }
965
989
  if (config.experimentalHooksValidator) {
966
990
  console.log(' ✓ Experimental gate-evidence/typecheck validator hooks')
967
991
  }
@@ -978,6 +1002,7 @@ function generateHarness(config: AgenticConfig, selectedIds: string[]): void {
978
1002
  if (selectedIds.includes('claude-code')) generateClaudeCode(config)
979
1003
  if (selectedIds.includes('codex')) generateCodex(config)
980
1004
  if (selectedIds.includes('cursor')) generateCursor(config)
1005
+ if (selectedIds.includes('github-copilot')) generateGithubCopilot(config)
981
1006
 
982
1007
  enforceGeneratedRootBudget(config)
983
1008
 
@@ -1003,7 +1028,7 @@ function enforceGeneratedRootBudget(
1003
1028
  function persistAgentSelection(targetDir: string, selectedIds: string[]): void {
1004
1029
  const manifestPath = join(targetDir, '.ai', 'skills', 'tiers.json')
1005
1030
  if (!existsSync(manifestPath)) return
1006
- const ignore = SELECTABLE.map((tool) => tool.id).filter((id) => !selectedIds.includes(id))
1031
+ const ignore = SKILL_MANAGED_AGENT_IDS.filter((id) => !selectedIds.includes(id))
1007
1032
  const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8')) as Record<string, unknown>
1008
1033
  if (ignore.length > 0) {
1009
1034
  manifest.agents = { ignore }