create-mercato-app 0.6.7-develop.6553.1.2ec7246e04 → 0.6.7-develop.6565.1.1026d85fff

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 (69) hide show
  1. package/agentic/shared/AGENTS.md.template +1 -1
  2. package/agentic/shared/ai/skills/tiers.schema.json +15 -0
  3. package/agentic/shared/scripts/install-skills.sh +244 -37
  4. package/dist/agentic/guides/module-facts.json +53 -53
  5. package/dist/agentic/guides/modules/ai_assistant.md +1 -1
  6. package/dist/agentic/guides/modules/api_docs.md +1 -1
  7. package/dist/agentic/guides/modules/api_keys.md +1 -1
  8. package/dist/agentic/guides/modules/attachments.md +1 -1
  9. package/dist/agentic/guides/modules/audit_logs.md +1 -1
  10. package/dist/agentic/guides/modules/auth.md +1 -1
  11. package/dist/agentic/guides/modules/business_rules.md +1 -1
  12. package/dist/agentic/guides/modules/catalog.md +1 -1
  13. package/dist/agentic/guides/modules/channel_gmail.md +1 -1
  14. package/dist/agentic/guides/modules/channel_imap.md +1 -1
  15. package/dist/agentic/guides/modules/checkout.md +1 -1
  16. package/dist/agentic/guides/modules/communication_channels.md +1 -1
  17. package/dist/agentic/guides/modules/configs.md +1 -1
  18. package/dist/agentic/guides/modules/content.md +1 -1
  19. package/dist/agentic/guides/modules/currencies.md +1 -1
  20. package/dist/agentic/guides/modules/customer_accounts.md +1 -1
  21. package/dist/agentic/guides/modules/customers.md +1 -1
  22. package/dist/agentic/guides/modules/dashboards.md +1 -1
  23. package/dist/agentic/guides/modules/data_sync.md +1 -1
  24. package/dist/agentic/guides/modules/dictionaries.md +1 -1
  25. package/dist/agentic/guides/modules/directory.md +1 -1
  26. package/dist/agentic/guides/modules/entities.md +1 -1
  27. package/dist/agentic/guides/modules/events.md +1 -1
  28. package/dist/agentic/guides/modules/feature_toggles.md +1 -1
  29. package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
  30. package/dist/agentic/guides/modules/generators.md +1 -1
  31. package/dist/agentic/guides/modules/inbox_ops.md +1 -1
  32. package/dist/agentic/guides/modules/integrations.md +1 -1
  33. package/dist/agentic/guides/modules/messages.md +1 -1
  34. package/dist/agentic/guides/modules/notifications.md +1 -1
  35. package/dist/agentic/guides/modules/onboarding.md +1 -1
  36. package/dist/agentic/guides/modules/payment_gateways.md +1 -1
  37. package/dist/agentic/guides/modules/perspectives.md +1 -1
  38. package/dist/agentic/guides/modules/planner.md +1 -1
  39. package/dist/agentic/guides/modules/portal.md +1 -1
  40. package/dist/agentic/guides/modules/progress.md +1 -1
  41. package/dist/agentic/guides/modules/query_index.md +1 -1
  42. package/dist/agentic/guides/modules/record_locks.md +1 -1
  43. package/dist/agentic/guides/modules/resources.md +1 -1
  44. package/dist/agentic/guides/modules/sales.md +1 -1
  45. package/dist/agentic/guides/modules/scheduler.md +1 -1
  46. package/dist/agentic/guides/modules/search.md +1 -1
  47. package/dist/agentic/guides/modules/security.md +1 -1
  48. package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
  49. package/dist/agentic/guides/modules/sso.md +1 -1
  50. package/dist/agentic/guides/modules/staff.md +1 -1
  51. package/dist/agentic/guides/modules/storage_s3.md +1 -1
  52. package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
  53. package/dist/agentic/guides/modules/sync_excel.md +1 -1
  54. package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
  55. package/dist/agentic/guides/modules/translations.md +1 -1
  56. package/dist/agentic/guides/modules/webhooks.md +1 -1
  57. package/dist/agentic/guides/modules/workflows.md +1 -1
  58. package/dist/agentic/shared/AGENTS.md.template +1 -1
  59. package/dist/agentic/shared/ai/skills/tiers.schema.json +15 -0
  60. package/dist/agentic/shared/scripts/install-skills.sh +244 -37
  61. package/dist/index.js +22 -30
  62. package/package.json +3 -3
  63. package/template/AGENTS.md +1 -1
  64. package/template/scripts/dev-orchestration-log-policy.mjs +1 -0
  65. package/template/scripts/dev-runtime-log-policy.mjs +29 -10
  66. package/template/src/i18n/de.json +1 -0
  67. package/template/src/i18n/en.json +1 -0
  68. package/template/src/i18n/es.json +1 -0
  69. package/template/src/i18n/pl.json +1 -0
