code-workspace-zhuiyi 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.env.example +3 -0
  2. package/LICENSE +21 -0
  3. package/README.md +168 -0
  4. package/README.zh-CN.md +168 -0
  5. package/artifacts/manifest.json +183 -0
  6. package/artifacts/templates/USER_GUIDE.template.md +1 -0
  7. package/artifacts/templates/agents/WORKSPACE_GUARD.md.template +73 -0
  8. package/artifacts/templates/agents/skills/code-workspace-resolve-branch/SKILL.md +88 -0
  9. package/artifacts/templates/agents/skills/code-workspace-resolve-branch/evals/evals.json +45 -0
  10. package/artifacts/templates/claude/commands/code-workspace/add-projects.md +23 -0
  11. package/artifacts/templates/codex/hooks.json +86 -0
  12. package/artifacts/templates/codex/skills/code-workspace-add-projects/SKILL.md +28 -0
  13. package/artifacts/templates/user-guide/en-US.md +97 -0
  14. package/artifacts/templates/user-guide/zh-CN.md +97 -0
  15. package/assets/i18n-icon.svg +1 -0
  16. package/assets/logo-vector.svg +30 -0
  17. package/assets/request_tip.mp3 +0 -0
  18. package/assets/session_finish.mp3 +0 -0
  19. package/bin/code-workspace.js +11 -0
  20. package/docs/extension-architecture.zh-CN.md +429 -0
  21. package/docs/extensions.md +64 -0
  22. package/docs/extensions.zh-CN.md +64 -0
  23. package/extensions/openspec-workspace/1.0.0/artifacts/claude/SKILL.md +16 -0
  24. package/extensions/openspec-workspace/1.0.0/artifacts/codex/SKILL.md +16 -0
  25. package/extensions/openspec-workspace/1.0.0/init.js +54 -0
  26. package/extensions/openspec-workspace/1.0.0/manifest.json +27 -0
  27. package/extensions/zhuiyi-jira-mcp/0.1.0/artifacts/claude/server.json +14 -0
  28. package/extensions/zhuiyi-jira-mcp/0.1.0/artifacts/codex/config.toml +11 -0
  29. package/extensions/zhuiyi-jira-mcp/0.1.0/init.js +53 -0
  30. package/extensions/zhuiyi-jira-mcp/0.1.0/lib/archive.js +196 -0
  31. package/extensions/zhuiyi-jira-mcp/0.1.0/manifest.json +38 -0
  32. package/extensions/zhuiyi-jira-mcp/0.1.0/release.json +15 -0
  33. package/package.json +58 -0
  34. package/schemas/extension-init-context-v1.json +39 -0
  35. package/schemas/extension-init-result-v1.json +42 -0
  36. package/schemas/extension-manifest-v1.json +55 -0
  37. package/schemas/extension-manifest-v2.json +84 -0
  38. package/schemas/extension-manifest-v3.json +84 -0
  39. package/spec/extension/v1/specification.en-US.md +171 -0
  40. package/spec/extension/v1/specification.zh-CN.md +170 -0
  41. package/src/cli/commands/completion.js +203 -0
  42. package/src/cli/commands/extension.js +193 -0
  43. package/src/cli/commands/help.js +36 -0
  44. package/src/cli/commands/init.js +200 -0
  45. package/src/cli/commands/monitor.js +62 -0
  46. package/src/cli/commands/permissions.js +33 -0
  47. package/src/cli/commands/project-branch-update.js +107 -0
  48. package/src/cli/commands/project-branch.js +433 -0
  49. package/src/cli/commands/project.js +259 -0
  50. package/src/cli/commands/update.js +146 -0
  51. package/src/cli/commands/workspace.js +24 -0
  52. package/src/cli/confirmation.js +22 -0
  53. package/src/cli/parser.js +108 -0
  54. package/src/cli/registry.js +108 -0
  55. package/src/cli/renderer.js +20 -0
  56. package/src/cli/result.js +118 -0
  57. package/src/cli.js +76 -0
  58. package/src/core/assets.js +83 -0
  59. package/src/core/config.js +378 -0
  60. package/src/core/diagnostics.js +20 -0
  61. package/src/core/directory-digest.js +34 -0
  62. package/src/core/doctor.js +115 -0
  63. package/src/core/errors.js +10 -0
  64. package/src/core/extension-artifacts-legacy.js +180 -0
  65. package/src/core/extension-artifacts.js +310 -0
  66. package/src/core/extensions.js +1216 -0
  67. package/src/core/fs.js +32 -0
  68. package/src/core/init-lock-holder.js +30 -0
  69. package/src/core/init-lock.js +159 -0
  70. package/src/core/init.js +160 -0
  71. package/src/core/initializer.js +233 -0
  72. package/src/core/language.js +102 -0
  73. package/src/core/managed-files.js +334 -0
  74. package/src/core/migration.js +68 -0
  75. package/src/core/permissions/claude.js +92 -0
  76. package/src/core/permissions/codex.js +126 -0
  77. package/src/core/permissions/common.js +44 -0
  78. package/src/core/permissions/index.js +163 -0
  79. package/src/core/project-branch-update.js +424 -0
  80. package/src/core/project-configuration.js +55 -0
  81. package/src/core/project.js +674 -0
  82. package/src/core/tools.js +34 -0
  83. package/src/core/transaction.js +132 -0
  84. package/src/core/validation.js +186 -0
  85. package/src/i18n/index.js +11 -0
  86. package/src/i18n/interpolate.js +7 -0
  87. package/src/i18n/locales/en-US.js +10 -0
  88. package/src/i18n/locales/zh-CN.js +11 -0
  89. package/src/i18n/registry.js +42 -0
  90. package/src/index.js +25 -0
  91. package/src/init/plan.js +12 -0
  92. package/src/init/ui.js +61 -0
  93. package/src/init/wizard.js +97 -0
  94. package/src/monitor/i18n/index.js +33 -0
  95. package/src/monitor/i18n/locales/en-US.js +85 -0
  96. package/src/monitor/i18n/locales/zh-CN.js +79 -0
  97. package/src/monitor/index.js +344 -0
  98. package/src/monitor/page.js +118 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "extensionSpecVersion": 1,
