rigjs 4.0.17 → 4.0.19

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.
@@ -38,6 +38,23 @@ export interface DeployTarget {
38
38
  original_regexp?: string;
39
39
  final?: string;
40
40
  } | undefined;
41
+ /**
42
+ * Edge provider for `rig publish` (回源改写 + 刷缓存).
43
+ * - 'cdn' (default): traditional Aliyun CDN, BatchSetCdnDomainConfig.
44
+ * - 'esa': Aliyun ESA (Edge Security Acceleration), site-scoped rewrite rules.
45
+ */
46
+ edge_provider?: 'cdn' | 'esa';
47
+ /**
48
+ * ESA OpenAPI endpoint. Only used when edge_provider === 'esa'.
49
+ * Defaults to 'esa.cn-hangzhou.aliyuncs.com'.
50
+ */
51
+ esa_endpoint?: string;
52
+ /**
53
+ * ESA site name (registrable zone, e.g. 'terncloud.com'). Only used when
54
+ * edge_provider === 'esa'. If omitted, derived from the endpoint domain's
55
+ * last two labels.
56
+ */
57
+ esa_site_name?: string;
41
58
  }
42
59
 
43
60
  /**
@@ -4,6 +4,7 @@ import crypto from 'crypto';
4
4
  import axios from 'axios';
5
5
  import * as uuid from 'uuid';
6
6
  import { DeployTarget } from '../CICD';
7
+ import { redactCdnUrl } from '@/utils/redact';
7
8
 
8
9
  type TFlag = 'break' | 'enhance_break' | null;
9
10
 
@@ -64,7 +65,7 @@ class CDN {
64
65
  });
65
66
 
66
67
  const url = `http://cdn.ap-southeast-1.aliyuncs.com?${paramConfig}`;
67
- console.log('cdn update url:', url);
68
+ console.log('cdn update url:', redactCdnUrl(url));
68
69
  const res = await axios.create().get(url);
69
70
  return res.data;
70
71
  }
@@ -0,0 +1,117 @@
1
+ import ESAClient, {
2
+ ListSitesRequest,
3
+ CreateRewriteUrlRuleRequest,
4
+ PurgeCachesRequest,
5
+ PurgeCachesRequestContent,
6
+ } from '@alicloud/esa20240910';
7
+ import { $OpenApiUtil } from '@alicloud/openapi-core';
8
+ import { DeployTarget } from '../CICD';
9
+
10
+ /**
11
+ * ESA (Aliyun Edge Security Acceleration) deploy adapter — the ESA-flavoured
12
+ * counterpart of {@link ./CDN.ts}. Used by `rig publish` when the deploy target
13
+ * sets `edge_provider: 'esa'`.
14
+ *
15
+ * Differences from traditional CDN:
16
+ * - ESA config is **site-scoped**: every operation needs a numeric `siteId`,
17
+ * resolved from the registrable zone (e.g. `terncloud.com`) via `ListSites`.
18
+ * - Back-to-origin rewrite uses `CreateRewriteUrlRule` (rule expression +
19
+ * static/dynamic target URI), not CDN's regex `back_to_origin_url_rewrite`.
20
+ * - Cache refresh uses `PurgeCaches`, not `RefreshObjectCaches`.
21
+ *
22
+ * Credentials come from the DeployTarget (injected via `-p ak=...&as=...`),
23
+ * never hard-coded here.
24
+ */
25
+ class ESA {
26
+ private client: ESAClient;
27
+ private siteIdCache: Map<string, number> = new Map();
28
+ private explicitSiteName?: string;
29
+
30
+ constructor(target: DeployTarget) {
31
+ const config = new $OpenApiUtil.Config({
32
+ accessKeyId: target.access_key,
33
+ accessKeySecret: target.access_secret,
34
+ endpoint: target.esa_endpoint || 'esa.cn-hangzhou.aliyuncs.com',
35
+ });
36
+ this.client = new ESAClient(config);
37
+ this.explicitSiteName = target.esa_site_name;
38
+ }
39
+
40
+ /**
41
+ * Registrable zone for a domain: `test-esa.terncloud.com` -> `terncloud.com`.
42
+ * (Good enough for normal `*.com` / `*.cn` zones; pass `esa_site_name`
43
+ * explicitly for multi-label public suffixes like `*.com.cn`.)
44
+ */
45
+ private siteNameFor(domain: string): string {
46
+ if (this.explicitSiteName) return this.explicitSiteName;
47
+ const parts = domain.split('.').filter(Boolean);
48
+ return parts.length <= 2 ? domain : parts.slice(-2).join('.');
49
+ }
50
+
51
+ /** Resolve (and cache) the numeric ESA siteId for a domain's zone. */
52
+ public async resolveSiteId(domain: string): Promise<number> {
53
+ const siteName = this.siteNameFor(domain);
54
+ const cached = this.siteIdCache.get(siteName);
55
+ if (cached) return cached;
56
+
57
+ const resp = await this.client.listSites(
58
+ new ListSitesRequest({ siteName, siteSearchType: 'exact' })
59
+ );
60
+ const sites = resp.body?.sites || [];
61
+ const site = sites.find((s) => s.siteName === siteName) || sites[0];
62
+ if (!site || site.siteId == null) {
63
+ throw new Error(
64
+ `ESA site not found for "${siteName}" (domain ${domain}). ` +
65
+ `Create the ESA site (zone) and bind the OSS origin in the ESA console/API first.`
66
+ );
67
+ }
68
+ this.siteIdCache.set(siteName, site.siteId);
69
+ return site.siteId;
70
+ }
71
+
72
+ /**
73
+ * Create a single back-to-origin URI rewrite rule.
74
+ * @param rule ESA rule expression. `true` matches all requests; otherwise a
75
+ * conditional expression, e.g. `(http.request.uri.path.file_name ne "")`.
76
+ * @param rewriteUriType `static` (fixed `uri`) or `dynamic` (`uri` is an expression).
77
+ * @param uri target URI after rewrite (static path or dynamic expression).
78
+ * @param sequence rule priority (lower runs first).
79
+ */
80
+ public async setRewriteRule(
81
+ domain: string,
82
+ ruleName: string,
83
+ rule: string,
84
+ rewriteUriType: 'static' | 'dynamic',
85
+ uri: string,
86
+ sequence: number
87
+ ) {
88
+ const siteId = await this.resolveSiteId(domain);
89
+ const resp = await this.client.createRewriteUrlRule(
90
+ new CreateRewriteUrlRuleRequest({
91
+ siteId,
92
+ ruleName,
93
+ rule,
94
+ ruleEnable: 'on',
95
+ rewriteUriType,
96
+ uri,
97
+ sequence,
98
+ })
99
+ );
100
+ return resp.body;
101
+ }
102
+
103
+ /** Purge cached files by URL. */
104
+ public async purgeCache(domain: string, urls: string[]) {
105
+ const siteId = await this.resolveSiteId(domain);
106
+ const resp = await this.client.purgeCaches(
107
+ new PurgeCachesRequest({
108
+ siteId,
109
+ type: 'file',
110
+ content: new PurgeCachesRequestContent({ files: urls }),
111
+ })
112
+ );
113
+ return resp.body;
114
+ }
115
+ }
116
+
117
+ export default ESA;
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import CICD from '@/classes/cicd/CICD';
4
4
  import CICDCmd from '@/classes/cicd/CICDCmd';
