@geraldmaron/construct 1.4.1 → 1.4.2

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 (63) hide show
  1. package/bin/construct +2 -1
  2. package/bin/construct-postinstall.mjs +27 -2
  3. package/config/tag-vocabulary.json +264 -0
  4. package/lib/config/schema.mjs +1 -1
  5. package/lib/doctor/diagnosis.mjs +20 -0
  6. package/lib/doctor/index.mjs +5 -0
  7. package/lib/embed/worker.mjs +5 -0
  8. package/lib/env-config.mjs +10 -2
  9. package/lib/export-validate.mjs +34 -2
  10. package/lib/host/readiness.mjs +109 -0
  11. package/lib/host-disposition.mjs +10 -1
  12. package/lib/install/stage-project.mjs +41 -4
  13. package/lib/intake/prepare.mjs +2 -0
  14. package/lib/mcp/destructive-approval.mjs +57 -0
  15. package/lib/mcp/server.mjs +208 -34
  16. package/lib/mcp/tool-rate-limit.mjs +47 -0
  17. package/lib/mcp/tool-safety.mjs +94 -0
  18. package/lib/mcp/tool-surface-parity.mjs +60 -0
  19. package/lib/mcp/tools/orchestration-run.mjs +45 -7
  20. package/lib/mcp/tools/project.mjs +25 -8
  21. package/lib/mcp/tools/storage.mjs +9 -1
  22. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  23. package/lib/mcp/tools/web-search.mjs +5 -76
  24. package/lib/mcp-platform-config.mjs +27 -18
  25. package/lib/oracle/daemon-entry.mjs +6 -0
  26. package/lib/orchestration/runtime.mjs +81 -42
  27. package/lib/orchestration/web-capability.mjs +59 -0
  28. package/lib/orchestration/worker.mjs +263 -19
  29. package/lib/output-quality.mjs +61 -2
  30. package/lib/path-policy.mjs +56 -0
  31. package/lib/providers/secret-audit-wiring.mjs +35 -18
  32. package/lib/providers/secret-resolver.mjs +28 -13
  33. package/lib/registry/catalog.mjs +6 -0
  34. package/lib/registry/loader.mjs +7 -1
  35. package/lib/registry/validate.mjs +3 -3
  36. package/lib/sandbox.mjs +1 -1
  37. package/lib/service-manager.mjs +59 -9
  38. package/lib/storage/admin.mjs +7 -2
  39. package/package.json +6 -1
  40. package/registry/agent-manifest.json +117 -0
  41. package/registry/capabilities.json +1880 -0
  42. package/schemas/brand-voice.schema.json +24 -0
  43. package/schemas/capability-registry.schema.json +72 -0
  44. package/schemas/certification-run.schema.json +130 -0
  45. package/schemas/demo-recording.schema.json +46 -0
  46. package/schemas/eval-dataset.schema.json +79 -0
  47. package/schemas/execution-capability-profile.schema.json +46 -0
  48. package/schemas/execution-policy.schema.json +114 -0
  49. package/schemas/improvement-proposal.schema.json +65 -0
  50. package/schemas/mcp-tool-output.schema.json +61 -0
  51. package/schemas/platform-capabilities.schema.json +83 -0
  52. package/schemas/project-config.schema.json +227 -0
  53. package/schemas/project-demo.schema.json +60 -0
  54. package/schemas/provider-behavior-matrix.schema.json +91 -0
  55. package/schemas/scope.schema.json +197 -0
  56. package/schemas/specialist-trace.schema.json +107 -0
  57. package/schemas/team.schema.json +99 -0
  58. package/schemas/unified-registry.schema.json +548 -0
  59. package/scripts/sync-specialists.mjs +52 -25
  60. package/specialists/org/specialists/cx-researcher.json +1 -0
  61. package/specialists/prompts/cx-researcher.md +9 -8
  62. package/vendor/pandoc-ext/README.md +3 -0
  63. package/vendor/pandoc-ext/diagram.lua +687 -0