4
+ "experimental": true,
5
+ "id": "openspec-workspace",
6
+ "name": "OpenSpec Workspace",
7
+ "version": "1.0.0",
8
+ "entry": "init.js",
9
+ "entrySha256": "5d95dc6261ef686f81a61cc09f0cf1d87bbb5014512be8665df2cc9f9b9dae24",
10
+ "timeoutMs": 60000,
11
+ "outputs": [
12
+ {
13
+ "id": "codex-propose-skill",
14
+ "kind": "file",
15
+ "ownership": "exclusive",
16
+ "target": ".codex/skills/code-workspace-openspec-propose/SKILL.md",
17
+ "tools": ["codex"]
18
+ },
19
+ {
20
+ "id": "claude-propose-skill",
21
+ "kind": "file",
22
+ "ownership": "exclusive",
23
+ "target": ".claude/skills/code-workspace-openspec-propose/SKILL.md",
24
+ "tools": ["claude"]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "command": "node",
3
+ "args": [
4
+ ".code-workspace/extensions/zhuiyi-jira-mcp/0.1.0/dist/index.js"
5
+ ],
6
+ "env": {
7
+ "JIRA_AUTH_TYPE": "cookie",
8
+ "JIRA_API_VERSION": "latest",
9
+ "JIRA_ALLOWED_HOSTS": "jira.in.wezhuiyi.com",
10
+ "JIRA_REQUIREMENT_FIELDS": "{\"acceptanceCriteria\":\"customfield_10042\",\"storyPoints\":\"customfield_10016\"}",
11
+ "JIRA_ATTACHMENT_DIR": ".jira-attachments",
12
+ "JIRA_ATTACHMENT_MAX_SIZE": "20MB"
13
+ }
14
+ }
@@ -0,0 +1,11 @@
1
+ [mcp_servers.zhuiyi-jira]
2
+ command = "node"
3
+ args = [".code-workspace/extensions/zhuiyi-jira-mcp/0.1.0/dist/index.js"]
4
+
5
+ [mcp_servers.zhuiyi-jira.env]
6
+ JIRA_AUTH_TYPE = "cookie"
7
+ JIRA_API_VERSION = "latest"
8
+ JIRA_ALLOWED_HOSTS = "jira.in.wezhuiyi.com"
9
+ JIRA_REQUIREMENT_FIELDS = '{"acceptanceCriteria":"customfield_10042","storyPoints":"customfield_10016"}'
10
+ JIRA_ATTACHMENT_DIR = ".jira-attachments"
11
+ JIRA_ATTACHMENT_MAX_SIZE = "20MB"
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const { prepareRelease } = require("./lib/archive");
7
+ const release = require("./release.json");
8
+
9
+ function option(name) {
10
+ const index = process.argv.indexOf(name);
11
+ return index >= 0 ? process.argv[index + 1] : null;
12
+ }
13
+
14
+ function copyOutput(outputRoot, source, target) {
15
+ const destination = path.join(outputRoot, ...target.split("/"));
16
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
17
+ fs.copyFileSync(path.join(__dirname, ...source.split("/")), destination);
18
+ }
19
+
20
+ async function main() {
21
+ const contextFile = option("--context");
22
+ const outputRoot = option("--output");
23
+ const resultFile = option("--result");
24
+ if (!contextFile || !outputRoot || !resultFile) throw new Error("Usage: init.js --context <file> --output <directory> --result <file>");
25
+ const context = JSON.parse(fs.readFileSync(contextFile, "utf8"));
26
+ if (context.schemaVersion !== 1 || context.extensionSpecVersion !== 1 || context.extension?.id !== "zhuiyi-jira-mcp" || context.extension.version !== "0.1.0" || !Array.isArray(context.tools)) throw new Error("Invalid extension context");
27
+
28
+ const outputs = [];
29
+ const runtime = path.join(outputRoot, "runtime");
30
+ await prepareRelease(release, runtime);
31
+ outputs.push({ id: "runtime", source: "runtime" });
32
+
33
+ if (context.tools.includes("codex")) {
34
+ copyOutput(outputRoot, "artifacts/codex/config.toml", "codex-config.toml");
35
+ outputs.push({ id: "codex-config", source: "codex-config.toml" });
36
+ }
37
+ if (context.tools.includes("claude")) {
38
+ copyOutput(outputRoot, "artifacts/claude/server.json", "claude-server.json");
39
+ outputs.push({ id: "claude-config", source: "claude-server.json" });
40
+ }
41
+
42
+ fs.writeFileSync(resultFile, `${JSON.stringify({
43
+ schemaVersion: 1,
44
+ extensionSpecVersion: 1,
45
+ extension: { id: "zhuiyi-jira-mcp", version: "0.1.0" },
46
+ outputs,
47
+ }, null, 2)}\n`, { mode: 0o600 });
48
+ }
49
+
50
+ main().catch((error) => {
51
+ process.stderr.write(`${error.code ? `${error.code}: ` : ""}${error.message}\n`);
52
+ process.exitCode = 1;
53
+ });
@@ -0,0 +1,196 @@
1
+ const crypto = require("node:crypto");
2
+ const fs = require("node:fs");
3
+ const https = require("node:https");
4
+ const path = require("node:path");
5
+ const zlib = require("node:zlib");
6
+
7
+ function archiveError(code, message, details = {}) {
8
+ return Object.assign(new Error(message), { code, details });
9
+ }
10
+
11
+ function safeRelativePath(value) {
12
+ const normalized = String(value || "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
13
+ if (!normalized || path.posix.isAbsolute(normalized) || normalized.split("/").some((part) => !part || part === "." || part === "..")) {
14
+ throw archiveError("JIRA_ARCHIVE_UNSAFE", `Unsafe archive path: ${value || "<empty>"}`, { path: value || null });
15
+ }
16
+ return normalized;
17
+ }
18
+
19
+ function tarString(buffer, start, length) {
20
+ return buffer.subarray(start, start + length).toString("utf8").replace(/\0.*$/s, "").trim();
21
+ }
22
+
23
+ function tarNumber(buffer, start, length) {
24
+ const value = tarString(buffer, start, length).replace(/^0+/, "") || "0";
25
+ if (!/^[0-7]+$/.test(value)) throw archiveError("JIRA_ARCHIVE_INVALID", `Invalid tar numeric field: ${value}`);
26
+ return Number.parseInt(value, 8);
27
+ }
28
+
29
+ function parsePax(content) {
30
+ const values = {};
31
+ let offset = 0;
32
+ while (offset < content.length) {
33
+ const space = content.indexOf(0x20, offset);
34
+ if (space < 0) throw archiveError("JIRA_ARCHIVE_INVALID", "Invalid PAX record length");
35
+ const length = Number(content.subarray(offset, space).toString("ascii"));
36
+ if (!Number.isInteger(length) || length <= 0 || offset + length > content.length) throw archiveError("JIRA_ARCHIVE_INVALID", "Invalid PAX record");
37
+ const record = content.subarray(space + 1, offset + length - 1).toString("utf8");
38
+ const equal = record.indexOf("=");
39
+ if (equal > 0) values[record.slice(0, equal)] = record.slice(equal + 1);
40
+ offset += length;
41
+ }
42
+ return values;
43
+ }
44
+
45
+ function assertTarChecksum(buffer, offset) {
46
+ const expected = tarNumber(buffer, offset + 148, 8);
47
+ let actual = 0;
48
+ for (let index = 0; index < 512; index += 1) actual += index >= 148 && index < 156 ? 0x20 : buffer[offset + index];
49
+ if (actual !== expected) throw archiveError("JIRA_ARCHIVE_INVALID", "Tar header checksum mismatch");
50
+ }
51
+
52
+ function extractTarGz(archiveFile, outputRoot, options) {
53
+ let tar;
54
+ try {
55
+ tar = zlib.gunzipSync(fs.readFileSync(archiveFile), { maxOutputLength: options.maxExtractBytes + 1024 * 1024 });
56
+ } catch (error) {
57
+ if (error.code === "ERR_BUFFER_TOO_LARGE" || /larger than/.test(error.message)) throw archiveError("JIRA_ARCHIVE_TOO_LARGE", `Archive expands beyond ${options.maxExtractBytes} bytes`, { limit: options.maxExtractBytes });
58
+ throw archiveError("JIRA_ARCHIVE_INVALID", `Cannot decompress tar.gz archive: ${error.message}`);
59
+ }
60
+ fs.mkdirSync(outputRoot, { recursive: true });
61
+ const seen = new Set();
62
+ let files = 0;
63
+ let extractedBytes = 0;
64
+ let offset = 0;
65
+ let pax = {};
66
+ let longPath = null;
67
+ const links = [];
68
+ while (offset + 512 <= tar.length) {
69
+ const header = tar.subarray(offset, offset + 512);
70
+ if (header.every((byte) => byte === 0)) break;
71
+ assertTarChecksum(tar, offset);
72
+ const type = String.fromCharCode(header[156] || 0x30);
73
+ const size = tarNumber(tar, offset + 124, 12);
74
+ const mode = tarNumber(tar, offset + 100, 8);
75
+ const prefix = tarString(tar, offset + 345, 155);
76
+ const rawName = tarString(tar, offset, 100);
77
+ let name = longPath || pax.path || (prefix ? `${prefix}/${rawName}` : rawName);
78
+ longPath = null;
79
+ pax = {};
80
+ const contentStart = offset + 512;
81
+ const contentEnd = contentStart + size;
82
+ if (contentEnd > tar.length) throw archiveError("JIRA_ARCHIVE_INVALID", "Truncated tar entry");
83
+ const content = tar.subarray(contentStart, contentEnd);
84
+ offset = contentStart + Math.ceil(size / 512) * 512;
85
+ if (type === "x") {
86
+ pax = parsePax(content);
87
+ continue;
88
+ }
89
+ if (type === "L") {
90
+ longPath = content.toString("utf8").replace(/\0.*$/s, "").trim();
91
+ continue;
92
+ }
93
+ if (!["0", "\0", "5", "2"].includes(type)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Unsupported tar entry type ${JSON.stringify(type)} for ${name}`, { path: name, type });
94
+ name = safeRelativePath(name);
95
+ const rootPrefix = `${options.root}/`;
96
+ if (name !== options.root && !name.startsWith(rootPrefix)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Archive entry is outside expected root ${options.root}: ${name}`, { path: name, root: options.root });
97
+ const relative = name === options.root ? "" : name.slice(rootPrefix.length);
98
+ if (!relative) {
99
+ if (type !== "5") throw archiveError("JIRA_ARCHIVE_UNSAFE", `Archive root must be a directory: ${name}`, { path: name });
100
+ continue;
101
+ }
102
+ const safe = safeRelativePath(relative);
103
+ if (seen.has(safe)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Duplicate archive path: ${safe}`, { path: safe });
104
+ seen.add(safe);
105
+ files += 1;
106
+ if (files > options.maxFiles) throw archiveError("JIRA_ARCHIVE_TOO_LARGE", `Archive contains more than ${options.maxFiles} entries`, { limit: options.maxFiles });
107
+ extractedBytes += type === "5" || type === "2" ? 0 : size;
108
+ if (extractedBytes > options.maxExtractBytes) throw archiveError("JIRA_ARCHIVE_TOO_LARGE", `Archive expands beyond ${options.maxExtractBytes} bytes`, { limit: options.maxExtractBytes });
109
+ const target = path.join(outputRoot, ...safe.split("/"));
110
+ if (!target.startsWith(`${path.resolve(outputRoot)}${path.sep}`)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Archive path escapes output: ${safe}`, { path: safe });
111
+ if (type === "5") fs.mkdirSync(target, { recursive: true, mode: mode & 0o777 });
112
+ else if (type === "2") {
113
+ const linkName = tarString(header, 157, 100);
114
+ if (!linkName || path.posix.isAbsolute(linkName)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Unsafe symbolic link target for ${safe}`, { path: safe, link: linkName });
115
+ links.push({ safe, target, linkName, mode: mode & 0o777 });
116
+ } else {
117
+ fs.mkdirSync(path.dirname(target), { recursive: true });
118
+ fs.writeFileSync(target, content, { mode: mode & 0o777 });
119
+ }
120
+ }
121
+ for (const link of links) {
122
+ const resolvedRelative = path.posix.normalize(path.posix.join(path.posix.dirname(link.safe), link.linkName));
123
+ if (resolvedRelative === ".." || resolvedRelative.startsWith("../") || path.posix.isAbsolute(resolvedRelative)) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Symbolic link escapes archive root: ${link.safe}`, { path: link.safe, link: link.linkName });
124
+ const source = path.join(outputRoot, ...resolvedRelative.split("/"));
125
+ if (!fs.existsSync(source) || !fs.lstatSync(source).isFile() || fs.lstatSync(source).isSymbolicLink()) throw archiveError("JIRA_ARCHIVE_UNSAFE", `Symbolic link does not resolve to a regular archive file: ${link.safe}`, { path: link.safe, link: link.linkName });
126
+ fs.mkdirSync(path.dirname(link.target), { recursive: true });
127
+ fs.copyFileSync(source, link.target);
128
+ fs.chmodSync(link.target, link.mode);
129
+ extractedBytes += fs.statSync(link.target).size;
130
+ if (extractedBytes > options.maxExtractBytes) throw archiveError("JIRA_ARCHIVE_TOO_LARGE", `Archive expands beyond ${options.maxExtractBytes} bytes`, { limit: options.maxExtractBytes });
131
+ }
132
+ return { files, extractedBytes };
133
+ }
134
+
135
+ function validatePackage(outputRoot, release) {
136
+ const packageFile = path.join(outputRoot, "package.json");
137
+ let value;
138
+ try { value = JSON.parse(fs.readFileSync(packageFile, "utf8")); } catch (error) { throw archiveError("JIRA_PACKAGE_INVALID", `Cannot read extracted package.json: ${error.message}`, { file: packageFile }); }
139
+ if (value.name !== release.package.name || value.version !== release.package.version) {
140
+ throw archiveError("JIRA_PACKAGE_INVALID", `Extracted package identity mismatch: expected ${release.package.name}@${release.package.version}`, { expected: release.package, actual: { name: value.name, version: value.version } });
141
+ }
142
+ const entry = path.join(outputRoot, ...release.entry.split("/"));
143
+ if (!fs.existsSync(entry) || !fs.lstatSync(entry).isFile() || fs.lstatSync(entry).isSymbolicLink()) throw archiveError("JIRA_PACKAGE_INVALID", `Missing package entry: ${release.entry}`, { entry: release.entry });
144
+ }
145
+
146
+ function download(url, file, options, redirects = 0) {
147
+ return new Promise((resolve, reject) => {
148
+ if (redirects > 5) return reject(archiveError("JIRA_DOWNLOAD_FAILED", "Too many download redirects", { url }));
149
+ let parsed;
150
+ try { parsed = new URL(url); } catch { return reject(archiveError("JIRA_DOWNLOAD_FAILED", `Invalid download URL: ${url}`, { url })); }
151
+ if (parsed.protocol !== "https:") return reject(archiveError("JIRA_DOWNLOAD_FAILED", `Download URL must use HTTPS: ${url}`, { url }));
152
+ if (!Array.isArray(options.allowedHosts) || !options.allowedHosts.includes(parsed.hostname)) {
153
+ return reject(archiveError("JIRA_DOWNLOAD_HOST_FORBIDDEN", `Download host is not allowed: ${parsed.hostname}`, { url, host: parsed.hostname }));
154
+ }
155
+ const request = https.get(parsed, { headers: { "user-agent": "zhuiyi-jira-mcp-extension/0.1.0" } }, (response) => {
156
+ if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
157
+ response.resume();
158
+ return download(new URL(response.headers.location, parsed).href, file, options, redirects + 1).then(resolve, reject);
159
+ }
160
+ if (response.statusCode !== 200) {
161
+ response.resume();
162
+ return reject(archiveError("JIRA_DOWNLOAD_FAILED", `Download returned HTTP ${response.statusCode}`, { url, statusCode: response.statusCode }));
163
+ }
164
+ const declared = Number(response.headers["content-length"] || 0);
165
+ if (declared > options.maxDownloadBytes) {
166
+ response.resume();
167
+ return reject(archiveError("JIRA_DOWNLOAD_TOO_LARGE", `Download exceeds ${options.maxDownloadBytes} bytes`, { url, limit: options.maxDownloadBytes, actual: declared }));
168
+ }
169
+ const stream = fs.createWriteStream(file, { flags: "wx", mode: 0o600 });
170
+ const hash = crypto.createHash("sha256");
171
+ let bytes = 0;
172
+ response.on("data", (chunk) => {
173
+ bytes += chunk.length;
174
+ if (bytes > options.maxDownloadBytes) request.destroy(archiveError("JIRA_DOWNLOAD_TOO_LARGE", `Download exceeds ${options.maxDownloadBytes} bytes`, { url, limit: options.maxDownloadBytes, actual: bytes }));
175
+ else hash.update(chunk);
176
+ });
177
+ response.pipe(stream);
178
+ stream.on("finish", () => stream.close(() => resolve({ bytes, sha256: hash.digest("hex"), finalUrl: parsed.href })));
179
+ stream.on("error", reject);
180
+ });
181
+ request.setTimeout(options.downloadTimeoutMs, () => request.destroy(archiveError("JIRA_DOWNLOAD_TIMEOUT", `Download timed out after ${options.downloadTimeoutMs}ms`, { url, timeoutMs: options.downloadTimeoutMs })));
182
+ request.on("error", (error) => reject(error.code?.startsWith("JIRA_") ? error : archiveError("JIRA_DOWNLOAD_FAILED", `Download failed: ${error.message}`, { url })));
183
+ });
184
+ }
185
+
186
+ async function prepareRelease(release, outputRoot, options = {}) {
187
+ const archiveFile = path.join(path.dirname(outputRoot), "zhuiyi-jira-mcp.tar.gz");
188
+ const downloaded = options.downloaded || await download(release.url, archiveFile, release);
189
+ if (downloaded.sha256 !== release.sha256) throw archiveError("JIRA_ARCHIVE_HASH_MISMATCH", `Archive hash mismatch for ${release.url}`, { url: release.url, expectedSha256: release.sha256, actualSha256: downloaded.sha256 });
190
+ const source = options.archiveFile || archiveFile;
191
+ const extracted = extractTarGz(source, outputRoot, release);
192
+ validatePackage(outputRoot, release);
193
+ return { ...downloaded, ...extracted };
194
+ }
195
+
196
+ module.exports = { archiveError, download, extractTarGz, prepareRelease, safeRelativePath, validatePackage };
@@ -0,0 +1,38 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "extensionSpecVersion": 1,
4
+ "experimental": true,
5
+ "id": "zhuiyi-jira-mcp",
6
+ "name": "Zhuiyi Jira MCP",
7
+ "version": "0.1.0",
8
+ "entry": "init.js",
9
+ "entrySha256": "af7f4a71835402235b3d763418ee1b4135da0125a85aff1153bfaa0f6c83a022",
10
+ "timeoutMs": 120000,
11
+ "capabilities": {
12
+ "networkHosts": ["gitee.com", "raw.giteeusercontent.com"]
13
+ },
14
+ "outputs": [
15
+ {
16
+ "id": "runtime",
17
+ "kind": "directory",
18
+ "ownership": "exclusive",
19
+ "target": ".code-workspace/extensions/zhuiyi-jira-mcp/0.1.0"
20
+ },
21
+ {
22
+ "id": "codex-config",
23
+ "kind": "text-block",
24
+ "ownership": "shared",
25
+ "target": ".codex/config.toml",
26
+ "format": "toml",
27
+ "tools": ["codex"]
28
+ },
29
+ {
30
+ "id": "claude-config",
31
+ "kind": "json-member",
32
+ "ownership": "shared",
33
+ "target": ".mcp.json",
34
+ "selector": "/mcpServers/zhuiyi-jira",
35
+ "tools": ["claude"]
36
+ }
37
+ ]
38
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "url": "https://gitee.com/liutaigang/zhuiyi-jira-mcp/raw/master/release/zhuiyi-jira-mcp-0.1.0.tar.gz",
3
+ "allowedHosts": ["gitee.com", "raw.giteeusercontent.com"],
4
+ "sha256": "74785207a75d1f7ea74ea893533835720baed016509ef4401c9b20f3b59a4afa",
5
+ "root": "zhuiyi-jira-mcp-0.1.0",
6
+ "entry": "dist/index.js",
7
+ "package": {
8
+ "name": "zhuiyi-jira-mcp",
9
+ "version": "0.1.0"
10
+ },
11
+ "maxDownloadBytes": 20971520,
12
+ "maxExtractBytes": 104857600,
13
+ "maxFiles": 10000,
14
+ "downloadTimeoutMs": 60000
15
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "code-workspace-zhuiyi",
3
+ "version": "0.1.0-beta.1",
4
+ "description": "Local multi-project registry and safety layer for AI coding workspaces",
5
+ "author": "icebearx-ai",
6
+ "license": "MIT",
7
+ "main": "src/index.js",
8
+ "exports": {
9
+ ".": "./src/index.js"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "bin": {
15
+ "code-workspace": "bin/code-workspace.js",
16
+ "code-w": "bin/code-workspace.js"
17
+ },
18
+ "files": [
19
+ "assets",
20
+ "bin",
21
+ "src/cli.js",
22
+ "src/cli",
23
+ "src/index.js",
24
+ "src/core",
25
+ "src/i18n",
26
+ "src/init",
27
+ "src/monitor",
28
+ "artifacts",
29
+ "extensions",
30
+ "docs/extension-architecture.zh-CN.md",
31
+ "spec",
32
+ "docs/extensions.md",
33
+ "docs/extensions.zh-CN.md",
34
+ "schemas",
35
+ ".env.example",
36
+ "README.md",
37
+ "README.zh-CN.md",
38
+ "LICENSE"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20.19.0"
42
+ },
43
+ "scripts": {
44
+ "test": "node --test src/__test__/*.test.js",
45
+ "dev:monitor": "node bin/code-workspace.js monitor",
46
+ "check": "npm test && npm run cli:architecture-check && npm run pack:check",
47
+ "cli:architecture-check": "node scripts/check-cli-architecture.js",
48
+ "pack:check": "npm pack --dry-run",
49
+ "cli": "node bin/code-workspace.js",
50
+ "prepublishOnly": "npm run check"
51
+ },
52
+ "dependencies": {
53
+ "@clack/prompts": "^1.7.0",
54
+ "@iarna/toml": "^2.2.5",
55
+ "js-yaml": "^5.2.2",
56
+ "proper-lockfile": "^4.1.2"
57
+ }
58
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://icebearx.ai/code-workspace/extension-init-context-v1.json",
4
+ "title": "Code Workspace extension init context v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "extensionSpecVersion", "extension", "workspace", "tools"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 1 },
10
+ "extensionSpecVersion": { "const": 1 },
11
+ "extension": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["id", "version"],
15
+ "properties": {
16
+ "id": { "$ref": "#/$defs/id" },
17
+ "version": { "type": "string", "minLength": 1 }
18
+ }
19
+ },
20
+ "workspace": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["name", "uuid", "language"],
24
+ "properties": {
25
+ "name": { "type": "string", "minLength": 1 },
26
+ "uuid": { "type": "string", "minLength": 1 },
27
+ "language": { "type": "string", "minLength": 1 }
28
+ }
29
+ },
30
+ "tools": {
31
+ "type": "array",
32
+ "uniqueItems": true,
33
+ "items": { "enum": ["claude", "codex"] }
34
+ }
35
+ },
36
+ "$defs": {
37
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" }
38
+ }
39
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://icebearx.ai/code-workspace/extension-init-result-v1.json",
4
+ "title": "Code Workspace extension init result v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "extensionSpecVersion", "extension", "outputs"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 1 },
10
+ "extensionSpecVersion": { "const": 1 },
11
+ "extension": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "required": ["id", "version"],
15
+ "properties": {
16
+ "id": { "$ref": "#/$defs/id" },
17
+ "version": { "type": "string", "minLength": 1 }
18
+ }
19
+ },
20
+ "outputs": {
21
+ "type": "array",
22
+ "minItems": 1,
23
+ "items": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["id", "source"],
27
+ "properties": {
28
+ "id": { "$ref": "#/$defs/id" },
29
+ "source": { "$ref": "#/$defs/relativePath" }
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "$defs": {
35
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
36
+ "relativePath": {
37
+ "type": "string",
38
+ "minLength": 1,
39
+ "pattern": "^(?!/)(?!.*\\\\)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//).+$"
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://icebearx.ai/code-workspace/extension-manifest-v1.json",
4
+ "title": "Code Workspace built-in extension manifest v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "experimental", "id", "name", "version", "entry", "entrySha256", "codeWorkspace", "timeoutMs", "artifacts"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 1 },
10
+ "experimental": { "const": true },
11
+ "id": { "$ref": "#/$defs/id" },
12
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
13
+ "version": { "type": "string" },
14
+ "entry": { "const": "init.js" },
15
+ "entrySha256": { "$ref": "#/$defs/sha256" },
16
+ "codeWorkspace": { "type": "string", "minLength": 1 },
17
+ "timeoutMs": { "type": "integer", "minimum": 1, "maximum": 300000 },
18
+ "artifacts": {
19
+ "type": "array",
20
+ "minItems": 1,
21
+ "items": {
22
+ "type": "object",
23
+ "additionalProperties": false,
24
+ "required": ["id", "kind", "sha256"],
25
+ "properties": {
26
+ "id": { "$ref": "#/$defs/id" },
27
+ "kind": { "enum": ["file", "codex-config-block", "codex-hooks"] },
28
+ "target": { "type": "string", "minLength": 1 },
29
+ "output": { "type": "string", "minLength": 1 },
30
+ "sha256": { "$ref": "#/$defs/sha256" },
31
+ "tools": {
32
+ "type": "array",
33
+ "minItems": 1,
34
+ "uniqueItems": true,
35
+ "items": { "enum": ["claude", "codex"] }
36
+ }
37
+ },
38
+ "allOf": [
39
+ {
40
+ "if": { "properties": { "kind": { "const": "file" } } },
41
+ "then": { "required": ["target"] }
42
+ },
43
+ {
44
+ "if": { "properties": { "kind": { "enum": ["codex-config-block", "codex-hooks"] } } },
45
+ "then": { "required": ["output"] }
46
+ }
47
+ ]
48
+ }
49
+ }
50
+ },
51
+ "$defs": {
52
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
53
+ "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
54
+ }
55
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://icebearx.ai/code-workspace/extension-manifest-v2.json",
4
+ "title": "Code Workspace built-in extension manifest v2",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "experimental", "id", "name", "version", "entry", "entrySha256", "codeWorkspace", "timeoutMs", "outputs"],
8
+ "properties": {
9
+ "schemaVersion": { "const": 2 },
10
+ "experimental": { "const": true },
11
+ "id": { "$ref": "#/$defs/id" },
12
+ "name": { "type": "string", "minLength": 1, "maxLength": 100 },
13
+ "version": { "type": "string" },
14
+ "entry": { "const": "init.js" },
15
+ "entrySha256": { "$ref": "#/$defs/sha256" },
16
+ "codeWorkspace": { "type": "string", "minLength": 1 },
17
+ "timeoutMs": { "type": "integer", "minimum": 1, "maximum": 300000 },
18
+ "capabilities": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "networkHosts": {
23
+ "type": "array",
24
+ "minItems": 1,
25
+ "uniqueItems": true,
26
+ "items": { "$ref": "#/$defs/hostname" }
27
+ }
28
+ }
29
+ },
30
+ "outputs": {
31
+ "type": "array",
32
+ "minItems": 1,
33
+ "items": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": ["id", "kind", "ownership", "target"],
37
+ "properties": {
38
+ "id": { "$ref": "#/$defs/id" },
39
+ "kind": { "enum": ["file", "directory", "text-block", "json-member"] },
40
+ "ownership": { "enum": ["exclusive", "shared"] },
41
+ "target": { "$ref": "#/$defs/relativePath" },
42
+ "selector": { "type": "string", "pattern": "^/(?:[^~/]|~[01])+(?:/(?:[^~/]|~[01])+)*$" },
43
+ "format": { "enum": ["text", "toml"] },
44
+ "tools": {
45
+ "type": "array",
46
+ "minItems": 1,
47
+ "uniqueItems": true,
48
+ "items": { "enum": ["claude", "codex"] }
49
+ }
50
+ },
51
+ "allOf": [
52
+ {
53
+ "if": { "properties": { "kind": { "enum": ["file", "directory"] } } },
54
+ "then": { "properties": { "ownership": { "const": "exclusive" } } }
55
+ },
56
+ {
57
+ "if": { "properties": { "kind": { "enum": ["text-block", "json-member"] } } },
58
+ "then": { "properties": { "ownership": { "const": "shared" } } }
59
+ },
60
+ {
61
+ "if": { "properties": { "kind": { "const": "json-member" } } },
62
+ "then": { "required": ["selector"] },
63
+ "else": { "properties": { "selector": false } }
64
+ },
65
+ {
66
+ "if": { "properties": { "kind": { "const": "text-block" } } },
67
+ "then": { "properties": { "format": { "enum": ["text", "toml"] } } },
68
+ "else": { "properties": { "format": false } }
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ "$defs": {
75
+ "id": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
76
+ "sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
77
+ "hostname": { "type": "string", "pattern": "^(?=.{1,253}$)[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?$" },
78
+ "relativePath": {
79
+ "type": "string",
80
+ "minLength": 1,
81
+ "pattern": "^(?!/)(?!.*\\\\)(?!.*(?:^|/)\\.{1,2}(?:/|$))(?!.*//).+$"
82
+ }
83
+ }
84
+ }