@@ -68,7 +68,7 @@ These per-module fact-sheets are **generated from source** and bundled for the m
68
68
 
69
69
  ### Skills: local + shared collection
70
70
 
71
- Skills come from two places. Repo-local skills (module scaffolding, UI design, troubleshooting, etc.) ship inside `.ai/skills/` and are installed into your agent harness by tier (see `.ai/skills/tiers.json`). The PR-automation and review skills below are maintained in the shared [open-mercato/skills](https://github.com/open-mercato/skills) collection — run **`yarn install-skills`** once to install the subset this app ships (`npx skills add`, then `npx skills update` on re-runs to refresh). `install-skills.sh` places each skill in whichever harness directory your CLI reads (`.agents/skills/` for Codex, `.claude/skills/<name>` for Claude Code, mirrored into `.codex/skills/`), so the tables below **reference external skills by name, not by a hard-coded harness path** — the directory depends on which agent you scaffolded, and pointing every reader at `.claude/skills/…` would mislead a Codex or Cursor scaffold. A folder under `.ai/skills/<name>/` matching an external skill is a repo-local **override** that the external skill reads on top of its built-in workflow (default-branch discovery, opt-in labels, `src/modules/…` layout). Repo-specific settings live in `.ai/agentic.config.json` and the tracker descriptor `.ai/trackers/github.md`.
71
+ Skills come from two places. Repo-local skills (module scaffolding, UI design, troubleshooting, etc.) ship inside `.ai/skills/` and are installed by tier (see `.ai/skills/tiers.json`). The PR-automation and review skills below are maintained in the shared [open-mercato/skills](https://github.com/open-mercato/skills) collection — run **`yarn install-skills`** once to install the subset this app ships (`npx skills add`, then `npx skills update` on re-runs to refresh). Both sources install into the canonical cross-agent directory **`.agents/skills/`**; Codex and Cursor read it natively, while Claude Code — which cannot — additionally gets per-skill symlinks under `.claude/skills/`. The tables below therefore **reference external skills by name, not by a hard-coded harness path**: the directory depends on which agent you scaffolded, and pointing every reader at `.claude/skills/…` would mislead a Codex or Cursor scaffold. A folder under `.ai/skills/<name>/` matching an external skill is a repo-local **override** that the external skill reads on top of its built-in workflow (default-branch discovery, opt-in labels, `src/modules/…` layout). Repo-specific settings live in `.ai/agentic.config.json` and the tracker descriptor `.ai/trackers/github.md`.
72
72
 
73
73
  ### Quality & Process
74
74
 
@@ -19,6 +19,21 @@
19
19
  },
20
20
  "description": "Tier names installed when install-skills.sh is invoked with no flags."
21
21
  },
