create-zudo-doc 4.4.12 → 4.5.0

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.
@@ -1,6 +1,20 @@
1
1
  import type { UserChoices } from "./prompts.js";
2
2
  import { getSecondaryLang } from "./utils.js";
3
3
  export { getSecondaryLang };
4
+ /**
5
+ * TypeScript mirror of `DEFAULT_SKILL_NAME` in `scripts/setup-doc-skill.sh`
6
+ * (`DEFAULT_SKILL_NAME="${PROJECT_NAME}-wisdom"`, suffix-aware since #3154).
7
+ * The generator cannot import the shell script, so this rule is duplicated
8
+ * here; the two are kept from drifting apart by the committed cross-artifact
9
+ * parity test (#3158).
10
+ *
11
+ * If `projectName` already ends in `-wisdom` (or is exactly `wisdom`), it is
12
+ * used verbatim — appending `-wisdom` again would double the suffix (e.g.
13
+ * `zudo-test-wisdom` → `zudo-test-wisdom-wisdom`), which matches no
14
+ * `.gitignore` entry and leaves the generated skill directory untracked.
15
+ * Otherwise `-wisdom` is appended.
16
+ */
17
+ export declare function deriveDocSkillName(projectName: string): string;
4
18
  /**
5
19
  * Pinned `@takazudo/zudo-doc` version used by `generatePackageJson()`.
6
20
  * Hoisted as a shared constant (kept even though it now has a single
@@ -18,5 +32,5 @@ export { getSecondaryLang };
18
32
  *
19
33
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
20
34
  */
21
- export declare const ZUDO_DOC_PIN = "^4.4.12";
35
+ export declare const ZUDO_DOC_PIN = "^4.5.0";
22
36
  export declare function scaffold(choices: UserChoices): Promise<void>;
package/dist/scaffold.js CHANGED
@@ -7,6 +7,25 @@ import { composeFeatures } from "./compose.js";
7
7
  import { featureModules } from "./features/index.js";
8
8
  import { capitalize, getSecondaryLang, hasAncestorPnpmWorkspace, pmRunCommand, } from "./utils.js";
9
9
  export { getSecondaryLang };
10
+ /**
11
+ * TypeScript mirror of `DEFAULT_SKILL_NAME` in `scripts/setup-doc-skill.sh`
12
+ * (`DEFAULT_SKILL_NAME="${PROJECT_NAME}-wisdom"`, suffix-aware since #3154).
13
+ * The generator cannot import the shell script, so this rule is duplicated
14
+ * here; the two are kept from drifting apart by the committed cross-artifact
15
+ * parity test (#3158).
16
+ *
17
+ * If `projectName` already ends in `-wisdom` (or is exactly `wisdom`), it is
18
+ * used verbatim — appending `-wisdom` again would double the suffix (e.g.
19
+ * `zudo-test-wisdom` → `zudo-test-wisdom-wisdom`), which matches no
20
+ * `.gitignore` entry and leaves the generated skill directory untracked.
21
+ * Otherwise `-wisdom` is appended.
22
+ */
23
+ export function deriveDocSkillName(projectName) {
24
+ if (projectName === "wisdom" || projectName.endsWith("-wisdom")) {
25
+ return projectName;
26
+ }
27
+ return `${projectName}-wisdom`;
28
+ }
10
29
  /**
11
30
  * Pinned `@takazudo/zudo-doc` version used by `generatePackageJson()`.
12
31
  * Hoisted as a shared constant (kept even though it now has a single
@@ -24,7 +43,7 @@ export { getSecondaryLang };
24
43
  *
25
44
  * Bumped in lockstep by scripts/release-create-zudo-doc.sh.
26
45
  */
