@open-mercato/cli 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.
- package/.turbo/turbo-build.log +3 -3
- package/AGENTS.md +1 -0
- package/dist/agentic/guides/module-facts.json +53 -53
- package/dist/agentic/guides/modules/ai_assistant.md +1 -1
- package/dist/agentic/guides/modules/api_docs.md +1 -1
- package/dist/agentic/guides/modules/api_keys.md +1 -1
- package/dist/agentic/guides/modules/attachments.md +1 -1
- package/dist/agentic/guides/modules/audit_logs.md +1 -1
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail.md +1 -1
- package/dist/agentic/guides/modules/channel_imap.md +1 -1
- package/dist/agentic/guides/modules/checkout.md +1 -1
- package/dist/agentic/guides/modules/communication_channels.md +1 -1
- package/dist/agentic/guides/modules/configs.md +1 -1
- package/dist/agentic/guides/modules/content.md +1 -1
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/dashboards.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +1 -1
- package/dist/agentic/guides/modules/directory.md +1 -1
- package/dist/agentic/guides/modules/entities.md +1 -1
- package/dist/agentic/guides/modules/events.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
- package/dist/agentic/guides/modules/generators.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +1 -1
- package/dist/agentic/guides/modules/notifications.md +1 -1
- package/dist/agentic/guides/modules/onboarding.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways.md +1 -1
- package/dist/agentic/guides/modules/perspectives.md +1 -1
- package/dist/agentic/guides/modules/planner.md +1 -1
- package/dist/agentic/guides/modules/portal.md +1 -1
- package/dist/agentic/guides/modules/progress.md +1 -1
- package/dist/agentic/guides/modules/query_index.md +1 -1
- package/dist/agentic/guides/modules/record_locks.md +1 -1
- package/dist/agentic/guides/modules/resources.md +1 -1
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/scheduler.md +1 -1
- package/dist/agentic/guides/modules/search.md +1 -1
- package/dist/agentic/guides/modules/security.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
- package/dist/agentic/guides/modules/sso.md +1 -1
- package/dist/agentic/guides/modules/staff.md +1 -1
- package/dist/agentic/guides/modules/storage_s3.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
- package/dist/agentic/guides/modules/sync_excel.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
- package/dist/agentic/guides/modules/translations.md +1 -1
- package/dist/agentic/guides/modules/webhooks.md +1 -1
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/agentic/shared/AGENTS.md.template +1 -1
- package/dist/agentic/shared/ai/skills/tiers.schema.json +15 -0
- package/dist/agentic/shared/scripts/install-skills.sh +244 -37
- package/dist/lib/__integration__/TC-INT-008.spec.js +9 -11
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +14 -2
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generate-watch-events.js +106 -0
- package/dist/lib/generate-watch-events.js.map +7 -0
- package/dist/lib/generators/entity-ids.js +20 -13
- package/dist/lib/generators/entity-ids.js.map +2 -2
- package/dist/lib/generators/openapi.js +114 -12
- package/dist/lib/generators/openapi.js.map +2 -2
- package/dist/lib/in-process-generate-watcher.js +13 -2
- package/dist/lib/in-process-generate-watcher.js.map +2 -2
- package/dist/mercato.js +68 -23
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +53 -2
- package/src/lib/__integration__/TC-INT-008.spec.ts +15 -15
- package/src/lib/__tests__/generate-watch-events.test.ts +112 -0
- package/src/lib/__tests__/generate-watch-structure.test.ts +20 -0
- package/src/lib/__tests__/in-process-generate-watcher.test.ts +193 -2
- package/src/lib/agentic-setup.ts +23 -4
- package/src/lib/generate-watch-events.ts +125 -0
- package/src/lib/generators/__tests__/entity-ids.test.ts +98 -1
- package/src/lib/generators/__tests__/openapi.test.ts +174 -0
- package/src/lib/generators/entity-ids.ts +33 -13
- package/src/lib/generators/openapi.ts +167 -14
- package/src/lib/in-process-generate-watcher.ts +35 -6
- package/src/mercato.ts +75 -27
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
set -eu
|
|
3
3
|
|
|
4
|
-
# Skill installer for a standalone Open Mercato app
|
|
5
|
-
#
|
|
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
|
|
8
|
-
#
|
|
9
|
-
# directory-level
|
|
10
|
-
#
|
|
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
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# skill
|
|
18
|
-
#
|
|
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
|
-
|
|
320
|
-
|
|
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
|
|
473
|
+
echo "info: removed skills under .agents/skills/."
|
|
324
474
|
fi
|
|
325
|
-
echo "info: removed all skill symlinks under .claude/skills/ and .
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
prepare_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
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
|
433
|
-
#
|
|
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}
|
|
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'
|
|
@@ -221,23 +221,21 @@ test.describe("TC-INT-008: CLI agentic init mirrors standalone scaffolding asset
|
|
|
221
221
|
expect(cursorRulesSource).toContain("sample-store");
|
|
222
222
|
expect(fs.existsSync(path.join(appDir, ".ai", "skills", "om-spec-writing"))).toBe(false);
|
|
223
223
|
expect(fs.existsSync(path.join(appDir, "scripts", "install-skills.sh"))).toBe(true);
|
|
224
|
-
const cursorSkillsLinkPath = path.join(appDir, ".cursor", "skills");
|
|
225
|
-
expect(fs.lstatSync(cursorSkillsLinkPath).isSymbolicLink()).toBe(true);
|
|
226
|
-
expect(normalizePath(fs.readlinkSync(cursorSkillsLinkPath))).toBe("../.ai/skills");
|
|
227
224
|
const tiersManifest = JSON.parse(
|
|
228
225
|
fs.readFileSync(path.join(agenticRoot, "shared", "ai", "skills", "tiers.json"), "utf8")
|
|
229
226
|
);
|
|
230
227
|
const defaultTierSkills = tiersManifest.default.flatMap((tierName) => tiersManifest.tiers[tierName].skills);
|
|
231
228
|
expect(defaultTierSkills.length).toBeGreaterThan(0);
|
|
232
|
-
for (const
|
|
233
|
-
const
|
|
234
|
-
expect(fs.lstatSync(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
229
|
+
for (const skillName of defaultTierSkills) {
|
|
230
|
+
const canonicalLinkPath = path.join(appDir, ".agents", "skills", skillName);
|
|
231
|
+
expect(fs.lstatSync(canonicalLinkPath).isSymbolicLink()).toBe(true);
|
|
232
|
+
expect(normalizePath(fs.readlinkSync(canonicalLinkPath))).toBe(`../../.ai/skills/${skillName}`);
|
|
233
|
+
const claudeLinkPath = path.join(appDir, ".claude", "skills", skillName);
|
|
234
|
+
expect(fs.lstatSync(claudeLinkPath).isSymbolicLink()).toBe(true);
|
|
235
|
+
expect(normalizePath(fs.readlinkSync(claudeLinkPath))).toBe(`../../.agents/skills/${skillName}`);
|
|
240
236
|
}
|
|
237
|
+
expect(fs.existsSync(path.join(appDir, ".codex", "skills"))).toBe(false);
|
|
238
|
+
expect(fs.existsSync(path.join(appDir, ".cursor", "skills"))).toBe(false);
|
|
241
239
|
} finally {
|
|
242
240
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
243
241
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/__integration__/TC-INT-008.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { execFileSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..')\nconst cliDir = path.join(repoRoot, 'packages', 'cli')\nconst cliBin = path.join(cliDir, 'dist', 'bin.js')\nconst cliBuildScript = path.join(cliDir, 'build.mjs')\nconst cliIntegrationRunnerPath = path.join(cliDir, 'src', 'lib', 'testing', 'integration.ts')\nconst standaloneTemplatePackageJsonPath = path.join(repoRoot, 'packages', 'create-app', 'template', 'package.json.template')\nconst agenticRoot = path.join(repoRoot, 'packages', 'create-app', 'agentic')\nconst packagesRoot = path.join(repoRoot, 'packages')\n\n// Modules the standalone fixture enables in src/modules.ts. Both are on the\n// fact-sheet allowlist, so agentic:init must ship exactly their fact-sheets\n// (enabled \u2229 allowlist \u2014 spec D6) and list them in the AGENTS.md marker block.\nconst FIXTURE_ENABLED_MODULES = ['customers', 'sales']\n\nfunction normalizePath(value: string): string {\n return value.split(path.sep).join('/')\n}\n\nfunction runCommand(command: string, args: string[], cwd: string): string {\n return execFileSync(command, args, {\n cwd,\n encoding: 'utf8',\n env: {\n ...process.env,\n FORCE_COLOR: '0',\n NODE_NO_WARNINGS: '1',\n // Keep the test hermetic: agentic:init runs scripts/install-skills.sh,\n // whose external step (`npx skills add`) needs the network. Local tier\n // symlinks are still installed.\n OM_SKIP_EXTERNAL_SKILLS: '1',\n },\n })\n}\n\nfunction runMercato(args: string[], cwd: string): string {\n return runCommand(process.execPath, [cliBin, ...args], cwd)\n}\n\nfunction writeFile(filePath: string, content: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, content)\n}\n\nfunction ensureCliBuilt(): void {\n runCommand(process.execPath, [cliBuildScript], cliDir)\n}\n\nfunction createStandaloneFixture(rootDir: string): string {\n const appDir = path.join(rootDir, 'sample-store')\n writeFile(\n path.join(appDir, 'package.json'),\n JSON.stringify(\n {\n name: 'sample-store',\n private: true,\n },\n null,\n 2,\n ),\n )\n const moduleEntries = FIXTURE_ENABLED_MODULES.map((moduleId) => ` { id: '${moduleId}' },`).join('\\n')\n writeFile(path.join(appDir, 'src', 'modules.ts'), `export const enabledModules = [\\n${moduleEntries}\\n]\\n`)\n return appDir\n}\n\nfunction listRelativeFiles(rootDir: string): string[] {\n if (!fs.existsSync(rootDir)) {\n return []\n }\n\n const collected: string[] = []\n for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {\n const absolutePath = path.join(rootDir, entry.name)\n if (entry.isDirectory()) {\n for (const nestedPath of listRelativeFiles(absolutePath)) {\n collected.push(path.join(entry.name, nestedPath))\n }\n continue\n }\n if (entry.isFile()) {\n collected.push(entry.name)\n }\n }\n\n return collected.map(normalizePath).sort()\n}\n\nfunction mapSharedSourceToOutput(relativePath: string): string {\n if (relativePath === 'AGENTS.md.template') {\n return 'AGENTS.md'\n }\n\n if (relativePath.startsWith('scripts/')) {\n return relativePath\n }\n\n if (!relativePath.startsWith('ai/')) {\n throw new Error(`Unexpected shared source path: ${relativePath}`)\n }\n\n return normalizePath(path.join('.ai', relativePath.slice('ai/'.length)))\n}\n\nfunction mapClaudeSourceToOutput(relativePath: string): string {\n if (relativePath === 'CLAUDE.md.template') {\n return 'CLAUDE.md'\n }\n if (relativePath === 'settings.json') {\n return '.claude/settings.json'\n }\n if (relativePath === 'mcp.json.example') {\n return '.mcp.json.example'\n }\n if (relativePath.startsWith('hooks/')) {\n return normalizePath(path.join('.claude', relativePath))\n }\n\n throw new Error(`Unexpected Claude source path: ${relativePath}`)\n}\n\nfunction mapCursorSourceToOutput(relativePath: string): string {\n if (relativePath === 'hooks.json') {\n return '.cursor/hooks.json'\n }\n if (relativePath === 'mcp.json.example') {\n return '.cursor/mcp.json.example'\n }\n if (relativePath.startsWith('hooks/') || relativePath.startsWith('rules/')) {\n return normalizePath(path.join('.cursor', relativePath))\n }\n\n throw new Error(`Unexpected Cursor source path: ${relativePath}`)\n}\n\nfunction mapCodexSourceToOutput(relativePath: string): string | null {\n if (relativePath === 'mcp.json.example') {\n return '.codex/mcp.json.example'\n }\n if (relativePath === 'enforcement-rules.md') {\n return null\n }\n\n throw new Error(`Unexpected Codex source path: ${relativePath}`)\n}\n\nfunction readPlaywrightConfigPathFromTemplate(): string {\n const packageTemplate = JSON.parse(fs.readFileSync(standaloneTemplatePackageJsonPath, 'utf8')) as {\n scripts?: Record<string, string>\n }\n const integrationScript = packageTemplate.scripts?.['test:integration']\n if (!integrationScript) {\n throw new Error('Standalone template is missing the test:integration script')\n }\n\n const configPathMatch = integrationScript.match(/--config\\s+([^\\s]+)/)\n if (!configPathMatch?.[1]) {\n throw new Error('Standalone template test:integration script is missing --config')\n }\n\n return normalizePath(configPathMatch[1])\n}\n\nfunction readPlaywrightConfigPathFromCliRunner(): string {\n const integrationRunnerSource = fs.readFileSync(cliIntegrationRunnerPath, 'utf8')\n const configPathMatch = integrationRunnerSource.match(/const PLAYWRIGHT_INTEGRATION_CONFIG_PATH = '([^']+)'/)\n if (!configPathMatch?.[1]) {\n throw new Error('CLI integration runner is missing PLAYWRIGHT_INTEGRATION_CONFIG_PATH')\n }\n\n return normalizePath(configPathMatch[1])\n}\n\nfunction expectedGuideOutputNames(): string[] {\n const collected = new Set<string>()\n\n // Static conceptual guides checked into create-app (e.g. module-system.md) are\n // bundled into dist/agentic/guides by the CLI build and copied wholesale.\n const staticGuidesRoot = path.join(agenticRoot, 'guides')\n if (fs.existsSync(staticGuidesRoot)) {\n for (const entry of fs.readdirSync(staticGuidesRoot, { withFileTypes: true })) {\n if (entry.isFile() && entry.name.endsWith('.md')) {\n collected.add(entry.name)\n }\n }\n }\n\n for (const packageName of fs.readdirSync(packagesRoot)) {\n const packageGuide = path.join(packagesRoot, packageName, 'agentic', 'standalone-guide.md')\n if (fs.existsSync(packageGuide)) {\n collected.add(`${packageName}.md`)\n }\n\n const modulesRoot = path.join(packagesRoot, packageName, 'src', 'modules')\n if (!fs.existsSync(modulesRoot)) {\n continue\n }\n\n for (const moduleName of fs.readdirSync(modulesRoot)) {\n const moduleGuide = path.join(modulesRoot, moduleName, 'agentic', 'standalone-guide.md')\n if (fs.existsSync(moduleGuide)) {\n collected.add(`${packageName}.${moduleName}.md`)\n }\n }\n }\n\n // Generated fact-sheet artifacts (spec 2026-06-27-ts-morph-module-fact-sheets):\n // the module-facts.json sidecar is copied as-is and fact-sheets are filtered to the\n // fixture's enabled modules. The legacy core.<module>.md redirect stubs are no longer\n // emitted (#3754).\n collected.add('module-facts.json')\n for (const moduleId of FIXTURE_ENABLED_MODULES) {\n collected.add(normalizePath(path.join('modules', `${moduleId}.md`)))\n }\n\n return Array.from(collected).sort()\n}\n\nfunction assertPathsExist(appDir: string, relativePaths: string[]): void {\n const missingPaths = relativePaths.filter((relativePath) => !fs.existsSync(path.join(appDir, relativePath)))\n expect(missingPaths).toEqual([])\n}\n\ntest.describe('TC-INT-008: CLI agentic init mirrors standalone scaffolding assets', () => {\n test.beforeAll(() => {\n ensureCliBuilt()\n })\n\n test('should run bootstrap-free and generate the shared, guide, and tool-specific agentic files', () => {\n const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-agentic-'))\n\n try {\n const appDir = createStandaloneFixture(tempRoot)\n const standalonePlaywrightConfigPath = readPlaywrightConfigPathFromTemplate()\n const cliPlaywrightConfigPath = readPlaywrightConfigPathFromCliRunner()\n const commandOutput = runMercato(['agentic:init', '--tool=claude-code,codex,cursor'], appDir)\n\n expect(cliPlaywrightConfigPath).toBe(standalonePlaywrightConfigPath)\n expect(commandOutput).toContain('Agentic setup complete:')\n expect(fs.existsSync(path.join(appDir, '.mercato', 'generated'))).toBe(false)\n\n const sharedOutputs = listRelativeFiles(path.join(agenticRoot, 'shared')).map(mapSharedSourceToOutput)\n const claudeOutputs = listRelativeFiles(path.join(agenticRoot, 'claude-code')).map(mapClaudeSourceToOutput)\n const cursorOutputs = listRelativeFiles(path.join(agenticRoot, 'cursor')).map(mapCursorSourceToOutput)\n const codexOutputs = listRelativeFiles(path.join(agenticRoot, 'codex'))\n .map(mapCodexSourceToOutput)\n .filter((relativePath): relativePath is string => relativePath !== null)\n\n expect(sharedOutputs).toContain(standalonePlaywrightConfigPath)\n assertPathsExist(appDir, [...sharedOutputs, ...claudeOutputs, ...cursorOutputs, ...codexOutputs])\n expect(fs.existsSync(path.join(appDir, standalonePlaywrightConfigPath))).toBe(true)\n\n const generatedGuideNames = listRelativeFiles(path.join(appDir, '.ai', 'guides'))\n expect(generatedGuideNames).toEqual(expectedGuideOutputNames())\n\n const agentsSource = fs.readFileSync(path.join(appDir, 'AGENTS.md'), 'utf8')\n expect(agentsSource).toContain('<!-- CODEX_ENFORCEMENT_RULES_START -->')\n expect(agentsSource).toContain('.ai/guides/core.md')\n\n // The Module-Specific Guides marker block lists exactly the enabled modules'\n // fact-sheets (enabled \u2229 allowlist), not the full bundled set.\n for (const moduleId of FIXTURE_ENABLED_MODULES) {\n expect(agentsSource).toContain(`.ai/guides/modules/${moduleId}.md`)\n }\n expect(agentsSource).not.toContain('.ai/guides/modules/auth.md')\n\n const specsReadmeSource = fs.readFileSync(path.join(appDir, '.ai', 'specs', 'README.md'), 'utf8')\n expect(specsReadmeSource).toContain('sample-store')\n\n const cursorRulesSource = fs.readFileSync(path.join(appDir, '.cursor', 'rules', 'open-mercato.mdc'), 'utf8')\n expect(cursorRulesSource).toContain('sample-store')\n\n // om-spec-writing moved to the external open-mercato/skills collection\n // (installed via `yarn install-skills`), so agentic:init must not ship a copy.\n expect(fs.existsSync(path.join(appDir, '.ai', 'skills', 'om-spec-writing'))).toBe(false)\n expect(fs.existsSync(path.join(appDir, 'scripts', 'install-skills.sh'))).toBe(true)\n\n // install-skills.sh (run by agentic:init) replaces the legacy directory-level\n // .claude/skills and .codex/skills symlinks with real directories holding one\n // symlink per default-tier local skill; .cursor/skills keeps the directory link.\n const cursorSkillsLinkPath = path.join(appDir, '.cursor', 'skills')\n expect(fs.lstatSync(cursorSkillsLinkPath).isSymbolicLink()).toBe(true)\n expect(normalizePath(fs.readlinkSync(cursorSkillsLinkPath))).toBe('../.ai/skills')\n\n const tiersManifest = JSON.parse(\n fs.readFileSync(path.join(agenticRoot, 'shared', 'ai', 'skills', 'tiers.json'), 'utf8'),\n ) as { default: string[]; tiers: Record<string, { skills: string[] }> }\n const defaultTierSkills = tiersManifest.default.flatMap((tierName) => tiersManifest.tiers[tierName].skills)\n expect(defaultTierSkills.length).toBeGreaterThan(0)\n\n for (const toolDir of ['.claude', '.codex']) {\n const harnessSkillsDir = path.join(appDir, toolDir, 'skills')\n expect(fs.lstatSync(harnessSkillsDir).isDirectory()).toBe(true)\n for (const skillName of defaultTierSkills) {\n const skillLinkPath = path.join(harnessSkillsDir, skillName)\n expect(fs.lstatSync(skillLinkPath).isSymbolicLink()).toBe(true)\n expect(normalizePath(fs.readlinkSync(skillLinkPath))).toBe(`../../.ai/skills/${skillName}`)\n }\n }\n } finally {\n fs.rmSync(tempRoot, { recursive: true, force: true })\n }\n })\n})\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { expect, test } from '@playwright/test'\nimport { execFileSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url))\nconst repoRoot = path.resolve(__dirname, '..', '..', '..', '..', '..')\nconst cliDir = path.join(repoRoot, 'packages', 'cli')\nconst cliBin = path.join(cliDir, 'dist', 'bin.js')\nconst cliBuildScript = path.join(cliDir, 'build.mjs')\nconst cliIntegrationRunnerPath = path.join(cliDir, 'src', 'lib', 'testing', 'integration.ts')\nconst standaloneTemplatePackageJsonPath = path.join(repoRoot, 'packages', 'create-app', 'template', 'package.json.template')\nconst agenticRoot = path.join(repoRoot, 'packages', 'create-app', 'agentic')\nconst packagesRoot = path.join(repoRoot, 'packages')\n\n// Modules the standalone fixture enables in src/modules.ts. Both are on the\n// fact-sheet allowlist, so agentic:init must ship exactly their fact-sheets\n// (enabled \u2229 allowlist \u2014 spec D6) and list them in the AGENTS.md marker block.\nconst FIXTURE_ENABLED_MODULES = ['customers', 'sales']\n\nfunction normalizePath(value: string): string {\n return value.split(path.sep).join('/')\n}\n\nfunction runCommand(command: string, args: string[], cwd: string): string {\n return execFileSync(command, args, {\n cwd,\n encoding: 'utf8',\n env: {\n ...process.env,\n FORCE_COLOR: '0',\n NODE_NO_WARNINGS: '1',\n // Keep the test hermetic: agentic:init runs scripts/install-skills.sh,\n // whose external step (`npx skills add`) needs the network. Local tier\n // symlinks are still installed.\n OM_SKIP_EXTERNAL_SKILLS: '1',\n },\n })\n}\n\nfunction runMercato(args: string[], cwd: string): string {\n return runCommand(process.execPath, [cliBin, ...args], cwd)\n}\n\nfunction writeFile(filePath: string, content: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true })\n fs.writeFileSync(filePath, content)\n}\n\nfunction ensureCliBuilt(): void {\n runCommand(process.execPath, [cliBuildScript], cliDir)\n}\n\nfunction createStandaloneFixture(rootDir: string): string {\n const appDir = path.join(rootDir, 'sample-store')\n writeFile(\n path.join(appDir, 'package.json'),\n JSON.stringify(\n {\n name: 'sample-store',\n private: true,\n },\n null,\n 2,\n ),\n )\n const moduleEntries = FIXTURE_ENABLED_MODULES.map((moduleId) => ` { id: '${moduleId}' },`).join('\\n')\n writeFile(path.join(appDir, 'src', 'modules.ts'), `export const enabledModules = [\\n${moduleEntries}\\n]\\n`)\n return appDir\n}\n\nfunction listRelativeFiles(rootDir: string): string[] {\n if (!fs.existsSync(rootDir)) {\n return []\n }\n\n const collected: string[] = []\n for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {\n const absolutePath = path.join(rootDir, entry.name)\n if (entry.isDirectory()) {\n for (const nestedPath of listRelativeFiles(absolutePath)) {\n collected.push(path.join(entry.name, nestedPath))\n }\n continue\n }\n if (entry.isFile()) {\n collected.push(entry.name)\n }\n }\n\n return collected.map(normalizePath).sort()\n}\n\nfunction mapSharedSourceToOutput(relativePath: string): string {\n if (relativePath === 'AGENTS.md.template') {\n return 'AGENTS.md'\n }\n\n if (relativePath.startsWith('scripts/')) {\n return relativePath\n }\n\n if (!relativePath.startsWith('ai/')) {\n throw new Error(`Unexpected shared source path: ${relativePath}`)\n }\n\n return normalizePath(path.join('.ai', relativePath.slice('ai/'.length)))\n}\n\nfunction mapClaudeSourceToOutput(relativePath: string): string {\n if (relativePath === 'CLAUDE.md.template') {\n return 'CLAUDE.md'\n }\n if (relativePath === 'settings.json') {\n return '.claude/settings.json'\n }\n if (relativePath === 'mcp.json.example') {\n return '.mcp.json.example'\n }\n if (relativePath.startsWith('hooks/')) {\n return normalizePath(path.join('.claude', relativePath))\n }\n\n throw new Error(`Unexpected Claude source path: ${relativePath}`)\n}\n\nfunction mapCursorSourceToOutput(relativePath: string): string {\n if (relativePath === 'hooks.json') {\n return '.cursor/hooks.json'\n }\n if (relativePath === 'mcp.json.example') {\n return '.cursor/mcp.json.example'\n }\n if (relativePath.startsWith('hooks/') || relativePath.startsWith('rules/')) {\n return normalizePath(path.join('.cursor', relativePath))\n }\n\n throw new Error(`Unexpected Cursor source path: ${relativePath}`)\n}\n\nfunction mapCodexSourceToOutput(relativePath: string): string | null {\n if (relativePath === 'mcp.json.example') {\n return '.codex/mcp.json.example'\n }\n if (relativePath === 'enforcement-rules.md') {\n return null\n }\n\n throw new Error(`Unexpected Codex source path: ${relativePath}`)\n}\n\nfunction readPlaywrightConfigPathFromTemplate(): string {\n const packageTemplate = JSON.parse(fs.readFileSync(standaloneTemplatePackageJsonPath, 'utf8')) as {\n scripts?: Record<string, string>\n }\n const integrationScript = packageTemplate.scripts?.['test:integration']\n if (!integrationScript) {\n throw new Error('Standalone template is missing the test:integration script')\n }\n\n const configPathMatch = integrationScript.match(/--config\\s+([^\\s]+)/)\n if (!configPathMatch?.[1]) {\n throw new Error('Standalone template test:integration script is missing --config')\n }\n\n return normalizePath(configPathMatch[1])\n}\n\nfunction readPlaywrightConfigPathFromCliRunner(): string {\n const integrationRunnerSource = fs.readFileSync(cliIntegrationRunnerPath, 'utf8')\n const configPathMatch = integrationRunnerSource.match(/const PLAYWRIGHT_INTEGRATION_CONFIG_PATH = '([^']+)'/)\n if (!configPathMatch?.[1]) {\n throw new Error('CLI integration runner is missing PLAYWRIGHT_INTEGRATION_CONFIG_PATH')\n }\n\n return normalizePath(configPathMatch[1])\n}\n\nfunction expectedGuideOutputNames(): string[] {\n const collected = new Set<string>()\n\n // Static conceptual guides checked into create-app (e.g. module-system.md) are\n // bundled into dist/agentic/guides by the CLI build and copied wholesale.\n const staticGuidesRoot = path.join(agenticRoot, 'guides')\n if (fs.existsSync(staticGuidesRoot)) {\n for (const entry of fs.readdirSync(staticGuidesRoot, { withFileTypes: true })) {\n if (entry.isFile() && entry.name.endsWith('.md')) {\n collected.add(entry.name)\n }\n }\n }\n\n for (const packageName of fs.readdirSync(packagesRoot)) {\n const packageGuide = path.join(packagesRoot, packageName, 'agentic', 'standalone-guide.md')\n if (fs.existsSync(packageGuide)) {\n collected.add(`${packageName}.md`)\n }\n\n const modulesRoot = path.join(packagesRoot, packageName, 'src', 'modules')\n if (!fs.existsSync(modulesRoot)) {\n continue\n }\n\n for (const moduleName of fs.readdirSync(modulesRoot)) {\n const moduleGuide = path.join(modulesRoot, moduleName, 'agentic', 'standalone-guide.md')\n if (fs.existsSync(moduleGuide)) {\n collected.add(`${packageName}.${moduleName}.md`)\n }\n }\n }\n\n // Generated fact-sheet artifacts (spec 2026-06-27-ts-morph-module-fact-sheets):\n // the module-facts.json sidecar is copied as-is and fact-sheets are filtered to the\n // fixture's enabled modules. The legacy core.<module>.md redirect stubs are no longer\n // emitted (#3754).\n collected.add('module-facts.json')\n for (const moduleId of FIXTURE_ENABLED_MODULES) {\n collected.add(normalizePath(path.join('modules', `${moduleId}.md`)))\n }\n\n return Array.from(collected).sort()\n}\n\nfunction assertPathsExist(appDir: string, relativePaths: string[]): void {\n const missingPaths = relativePaths.filter((relativePath) => !fs.existsSync(path.join(appDir, relativePath)))\n expect(missingPaths).toEqual([])\n}\n\ntest.describe('TC-INT-008: CLI agentic init mirrors standalone scaffolding assets', () => {\n test.beforeAll(() => {\n ensureCliBuilt()\n })\n\n test('should run bootstrap-free and generate the shared, guide, and tool-specific agentic files', () => {\n const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'mercato-cli-agentic-'))\n\n try {\n const appDir = createStandaloneFixture(tempRoot)\n const standalonePlaywrightConfigPath = readPlaywrightConfigPathFromTemplate()\n const cliPlaywrightConfigPath = readPlaywrightConfigPathFromCliRunner()\n const commandOutput = runMercato(['agentic:init', '--tool=claude-code,codex,cursor'], appDir)\n\n expect(cliPlaywrightConfigPath).toBe(standalonePlaywrightConfigPath)\n expect(commandOutput).toContain('Agentic setup complete:')\n expect(fs.existsSync(path.join(appDir, '.mercato', 'generated'))).toBe(false)\n\n const sharedOutputs = listRelativeFiles(path.join(agenticRoot, 'shared')).map(mapSharedSourceToOutput)\n const claudeOutputs = listRelativeFiles(path.join(agenticRoot, 'claude-code')).map(mapClaudeSourceToOutput)\n const cursorOutputs = listRelativeFiles(path.join(agenticRoot, 'cursor')).map(mapCursorSourceToOutput)\n const codexOutputs = listRelativeFiles(path.join(agenticRoot, 'codex'))\n .map(mapCodexSourceToOutput)\n .filter((relativePath): relativePath is string => relativePath !== null)\n\n expect(sharedOutputs).toContain(standalonePlaywrightConfigPath)\n assertPathsExist(appDir, [...sharedOutputs, ...claudeOutputs, ...cursorOutputs, ...codexOutputs])\n expect(fs.existsSync(path.join(appDir, standalonePlaywrightConfigPath))).toBe(true)\n\n const generatedGuideNames = listRelativeFiles(path.join(appDir, '.ai', 'guides'))\n expect(generatedGuideNames).toEqual(expectedGuideOutputNames())\n\n const agentsSource = fs.readFileSync(path.join(appDir, 'AGENTS.md'), 'utf8')\n expect(agentsSource).toContain('<!-- CODEX_ENFORCEMENT_RULES_START -->')\n expect(agentsSource).toContain('.ai/guides/core.md')\n\n // The Module-Specific Guides marker block lists exactly the enabled modules'\n // fact-sheets (enabled \u2229 allowlist), not the full bundled set.\n for (const moduleId of FIXTURE_ENABLED_MODULES) {\n expect(agentsSource).toContain(`.ai/guides/modules/${moduleId}.md`)\n }\n expect(agentsSource).not.toContain('.ai/guides/modules/auth.md')\n\n const specsReadmeSource = fs.readFileSync(path.join(appDir, '.ai', 'specs', 'README.md'), 'utf8')\n expect(specsReadmeSource).toContain('sample-store')\n\n const cursorRulesSource = fs.readFileSync(path.join(appDir, '.cursor', 'rules', 'open-mercato.mdc'), 'utf8')\n expect(cursorRulesSource).toContain('sample-store')\n\n // om-spec-writing moved to the external open-mercato/skills collection\n // (installed via `yarn install-skills`), so agentic:init must not ship a copy.\n expect(fs.existsSync(path.join(appDir, '.ai', 'skills', 'om-spec-writing'))).toBe(false)\n expect(fs.existsSync(path.join(appDir, 'scripts', 'install-skills.sh'))).toBe(true)\n\n // install-skills.sh (run by agentic:init) installs every local tier skill once,\n // into the canonical .agents/skills/. Codex and Cursor read that directory\n // natively, so they get no skills directory of their own; Claude Code cannot,\n // so it keeps a link layer pointing back at the canonical copy.\n const tiersManifest = JSON.parse(\n fs.readFileSync(path.join(agenticRoot, 'shared', 'ai', 'skills', 'tiers.json'), 'utf8'),\n ) as { default: string[]; tiers: Record<string, { skills: string[] }> }\n const defaultTierSkills = tiersManifest.default.flatMap((tierName) => tiersManifest.tiers[tierName].skills)\n expect(defaultTierSkills.length).toBeGreaterThan(0)\n\n for (const skillName of defaultTierSkills) {\n const canonicalLinkPath = path.join(appDir, '.agents', 'skills', skillName)\n expect(fs.lstatSync(canonicalLinkPath).isSymbolicLink()).toBe(true)\n expect(normalizePath(fs.readlinkSync(canonicalLinkPath))).toBe(`../../.ai/skills/${skillName}`)\n\n const claudeLinkPath = path.join(appDir, '.claude', 'skills', skillName)\n expect(fs.lstatSync(claudeLinkPath).isSymbolicLink()).toBe(true)\n expect(normalizePath(fs.readlinkSync(claudeLinkPath))).toBe(`../../.agents/skills/${skillName}`)\n }\n\n expect(fs.existsSync(path.join(appDir, '.codex', 'skills'))).toBe(false)\n expect(fs.existsSync(path.join(appDir, '.cursor', 'skills'))).toBe(false)\n } finally {\n fs.rmSync(tempRoot, { recursive: true, force: true })\n }\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -297,7 +297,6 @@ function generateCodex(config) {
|
|
|
297
297
|
writeFileSync(agentsPath, agents);
|
|
298
298
|
}
|
|
299
299
|
copyFile(srcDir, "mcp.json.example", join(targetDir, ".codex", "mcp.json.example"));
|
|
300
|
-
ensureSkillsLink(join(targetDir, ".codex", "skills"), join("..", ".ai", "skills"));
|
|
301
300
|
}
|
|
302
301
|
function generateCursor(config) {
|
|
303
302
|
const { targetDir } = config;
|
|
@@ -308,7 +307,6 @@ function generateCursor(config) {
|
|
|
308
307
|
copyFile(srcDir, "hooks.json", join(targetDir, ".cursor", "hooks.json"));
|
|
309
308
|
copyFile(srcDir, "hooks/entity-migration-check.mjs", join(targetDir, ".cursor", "hooks", "entity-migration-check.mjs"));
|
|
310
309
|
copyFile(srcDir, "mcp.json.example", join(targetDir, ".cursor", "mcp.json.example"));
|
|
311
|
-
ensureSkillsLink(join(targetDir, ".cursor", "skills"), join("..", ".ai", "skills"));
|
|
312
310
|
}
|
|
313
311
|
function ensureSkillsLink(linkPath, target) {
|
|
314
312
|
ensureDir(linkPath);
|
|
@@ -381,6 +379,7 @@ async function runAgenticSetup(targetDir, ask, options) {
|
|
|
381
379
|
if (selectedIds.includes("claude-code")) generateClaudeCode(config);
|
|
382
380
|
if (selectedIds.includes("codex")) generateCodex(config);
|
|
383
381
|
if (selectedIds.includes("cursor")) generateCursor(config);
|
|
382
|
+
persistAgentSelection(targetDir, selectedIds);
|
|
384
383
|
installSkills(targetDir);
|
|
385
384
|
console.log("");
|
|
386
385
|
console.log(" Agentic setup complete:");
|
|
@@ -400,6 +399,19 @@ async function runAgenticSetup(targetDir, ask, options) {
|
|
|
400
399
|
console.log(" refresh the external open-mercato/skills subset.");
|
|
401
400
|
console.log("");
|
|
402
401
|
}
|
|
402
|
+
function persistAgentSelection(targetDir, selectedIds) {
|
|
403
|
+
const manifestPath = join(targetDir, ".ai", "skills", "tiers.json");
|
|
404
|
+
if (!existsSync(manifestPath)) return;
|
|
405
|
+
const ignore = SELECTABLE.map((tool) => tool.id).filter((id) => !selectedIds.includes(id));
|
|
406
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
407
|
+
if (ignore.length > 0) {
|
|
408
|
+
manifest.agents = { ignore };
|
|
409
|
+
} else {
|
|
410
|
+
delete manifest.agents;
|
|
411
|
+
}
|
|
412
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
413
|
+
`);
|
|
414
|
+
}
|
|
403
415
|
function installSkills(targetDir) {
|
|
404
416
|
const installScript = join(targetDir, "scripts", "install-skills.sh");
|
|
405
417
|
if (!existsSync(installScript)) return;
|