@@ -0,0 +1,548 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://geraldmaron.github.io/construct/schemas/unified-registry.schema.json",
4
+ "title": "Construct Unified Registry",
5
+ "description": "Single source of truth for teams, specialists, roles, contracts, and policies. Consolidates what was previously split across five separate files: teams.json, teams-registry.json, registry.json, contracts.json, and role-manifests.json.",
6
+ "type": "object",
7
+ "required": ["version", "teams", "specialists", "contracts", "policies"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "version": {
11
+ "enum": [2, 3],
12
+ "description": "Schema version. Version 3 is the modular org assembly."
13
+ },
14
+ "teams": {
15
+ "type": "object",
16
+ "description": "Map of team id -> team definition. Teams are the primary organizational unit with explicit decision rights, escalation paths, and charter.",
17
+ "additionalProperties": {
18
+ "$ref": "#/$defs/team"
19
+ },
20
+ "minProperties": 1
21
+ },
22
+ "specialists": {
23
+ "type": "object",
24
+ "description": "Map of specialist id (e.g., cx-engineer) -> specialist definition. Every specialist declares exactly one team.",
25
+ "additionalProperties": {
26
+ "$ref": "#/$defs/specialist"
27
+ },
28
+ "minProperties": 1
29
+ },
30
+ "contracts": {
31
+ "type": "object",
32
+ "description": "Map of contract id -> contract definition. Contracts are producer→consumer handoffs with preconditions, postconditions, and schemas.",
33
+ "additionalProperties": {
34
+ "$ref": "#/$defs/contract"
35
+ }
36
+ },
37
+ "policies": {
38
+ "type": "object",
39
+ "description": "Map of policy id -> policy definition. Policies enforce team decision rights, approval requirements, and governance gates.",
40
+ "additionalProperties": {
41
+ "$ref": "#/$defs/policy"
42
+ }
43
+ }
44
+ },
45
+ "$defs": {
46
+ "team": {
47
+ "type": "object",
48
+ "description": "Organizational team with explicit owner, decision rights, forbidden decisions, escalation path, and charter.",
49
+ "required": ["id", "name", "owner", "roles", "charter"],
50
+ "additionalProperties": false,
51
+ "properties": {
52
+ "id": {
53
+ "type": "string",
54
+ "pattern": "^[a-z][a-z0-9-]{1,40}$",
55
+ "description": "Unique team identifier (e.g., product-group, engineering-group, quality-group)."
56
+ },
57
+ "name": {
58
+ "type": "string",
59
+ "minLength": 1,
60
+ "maxLength": 80,
61
+ "description": "Human-readable team name."
62
+ },
63
+ "owner": {
64
+ "type": "string",
65
+ "pattern": "^[a-z][a-z0-9-]{1,40}$",
66
+ "description": "Role id of the team owner/lead. Must be a role that has at least one specialist assigned."
67
+ },
68
+ "roles": {
69
+ "type": "array",
70
+ "minItems": 1,
71
+ "maxItems": 20,
72
+ "description": "List of role ids that constitute this team.",
73
+ "items": {
74
+ "type": "string",
75
+ "pattern": "^[a-z][a-z0-9-]{1,40}$"
76
+ }
77
+ },
78
+ "decisionRights": {
79
+ "type": "array",
80
+ "description": "Decisions this team is authorized to make (e.g., intake-triage, design-approval, deployment, scope-change).",
81
+ "items": {
82
+ "type": "string",
83
+ "pattern": "^[a-z][a-z0-9-]{1,40}$"
84
+ }
85
+ },
86
+ "forbiddenDecisions": {
87
+ "type": "array",
88
+ "description": "Decisions this team explicitly cannot make. Attempts escalate automatically.",
89
+ "items": {
90
+ "type": "string",
91
+ "pattern": "^[a-z][a-z0-9-]{1,40}$"
92
+ }
93
+ },
94
+ "escalationPath": {
95
+ "type": "array",
96
+ "description": "Chain of escalation for unresolved decisions. First element is the team owner role, then higher-level escalations.",
97
+ "minItems": 1,
98
+ "items": {
99
+ "type": "string",
100
+ "pattern": "^[a-z][a-z0-9-]{1,40}$"
101
+ }
102
+ },
103
+ "charter": {
104
+ "type": "string",
105
+ "minLength": 20,
106
+ "maxLength": 800,
107
+ "description": "One-paragraph mission statement: what this team owns, what it doesn't own, and key constraints."
108
+ },
109
+ "contact": {
110
+ "type": "object",
111
+ "description": "How to reach this team.",
112
+ "additionalProperties": false,
113
+ "properties": {
114
+ "slack": {
115
+ "type": "string",
116
+ "description": "Slack channel (e.g., #product, #engineering)."
117
+ },
118
+ "email": {
119
+ "type": "string",
120
+ "format": "email",
121
+ "description": "Team email address."
122
+ },
123
+ "owner": {
124
+ "type": "string",
125
+ "description": "Direct contact for the team owner."
126
+ }
127
+ }
128
+ },
129
+ "evidence": {
130
+ "type": "array",
131
+ "description": "Primary sources justifying this team's structure (interviews, job specs, ADRs, organizational research).",
132
+ "items": {
133
+ "type": "string"
134
+ }
135
+ },
136
+ "sources": {
137
+ "type": "array",
138
+ "description": "Integration resources this team binds (GitHub repos, Jira/Linear projects, Slack channels) with typed selectors and optional filters. Resolved by resolveTeamSources() and threaded into embed/provider_fetch.",
139
+ "items": { "$ref": "#/$defs/sourceTarget" }
140
+ }
141
+ }
142
+ },
143
+ "sourceTarget": {
144
+ "type": "object",
145
+ "description": "A typed integration binding. Deep-validated at runtime by lib/config/source-targets.mjs validateSourceTarget.",
146
+ "required": ["id", "provider", "selector"],
147
+ "additionalProperties": false,
148
+ "properties": {
149
+ "id": {
150
+ "type": "string",
151
+ "pattern": "^[A-Za-z0-9_-]{1,64}$",
152
+ "description": "Stable id for this binding (unique within the team's sources)."
153
+ },
154
+ "provider": {
155
+ "type": "string",
156
+ "enum": ["github", "jira", "linear", "slack"],
157
+ "description": "Integration provider."
158
+ },
159
+ "selector": {
160
+ "type": "object",
161
+ "description": "Provider-specific selector (github: {repo}, jira: {project}, linear: {team}, slack: {channel, intent?}).",
162
+ "minProperties": 1
163
+ },
164
+ "filters": {
165
+ "type": "object",
166
+ "description": "Optional per-target filters threaded into fetch (e.g., jira {jql}, github {refs, limit}, slack {oldest})."
167
+ }
168
+ }
169
+ },
170
+ "specialist": {
171
+ "type": "object",
172
+ "description": "An AI specialist (cx-* agent) with skills, fence, model tier, and event watchers.",
173
+ "required": ["name", "team"],
174
+ "additionalProperties": false,
175
+ "properties": {
176
+ "name": {
177
+ "type": "string",
178
+ "pattern": "^[a-z][a-z0-9-]{1,40}$",
179
+ "description": "Specialist id without cx- prefix (e.g., engineer, qa, architect). Combined with prefix to create cx-engineer, cx-qa, etc."
180
+ },
181
+ "displayName": {
182
+ "type": "string",
183
+ "description": "Human-readable specialist name (e.g., Engineer, Quality Assurance, Architect)."
184
+ },
185
+ "description": {
186
+ "type": "string",
187
+ "description": "Short description of what this specialist does."
188
+ },
189
+ "team": {
190
+ "type": "string",
191
+ "pattern": "^[a-z][a-z0-9-]{1,40}$",
192
+ "description": "Team id this specialist belongs to. Every specialist declares exactly one team."
193
+ },
194
+ "role": {
195
+ "type": "string",
196
+ "enum": ["owner", "member", "temporary"],
197
+ "description": "Role within the team: owner (team lead), member (regular), or temporary."
198
+ },
199
+ "modelTier": {
200
+ "type": "string",
201
+ "enum": ["standard", "reasoning"],
202
+ "description": "Which model tier to use for this specialist: standard or reasoning."
203
+ },
204
+ "reasoningEffort": {
205
+ "type": "string",
206
+ "enum": ["low", "medium", "high"],
207
+ "description": "Effort level for extended thinking (if using reasoning model)."
208
+ },
209
+ "skills": {
210
+ "type": "array",
211
+ "description": "List of skill paths this specialist has (e.g., docs/prd-workflow, architecture/api-design).",
212
+ "items": {
213
+ "type": "string"
214
+ }
215
+ },
216
+ "events": {
217
+ "type": "array",
218
+ "description": "Trigger events that activate this specialist (e.g., test.fail, handoff.received, pr.opened).",
219
+ "items": {
220
+ "type": "string"
221
+ }
222
+ },
223
+ "fence": {
224
+ "type": "object",
225
+ "description": "Access control fence for this specialist.",
226
+ "additionalProperties": false,
227
+ "properties": {
228
+ "allowedPaths": {
229
+ "type": "array",
230
+ "description": "Paths/glob patterns this specialist can read/write.",
231
+ "items": {
232
+ "type": "string"
233
+ }
234
+ },
235
+ "allowedCommands": {
236
+ "type": "array",
237
+ "description": "Commands this specialist can execute (bash, bd, git, etc.).",
238
+ "items": {
239
+ "type": "string"
240
+ }
241
+ },
242
+ "allowedBdLabels": {
243
+ "type": "array",
244
+ "description": "Beads labels this specialist can use when creating/updating issues.",
245
+ "items": {
246
+ "type": "string"
247
+ }
248
+ },
249
+ "approvalRequired": {
250
+ "type": "array",
251
+ "description": "Actions requiring approval before execution (e.g., commit, push, edit:lib/**).",
252
+ "items": {
253
+ "type": "string"
254
+ }
255
+ }
256
+ }
257
+ },
258
+ "docArtifacts": {
259
+ "type": "array",
260
+ "description": "Doc artifact types this specialist can author (e.g., prd, adr, memo, incident-report).",
261
+ "items": {
262
+ "type": "string"
263
+ }
264
+ },
265
+ "watchConditions": {
266
+ "type": "array",
267
+ "description": "Conditions that should trigger proactive engagement (e.g., high-ambiguity-deep-work, flaky-tests, stale-alignment-census).",
268
+ "items": {
269
+ "type": "string"
270
+ }
271
+ },
272
+ "prompt": {
273
+ "type": "object",
274
+ "description": "Optional prompt configuration overrides for this specialist.",
275
+ "additionalProperties": false,
276
+ "properties": {
277
+ "file": {
278
+ "type": "string",
279
+ "description": "Path to specialist prompt file."
280
+ },
281
+ "customInstructions": {
282
+ "type": "string",
283
+ "description": "Custom instructions appended to the base prompt."
284
+ }
285
+ }
286
+ },
287
+ "permissions": {
288
+ "type": "object",
289
+ "description": "Fine-grained permissions (edit, bash, webfetch, etc.).",
290
+ "additionalProperties": {
291
+ "type": "string",
292
+ "enum": ["allow", "deny"]
293
+ }
294
+ }
295
+ }
296
+ },
297
+ "contract": {
298
+ "type": "object",
299
+ "description": "Service contract between producer and consumer specialists. Defines inputs, outputs, preconditions, postconditions, and team boundaries.",
300
+ "required": ["id", "producer", "consumer"],
301
+ "additionalProperties": false,
302
+ "properties": {
303
+ "id": {
304
+ "type": "string",
305
+ "pattern": "^[a-z][a-z0-9-]{1,50}$",
306
+ "description": "Unique contract identifier (e.g., construct-to-orchestrator, researcher-to-architect)."
307
+ },
308
+ "producer": {
309
+ "type": "string",
310
+ "description": "Specialist id or role that produces the output (e.g., cx-researcher, user, construct)."
311
+ },
312
+ "consumer": {
313
+ "type": "string",
314
+ "description": "Specialist id or role that consumes the output (e.g., cx-architect, cx-product-manager, construct)."
315
+ },
316
+ "trigger": {
317
+ "type": "object",
318
+ "description": "Conditions that trigger this contract.",
319
+ "additionalProperties": true
320
+ },
321
+ "input": {
322
+ "type": "object",
323
+ "description": "Input shape and requirements.",
324
+ "additionalProperties": true,
325
+ "properties": {
326
+ "shape": {
327
+ "type": "string",
328
+ "description": "Named shape (e.g., task-packet, research-brief, evidence-brief)."
329
+ },
330
+ "mustContain": {
331
+ "type": "array",
332
+ "description": "Fields that must be present in the input.",
333
+ "items": {
334
+ "type": "string"
335
+ }
336
+ }
337
+ }
338
+ },
339
+ "output": {
340
+ "type": "object",
341
+ "description": "Output shape and requirements.",
342
+ "additionalProperties": true,
343
+ "properties": {
344
+ "shape": {
345
+ "type": "string",
346
+ "description": "Named shape (e.g., routed-plan, decision, prd-handoff)."
347
+ },
348
+ "mustContain": {
349
+ "type": "array",
350
+ "description": "Fields that must be present in the output.",
351
+ "items": {
352
+ "type": "string"
353
+ }
354
+ },
355
+ "schema": {
356
+ "type": "string",
357
+ "description": "Path to JSON Schema for the output."
358
+ }
359
+ }
360
+ },
361
+ "preconditions": {
362
+ "type": "array",
363
+ "description": "Conditions that must be true before this contract executes.",
364
+ "items": {
365
+ "type": "string"
366
+ }
367
+ },
368
+ "postconditions": {
369
+ "type": "array",
370
+ "description": "Conditions that must be true after this contract completes.",
371
+ "items": {
372
+ "type": "string"
373
+ }
374
+ },
375
+ "teamBoundary": {
376
+ "type": "object",
377
+ "description": "Whether this handoff crosses a team boundary and what approval is needed.",
378
+ "additionalProperties": false,
379
+ "properties": {
380
+ "crosses": {
381
+ "type": "boolean",
382
+ "description": "Whether this contract crosses a team boundary."
383
+ },
384
+ "producerTeam": {
385
+ "type": "string",
386
+ "description": "Team id of the producer."
387
+ },
388
+ "consumerTeam": {
389
+ "type": "string",
390
+ "description": "Team id of the consumer."
391
+ },
392
+ "requiresApprovalFrom": {
393
+ "type": "array",
394
+ "description": "Team ids that must approve this cross-team handoff.",
395
+ "items": {
396
+ "type": "string"
397
+ }
398
+ }
399
+ }
400
+ },
401
+ "description": {
402
+ "type": "string",
403
+ "description": "Human-readable description of this contract."
404
+ },
405
+ "skillHints": {
406
+ "type": "array",
407
+ "description": "Optional routing hints — skill ids that inform dispatch context. Producer/consumer ids remain authoritative.",
408
+ "items": {
409
+ "type": "string",
410
+ "minLength": 1
411
+ },
412
+ "maxItems": 12
413
+ }
414
+ }
415
+ },
416
+ "policy": {
417
+ "type": "object",
418
+ "description": "Governance policy enforcing team decision rights, approval requirements, and gates.",
419
+ "required": ["id", "owner"],
420
+ "additionalProperties": false,
421
+ "properties": {
422
+ "id": {
423
+ "type": "string",
424
+ "pattern": "^[a-z][a-z0-9-]{1,50}$",
425
+ "description": "Unique policy identifier (e.g., release-gates, security-approval, design-approval)."
426
+ },
427
+ "owner": {
428
+ "type": "string",
429
+ "pattern": "^[a-z][a-z0-9-]{1,40}$",
430
+ "description": "Team id that owns and enforces this policy."
431
+ },
432
+ "description": {
433
+ "type": "string",
434
+ "description": "Human-readable description of what this policy enforces."
435
+ },
436
+ "enforcement": {
437
+ "type": "string",
438
+ "enum": ["hard", "soft"],
439
+ "description": "hard = blocks execution, soft = warns but allows."
440
+ },
441
+ "mode": {
442
+ "type": "string",
443
+ "enum": ["approval", "veto", "audit", "escalation"],
444
+ "description": "How this policy is enforced: approval (must approve), veto (can block), audit (log only), escalation (route upward)."
445
+ },
446
+ "requiresApprovalFrom": {
447
+ "type": "array",
448
+ "description": "Team ids that must approve before execution.",
449
+ "items": {
450
+ "type": "string"
451
+ }
452
+ },
453
+ "mayVetoFrom": {
454
+ "type": "array",
455
+ "description": "Team ids that can veto this decision.",
456
+ "items": {
457
+ "type": "string"
458
+ }
459
+ },
460
+ "escalatesTo": {
461
+ "type": "array",
462
+ "description": "Team ids to escalate to if blocked or disputed.",
463
+ "items": {
464
+ "type": "string"
465
+ }
466
+ },
467
+ "decisionRights": {
468
+ "type": "array",
469
+ "description": "Decisions this policy governs (e.g., deployment, scope-change, security-approval).",
470
+ "items": {
471
+ "type": "string"
472
+ }
473
+ },
474
+ "conditions": {
475
+ "type": "array",
476
+ "description": "Conditions under which this policy applies.",
477
+ "items": {
478
+ "type": "string"
479
+ }
480
+ },
481
+ "evidence": {
482
+ "type": "array",
483
+ "description": "References to source documents or decisions justifying this policy.",
484
+ "items": {
485
+ "type": "string"
486
+ }
487
+ }
488
+ }
489
+ }
490
+ },
491
+ "allOf": [
492
+ {
493
+ "description": "Validate that every specialist declares exactly one team, and that team exists.",
494
+ "not": {
495
+ "required": ["_validateSpecialistTeams"],
496
+ "properties": {
497
+ "_validateSpecialistTeams": {
498
+ "const": false
499
+ }
500
+ }
501
+ }
502
+ },
503
+ {
504
+ "description": "Validate that every team has at least one specialist assigned to it.",
505
+ "not": {
506
+ "required": ["_validateTeamAssignment"],
507
+ "properties": {
508
+ "_validateTeamAssignment": {
509
+ "const": false
510
+ }
511
+ }
512
+ }
513
+ },
514
+ {
515
+ "description": "Validate that every team owner is a role, and that role has at least one specialist.",
516
+ "not": {
517
+ "required": ["_validateTeamOwner"],
518
+ "properties": {
519
+ "_validateTeamOwner": {
520
+ "const": false
521
+ }
522
+ }
523
+ }
524
+ },
525
+ {
526
+ "description": "Validate that every contract producer and consumer exist in the specialists map.",
527
+ "not": {
528
+ "required": ["_validateContractSpecialists"],
529
+ "properties": {
530
+ "_validateContractSpecialists": {
531
+ "const": false
532
+ }
533
+ }
534
+ }
535
+ },
536
+ {
537
+ "description": "Validate that no specialist names collide across teams.",
538
+ "not": {
539
+ "required": ["_validateSpecialistNames"],
540
+ "properties": {
541
+ "_validateSpecialistNames": {
542
+ "const": false
543
+ }
544
+ }
545
+ }
546
+ }
547
+ ]
548
+ }
@@ -117,7 +117,6 @@ function unifiedToLegacyRegistry(unified) {
117
117
  };
