create-zudo-doc 4.4.13 → 5.0.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.
- package/dist/claude-md-gen.js +1 -1
- package/dist/constants.js +7 -0
- package/dist/features/index.js +2 -0
- package/dist/features/toc-toggle.d.ts +11 -0
- package/dist/features/toc-toggle.js +13 -0
- package/dist/scaffold.d.ts +15 -1
- package/dist/scaffold.js +56 -20
- package/dist/zfb-config-gen.js +3 -0
- package/package.json +1 -2
- package/templates/base/public/favicon-16x16.png +0 -0
- package/templates/base/public/favicon-32x32.png +0 -0
- package/templates/base/public/favicon.ico +0 -0
- package/templates/base/public/favicon.svg +4 -0
- package/templates/base/scripts/setup-doc-skill.sh +194 -5
package/dist/claude-md-gen.js
CHANGED
|
@@ -19,7 +19,7 @@ export function generateCLAUDEFile(choices) {
|
|
|
19
19
|
lines.push(``);
|
|
20
20
|
lines.push(`- **zfb** — documentation build framework`);
|
|
21
21
|
lines.push(`- **MDX** — content format, authored under \`src/content/\``);
|
|
22
|
-
lines.push(`- **Tailwind CSS v4** —
|
|
22
|
+
lines.push(`- **Tailwind CSS v4** — compiled by zfb's embedded Tailwind engine (no \`@tailwindcss/vite\` plugin, no \`tailwindcss\` dependency); \`src/styles/global.css\` imports \`tailwindcss/preflight\` + \`tailwindcss/utilities\` and zfb's internal resolver serves both`);
|
|
23
23
|
lines.push(`- **Preact** — for interactive islands only (with compat mode for React API)`);
|
|
24
24
|
lines.push("- **zfb semantic highlighting** — native build-time fenced-code rendering plus lazy `@takazudo/zfb-md-wasm` for HtmlPreview; both emit `hi-*` classes resolved through `--zd-syntax-*` design tokens");
|
|
25
25
|
lines.push(`- **@takazudo/zudo-doc** — the package that owns everything: layout, chrome, islands, default \`@theme\` design tokens, and (via \`packageOwnedRoutes\`, on by default) the doc routes themselves`);
|
package/dist/constants.js
CHANGED
|
@@ -247,6 +247,13 @@ export const FEATURES = [
|
|
|
247
247
|
default: false,
|
|
248
248
|
cliFlag: "sidebar-toggle",
|
|
249
249
|
},
|
|
250
|
+
{
|
|
251
|
+
value: "tocToggle",
|
|
252
|
+
label: "TOC toggle",
|
|
253
|
+
hint: "Show/hide the desktop table of contents",
|
|
254
|
+
default: false,
|
|
255
|
+
cliFlag: "toc-toggle",
|
|
256
|
+
},
|
|
250
257
|
{
|
|
251
258
|
value: "versioning",
|
|
252
259
|
label: "Versioning",
|
package/dist/features/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { searchFeature } from "./search.js";
|
|
|
10
10
|
import { footerFeature } from "./footer.js";
|
|
11
11
|
import { sidebarResizerFeature } from "./sidebar-resizer.js";
|
|
12
12
|
import { sidebarToggleFeature } from "./sidebar-toggle.js";
|
|
13
|
+
import { tocToggleFeature } from "./toc-toggle.js";
|
|
13
14
|
import { docHistoryFeature } from "./doc-history.js";
|
|
14
15
|
import { llmsTxtFeature } from "./llms-txt.js";
|
|
15
16
|
import { claudeResourcesFeature } from "./claude-resources.js";
|
|
@@ -39,6 +40,7 @@ export const featureModules = {
|
|
|
39
40
|
themePackSwitcher: themePackSwitcherFeature,
|
|
40
41
|
sidebarResizer: sidebarResizerFeature,
|
|
41
42
|
sidebarToggle: sidebarToggleFeature,
|
|
43
|
+
tocToggle: tocToggleFeature,
|
|
42
44
|
versioning: versioningFeature,
|
|
43
45
|
docHistory: docHistoryFeature,
|
|
44
46
|
bodyFootUtil: bodyFootUtilFeature,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FeatureModule } from "../compose.js";
|
|
2
|
+
/**
|
|
3
|
+
* TOC-toggle feature.
|
|
4
|
+
*
|
|
5
|
+
* Purely a `zudoDoc({ tocToggle: true })` field (see `zfb-config-gen.ts`).
|
|
6
|
+
* `DesktopTocToggle` is fully package-owned
|
|
7
|
+
* (`@takazudo/zudo-doc/desktop-toc-toggle-island`, wired into the doc-page
|
|
8
|
+
* shell directly) and its CSS ships unconditionally from
|
|
9
|
+
* `@takazudo/zudo-doc/features.css` — nothing to inject or copy.
|
|
10
|
+
*/
|
|
11
|
+
export declare const tocToggleFeature: FeatureModule;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOC-toggle feature.
|
|
3
|
+
*
|
|
4
|
+
* Purely a `zudoDoc({ tocToggle: true })` field (see `zfb-config-gen.ts`).
|
|
5
|
+
* `DesktopTocToggle` is fully package-owned
|
|
6
|
+
* (`@takazudo/zudo-doc/desktop-toc-toggle-island`, wired into the doc-page
|
|
7
|
+
* shell directly) and its CSS ships unconditionally from
|
|
8
|
+
* `@takazudo/zudo-doc/features.css` — nothing to inject or copy.
|
|
9
|
+
*/
|
|
10
|
+
export const tocToggleFeature = () => ({
|
|
11
|
+
name: "tocToggle",
|
|
12
|
+
injections: [],
|
|
13
|
+
});
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -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 = "^
|
|
35
|
+
export declare const ZUDO_DOC_PIN = "^5.0.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 = "^
|
|
46
|
+
export const ZUDO_DOC_PIN = "^5.0.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 (
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
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
|
-
|
|
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/${
|
|
375
|
+
gitignoreLines.push(`.claude/skills/${skillName}/docs-ja`, `.codex/skills/${skillName}/docs-ja`);
|
|
356
376
|
}
|
|
357
377
|
gitignoreLines.push("");
|
|
358
378
|
}
|
|
@@ -550,9 +570,18 @@ 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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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
|
+
// 2.0.0: BREAKING — the `githubAutolinks` markdown feature was removed
|
|
577
|
+
// (`GithubAutolinksConfig` is gone from `zfb/config`, and a config still
|
|
578
|
+
// setting it hard-errors at load). A fresh scaffold never emitted it, so
|
|
579
|
+
// there is no generator-side migration. Additive alongside it:
|
|
580
|
+
// `strictContentBridge`, a build-only gate that fails `zfb build` when a
|
|
581
|
+
// collection entry falls back to `<pre data-zfb-content-fallback>`.
|
|
582
|
+
"@takazudo/zfb": "2.1.0",
|
|
583
|
+
"@takazudo/zfb-runtime": "2.1.0",
|
|
584
|
+
"@takazudo/zfb-md-wasm": "2.1.0",
|
|
556
585
|
// @takazudo/zudo-doc — published from this monorepo via
|
|
557
586
|
// .github/workflows/publish-zudo-doc.yml. The pin here is bumped in
|
|
558
587
|
// lockstep by scripts/release-create-zudo-doc.sh whenever zudo-doc's
|
|
@@ -587,10 +616,6 @@ function generatePackageJson(choices) {
|
|
|
587
616
|
// compiles. Same pin as host. Caught by W6B (#1735) consumer-build
|
|
588
617
|
// verification.
|
|
589
618
|
"preact-render-to-string": "^6.6.6",
|
|
590
|
-
"gray-matter": "^4.0.0",
|
|
591
|
-
mermaid: "^11.12.3",
|
|
592
|
-
"remark-cjk-friendly": "^2.0.1",
|
|
593
|
-
"remark-directive": "^3.0.0",
|
|
594
619
|
// katex — server-side LaTeX renderer used by the always-on
|
|
595
620
|
// pages/lib/_math-block.tsx (called from pages/_mdx-components.ts
|
|
596
621
|
// for `$…$` and `$$…$$` math nodes). Caught by W6B (#1735)
|
|
@@ -640,11 +665,17 @@ function generatePackageJson(choices) {
|
|
|
640
665
|
// packages/zudo-doc/src/__tests__/optional-peer-reachability.test.ts.)
|
|
641
666
|
};
|
|
642
667
|
const devDeps = {
|
|
643
|
-
"@tailwindcss/vite": "^4.2.0",
|
|
644
|
-
tailwindcss: "^4.2.0",
|
|
645
668
|
typescript: "^5.9.0",
|
|
646
669
|
"@types/node": "^22.0.0",
|
|
647
|
-
|
|
670
|
+
// @types/react is intentionally ABSENT (#3181/#3183). tsconfig.base.json
|
|
671
|
+
// now sets `jsx: "react-jsx"` + `jsxImportSource: "preact"`, so TypeScript
|
|
672
|
+
// resolves `JSX.IntrinsicElements` from preact/jsx-runtime's own types —
|
|
673
|
+
// not from a global @types/react namespace. Ejected components (e.g.
|
|
674
|
+
// `src/components/zudo-doc/theme-toggle/`) typecheck cleanly under
|
|
675
|
+
// `zfb check` without it; verified by the create-zudo-doc `test:slow`
|
|
676
|
+
// post-eject build. Do not re-add this dep to work around a typecheck
|
|
677
|
+
// failure — that would mean the react-jsx flip regressed, which is worth
|
|
678
|
+
// reporting against #3181, not papering over here.
|
|
648
679
|
// html-validate dropped — check:html is no longer a default script
|
|
649
680
|
// (see the scripts block below; `.htmlvalidate.json` no longer ships).
|
|
650
681
|
};
|
|
@@ -672,7 +703,7 @@ function generatePackageJson(choices) {
|
|
|
672
703
|
// `/exclude` at module scope from the always-bundled chrome graph; #3110
|
|
673
704
|
// moved compileExclude into @takazudo/zudo-doc, so docHistory-OFF projects
|
|
674
705
|
// no longer need the package at all.
|
|
675
|
-
deps["@takazudo/zudo-doc-history-server"] = "^
|
|
706
|
+
deps["@takazudo/zudo-doc-history-server"] = "^5.0.0";
|
|
676
707
|
// tsx is no longer needed here: the relocated package plugin imports the
|
|
677
708
|
// runner directly (no `tsx -e` spawn) since the package ships compiled
|
|
678
709
|
// dist/ — package-first migration #2321 (#2337).
|
|
@@ -741,6 +772,11 @@ function generatePackageJson(choices) {
|
|
|
741
772
|
"tags-suggest --config src/config/tag-vocabulary.ts";
|
|
742
773
|
}
|
|
743
774
|
if (choices.features.includes("skillSymlinker")) {
|
|
775
|
+
// Deliberately omit --no-link-tracked-skills here: automatic tracked-skill
|
|
776
|
+
// linking (#3152) is the intended default for generated projects. Only the
|
|
777
|
+
// zudo-doc monorepo's own root package.json passes the opt-out, because it
|
|
778
|
+
// has 20 directories under `.claude/skills/` and a blanket loop would export
|
|
779
|
+
// 19 project-specific skills into the maintainer's global skills dir (#3157).
|
|
744
780
|
scripts["setup:doc-skill"] = "bash scripts/setup-doc-skill.sh";
|
|
745
781
|
scripts["setup:doc-skill-silent"] =
|
|
746
782
|
"bash scripts/setup-doc-skill.sh --silent";
|
package/dist/zfb-config-gen.js
CHANGED
|
@@ -56,6 +56,7 @@ export const DEFAULT_MIRROR = {
|
|
|
56
56
|
designTokenPanel: false,
|
|
57
57
|
sidebarResizer: false,
|
|
58
58
|
sidebarToggle: false,
|
|
59
|
+
tocToggle: false,
|
|
59
60
|
imageEnlarge: false,
|
|
60
61
|
findInPage: false,
|
|
61
62
|
dynamicPageTransition: false,
|
|
@@ -219,6 +220,7 @@ function buildDesiredConfig(choices) {
|
|
|
219
220
|
desired.designTokenPanel = choices.features.includes("designTokenPanel");
|
|
220
221
|
desired.sidebarResizer = choices.features.includes("sidebarResizer");
|
|
221
222
|
desired.sidebarToggle = choices.features.includes("sidebarToggle");
|
|
223
|
+
desired.tocToggle = choices.features.includes("tocToggle");
|
|
222
224
|
desired.imageEnlarge = choices.features.includes("imageEnlarge");
|
|
223
225
|
// findInPage rides the existing tauri feature (#2690) — the Cmd/Ctrl+F find
|
|
224
226
|
// bar only makes sense inside the Tauri desktop shell, so it has no CLI
|
|
@@ -358,6 +360,7 @@ const FIELD_ORDER = [
|
|
|
358
360
|
"designTokenPanel",
|
|
359
361
|
"sidebarResizer",
|
|
360
362
|
"sidebarToggle",
|
|
363
|
+
"tocToggle",
|
|
361
364
|
"imageEnlarge",
|
|
362
365
|
"findInPage",
|
|
363
366
|
"dynamicPageTransition",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-zudo-doc",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Create a new zudo-doc documentation site",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Takeshi Takatsudo",
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"@types/fs-extra": "^11.0.4",
|
|
62
62
|
"@types/minimist": "^1.2.5",
|
|
63
63
|
"@types/node": "^22.0.0",
|
|
64
|
-
"gray-matter": "^4.0.3",
|
|
65
64
|
"typescript": "^5.9.0",
|
|
66
65
|
"vitest": "^4.1.0"
|
|
67
66
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
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:
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
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 ""
|