22
+ "agents": {
23
+ "type": "object",
24
+ "additionalProperties": false,
25
+ "description": "Agent harness settings for install-skills.sh. Skills always install into the canonical .agents/skills/ directory; per-agent symlinks are created only for agents that cannot read it.",
26
+ "properties": {
27
+ "ignore": {
28
+ "type": "array",
29
+ "description": "Agents whose directories install-skills.sh must never write. Overridden by --ignore-agents.",
30
+ "items": {
31
+ "type": "string",
32
+ "enum": ["claude-code", "codex", "cursor"]
33
+ }
34
+ }
35
+ }
36
+ },
22
37
  "external": {
23
38
  "type": "object",
24
39
  "additionalProperties": false,
@@ -1,21 +1,34 @@
1
1
  #!/bin/sh
2
2
  set -eu
3
3
 
4
- # Skill installer for a standalone Open Mercato app (Claude Code + Codex).
5
- # Two sources are mixed in:
4
+ # Skill installer for a standalone Open Mercato app. Skills live in ONE
5
+ # canonical place — the cross-agent directory .agents/skills/ — and two sources
6
+ # are mixed into it:
6
7
  #
7
- # 1. Local tiered skills: reads .ai/skills/tiers.json and creates per-skill
8
- # symlinks under .claude/skills/ and .codex/skills/. If a legacy
9
- # directory-level symlink (e.g. .claude/skills -> ../.ai/skills) is found,
10
- # it is replaced with a real directory of per-skill symlinks.
8
+ # 1. Local tiered skills: reads .ai/skills/tiers.json and symlinks each
9
+ # selected skill into .agents/skills/<name> -> ../../.ai/skills/<name>.
10
+ # Legacy directory-level symlinks (e.g. .claude/skills -> ../.ai/skills)
11
+ # left by an older scaffold are cleaned up.
11
12
  # 2. External shared skills: installs the subset this app ships from the
12
- # open-mercato/skills collection via `npx skills add` into .agents/skills/
13
- # (read natively by Codex; the CLI also symlinks .claude/skills/<name>),
14
- # then `npx skills update` so a re-run refreshes them to the latest
15
- # published versions. The external source + explicit skill list live under
16
- # `external` in tiers.json. A folder under .ai/skills/ matching an external
17
- # skill name is a repo-local override the external skill reads in place; it
18
- # is never symlinked into the harness directories.
13
+ # open-mercato/skills collection via `npx skills add` into the same
14
+ # .agents/skills/ directory, then `npx skills update` so a re-run refreshes
15
+ # them to the latest published versions. The external source + explicit
16
+ # skill list live under `external` in tiers.json. A folder under .ai/skills/
17
+ # matching an external skill name is a repo-local override the external
18
+ # skill reads in place; it is never symlinked into the canonical or
19
+ # per-agent directories.
20
+ #
21
+ # Agent support matrix. Per-agent symlinks are created ONLY for agents that
22
+ # cannot read the canonical project-level .agents/skills/ directory:
23
+ #
24
+ # agent id project directory reads .agents/skills/ per-agent symlinks
25
+ # claude-code .claude/skills no yes (written by this script)
26
+ # codex .codex/skills yes no
27
+ # cursor .cursor/skills yes no
28
+ #
29
+ # The "reads .agents/skills/" column follows the universal-agent list of the
30
+ # `skills` CLI (vercel-labs/skills). An agent that cannot read the canonical
31
+ # directory MUST keep its symlinks, or its skills silently disappear.
19
32
  #
20
33
  # The scaffold is offline: `npx skills add`/`update` only run when you invoke
21
34
  # this script (yarn install-skills). Pass --no-external (or set
@@ -26,6 +39,8 @@ set -eu
26
39
  # install-skills.sh --with <csv> # default + extra tiers (additive)
27
40
  # install-skills.sh --tiers <csv> # exactly the listed tiers (replaces default)
28
41
  # install-skills.sh --all # every tier
42
+ # install-skills.sh --legacy-links # also symlink every skill into .claude/skills/ and .codex/skills/
43
+ # install-skills.sh --ignore-agents <csv> # never write these agents' directories
29
44
  # install-skills.sh --no-external # skip the npx external-skills install/update step (offline)
30
45
  # install-skills.sh --list # print tier table and exit
31
46
  # install-skills.sh --clean # remove all skill symlinks and exit
@@ -37,10 +52,19 @@ Usage: install-skills.sh [options]
37
52
 
38
53
  Options:
39
54
  (no options) Install the default tier set from .ai/skills/tiers.json
40
- plus the external open-mercato/skills subset this app ships.
55
+ plus the external open-mercato/skills subset this app ships
56
+ into the canonical .agents/skills/ directory. Agents that
57
+ cannot read it (Claude Code) also get per-skill symlinks.
41
58
  --with <csv> Install default tiers plus the given tier names (additive).
42
59
  --tiers <csv> Install exactly the given tier names (replaces default).
43
60
  --all Install every tier defined in tiers.json.
61
+ --legacy-links Restore the pre-canonical layout: symlink every skill into
62
+ .claude/skills/ AND .codex/skills/, even for agents that
63
+ read .agents/skills/ natively.
64
+ --ignore-agents <csv>
65
+ Never write these agents' directories (claude-code, codex,
66
+ cursor). Defaults to `agents.ignore` in tiers.json; this
67
+ flag overrides it.
44
68
  --no-external Skip the external-collection step — `npx skills add` on