118
118
  }
119
119
 
120
- clearCache();
121
120
  clearCache();
122
121
  const unified = loadRegistry({ rootDir: root });
123
122
  const registry = unifiedToLegacyRegistry(unified);
@@ -773,7 +772,7 @@ function orchestrationMicroPrompt(platform) {
773
772
  `Example — the user says "add rate limiting to the API". Your first action is a tool call, not prose:\n` +
774
773
  ` call ${policyTool} { "request": "add rate limiting to the API" }\n` +
775
774
  `If the route is focused/orchestrated specialist work, call \`${runTool}\` with the same request. If the route suggests a workflow such as \`research-synthesis\`, pass it as \`workflow_type\`. Do not narrate completed research unless \`${runTool}\` or evidence tools actually ran.\n\n` +
776
- `If a request needs a capability you lack — live web/network access, external data, code execution — do not refuse or tell the user to run it themselves. Route it via \`${runTool}\` to the specialist that holds the capability (web access lives with the researcher), or ask one clarifying question when the target is ambiguous.`
775
+ `If a request needs a capability you lack — live web/network access, external data, code execution — route it via \`${runTool}\` to the specialist that holds it (the researcher performs live web retrieval when a web path is available). Do not tell the user to run it themselves. But if \`${runTool}\` reports the capability was unavailable (degraded with \`capability-unavailable\`, or a prepare-only result), say plainly it could not be reached and return an insufficient-evidence result — never fabricate URLs, dates, quotes, or citations. Ask one clarifying question when the target is ambiguous.`
777
776
  );