27
- export const ZUDO_DOC_PIN = "^4.4.12";
46
+ export const ZUDO_DOC_PIN = "^4.5.0";
28
47
  /**
29
48
  * Files in `templates/base/**` that must not be copied by the unconditional
30
49
  * base mirror. Each entry is matched against the path relative to
@@ -343,16 +362,17 @@ export async function scaffold(choices) {
343
362
  // on the same feature above), so only emit its ignore entries then —
344
363
  // otherwise they would be dead rules that could silently hide an unrelated
345
364
  // skill a user later installs under a matching name. The skill name is
346
- // deterministic (always `<projectName>-wisdom`, matching DEFAULT_SKILL_NAME
347
- // in scripts/setup-doc-skill.sh and the package name), so these entries match
348
- // the directory the script creates. The setup script can target either
349
- // .claude or .codex, so ignore both possible generated directories. The
350
- // docs-ja symlink only exists for i18n projects (the script creates it
351
- // conditionally), so gate those lines on i18n.
365
+ // deterministic (deriveDocSkillName(), matching DEFAULT_SKILL_NAME in
366
+ // scripts/setup-doc-skill.sh), so these entries match the directory the
367
+ // script creates. The setup script can target either .claude or .codex, so
368
+ // ignore both possible generated directories. The docs-ja symlink only
369
+ // exists for i18n projects (the script creates it conditionally), so gate
370
+ // those lines on i18n.
352
371
  if (choices.features.includes("skillSymlinker")) {
353
- gitignoreLines.push("# Generated doc-lookup skill", `.claude/skills/${choices.projectName}-wisdom/SKILL.md`, `.claude/skills/${choices.projectName}-wisdom/docs`, `.codex/skills/${choices.projectName}-wisdom/SKILL.md`, `.codex/skills/${choices.projectName}-wisdom/docs`);
372
+ const skillName = deriveDocSkillName(choices.projectName);
373
+ gitignoreLines.push("# Generated doc-lookup skill", `.claude/skills/${skillName}/SKILL.md`, `.claude/skills/${skillName}/docs`, `.codex/skills/${skillName}/SKILL.md`, `.codex/skills/${skillName}/docs`);
354
374
  if (choices.features.includes("i18n")) {
355
- gitignoreLines.push(`.claude/skills/${choices.projectName}-wisdom/docs-ja`, `.codex/skills/${choices.projectName}-wisdom/docs-ja`);
375
+ gitignoreLines.push(`.claude/skills/${skillName}/docs-ja`, `.codex/skills/${skillName}/docs-ja`);
356
376
  }
357
377
  gitignoreLines.push("");
358
378
  }
@@ -550,9 +570,12 @@ function generatePackageJson(choices) {
550
570
  // singleton across host-callable wiring (zfb#1652/#1653). The zfb family
551
571
  // must stay in lockstep because the WASM browser entry depends on its
552
572
  // resource-aware island pipeline.
553
- "@takazudo/zfb": "0.1.0-next.98",
554
- "@takazudo/zfb-runtime": "0.1.0-next.98",
555
- "@takazudo/zfb-md-wasm": "0.1.0-next.98",
573
+ // 1.1.0: container-directive fixes (multi-block bodies, unclosed-opener
574
+ // diagnostics) plus highlighting for fences nested in MDX JSX bodies and
575
+ // directives. Content-rendering fixes, additive.
576
+ "@takazudo/zfb": "1.1.0",
577
+ "@takazudo/zfb-runtime": "1.1.0",
578
+ "@takazudo/zfb-md-wasm": "1.1.0",
556
579
  // @takazudo/zudo-doc — published from this monorepo via
557
580
  // .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
558
581
  // lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
@@ -672,7 +695,7 @@ function generatePackageJson(choices) {
672
695
  // `/exclude` at module scope from the always-bundled chrome graph; #3110
673
696
  // moved compileExclude into @takazudo/zudo-doc, so docHistory-OFF projects
674
697
  // no longer need the package at all.
675
- deps["@takazudo/zudo-doc-history-server"] = "^4.4.12";
698
+ deps["@takazudo/zudo-doc-history-server"] = "^4.5.0";
676
699
  // tsx is no longer needed here: the relocated package plugin imports the
677
700
  // runner directly (no `tsx -e` spawn) since the package ships compiled
678
701
  // dist/ — package-first migration #2321 (#2337).
@@ -741,6 +764,11 @@ function generatePackageJson(choices) {
741
764
  "tags-suggest --config src/config/tag-vocabulary.ts";
742
765
  }
743
766
  if (choices.features.includes("skillSymlinker")) {
767
+ // Deliberately omit --no-link-tracked-skills here: automatic tracked-skill
768
+ // linking (#3152) is the intended default for generated projects. Only the
769
+ // zudo-doc monorepo's own root package.json passes the opt-out, because it
770
+ // has 20 directories under `.claude/skills/` and a blanket loop would export
771
+ // 19 project-specific skills into the maintainer's global skills dir (#3157).
744
772
  scripts["setup:doc-skill"] = "bash scripts/setup-doc-skill.sh";
745
773
  scripts["setup:doc-skill-silent"] =
746
774
  "bash scripts/setup-doc-skill.sh --silent";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-zudo-doc",
3
- "version": "4.4.12",
3
+ "version": "4.5.0",
4
4
  "description": "Create a new zudo-doc documentation site",
5
5
  "license": "MIT",
6
6
  "author": "Takeshi Takatsudo",
@@ -10,6 +10,13 @@ set -euo pipefail
10
10
 
11
11
  TARGET_MODE="auto"
12
12
 
13
+ # Tracked-skill linking (zudolab/zudo-doc#3156) is default-ON: #3152 asked for
14
+ # automatic linking of a site's own hand-written skills, not just the
15
+ # generated one. --no-link-tracked-skills opts out (e.g. this monorepo's own
16
+ # npm scripts, which would otherwise export ~20 project-specific skills into
17
+ # the user's global skills dir -- see zudolab/zudo-doc#3157).
18
+ LINK_TRACKED_SKILLS="true"
19
+
13
20
  # Accept --silent (alias -y) for parity with the consuming-site convention:
14
21
  # scaffolded sites expose `setup:doc-skill-silent` = `bash scripts/setup-doc-skill.sh
15
22
  # --silent`. This script is already non-interactive (the skill name is deterministic
@@ -31,6 +38,7 @@ while [ $# -gt 0 ]; do
31
38
  TARGET_MODE="${1#--target=}"
32
39
  shift
33
40
  ;;
41
+ --no-link-tracked-skills) LINK_TRACKED_SKILLS="false"; shift ;;
34
42
  --) shift; break ;;
35
43
  -*) echo "Error: unknown flag '$1'" >&2; exit 1 ;;
36
44
  *) break ;;
@@ -49,16 +57,29 @@ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
49
57
 
50
58
  # Read project name from package.json
51
59
  PROJECT_NAME=$(node -e "console.log(require('$ROOT_DIR/package.json').name || 'my-project')")
52
- DEFAULT_SKILL_NAME="${PROJECT_NAME}-wisdom"
60
+
61
+ # Pre-#3154 default: always `<projectName>-wisdom`, which doubles the suffix
62
+ # when PROJECT_NAME already ends in "-wisdom" (e.g. "zudo-test-wisdom" ->
63
+ # "zudo-test-wisdom-wisdom"). Kept around only to detect leftover legacy
64
+ # directories from that behavior -- see the migration-warning block below.
65
+ LEGACY_DEFAULT_SKILL_NAME="${PROJECT_NAME}-wisdom"
66
+ if [[ "$PROJECT_NAME" == *-wisdom ]] || [[ "$PROJECT_NAME" == "wisdom" ]]; then
67
+ DEFAULT_SKILL_NAME="$PROJECT_NAME"
68
+ else
69
+ DEFAULT_SKILL_NAME="$LEGACY_DEFAULT_SKILL_NAME"
70
+ fi
53
71
 
54
72
  echo ""
55
73
  echo "=== zudo-doc Skill Setup ==="
56
74
  echo ""
57
75
 
58
- # Skill name is DETERMINISTIC: always `<projectName>-wisdom`. The scaffolded
59
- # .gitignore (emitted by create-zudo-doc) hard-codes this exact name, so the
60
- # generated skill directory must match it — an interactive prompt would let the
61
- # name drift from the gitignore entry and leave the skill showing as untracked
76
+ # Skill name is DETERMINISTIC and suffix-aware: verbatim when PROJECT_NAME
77
+ # already ends in "-wisdom" (or is exactly "wisdom"), otherwise
78
+ # "<projectName>-wisdom" (zudolab/zudo-doc#3154). The scaffolded .gitignore
79
+ # (emitted by create-zudo-doc's packages/create-zudo-doc/src/scaffold.ts,
80
+ # which computes the same name) hard-codes this exact rule, so the generated
81
+ # skill directory must match it — an interactive prompt would let the name
82
+ # drift from the gitignore entry and leave the skill showing as untracked
62
83
  # (zudolab/zudo-doc#2173). An explicit override is still allowed via the first
63
84
  # CLI arg or the SKILL_NAME env var (consumers who override must also update
64
85
  # their .gitignore), but never via an interactive prompt.
@@ -70,6 +91,92 @@ if [[ ! "$SKILL_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
70
91
  exit 1
71
92
  fi
72
93
 
94
+ # Migration warning (zudolab/zudo-doc#3154, D3): the suffix-aware rule above is
95
+ # a BREAKING change for existing "*-wisdom" projects. Their .gitignore pins the
96
+ # old, doubled name, so after picking up this fix they get a NEW, unignored
97
+ # skill directory while the stale doubled one remains on disk -- the symptom
98
+ # moves rather than disappears. Detect that and tell the user what to do by
99
+ # hand; never delete anything automatically, it is the user's tree.
100
+ # Three independent symptoms are checked, and ANY one of them fires the
101
+ # warning: stale ignore rules (the .gitignore still names the doubled skill --
102
+ # on its own that already means the NEW directory is unignored, which is the
103
+ # whole problem, even when the legacy directory was never generated or has
104
+ # already been deleted), a leftover legacy directory, and a leftover global
105
+ # symlink. Only the steps for the symptoms actually found are printed.
106
+ # Only fires when SKILL_NAME still equals DEFAULT_SKILL_NAME -- an explicit
107
+ # $1/SKILL_NAME override means the derived name (and this guidance about it)
108
+ # doesn't apply to what the script is actually about to create.
109
+ if [ "$SKILL_NAME" = "$DEFAULT_SKILL_NAME" ] && [ "$DEFAULT_SKILL_NAME" != "$LEGACY_DEFAULT_SKILL_NAME" ]; then
110
+ # The ignore rules are repo-level, so this is checked once rather than per
111
+ # target -- both ".claude/skills/<name>/" and ".codex/skills/<name>/" entries
112
+ # live in the same file (create-zudo-doc emits it at the project root).
113
+ # -F: PROJECT_NAME comes from package.json and may contain regex characters.
114
+ legacy_gitignore=""
115
+ if [ -f "$ROOT_DIR/.gitignore" ] &&
116
+ grep -qF "skills/$LEGACY_DEFAULT_SKILL_NAME/" "$ROOT_DIR/.gitignore"; then
117
+ legacy_gitignore="$ROOT_DIR/.gitignore"
118
+ fi
119
+
120
+ legacy_dirs=()
121
+ legacy_links=()
122
+ for target in claude codex; do
123
+ legacy_dir="$ROOT_DIR/.$target/skills/$LEGACY_DEFAULT_SKILL_NAME"
124
+ if [ -d "$legacy_dir" ]; then
125
+ legacy_dirs+=("$legacy_dir")
126
+ fi
127
+ # -L only (which also catches a dangling link): this script only ever
128
+ # creates SYMLINKS in the global skills dir, so a real file or directory
129
+ # sitting at that path is user-owned and none of our business -- calling it
130
+ # a "stale global symlink" and suggesting `rm -f` would advise deleting
131
+ # someone else's file (and would simply fail for a directory).
132
+ legacy_global_link="$HOME/.$target/skills/$LEGACY_DEFAULT_SKILL_NAME"
133
+ if [ -L "$legacy_global_link" ]; then
134
+ legacy_links+=("$legacy_global_link")
135
+ fi
136
+ done
137
+
138
+ if [ -n "$legacy_gitignore" ] ||
139
+ [ "${#legacy_dirs[@]}" -gt 0 ] ||
140
+ [ "${#legacy_links[@]}" -gt 0 ]; then
141
+ echo "WARNING: stale references to the legacy skill name '$LEGACY_DEFAULT_SKILL_NAME' detected."
142
+ echo " The skill-name derivation rule changed; this project's skill now resolves to:"
143
+ echo " $DEFAULT_SKILL_NAME"
144
+ echo " Found:"
145
+ if [ -n "$legacy_gitignore" ]; then
146
+ echo " - stale ignore rules in $legacy_gitignore"
147
+ fi
148
+ if [ "${#legacy_dirs[@]}" -gt 0 ]; then
149
+ for legacy_dir in "${legacy_dirs[@]}"; do
150
+ echo " - legacy skill directory: $legacy_dir"
151
+ done
152
+ fi
153
+ if [ "${#legacy_links[@]}" -gt 0 ]; then
154
+ for legacy_global_link in "${legacy_links[@]}"; do
155
+ echo " - stale global symlink: $legacy_global_link"
156
+ done
157
+ fi
158
+ echo " Manual steps:"
159
+ step=1
160
+ if [ -n "$legacy_gitignore" ]; then
161
+ echo " $step. Update the ignore rules for '$LEGACY_DEFAULT_SKILL_NAME' to '$DEFAULT_SKILL_NAME' in $legacy_gitignore."
162
+ step=$((step + 1))
163
+ fi
164
+ if [ "${#legacy_dirs[@]}" -gt 0 ]; then
165
+ for legacy_dir in "${legacy_dirs[@]}"; do
166
+ echo " $step. Delete the stale directory once migrated: rm -rf \"$legacy_dir\""
167
+ step=$((step + 1))
168
+ done
169
+ fi
170
+ if [ "${#legacy_links[@]}" -gt 0 ]; then
171
+ for legacy_global_link in "${legacy_links[@]}"; do
172
+ echo " $step. Remove the stale global symlink: rm -f \"$legacy_global_link\""
173
+ step=$((step + 1))
174
+ done
175
+ fi
176
+ echo ""
177
+ fi
178
+ fi
179
+
73
180
  # Resolve the main repo root (handles git worktrees correctly)
74
181
  # Use the main worktree path so symlinks survive worktree removal
75
182
  REPO_ROOT="$(git -C "$ROOT_DIR" worktree list | head -1 | awk '{print $1}')"
@@ -104,6 +211,46 @@ ensure_symlink() {
104
211
  ln -s "$target" "$link_path"
105
212
  }
106
213
 
214
+ # Helper: link a single tracked (hand-written) skill into the global skills
215
+ # dir WITHOUT ever deleting something this script doesn't own
216
+ # (zudolab/zudo-doc#3156, D4). Unlike ensure_symlink (rm -rf-based -- safe
217
+ # only for the generated skill, whose global name this project owns),
218
+ # tracked-skill names are arbitrary and could collide with a user-owned
219
+ # global skill or a name already claimed by another project, so an existing
220
+ # entry that isn't already our own correct link is left untouched, with a
221
+ # warning.
222
+ safe_link_tracked_skill() {
223
+ local link_path="$1"
224
+ local link_target="$2"
225
+ local skill_name="$3"
226
+ local target="$4"
227
+
228
+ if [ -L "$link_path" ]; then
229
+ local current_target
230
+ current_target="$(readlink "$link_path")"
231
+ if [ "$current_target" = "$link_target" ]; then
232
+ return 0 # already correct -> no-op
233
+ fi
234
+ if [ -e "$link_path" ]; then
235
+ echo "WARNING: [$target] skipping tracked skill '$skill_name': $link_path already links to $current_target"
236
+ return 0
237
+ fi
238
+ # Dangling symlink (broken target) -> safe to replace.
239
+ rm -f "$link_path"
240
+ ln -s "$link_target" "$link_path"
241
+ echo " [$target] Linked tracked skill '$skill_name' -> $link_target"
242
+ return 0
243
+ fi
244
+
245
+ if [ -e "$link_path" ]; then
246
+ echo "WARNING: [$target] skipping tracked skill '$skill_name': $link_path is a real file/directory, not a symlink"
247
+ return 0
248
+ fi
249
+
250
+ ln -s "$link_target" "$link_path"
251
+ echo " [$target] Linked tracked skill '$skill_name' -> $link_target"
252
+ }
253
+
107
254
  DOCS_JA_DIR="$ROOT_DIR/src/content/docs-ja"
108
255
  HAS_JA=""
109
256
  if [ -d "$DOCS_JA_DIR" ]; then
@@ -283,18 +430,60 @@ JAEOF
283
430
  echo " [$target] Global symlink: $global_skills_dir/$SKILL_NAME"
284
431
  }
285
432
 
433
+ # Symlink the site's OWN hand-written skills into the global skills dir
434
+ # (zudolab/zudo-doc#3156). Discovery walks the target's own project skills
435
+ # directory dynamically -- not a hard-coded list, so it stays correct as
436
+ # sites add skills. Sources resolve through MAIN_PROJECT_DIR, not ROOT_DIR,
437
+ # the same way the generated skill's docs/docs-ja symlinks do (see the
438
+ # REPO_ROOT/MAIN_PROJECT_DIR comment above) so the links survive worktree
439
+ # removal. Target-local only: each target walks only its own
440
+ # ".$target/skills/" -- a missing directory is a silent no-op, and there is
441
+ # no ".claude" -> ".codex" fallback.
442
+ link_tracked_skills() {
443
+ local target="$1"
444
+ local skills_root="$MAIN_PROJECT_DIR/.$target/skills"
445
+ local global_skills_dir="$HOME/.$target/skills"
446
+
447
+ [ -d "$skills_root" ] || return 0
448
+
449
+ mkdir -p "$global_skills_dir"
450
+
451
+ local dir name
452
+ for dir in "$skills_root"/*/; do
453
+ [ -d "$dir" ] || continue
454
+ name="$(basename "$dir")"
455
+
456
+ # Skip the just-generated skill (already linked above via ensure_symlink)
457
+ # and the legacy doubled-suffix name (zudolab/zudo-doc#3154) -- otherwise
458
+ # a stale leftover directory left by that rename gets misread as a
459
+ # hand-written skill and exported globally, compounding both bugs.
460
+ [ "$name" = "$SKILL_NAME" ] && continue
461
+ [ "$name" = "$LEGACY_DEFAULT_SKILL_NAME" ] && continue
462
+
463
+ # A candidate qualifies only if it contains SKILL.md -- iterating every
464
+ # directory does not prove it is a skill.
465
+ [ -f "${dir}SKILL.md" ] || continue
466
+
467
+ safe_link_tracked_skill "$global_skills_dir/$name" "${dir%/}" "$name" "$target"
468
+ done
469
+ }
470
+
286
471
  read -r -a TARGETS <<< "$(resolve_targets)"
287
472
  echo "Target: $TARGET_MODE -> ${TARGETS[*]}"
288
473
  echo ""
289
474
 
290
475
  for target in "${TARGETS[@]}"; do
291
476
  generate_skill "$target"
477
+ if [ "$LINK_TRACKED_SKILLS" = "true" ]; then
478
+ link_tracked_skills "$target"
479
+ fi
292
480
  done
293
481
 
294
482
  echo ""
295
483
  echo "Done! Skill '$SKILL_NAME' is ready."
296
484
  echo ""
297
485
  echo "Use --target claude, --target codex, or --target both to override auto-detection."
486
+ echo "Use --no-link-tracked-skills to skip linking this project's own hand-written skills."
298
487
  echo "In Claude Code, use: /$SKILL_NAME <topic>"
299
488
  echo "In Codex, mention the skill by name when asking about this documentation."
300
489
  echo ""