45
69
  first run and `npx skills update` on re-runs (also:
46
70
  OM_SKIP_EXTERNAL_SKILLS=1). Use when offline.
@@ -91,6 +115,9 @@ with_csv=""
91
115
  tiers_csv=""
92
116
  list_only=0
93
117
  clean_only=0
118
+ legacy_links=0
119
+ ignore_agents_csv=""
120
+ ignore_agents_set=0
94
121
  # Any non-empty value other than "0" skips the external npx step.
95
122
  case "${OM_SKIP_EXTERNAL_SKILLS:-0}" in
96
123
  ""|0) no_external=0 ;;
@@ -125,6 +152,24 @@ while [ $# -gt 0 ]; do
125
152
  no_external=1
126
153
  shift
127
154
  ;;
155
+ --legacy-links)
156
+ legacy_links=1
157
+ shift
158
+ ;;
159
+ --ignore-agents)
160
+ if [ $# -lt 2 ]; then
161
+ echo "install-skills: --ignore-agents requires a comma-separated list of agent ids." >&2
162
+ exit 1
163
+ fi
164
+ ignore_agents_csv="$2"
165
+ ignore_agents_set=1
166
+ shift 2
167
+ ;;
168
+ --ignore-agents=*)
169
+ ignore_agents_csv="${1#--ignore-agents=}"
170
+ ignore_agents_set=1
171
+ shift
172
+ ;;
128
173
  --all)
129
174
  set_mode all
130
175
  shift