778
777
  }
779
778
 
@@ -1368,15 +1367,14 @@ When using this prompt, stay within the role above and adapt to the current repo
1368
1367
  // recognized here, so the orchestrator needs its own VS Code agent or it lists
1369
1368
  // in the picker with no usable tools.
1370
1369
 
1371
- const COPILOT_AGENT_TOOLS = [
1372
- "construct-mcp/*",
1373
- "web/fetch",
1374
- "web/githubRepo",
1370
+ export const COPILOT_AGENT_TOOLS = [
1371
+ "construct-mcp/orchestration_policy",
1372
+ "construct-mcp/orchestration_run",
1373
+ "construct-mcp/orchestration_readiness",
1375
1374
  "search/codebase",
1376
1375
  "search/usages",
1377
1376
  "search/fileSearch",
1378
1377
  "read/problems",
1379
- "edit/editFiles",
1380
1378
  ];
1381
1379
 
1382
1380
  function copilotAgentFile(entry, allEntries) {
@@ -1495,32 +1493,61 @@ function getVSCodeUserMcpPaths() {
1495
1493
 
1496
1494
  export function mcpEntryPointsOutsideToolkit(entry, root) {
1497
1495
  const args = Array.isArray(entry?.args) ? entry.args : [];
1498
- return args.some(
1499
- (arg) => typeof arg === "string"
1500
- && /\/lib\/mcp\/[a-z0-9-]+\.mjs$/.test(arg)
1501
- && !arg.startsWith(`${root}/`),
1502
- );
1496
+ return args.some((arg) => {
1497
+ if (typeof arg !== "string") return false;
1498
+ const normalArg = arg.replace(/\\/g, "/");
1499
+ const normalRoot = root.replace(/\\/g, "/");
1500
+ return /\/lib\/mcp\/[a-z0-9-]+\.mjs$/i.test(normalArg)
1501
+ && !normalArg.startsWith(`${normalRoot}/`);
1502
+ });
1503
1503
  }
1504
1504
 
1505
- // VS Code scans both `.github/agents` and `.claude/agents`, so the orchestrator
1506
- // would list twice once with VS Code tools (.github/agents) and once with
1507
- // Claude tool names VS Code ignores (.claude/agents). `chat.agentFilesLocations`
1508
- // is the documented lever to pin the scan; its power over the built-in
1509
- // `.claude/agents` compatibility scan is version-dependent, so this is a
1510
- // best-effort hint, not a guarantee. Merge only into a strictly-parseable file
1511
- // and never overwrite an existing choice, so a commented (JSONC) or
1512
- // user-customized settings.json is left untouched.
1505
+ // Workspace defaults Construct manages for VS Code chat. `chat.agentFilesLocations`
1506
+ // pins the agent scan to `.github/agents` so the orchestrator does not list twice
1507
+ // (VS Code also scans `.claude/agents`, whose Claude tool names it ignores); its
1508
+ // power over that built-in compatibility scan is version-dependent, so it is a
1509
+ // best-effort hint. `chat.mcp.autostart` (VS Code ≥1.105, string enum) set to
1510
+ // `always` eager-starts MCP servers so `construct-mcp` is live without a manual
1511
+ // Start each session — the orchestrator's first move is an MCP call, so a dormant
1512
+ // server otherwise reads as "enable the MCP server". Neither removes the one-time
1513
+ // per-developer MCP trust grant, which VS Code stores locally, not in committed
1514
+ // config. Each key is applied only when unset, and a settings.json that is not
1515
+ // strict JSON (commented/JSONC or user-customized) is left untouched.
1516
+
1517
+ const VSCODE_MANAGED_SETTINGS = {
1518
+ "chat.agentFilesLocations": { ".github/agents": true, ".claude/agents": false },
1519
+ "chat.mcp.autoStart": "always",
1520
+ };
1521
+
1522
+ // Strip full-line JSONC comments (`// …`) and trailing commas before JSON.parse.
1523
+ // Handles the common VS Code settings.json patterns (line comments, trailing commas);
1524
+ // does not attempt to handle inline comments after values.
1525
+
1526
+ function parseJsoncContent(text) {
1527
+ const stripped = text
1528
+ .split('\n')
1529
+ .map((line) => {
1530
+ const t = line.trimStart();
1531
+ return t.startsWith('//') ? '' : line;
1532
+ })
1533
+ .join('\n')
1534
+ .replace(/,(\s*[}\]])/g, '$1');
1535
+ return JSON.parse(stripped);
1536
+ }
1513
1537
 
1514
- export function pinVscodeAgentLocations(targetDir) {
1538
+ export function pinVscodeChatSettings(targetDir) {
1515
1539
  if (DRY_RUN) return;
1516
1540
  const settingsPath = path.join(targetDir, ".vscode", "settings.json");
1517
1541
  let settings = {};
1518
1542
  if (fs.existsSync(settingsPath)) {
1519
- try { settings = JSON.parse(fs.readFileSync(settingsPath, "utf8")) || {}; }
1543
+ try { settings = parseJsoncContent(fs.readFileSync(settingsPath, "utf8")) || {}; }
1520
1544
  catch { return; }
1521
1545
  }
1522
- if (settings["chat.agentFilesLocations"]) return;
1523
- settings["chat.agentFilesLocations"] = { ".github/agents": true, ".claude/agents": false };
1546
+ let changed = false;
1547
+ for (const [key, value] of Object.entries(VSCODE_MANAGED_SETTINGS)) {
1548
+ if (settings[key] === undefined) { settings[key] = value; changed = true; }
1549
+ }
1550
+ if (!changed) return;
1524
1551
  mkdirp(path.dirname(settingsPath));
1525
1552
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1526
1553
  }
@@ -1581,7 +1608,7 @@ function syncVSCode(targetDir = null, wants = true) {
1581
1608
  mkdirp(path.dirname(mcpPath));
1582
1609
  fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
1583
1610
  }
1584
- pinVscodeAgentLocations(targetDir);
1611
+ pinVscodeChatSettings(targetDir);
1585
1612
  return true;
1586
1613
  }
1587
1614