5
5
  import AliOSS from '@/classes/cicd/Deploy/AliDeploy';
6
+ import { redactTarget } from '@/utils/redact';
6
7
 
7
8
  let filesList: string[] = [];
8
9
  const traverseFolder = (url: string) => {
@@ -33,7 +34,7 @@ export default async (cmd: any) => {
33
34
  const target = Array.isArray(cicdCmd.cicd.target)
34
35
  ? cicdCmd.cicd.target[0]
35
36
  : cicdCmd.cicd.target;
36
- console.log('oss tagert', target);
37
+ console.log('oss tagert', redactTarget(target));
37
38
  const aliOss = new AliOSS(target);
38
39
  console.log('Please Wait for Upload OSS...');
39
40
  if (!cicdCmd.endpoints || cicdCmd.endpoints.length === 0) {
package/lib/init/index.ts CHANGED
@@ -6,7 +6,19 @@
6
6
  */
7
7
  import print from '../print';
8
8
  import fs from 'fs';
9
- import axios from 'axios';
9
+
10
+ const PACKAGE_RIG_TEMPLATE = `{
11
+ // package.rig.json5 — rig configuration
12
+ // Field reference: see the rig-package / rig-cicd skills.
13
+ dependencies: {
14
+ // 'lib-name': {
15
+ // source: 'git@github.com:org/repo.git', // git ssh url
16
+ // version: '1.0.0', // git tag (semver)
17
+ // dev: false // true => clone into rig_dev/ and develop locally
18
+ // }
19
+ }
20
+ }
21
+ `;
10
22
 
11
23
  export default async () => {
12
24
  try {
@@ -19,11 +31,9 @@ export default async () => {
19
31
  if (fs.existsSync('package.rig.json5')) {
20
32
  print.info('package.rig.json5 already exists~');
21
33
  } else {
22
- //创建package.rig.json5
34
+ //创建package.rig.json5 — 本地模板,留空 dependencies,无示例库引用
23
35
  print.info('create package.rig.json5');
24
- const resPackageRigJSON5 = await axios.get('https://gist.githubusercontent.com/FlashHand/ea156ac4930b05832ad7c568f7f00cdd/raw/48a4321ccf1b0339a15f6454336f289b8b5bad58/package.rig.json5');
25
- const packageRigJSON5 = resPackageRigJSON5.data;
26
- fs.writeFileSync('./package.rig.json5', packageRigJSON5);
36
+ fs.writeFileSync('./package.rig.json5', PACKAGE_RIG_TEMPLATE);
27
37
  }
28
38
  //检查是否存在rig_helper.js
29
39
  // if (fs.existsSync(`${process.cwd()}/rig_helper.js`)) {
@@ -95,8 +105,7 @@ export default async () => {
95
105
  postinstall: "rig postinstall",
96
106
  },
97
107
  devDependencies: {
98
- json5: '2.2.1',
99
- "rig-helper": '^1.0.2'
108
+ json5: '2.2.1'
100
109
  }
101
110
  }
102
111
  pkgJSON.private = inserted.private;
@@ -125,11 +134,9 @@ export default async () => {
125
134
  }
126
135
  if (pkgJSON.devDependencies) {
127
136
  pkgJSON.devDependencies.json5 = inserted.devDependencies.json5;
128
- pkgJSON.devDependencies["rig-helper"] = inserted.devDependencies["rig-helper"];
129
137
  } else {
130
138
  pkgJSON.devDependencies = inserted.devDependencies;
131
139
  }
132
- //检查是否存在rig-helper
133
140
  fs.writeFileSync('package.json', JSON.stringify(pkgJSON, null, 2));
134
141
  print.succeed('rig init succeed');
135
142
  } catch (e) {
@@ -1,6 +1,7 @@
1
- import CICD from '@/classes/cicd/CICD';
1
+ import CICD, { DeployTarget } from '@/classes/cicd/CICD';
2
2
  import CICDCmd from '@/classes/cicd/CICDCmd';
3
3
  import CDN from '@/classes/cicd/Deploy/CDN';
4
+ import ESA from '@/classes/cicd/Deploy/ESA';
4
5
 
5
6
  const delay = async (ms: number) => {
6
7
  await new Promise((resolve) => {
@@ -69,6 +70,76 @@ const refreshCache = async (urls: string[], cdn: CDN) => {
69
70
  console.log('RefreshCache Done');
70
71
  };
71
72
 
73
+ /**
74
+ * ESA publish path (edge_provider === 'esa'). Sets back-to-origin URI rewrite
75
+ * rules per domain, then purges the entry URLs.
76
+ *
77
+ * Mapping vs CDN: hash mode only needs the entry page rewritten to
78
+ * `/<deployDir>/index.html` (publicPath already carries deployDir so static
79
+ * files resolve at the OSS origin directly). history/mpa additionally prepend
80
+ * deployDir for static files and fall back to the entry for extension-less
81
+ * routes — those dynamic-rewrite expressions need validation on a live ESA site.
82
+ */
83
+ const publishViaEsa = async (
84
+ cicd: CICD,
85
+ cicdCmd: CICDCmd,
86
+ target: DeployTarget
87
+ ) => {
88
+ const esa = new ESA(target);
89
+ if (!cicdCmd.endpoints || cicdCmd.endpoints.length === 0) {
90
+ throw new Error('Endpoints.length Can Not Be 0!');
91
+ }
92
+ const purgeByDomain: { [domain: string]: string[] } = {};
93
+ for (const endpoint of cicdCmd.endpoints) {
94
+ const deployDir = endpoint.deployDir.replace(/\\/g, '/');
95
+ const webEntryPath = endpoint.web_entry_path || target.web_entry_path || '/';
96
+ const ruleBase = `rig-${deployDir.replace(/[^\w]+/g, '-')}`.replace(
97
+ /-+/g,
98
+ '-'
99
+ );
100
+ for (const domain of endpoint.domains) {
101
+ if (cicd.web_type === 'hash') {
102
+ await esa.setRewriteRule(
103
+ domain,
104
+ `${ruleBase}-entry`,
105
+ `(http.request.uri.path eq "${webEntryPath}")`,
106
+ 'static',
107
+ `/${deployDir}/index.html`,
108
+ 1
109
+ );
110
+ } else {
111
+ await esa.setRewriteRule(
112
+ domain,
113
+ `${ruleBase}-file`,
114
+ `(http.request.uri.path.extension ne "")`,
115
+ 'dynamic',
116
+ `concat("/${deployDir}", http.request.uri.path)`,
117
+ 1
118
+ );
119
+ await esa.setRewriteRule(
120
+ domain,
121
+ `${ruleBase}-entry`,
122
+ 'true',
123
+ cicd.web_type === 'mpa' ? 'dynamic' : 'static',
124
+ cicd.web_type === 'mpa'
125
+ ? `concat("/${deployDir}", http.request.uri.path, ".html")`
126
+ : `/${deployDir}/index.html`,
127
+ 2
128
+ );
129
+ }
130
+ if (!purgeByDomain[domain]) {
131
+ purgeByDomain[domain] = [];
132
+ }
133
+ purgeByDomain[domain].push(`https://${domain}${webEntryPath}`);
134
+ }
135
+ }
136
+ for (const domain of Object.keys(purgeByDomain)) {
137
+ console.log(`ESA purge ${domain}:`, purgeByDomain[domain]);
138
+ await esa.purgeCache(domain, purgeByDomain[domain]);
139
+ }
140
+ console.log('ESA rewrite rules + purge done');
141
+ };
142
+
72
143
  export default async (cmd: any) => {
73
144
  try {
74
145
  const rewriteConfigs: {
@@ -99,6 +170,12 @@ export default async (cmd: any) => {
99
170
  ? cicdCmd.cicd.target[0]
100
171
  : cicdCmd.cicd.target;
101
172
 
173
+ if (target.edge_provider === 'esa') {
174
+ await publishViaEsa(cicd, cicdCmd, target);
175
+ console.log('Publish Done-----');
176
+ return;
177
+ }
178
+
102
179
  const cdn = new CDN(target);
103
180
  const urls: string[] = [];
104
181
  if (!cicdCmd.endpoints || cicdCmd.endpoints.length === 0) {
@@ -0,0 +1,48 @@
1
+ // Helpers to keep cloud credentials out of stdout.
2
+ //
3
+ // rig's deploy/publish flow prints (a) the resolved deploy target and
4
+ // (b) every Aliyun CDN API URL. Both carry the AccessKeyId / AccessKeySecret
5
+ // in clear, which makes the console output unsafe to copy/paste into issues,
6
+ // CI logs, or chat.
7
+ //
8
+ // Use `maskSecret` for short identifiers (keeps a head+tail hint so two
9
+ // different keys are still distinguishable in logs), `redactTarget` before
10
+ // console-logging a DeployTarget, and `redactCdnUrl` before logging any
11
+ // signed Aliyun OpenAPI URL.
12
+
13
+ /** Mask a credential while keeping a short prefix + suffix for debuggability. */
14
+ export function maskSecret(s: string | undefined | null): string {
15
+ if (!s) return '';
16
+ if (s.length <= 8) return '****';
17
+ return `${s.slice(0, 4)}…${s.slice(-4)}`;
18
+ }
19
+
20
+ /**
21
+ * Return a shallow copy of a DeployTarget-shaped object with `access_key`
22
+ * and `access_secret` masked. Unknown keys pass through unchanged.
23
+ */
24
+ export function redactTarget<T extends Record<string, any>>(target: T): T {
25
+ if (!target || typeof target !== 'object') return target;
26
+ const out: Record<string, any> = { ...target };
27
+ if (typeof out.access_key === 'string') out.access_key = maskSecret(out.access_key);
28
+ if (typeof out.access_secret === 'string') out.access_secret = maskSecret(out.access_secret);
29
+ return out as T;
30
+ }
31
+
32
+ /**
33
+ * Redact `AccessKeyId` and `Signature` query parameters from an Aliyun
34
+ * OpenAPI URL so the URL can be safely logged. Leaves all other params
35
+ * (Action, Timestamp, etc.) intact for debugging.
36
+ */
37
+ export function redactCdnUrl(url: string): string {
38
+ if (!url) return url;
39
+ return url
40
+ .replace(/([?&]AccessKeyId=)([^&]+)/i, (_m, p1, v) => `${p1}${maskSecret(v)}`)
41
+ .replace(/([?&]Signature=)([^&]+)/i, (_m, p1) => `${p1}REDACTED`);
42
+ }
43
+
44
+ export default {
45
+ maskSecret,
46
+ redactTarget,
47
+ redactCdnUrl,
48
+ };
package/lib/wiki/lint.ts CHANGED
@@ -42,6 +42,7 @@ interface Findings {
42
42
  const REQUIRED_KEYS = ['type', 'sources', 'ingested-at', 'last-updated'] as const;
43
43
  const SOURCE_EXTRA_KEYS = ['source-sha', 'source-path'] as const;
44
44
  const WIKI_SUBDIRS = ['sources', 'entities', 'concepts', 'synthesis', 'queries'] as const;
45
+ const TOP_LEVEL_WIKILINK_TARGETS = ['index', 'overview', 'log', 'reviews', 'purpose', 'schema'] as const;
45
46
 
46
47
  export default async function wikiLint(opts: LintOpts): Promise<void> {
47
48
  const target = requireVault();
@@ -89,6 +90,10 @@ function lintOne(wiki: WikiEntry): Findings {
89
90
  }
90
91
 
91
92
  const slugToRel = new Map<string, string>();
93
+ for (const slug of TOP_LEVEL_WIKILINK_TARGETS) {
94
+ const rel = `${slug}.md`;
95
+ if (fs.existsSync(path.join(wiki.path, rel))) slugToRel.set(slug, rel);
96
+ }
92
97
  for (const p of pages) slugToRel.set(p.slug, p.rel);
93
98
 
94
99
  const linkedSlugs = new Set<string>();
@@ -103,7 +108,7 @@ function lintOne(wiki: WikiEntry): Findings {
103
108
  }
104
109
  const sourcePath = String(p.frontmatter['source-path'] || '');
105
110
  if (sourcePath) {
106
- const abs = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(wiki.path, sourcePath);
111
+ const abs = resolveSourcePath(wiki, sourcePath);
107
112
  if (!fs.existsSync(abs)) {
108
113
  f.missingRawSource.push({ rel: p.rel, sourcePath });
109
114
  } else {
@@ -188,6 +193,23 @@ function extractWikilinks(body: string): string[] {
188
193
  return out;
189
194
  }
190
195
 
196
+ function resolveSourcePath(wiki: WikiEntry, sourcePath: string): string {
197
+ const obsidian = parseObsidianFilePath(sourcePath);
198
+ if (obsidian) return path.resolve(path.dirname(wiki.path), obsidian);
199
+ return path.isAbsolute(sourcePath) ? sourcePath : path.resolve(wiki.path, sourcePath);
200
+ }
201
+
202
+ function parseObsidianFilePath(sourcePath: string): string | null {
203
+ if (!sourcePath.startsWith('obsidian://open?')) return null;
204
+ try {
205
+ const url = new URL(sourcePath);
206
+ const file = url.searchParams.get('file');
207
+ return file && file.trim() ? file : null;
208
+ } catch {
209
+ return null;
210
+ }
211
+ }
212
+
191
213
  function sha256(file: string): string {
192
214
  return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
193
215
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rigjs",
3
- "version": "4.0.17",
4
- "versionCode": 26052424,
3
+ "version": "4.0.19",
4
+ "versionCode": 26052701,
5
5
  "description": "A multi-repos dev tool based on yarn and git.Rigjs is intended to be the simplest way to develop,share and deliver codes between different developers or different projects.",
6
6
  "keywords": [
7
7
  "modular",
@@ -36,9 +36,13 @@
36
36
  "doc",
37
37
  "scripts",
38
38
  ".claude/skills/rig-wiki",
39
+ ".claude/skills/rig-package",
40
+ ".claude/skills/rig-cicd",
39
41
  ".claude-plugin",
40
42
  "RIG_WIKI_SKILL.md",
41
43
  "RIG_CREW_SKILL.md",
44
+ "RIG_PACKAGE_SKILL.md",
45
+ "RIG_CICD_SKILL.md",
42
46
  "skills.md",
43
47
  "index.js",
44
48
  "README.md",
@@ -56,13 +60,17 @@
56
60
  "t": "node lib/rig/index.js tag",
57
61
  "deliver": "node scripts/publish.mjs",
58
62
  "deliver:alpha": "rig tag && yarn build && node scripts/publish.mjs --tag alpha",
59
- "build": "esbuild lib/rig/index.ts --platform=node --bundle --sourcemap=inline --minify --outfile=built/index.js --external:shelljs --external:better-sqlite3 --external:bindings --external:proxy-agent --external:@tobilu/qmd",
63
+ "build": "esbuild lib/rig/index.ts --platform=node --bundle --sourcemap=inline --minify --outfile=built/index.js --external:shelljs --external:better-sqlite3 --external:bindings --external:proxy-agent --external:@tobilu/qmd && yarn scan:built",
64
+ "scan:built": "GOMAXPROCS=2 gitleaks detect --source built --no-git --no-banner --redact --exit-code 1",
60
65
  "version:code": "node scripts/version-code.mjs",
61
66
  "version:code:peek": "node scripts/version-code.mjs --peek",
62
67
  "prepublishOnly": "node scripts/version-code.mjs && node scripts/sync-skill.mjs && yarn build",
63
68
  "postinstall": "node scripts/postinstall.mjs"
64
69
  },
65
70
  "dependencies": {
71
+ "@alicloud/esa20240910": "^3.1.2",
72
+ "@alicloud/openapi-client": "^0.4.15",
73
+ "@alicloud/tea-util": "^1.4.11",
66
74
  "@tobilu/qmd": "~2.5.2",
67
75
  "@types/ali-oss": "^6.16.3",
68
76
  "@types/json5": "^2.2.0",
@@ -14,6 +14,8 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
14
14
 
15
15
  const skills = [
16
16
  ['RIG_WIKI_SKILL.md', '.claude/skills/rig-wiki/SKILL.md'],
17
+ ['RIG_PACKAGE_SKILL.md', '.claude/skills/rig-package/SKILL.md'],
18
+ ['RIG_CICD_SKILL.md', '.claude/skills/rig-cicd/SKILL.md'],
17
19
  ];
18
20
 
19
21
  for (const [srcRel, destRel] of skills) {
package/skills.md CHANGED
@@ -8,6 +8,8 @@ This page is the skill index for the `rigjs` package. The root `README.md` keeps
8
8
  |---|---|---|---|---|
9
9
  | `rig-wiki` | [`RIG_WIKI_SKILL.md`](./RIG_WIKI_SKILL.md) | [`.claude/skills/rig-wiki/SKILL.md`](./.claude/skills/rig-wiki/SKILL.md) | `rig wiki *` | Karpathy-style LLM wiki operations: scan, fetch, ingest, query, lint, rebuild. |
10
10
  | `rig-crew` | [`RIG_CREW_SKILL.md`](./RIG_CREW_SKILL.md) | (none — vault-level guidance) | `rig crew *` | File-backed, Leader-first multi-agent coordination over an Obsidian vault. |
11
+ | `rig-package` | [`RIG_PACKAGE_SKILL.md`](./RIG_PACKAGE_SKILL.md) | [`.claude/skills/rig-package/SKILL.md`](./.claude/skills/rig-package/SKILL.md) | `rig init` / `install` / `add` / `dev` / `tag` | Git-tag + ssh package manager that replaces a private npm registry; documents every `package.rig.json5#dependencies` field. |
12
+ | `rig-cicd` | [`RIG_CICD_SKILL.md`](./RIG_CICD_SKILL.md) | [`.claude/skills/rig-cicd/SKILL.md`](./.claude/skills/rig-cicd/SKILL.md) | `rig build` / `deploy` / `publish` | Aliyun OSS + CDN static-site CI/CD; one bucket → many sites via CDN URI rewrites set during `rig publish`. Supports hash, history, mpa, pre-built HTML dirs. |
11
13
 
12
14
  `rig-crew` is intentionally not copied into the rigjs package's own `.claude/skills/`. Its instructions belong at the Vault level (the project that uses crew), not at the tool level (rigjs itself).
13
15
 
@@ -89,6 +91,8 @@ Canonical skill files live at the package root:
89
91
 
90
92
  - [`RIG_WIKI_SKILL.md`](./RIG_WIKI_SKILL.md)
91
93
  - [`RIG_CREW_SKILL.md`](./RIG_CREW_SKILL.md)
94
+ - [`RIG_PACKAGE_SKILL.md`](./RIG_PACKAGE_SKILL.md)
95
+ - [`RIG_CICD_SKILL.md`](./RIG_CICD_SKILL.md)
92
96
 
93
97
  A package-internal mirror lives under `.claude/skills/` so the rig package itself (when checked out by another agent) can read its own skills:
94
98
 
@@ -96,7 +100,7 @@ A package-internal mirror lives under `.claude/skills/` so the rig package itsel
96
100
  node scripts/sync-skill.mjs
97
101
  ```
98
102
 
99
- `prepublishOnly` runs the sync script before packaging. Today this plugin-copy set only includes `rig-wiki`; `rig-crew` remains Vault-level guidance and has no in-package `.claude/skills/` copy.
103
+ `prepublishOnly` runs the sync script before packaging. The plugin-copy set covers `rig-wiki`, `rig-package`, and `rig-cicd`; `rig-crew` remains Vault-level guidance and has no in-package `.claude/skills/` copy.
100
104
 
101
105
  ## Documentation Policy
102
106