@@ -206,6 +251,96 @@ dedup_lines() {
206
251
 
207
252
  agents_dir="${repo_root}/.agents/skills"
208
253
 
254
+ # See the agent support matrix in the header comment.
255
+ known_agents="claude-code codex cursor"
256
+ legacy_agents="claude-code codex"
257
+
258
+ agent_dir() {
259
+ case "$1" in
260
+ claude-code) printf '%s' "${repo_root}/.claude/skills" ;;
261
+ codex) printf '%s' "${repo_root}/.codex/skills" ;;
262
+ cursor) printf '%s' "${repo_root}/.cursor/skills" ;;
263
+ esac
264
+ }
265
+
266
+ agent_reads_canonical() {
267
+ case "$1" in
268
+ codex|cursor) return 0 ;;
269
+ *) return 1 ;;
270
+ esac
271
+ }
272
+
273
+ agent_known() {
274
+ needle="$1"
275
+ for candidate in ${known_agents}; do
276
+ if [ "${candidate}" = "${needle}" ]; then
277
+ return 0
278
+ fi
279
+ done
280
+ return 1
281
+ }
282
+
283
+ is_legacy_agent() {
284
+ needle="$1"
285
+ for candidate in ${legacy_agents}; do
286
+ if [ "${candidate}" = "${needle}" ]; then
287
+ return 0
288
+ fi
289
+ done
290
+ return 1
291
+ }
292
+
293
+ # The CLI flag overrides `agents.ignore` in tiers.json.
294
+ if [ "${ignore_agents_set}" -eq 1 ]; then
295
+ ignored_agents=$(split_csv "${ignore_agents_csv}")
296
+ else
297
+ ignored_agents=$(jq -r '.agents.ignore[]?' "${manifest}")
298
+ fi
299
+
300
+ for agent in ${ignored_agents}; do
301
+ if ! agent_known "${agent}"; then
302
+ echo "install-skills: unknown agent '${agent}'." >&2
303
+ echo " Valid agents: $(printf '%s' "${known_agents}")" >&2
304
+ exit 1
305
+ fi
306
+ done
307
+
308
+ is_ignored_agent() {
309
+ needle="$1"
310
+ for candidate in ${ignored_agents}; do
311
+ if [ "${candidate}" = "${needle}" ]; then
312
+ return 0
313
+ fi
314
+ done
315
+ return 1
316
+ }
317
+
318
+ # Agents that get their own per-skill symlinks: in the default layout only the
319
+ # ones that cannot read .agents/skills/; with --legacy-links the historical pair.
320
+ link_agents=""
321
+ for agent in ${known_agents}; do
322
+ if is_ignored_agent "${agent}"; then
323
+ continue
324
+ fi
325
+ if [ "${legacy_links}" -eq 1 ]; then
326
+ if is_legacy_agent "${agent}"; then
327
+ link_agents="${link_agents} ${agent}"
328
+ fi
329
+ elif ! agent_reads_canonical "${agent}"; then
330
+ link_agents="${link_agents} ${agent}"
331
+ fi
332
+ done
333
+
334
+ is_link_agent() {
335
+ needle="$1"
336
+ for candidate in ${link_agents}; do
337
+ if [ "${candidate}" = "${needle}" ]; then
338
+ return 0
339
+ fi
340
+ done
341
+ return 1
342
+ }
343
+
209
344
  resolves_into_skills_dir() {
210
345
  link_path="$1"
211
346
  resolved=$(readlink -f -- "${link_path}" 2>/dev/null || true)
@@ -288,6 +423,18 @@ sweep_harness() {
288
423
  done
289
424
  }
290
425
 
426
+ # Drop links to skills the external collection no longer ships: their target
427
+ # under .agents/skills/ is gone, so the link dangles.
428
+ prune_broken_links() {
429
+ harness_dir="$1"
430
+ [ -d "${harness_dir}" ] || return 0
431
+ for entry in "${harness_dir}"/*; do
432
+ [ -L "${entry}" ] || continue
433
+ [ -e "${entry}" ] && continue
434
+ rm -f "${entry}"
435
+ done
436
+ }
437
+
291
438
  print_list() {
292
439
  for tier in ${all_tier_names}; do
293
440
  count=$(tier_skill_count "${tier}")
@@ -316,13 +463,16 @@ if [ "${list_only}" -eq 1 ]; then
316
463
  fi
317
464
 
318
465
  if [ "${clean_only}" -eq 1 ]; then
319
- clean_harness "${repo_root}/.claude/skills"
320
- clean_harness "${repo_root}/.codex/skills"
466
+ # Sweep every known agent directory (both the canonical link layer and any
467
+ # leftovers from the legacy per-agent layout), then the canonical directory.
468
+ for agent in ${known_agents}; do
469
+ clean_harness "$(agent_dir "${agent}")"
470
+ done
321
471
  if [ -d "${agents_dir}" ]; then
322
472
  rm -rf "${agents_dir}"
323
- echo "info: removed external skill copies under .agents/skills/."
473
+ echo "info: removed skills under .agents/skills/."
324
474
  fi
325
- echo "info: removed all skill symlinks under .claude/skills/ and .codex/skills/."
475
+ echo "info: removed all skill symlinks under .claude/skills/, .codex/skills/ and .cursor/skills/."
326
476
  exit 0
327
477
  fi
328
478
 
@@ -403,9 +553,9 @@ is_external_skill() {
403
553
  return 1
404
554
  }
405
555
 
406
- install_into_harness() {
407
- harness_dir="$1"
408
- prepare_harness_dir "${harness_dir}"
556
+ # Local tier skills live once, in the canonical cross-agent directory.
557
+ install_canonical() {
558
+ prepare_harness_dir "${agents_dir}"
409
559
  for skill in ${selected_skills}; do
410
560
  if is_external_skill "${skill}"; then
411
561
  # Owned by the external collection; a same-named .ai/skills/ folder is a
@@ -418,19 +568,59 @@ install_into_harness() {
418
568
  echo "install-skills: skill folder '${skill_target}' is missing on disk." >&2
419
569
  exit 1
420
570
  fi
421
- link_path="${harness_dir}/${skill}"
422
- ln -sfn "../../.ai/skills/${skill}" "${link_path}"
571
+ ln -sfn "../../.ai/skills/${skill}" "${agents_dir}/${skill}"
423
572
  done
573
+ sweep_harness "${agents_dir}" "${selected_skills}"
574
+ }
575
+
576
+ # Link layer for agents that cannot read the canonical directory. External
577
+ # skills are linked by the skills CLI itself (via the --agent args below), so
578
+ # only the local tier skills are linked here.
579
+ install_agent_links() {
580
+ agent="$1"
581
+ harness_dir=$(agent_dir "${agent}")
582
+ prepare_harness_dir "${harness_dir}"
583
+ for skill in ${selected_skills}; do
584
+ if is_external_skill "${skill}"; then
585
+ continue
586
+ fi
587
+ if [ "${legacy_links}" -eq 1 ]; then
588
+ ln -sfn "../../.ai/skills/${skill}" "${harness_dir}/${skill}"
589
+ else
590
+ ln -sfn "../../.agents/skills/${skill}" "${harness_dir}/${skill}"
591
+ fi
592
+ done
593
+ link_external_skills "${harness_dir}"
424
594
  sweep_harness "${harness_dir}" "${selected_skills}"
595
+ prune_broken_links "${harness_dir}"
425
596
  }
426
597
 
427
- install_into_harness "${repo_root}/.claude/skills"
428
- install_into_harness "${repo_root}/.codex/skills"
598
+ # Mirror every external skill (a real directory under the canonical dir) into an
599
+ # agent's own directory. Local tier skills are symlinks and are skipped here.
600
+ #
601
+ # `npx skills add --agent <id>` is supposed to write these links itself, but it
602
+ # only does so reliably for an agent directory that already exists (see
603
+ # vercel-labs/skills#744) and the canonical layout no longer pre-creates one. So
604
+ # the script owns this layer end to end: an agent that cannot read
605
+ # .agents/skills/ would otherwise silently lose the whole shared collection.
606
+ link_external_skills() {
607
+ harness_dir="$1"
608
+ [ -d "${agents_dir}" ] || return 0
609
+ for entry in "${agents_dir}"/*; do
610
+ [ -L "${entry}" ] && continue
611
+ [ -d "${entry}" ] || continue
612
+ external_name=$(basename "${entry}")
613
+ ln -sfn "../../.agents/skills/${external_name}" "${harness_dir}/${external_name}"
614
+ done
615
+ }
429
616
 
430
617
  # Mix in the external shared collection (open-mercato/skills). Only the explicit
431
618
  # subset this app ships (external.skills) is installed — never the whole
432
- # collection. The npx CLI copies each skill into .agents/skills/ and symlinks
433
- # .claude/skills/<name> because that directory exists.
619
+ # collection. The npx CLI copies each skill into .agents/skills/. This runs BEFORE
620
+ # the link layer is written: `skills update --project` owns .agents/skills/ and
621
+ # would otherwise prune entries it does not know about. The agent directories are
622
+ # written afterwards by install_agent_links, which does not rely on the CLI's own
623
+ # symlinking.
434
624
  external_source=$(jq -r '.external.source // empty' "${manifest}")
435
625
  external_status="none"
436
626
  # The skills CLI matches each --skill value against skill names verbatim (no
@@ -439,13 +629,21 @@ external_skill_args=""
439
629
  for external_skill in $(printf '%s\n' "${external_skills_list}" | dedup_lines); do
440
630
  external_skill_args="${external_skill_args} --skill ${external_skill}"
441
631
  done
632
+ external_agent_args=""
633
+ for agent in ${link_agents}; do
634
+ external_agent_args="${external_agent_args} --agent ${agent}"
635
+ done
636
+ if [ -z "${external_agent_args}" ]; then
637
+ # No agent needs its own directory — install into .agents/skills/ only.
638
+ external_agent_args=" --agent universal"
639
+ fi
442
640
  if [ -n "${external_source}" ] && [ -n "${external_skill_args}" ]; then
443
641
  if [ "${no_external}" = "1" ]; then
444
642
  external_status="skipped (--no-external)"
445
643
  elif ! command -v npx >/dev/null 2>&1; then
446
644
  external_status="skipped (npx not found)"
447
645
  echo "install-skills: warning: npx not found; skipping external skills from ${external_source}." >&2
448
- elif (cd "${repo_root}" && npx -y skills add "${external_source}" ${external_skill_args} --agent claude-code --agent codex -y); then
646
+ elif (cd "${repo_root}" && npx -y skills add "${external_source}" ${external_skill_args} ${external_agent_args} -y); then
449
647
  external_status="installed from ${external_source}"
450
648
  # `add` seeds the subset; a follow-up `update` bumps already-installed skills
451
649
  # to the latest published versions on a re-run. Non-fatal when offline mid-run.
@@ -455,15 +653,6 @@ if [ -n "${external_source}" ] && [ -n "${external_skill_args}" ]; then
455
653
  echo "install-skills: warning: could not update external skills to latest;" >&2
456
654
  echo " the installed versions are kept. Re-run when online to refresh." >&2
457
655
  fi
458
- # The npx CLI symlinks .claude/skills/<name> itself and expects Codex to read
459
- # .agents/skills/ natively; mirror the symlinks into .codex/skills/ too.
460
- if [ -d "${agents_dir}" ]; then
461
- mkdir -p "${repo_root}/.codex/skills"
462
- for external_entry in "${agents_dir}"/*; do
463
- [ -d "${external_entry}" ] || continue
464
- ln -sfn "../../.agents/skills/$(basename "${external_entry}")" "${repo_root}/.codex/skills/$(basename "${external_entry}")"
465
- done
466
- fi
467
656
  else
468
657
  external_status="FAILED"
469
658
  echo "install-skills: warning: installing external skills from ${external_source} failed;" >&2
@@ -471,6 +660,18 @@ if [ -n "${external_source}" ] && [ -n "${external_skill_args}" ]; then
471
660
  fi
472
661
  fi
473
662
 
663
+ install_canonical
664
+
665
+ for agent in ${known_agents}; do
666
+ if is_link_agent "${agent}"; then
667
+ install_agent_links "${agent}"
668
+ else
669
+ # Not a link target (reads the canonical directory, or ignored): drop any
670
+ # stale symlinks left behind by the legacy per-agent layout.
671
+ clean_harness "$(agent_dir "${agent}")"
672
+ fi
673
+ done
674
+
474
675
  skill_count=$(printf '%s\n' "${selected_skills}" | grep -c '.' || true)
475
676
  tier_count=$(printf '%s\n' "${selected_tiers}" | grep -c '.' || true)
476
677
  tier_summary=$(printf '%s\n' "${selected_tiers}" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
@@ -479,6 +680,12 @@ printf 'Installed %s local skills across %s tiers: %s.\n' "${skill_count}" "${ti
479
680
  if [ "${external_status}" != "none" ]; then
480
681
  printf 'External skills: %s.\n' "${external_status}"
481
682
  fi
683
+ link_summary=$(printf '%s' "${link_agents}" | sed 's/^ *//; s/ /, /g')
684
+ if [ -z "${link_summary}" ]; then
685
+ printf 'Layout: .agents/skills/ (canonical); no per-agent symlinks.\n'
686
+ else
687
+ printf 'Layout: .agents/skills/ (canonical); per-agent symlinks: %s.\n' "${link_summary}"
688
+ fi
482
689
 
483
690
  if [ -z "${mode}" ]; then
484
691
  cat <<'EOF'