openxiangda 2.1.0 → 2.2.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/README.md CHANGED
@@ -10,6 +10,8 @@ pnpm openxiangda skill install --workspace . --force
10
10
  pnpm openxiangda dev
11
11
  ```
12
12
 
13
+ 创建和技能安装默认检查内部支持协作所需的 DWS。`support status --json` 查看状态,`support bootstrap` 安装/复用官方完整技能,`support login` 引导 OAuth,`support join` 打开配置中的原邀请链接,由用户在钉钉完成加入。等待接入不阻塞应用工作;离线/CI 可加 `--skip-support`。咨询和持续跟进使用随统一入口安装的 `openxiangda-support` 技能,V1/V2 保持各自项目引擎。
14
+
13
15
  应用导入统一子路径:
14
16
 
15
17
  | 子路径 | 用途 |
@@ -3,17 +3,19 @@ import { flagValue, fail } from './workspace.js';
3
3
  import { update } from './update.js';
4
4
  import { assessMigration } from './migrate.js';
5
5
  import { installDistributionSkills } from './skills.js';
6
+ import { supportOperation } from './support.js';
6
7
 
7
8
  export async function distributionCommand(context, args) {
8
9
  const command = args[0];
9
10
  const skillInstall = command === 'skill' && args[1] === 'install';
10
- if (!['version', 'update', 'changelog', 'migrate'].includes(command) && !skillInstall) return false;
11
+ if (!['version', 'update', 'changelog', 'migrate', 'support'].includes(command) && !skillInstall) return false;
11
12
  validateArguments(command, args.slice(1));
12
13
  if (args.includes('--help') || args.includes('-h')) {
13
- process.stdout.write('openxiangda version [--json]\nopenxiangda update check|install [--target workspace|launcher] [--dry-run] [--json]\nopenxiangda changelog [version] [--json]\nopenxiangda migrate assess --to v2 [--json]\nopenxiangda skill install [--workspace <directory> | --destination <directory>] [--agent codex|claude|qoder|dual] [--force] [--dry-run]\n以上命令支持 --cwd <directory>。新应用默认 V2,项目升级保持原代际。\n');
14
+ process.stdout.write('openxiangda version [--json]\nopenxiangda update check|install [--target workspace|launcher] [--dry-run] [--json]\nopenxiangda changelog [version] [--json]\nopenxiangda migrate assess --to v2 [--json]\nopenxiangda skill install [--workspace <directory> | --destination <directory>] [--agent codex|claude|qoder|dual] [--force] [--dry-run] [--skip-support]\nopenxiangda support status|bootstrap|login|join [--profile <corpId:userId>] [--agent <DWS agent>] [--device] [--force] [--dry-run] [--json]\n以上命令支持 --cwd <directory>。新应用默认 V2,项目升级保持原代际。支持接入等待用户操作时不阻塞应用创建。\n');
14
15
  return true;
15
16
  }
16
17
  let data;
18
+ if (command === 'support') data = await supportOperation(context.packageRoot, args);
17
19
  if (skillInstall) data = await installDistributionSkills(context, args);
18
20
  if (command === 'version') data = {
19
21
  productVersion: context.manifest.version,
@@ -38,8 +40,8 @@ export async function distributionCommand(context, args) {
38
40
  }
39
41
 
40
42
  function validateArguments(command, args) {
41
- const booleans = new Set(['--json', '--help', '-h', ...(command === 'skill' ? ['--force', '--dry-run'] : command === 'update' ? ['--dry-run'] : [])]);
42
- const values = new Set(['--cwd', ...({ update: ['--target', '--registry'], changelog: ['--registry'], migrate: ['--to'], skill: ['--workspace', '--destination', '--dest', '--agent'] }[command] || [])]);
43
+ const booleans = new Set(['--json', '--help', '-h', ...(command === 'skill' ? ['--force', '--dry-run', '--skip-support'] : command === 'support' ? ['--force', '--dry-run', '--device'] : command === 'update' ? ['--dry-run'] : [])]);
44
+ const values = new Set(['--cwd', ...({ update: ['--target', '--registry'], changelog: ['--registry'], migrate: ['--to'], skill: ['--workspace', '--destination', '--dest', '--agent'], support: ['--profile', '--agent'] }[command] || [])]);
43
45
  const seen = new Set(); let positionals = 0;
44
46
  for (let i = 0; i < args.length; i++) {
45
47
  const argument = args[i], key = argument.split('=')[0];
@@ -3,9 +3,14 @@ import { join } from 'node:path';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { discoverWorkspace, flagValue, readJson, resolveEngine } from './workspace.js';
5
5
  import { distributionCommand } from './commands.js';
6
+ import { bootstrapSupport } from './support.js';
6
7
 
7
8
  export async function launch(packageRoot, args = process.argv.slice(2)) {
8
9
  const manifest = readJson(join(packageRoot, 'package.json'));
10
+ if (args[0] === 'support') {
11
+ await distributionCommand({ manifest, packageRoot }, args);
12
+ return;
13
+ }
9
14
  const workspace = discoverWorkspace(flagValue(args, '--cwd') || (args[0] === 'skill' ? flagValue(args, '--workspace') : null) || process.cwd());
10
15
  // Launcher-only updates must also work when a project's dependencies are not yet installed.
11
16
  const launcherUpdate = args[0] === 'update' && flagValue(args, '--target') === 'launcher';
@@ -13,7 +18,7 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
13
18
  const context = { manifest, packageRoot, workspace, engine };
14
19
  if (await distributionCommand(context, args)) return;
15
20
  if (!args.length || args[0] === '--help' || args[0] === '-h') {
16
- process.stdout.write('统一分发命令:version、update check|install、changelog、migrate assess。\nSkill 安装支持 --workspace、--destination 和 --agent;正文跟随项目版本。\n\n');
21
+ process.stdout.write('统一分发命令:version、update check|install、changelog、migrate assess、support status|bootstrap|login|join。\nSkill 安装支持 --workspace、--destination 和 --agent;正文跟随项目版本。创建和技能刷新默认接入支持协作,离线/CI 可加 --skip-support。\n\n');
17
22
  }
18
23
  const env = { ...process.env };
19
24
  for (const key of ['OPENXIANGDA_DISTRIBUTION_NAME', 'OPENXIANGDA_DISTRIBUTION_VERSION', 'OPENXIANGDA_DOCUMENTATION_ROOT', 'OPENXIANGDA_SKILLS_ROOT']) delete env[key];
@@ -23,7 +28,9 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
23
28
  env.OPENXIANGDA_DOCUMENTATION_ROOT = join(engine.packageRoot, 'documentation');
24
29
  if (existsSync(join(engine.packageRoot, 'skills'))) env.OPENXIANGDA_SKILLS_ROOT = join(engine.packageRoot, 'skills');
25
30
  }
26
- const forwarded = engine.generation === 'v1' ? removeCwd(args) : args;
31
+ const initializes = args[0] === 'create' || args[0] === 'workspace' && args[1] === 'init' || args[0] === 'skill' && args[1] === 'bootstrap';
32
+ const engineArgs = initializes ? args.filter(argument => argument !== '--skip-support') : args;
33
+ const forwarded = engine.generation === 'v1' ? removeCwd(engineArgs) : engineArgs;
27
34
  const child = spawn(process.execPath, [engine.entry, ...forwarded], {
28
35
  cwd: engine.generation === 'v1' ? workspace?.root || process.cwd() : process.cwd(), env, stdio: 'inherit',
29
36
  });
@@ -36,6 +43,10 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
36
43
  child.once('exit', (code, signal) => resolve({ code, signal }));
37
44
  });
38
45
  process.exitCode = code ?? (signal === 'SIGINT' ? 130 : 143);
46
+ if (code === 0 && initializes) {
47
+ const support = await bootstrapSupport(packageRoot, args);
48
+ if (support.state !== 'skipped') process.stderr.write(`${JSON.stringify({ operation: 'support bootstrap', data: support })}\n`);
49
+ }
39
50
  } finally { signals.forEach((signal, i) => process.removeListener(signal, handlers[i])); }
40
51
  }
41
52
 
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from 'node:path';
4
4
  import { createRequire } from 'node:module';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { fail, flagValue, readJson } from './workspace.js';
7
+ import { bootstrapSupport } from './support.js';
7
8
 
8
9
  export async function installDistributionSkills(context, args) {
9
10
  const workspace = flagValue(args, '--workspace');
@@ -33,6 +34,7 @@ export async function installDistributionSkills(context, args) {
33
34
  }
34
35
  const router = join(source, 'openxiangda');
35
36
  cpSync(join(context.packageRoot, 'launcher-skill/openxiangda'), router, { recursive: true });
37
+ cpSync(join(context.packageRoot, 'launcher-skill/openxiangda-support'), join(source, 'openxiangda-support'), { recursive: true });
36
38
  const file = join(router, 'SKILL.md');
37
39
  writeFileSync(file, readFileSync(file, 'utf8').replaceAll('__OPENXIANGDA_VERSION__', context.manifest.version));
38
40
  const names = readdirSync(source, { withFileTypes: true }).filter(entry => entry.isDirectory()).map(entry => entry.name).sort();
@@ -45,7 +47,8 @@ export async function installDistributionSkills(context, args) {
45
47
  const { refreshWorkspaceGuidance } = await importEngineModule(context.engine.packageRoot, 'openxiangda-skill-kit');
46
48
  guidance = refreshWorkspaceGuidance(resolve(workspace), guidanceTemplate);
47
49
  }
48
- return { destinations, installed: names, engineVersion: context.engine.version, generation: context.engine.generation, dryRun: args.includes('--dry-run'), ...(guidance ? { guidance } : {}) };
50
+ const support = await bootstrapSupport(context.packageRoot, args);
51
+ return { destinations, installed: names, engineVersion: context.engine.version, generation: context.engine.generation, dryRun: args.includes('--dry-run'), support, ...(guidance ? { guidance } : {}) };
49
52
  } finally { rmSync(staging, { recursive: true, force: true }); }
50
53
  }
51
54
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": "openxiangda.distribution-commands/v1",
3
+ "commands": ["support:status", "support:bootstrap", "support:login", "support:join"]
4
+ }
@@ -0,0 +1,190 @@
1
+ import { cpSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { fail, flagValue, readJson } from './workspace.js';
7
+ import supportCommands from './support-commands.json' with { type: 'json' };
8
+
9
+ export const SUPPORT_CHANNEL_URL = 'https://evaluate-oss.oss-cn-hangzhou.aliyuncs.com/static/lowcode/support/channel.json';
10
+ export const DWS_STABLE_VERSION = '1.0.61';
11
+ const coreReferences = ['dingtalk-shared/SKILL.md', 'dingtalk-misc/references/profile.md', 'dingtalk-chat/SKILL.md', 'dingtalk-event/SKILL.md'];
12
+
13
+ export async function loadSupportChannel(fetcher = fetch) {
14
+ const response = await fetcher(SUPPORT_CHANNEL_URL, { signal: AbortSignal.timeout(10_000), redirect: 'error' });
15
+ if (!response.ok) fail('SUPPORT_CHANNEL_UNAVAILABLE', `HTTP ${response.status}`);
16
+ const chunks = []; let size = 0;
17
+ for await (const chunk of response.body) {
18
+ size += chunk.length;
19
+ if (size > 16 * 1024) fail('SUPPORT_CHANNEL_TOO_LARGE', '通道配置超过 16 KiB');
20
+ chunks.push(chunk);
21
+ }
22
+ const channel = JSON.parse(Buffer.concat(chunks).toString('utf8'));
23
+ const identifier = value => typeof value === 'string' && value.length > 0 && value.length <= 512 && !/[\s,:]/.test(value);
24
+ if (channel.schemaVersion !== 'openxiangda.support-channel/v1' || !identifier(channel.id) || !identifier(channel.corpId)
25
+ || typeof channel.conversationId !== 'string' || !channel.conversationId || channel.conversationId.length > 1024
26
+ || typeof channel.name !== 'string' || channel.name.length > 256 || !Number.isSafeInteger(channel.revision) || channel.revision < 1) {
27
+ fail('SUPPORT_CHANNEL_INVALID', '通道配置不符合当前契约');
28
+ }
29
+ let invitation;
30
+ try { invitation = new URL(channel.inviteUrl); } catch { fail('SUPPORT_INVITE_URL_INVALID', '邀请地址格式无效'); }
31
+ if (typeof channel.inviteUrl !== 'string' || channel.inviteUrl.length > 8192 || /[\r\n\0]/.test(channel.inviteUrl)
32
+ || !['https:', 'dingtalk:'].includes(invitation.protocol) || invitation.username || invitation.password) {
33
+ fail('SUPPORT_INVITE_URL_INVALID', '邀请地址协议无效');
34
+ }
35
+ return channel;
36
+ }
37
+
38
+ export function selectSupportProfile(profiles, corpId, requested) {
39
+ if (requested) {
40
+ if (!/^[^:\s,]+:[^:\s,]+$/.test(requested)) fail('SUPPORT_PROFILE_INVALID', '使用 profile list 返回的 corpId:userId');
41
+ if (requested.split(':')[0] !== corpId) fail('SUPPORT_PROFILE_ORGANIZATION_MISMATCH', '所选账号不属于支持通道组织');
42
+ return profiles.find(item => item.profile === requested && item.corpId === corpId && `${item.corpId}:${item.userId}` === requested) || null;
43
+ }
44
+ const candidates = profiles.filter(item => item.corpId === corpId && item.isOrgCurrent === true && item.profile === `${item.corpId}:${item.userId}`);
45
+ return candidates.length === 1 ? candidates[0] : null;
46
+ }
47
+
48
+ export function supportVersion(output) {
49
+ const match = /\bdws version v?(\d+)\.(\d+)\.(\d+)(?![\d.-])/.exec(output);
50
+ if (!match) return null;
51
+ const current = match.slice(1).map(Number), minimum = DWS_STABLE_VERSION.split('.').map(Number);
52
+ for (let i = 0; i < 3; i++) {
53
+ if (current[i] > minimum[i]) return current.join('.');
54
+ if (current[i] < minimum[i]) return null;
55
+ }
56
+ return current.join('.');
57
+ }
58
+
59
+ export async function supportOperation(packageRoot, args, overrides = {}) {
60
+ const action = args[1] || 'status';
61
+ if (!supportCommands.commands.includes(`support:${action}`)) fail('SUPPORT_ACTION_INVALID', action);
62
+ const taskHome = overrides.home || homedir();
63
+ const stateFile = overrides.stateFile || join(process.env.XDG_CONFIG_HOME || join(taskHome, '.config'), 'openxiangda', 'support.json');
64
+ const skillsHome = overrides.skillsHome || join(taskHome, '.agents', 'skills');
65
+ const run = overrides.run || runSupportProcess;
66
+ const channelLoader = overrides.loadChannel || loadSupportChannel;
67
+ const requested = flagValue(args, '--profile');
68
+ const target = flagValue(args, '--agent') || 'all';
69
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(target)) fail('SUPPORT_AGENT_INVALID', target);
70
+ if (args.includes('--dry-run')) return { state: 'planned', action, channelUrl: SUPPORT_CHANNEL_URL, dwsPackage: `dingtalk-workspace-cli@${DWS_STABLE_VERSION}`, skills: 'official-full-multi', target, userActionRequired: ['OAuth', 'DingTalk group join'] };
71
+ const result = { state: 'setup_required', action, channelUrl: SUPPORT_CHANNEL_URL, dws: { available: false }, skills: { ready: false }, nextActions: [] };
72
+ let releaseLock = () => {};
73
+ try {
74
+ if (action !== 'status') {
75
+ mkdirSync(dirname(stateFile), { recursive: true, mode: 0o700 });
76
+ const lock = `${stateFile}.lock`;
77
+ try { mkdirSync(lock, { mode: 0o700 }); } catch (error) { if (error.code === 'EEXIST') fail('SUPPORT_BUSY', `另一个接入操作正在执行;检查 ${lock}`); throw error; }
78
+ releaseLock = () => rmSync(lock, { recursive: true, force: true });
79
+ }
80
+ const saved = existsSync(stateFile) ? readJson(stateFile) : {};
81
+ const persist = patch => {
82
+ const temporary = `${stateFile}.${randomUUID()}.tmp`;
83
+ writeFileSync(temporary, `${JSON.stringify({ ...saved, ...patch, schemaVersion: 'openxiangda.support-state/v1' }, null, 2)}\n`, { mode: 0o600 });
84
+ try { renameSync(temporary, stateFile); } finally { rmSync(temporary, { force: true }); }
85
+ Object.assign(saved, patch);
86
+ };
87
+ let versionResult = await run('dws', ['--version']);
88
+ let version = versionResult.status === 0 ? supportVersion(versionResult.stdout) : null;
89
+ if (!version && action === 'bootstrap') {
90
+ const installed = await run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '--global', `dingtalk-workspace-cli@${DWS_STABLE_VERSION}`, '--registry', 'https://registry.npmjs.org', '--no-fund', '--no-audit'], { timeout: 120_000 });
91
+ if (installed.status !== 0) return pending(result, 'dws_unavailable', 'SUPPORT_DWS_INSTALL_FAILED', 'openxiangda support bootstrap');
92
+ versionResult = await run('dws', ['--version']);
93
+ version = versionResult.status === 0 ? supportVersion(versionResult.stdout) : null;
94
+ }
95
+ if (!version) return pending(result, 'dws_unavailable', 'SUPPORT_DWS_STABLE_REQUIRED', 'openxiangda support bootstrap');
96
+ result.dws = { available: true, version };
97
+ const referencesPresent = () => coreReferences.every(file => existsSync(join(skillsHome, file)));
98
+ if (action === 'bootstrap') {
99
+ installSupportGuidance(packageRoot, skillsHome);
100
+ if (args.includes('--force') || saved.skillsVersion !== version || saved.skillsTarget !== target || !referencesPresent()) {
101
+ const setup = await run('dws', ['skill', 'setup', '--mode', 'multi', '--target', target, '--yes'], { timeout: 60_000, officialSkills: true });
102
+ if (setup.status !== 0 || !referencesPresent()) return pending(result, 'setup_required', 'SUPPORT_DWS_SKILLS_INCOMPLETE', 'openxiangda support bootstrap --force');
103
+ persist({ skillsVersion: version, skillsTarget: target });
104
+ }
105
+ }
106
+ result.skills = { ready: referencesPresent(), bundle: saved.skillsVersion === version ? 'official-full-multi' : 'not_verified' };
107
+ let channel;
108
+ try { channel = await channelLoader(); } catch (error) { return pending(result, 'channel_unavailable', error.code || 'SUPPORT_CHANNEL_UNAVAILABLE', 'openxiangda support status'); }
109
+ result.channel = { id: channel.id, name: channel.name, revision: channel.revision, corpId: channel.corpId, conversationId: channel.conversationId };
110
+ const json = async command => {
111
+ const output = await run('dws', [...command, '--format', 'json']);
112
+ if (output.status !== 0) return null;
113
+ try { const parsed = JSON.parse(output.stdout); return parsed?.error || parsed?.success === false ? null : parsed; } catch { return null; }
114
+ };
115
+ const listProfiles = async () => {
116
+ const data = await json(['profile', 'list']);
117
+ return Array.isArray(data?.profiles) ? data.profiles : [];
118
+ };
119
+ let profile = selectSupportProfile(await listProfiles(), channel.corpId, requested || saved.profile);
120
+ if (action === 'login') {
121
+ const loginProfile = profile?.profile || requested || channel.corpId;
122
+ const login = await run('dws', ['auth', 'login', '--profile', loginProfile, ...(args.includes('--device') ? ['--device'] : []), '--format', 'json'], { timeout: 180_000, interactive: true });
123
+ if (login.status !== 0) return pending(result, 'authorization_required', 'SUPPORT_AUTH_PENDING', 'openxiangda support login');
124
+ profile = selectSupportProfile(await listProfiles(), channel.corpId, requested || saved.profile);
125
+ }
126
+ if (!profile) return pending(result, 'profile_required', 'SUPPORT_PROFILE_REQUIRED', 'openxiangda support login');
127
+ result.profile = profile.profile;
128
+ const auth = await json(['auth', 'status', '--profile', profile.profile]);
129
+ if (!auth || auth.authenticated !== true || auth.token_valid !== true) return pending(result, 'authorization_required', 'SUPPORT_AUTH_REQUIRED', 'openxiangda support login');
130
+ if (auth.corp_id !== channel.corpId || auth.user_id !== profile.userId) return pending(result, 'profile_required', 'SUPPORT_AUTH_IDENTITY_MISMATCH', 'openxiangda support status --profile <corpId:userId>');
131
+ if (action !== 'status') persist({ profile: profile.profile });
132
+ result.authorization = { verified: true };
133
+ const group = await json(['chat', '+chat-members-list', '--conversation-id', channel.conversationId, '--member-types', 'user', '--page-limit', '1', '--profile', profile.profile]);
134
+ if (group?.conversationId === channel.conversationId && Array.isArray(group.users) && group.users.length > 0 && !(group.failures?.length) && !(group.buckets?.users?.failures?.length)) {
135
+ result.group = { access: 'verified', membership: 'not_independently_verified', organizationRestriction: 'not_independently_verified', complete: group.complete === true };
136
+ result.state = result.skills.ready ? 'ready' : 'setup_required';
137
+ if (!result.skills.ready) result.nextActions = [{ command: 'openxiangda support bootstrap' }];
138
+ return result;
139
+ }
140
+ result.group = { access: 'unverified', membership: 'unverified', organizationRestriction: 'not_independently_verified' };
141
+ if (action === 'join') {
142
+ const opened = overrides.openUrl ? await overrides.openUrl(channel.inviteUrl) : await openSupportUrl(channel.inviteUrl, run);
143
+ result.inviteUrl = channel.inviteUrl;
144
+ result.browserOpened = opened === true;
145
+ return pending(result, 'pending_user_action', 'SUPPORT_JOIN_PENDING', 'openxiangda support status');
146
+ }
147
+ return pending(result, 'group_unavailable', 'SUPPORT_GROUP_ACCESS_UNVERIFIED', 'openxiangda support join');
148
+ } catch (error) {
149
+ return pending(result, 'unavailable', error.code || 'SUPPORT_UNAVAILABLE', 'openxiangda support bootstrap');
150
+ } finally { releaseLock(); }
151
+ }
152
+
153
+ function pending(result, state, code, command) {
154
+ return { ...result, state, code, nextActions: [{ command }] };
155
+ }
156
+
157
+ export function installSupportGuidance(packageRoot, skillsHome) {
158
+ const source = join(packageRoot, 'launcher-skill', 'openxiangda-support');
159
+ mkdirSync(skillsHome, { recursive: true });
160
+ cpSync(source, join(skillsHome, 'openxiangda-support'), { recursive: true });
161
+ }
162
+
163
+ export async function bootstrapSupport(packageRoot, args = [], overrides = {}) {
164
+ if (args.includes('--skip-support') || args.includes('--dry-run') || args.includes('--help') || args.includes('-h')) return { state: 'skipped' };
165
+ return supportOperation(packageRoot, ['support', 'bootstrap', ...(args.includes('--force') ? ['--force'] : [])], overrides);
166
+ }
167
+
168
+ async function openSupportUrl(url, run) {
169
+ const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer.exe' : 'xdg-open';
170
+ return (await run(command, [url], { timeout: 10_000 })).status === 0;
171
+ }
172
+
173
+ export function runSupportProcess(command, args, options = {}) {
174
+ return new Promise(resolve => {
175
+ let stdout = '', stderr = '', settled = false, outputSize = 0;
176
+ const env = { ...process.env };
177
+ if (options.officialSkills) delete env.DWS_SKILL_SOURCE;
178
+ const child = spawn(command, args, { env, stdio: options.interactive ? ['inherit', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'], windowsHide: true });
179
+ const timer = setTimeout(() => child.kill('SIGKILL'), options.timeout || 40_000);
180
+ const finish = status => { if (!settled) { settled = true; clearTimeout(timer); resolve({ status, stdout, stderr }); } };
181
+ const collect = stream => chunk => {
182
+ outputSize += chunk.length;
183
+ if (outputSize > 1024 * 1024) { child.kill('SIGKILL'); return; }
184
+ if (stream === 'stdout') stdout += chunk; else stderr += chunk;
185
+ if (options.interactive) process.stderr.write(chunk);
186
+ };
187
+ child.stdout.on('data', collect('stdout')); child.stderr.on('data', collect('stderr'));
188
+ child.once('error', () => finish(-1)); child.once('close', code => finish(code ?? -1));
189
+ });
190
+ }
@@ -15,6 +15,7 @@
15
15
  - 默认读取 AppSpec 当前规格、设计索引、活动变更和阶段缺口。新应用先用自然语言对话主动发现模块,完成本期 PRD、旅程、逐页交互、视觉/原型、权限与架构设计及实际确认,形成内容摘要绑定的评审基线后再制定实施计划和实现业务;参见 docs product-design 和 docs interaction-patterns。既有应用仅修订受影响设计,不把 AI 建议写成用户确认。总纲保存长期规则,本轮变更关联需求、任务、源码与 AC 验收;测试发布前完成设计与计划,生产晋级前保存绑定原测试运行及包摘要的实际验收报告。更新当前规则、验证与发布结果和交接后归档;不得编造确认或通过结果。
16
16
  - 根据变化风险记录业务意图。无行为变化引用已有记录;已有授权和已确认意图不重复向用户请求机械确认。真实角色和浏览器验收与本地测试分别记录。
17
17
  - 失败保留错误码、指针和原候选;查询平台状态后使用允许的恢复操作,不自动重放未知结果。
18
+ - 平台契约、诊断和产品/架构咨询按 openxiangda-support 技能持续跟进已授权案例。使用 support status 查看 DWS 接入状态;待 OAuth、钉钉入群或网络恢复不阻塞独立开发,后台续跑以实际宿主监听/定时配置为准。
18
19
 
19
20
  MCP 使用同一项目 CLI:`pnpm exec openxiangda --mcp-stdio --cwd <workspace>`。先读取 workspace_context,再按任务读取 docs_read 和当前契约。登录、创建及长期 dev 使用 CLI/终端。
20
21
 
@@ -4,6 +4,10 @@
4
4
 
5
5
  ## 测试部署
6
6
 
7
+ 托管源码应用通过 `pnpm openxiangda source push -m "本轮变更说明"` 完成提交与推送。
8
+ 发布系统在平台绑定仓库中核验精确源码提交,并沿用 AppVersion 的源码和制品摘要关联。
9
+ 本地构建仍是当前交付方式;源码提交存在不等于平台已独立验证制品由该提交构建。
10
+
7
11
  发布前,先将本轮源码、生成契约及必要记录合入并推送仓库的远端默认主分支,然后从干净且同步的主分支工作区发布。工具从 origin 的远端 HEAD 识别主分支,不把任务分支的 upstream 当作主线。未提交、未推送、未合并或落后主线的问题会在构建和上传前返回;工具不会自动合并分支或覆盖其他会话的改动。
8
12
 
9
13
  开发开始时先同步主线并读取项目现状,开发完成包括提交、推送与主线整合。每个工作区保持一个写者;需要并行时使用独立目录并明确各任务范围。日常 dev/check 仍可验证未提交源码。没有 Git 远端的项目应先建立并绑定仓库再发布。
@@ -11,10 +11,10 @@ OpenXiangda 2.0 默认生成 React 应用和共享契约。普通 CRUD、标准
11
11
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
12
12
 
13
13
  ```bash
14
- pnpm dlx openxiangda@2.1.0 skill install --force
15
- pnpm dlx openxiangda@2.1.0 auth status --base-url <平台地址> --json
16
- pnpm dlx openxiangda@2.1.0 login --base-url https://platform.example.com
17
- pnpm dlx openxiangda@2.1.0 create my-app --base-url https://platform.example.com
14
+ pnpm dlx openxiangda@2.2.0 skill install --force
15
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
16
+ pnpm dlx openxiangda@2.2.0 login --base-url https://platform.example.com
17
+ pnpm dlx openxiangda@2.2.0 create my-app --base-url https://platform.example.com
18
18
  cd my-app
19
19
  pnpm openxiangda context --json
20
20
  pnpm openxiangda dev
@@ -26,6 +26,23 @@ pnpm openxiangda dev
26
26
 
27
27
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
28
28
 
29
+ ### 内部支持协作
30
+
31
+ 统一入口在成功创建应用、V1 workspace init/skill bootstrap 和 skill install 后检查 DWS。缺失时安装审核过的官方稳定版和完整 multi 技能(包括 shared、misc/profile 等跨技能引用),复用可用版本;不会自动登录、加入群或发送消息。支持接入结果独立于应用创建,JSON 创建结果保持原结构,接入进度写 stderr。
32
+
33
+ ```bash
34
+ pnpm openxiangda support status --json
35
+ pnpm openxiangda support bootstrap
36
+ pnpm openxiangda support login
37
+ pnpm openxiangda support join
38
+ ```
39
+
40
+ 首次 OAuth 由用户完成,SSH 使用 `support login --device`。支持侧保存 DWS 精确 profile 指针,多账号可传 `--profile <corpId:userId>`;不修改 DWS 或平台默认账号。通道来自外置配置,`join` 原样打开邀请链接,由用户在钉钉完成加入,再执行 `status` 回读。群可访问、成员身份与组织限制分别报告;钉钉负责真实权限判断,不凭链接推定。
41
+
42
+ 等待授权、入群、网络或安装权限时可以继续独立开发。离线/CI 创建和技能安装可加 `--skip-support`,之后运行 `support bootstrap` 恢复。完整官方技能由 DWS 自身安装/备份;自动接入使用 DWS 的 all 目标检测宿主,应用技能安装的 --agent 只决定应用技能位置。`support bootstrap --force --agent <DWS目标宿主>` 可显式指定 DWS 宿主并刷新。`support --help` 查看命令,支持命令无需应用依赖已安装;独立 V1 引擎没有这些分发命令,应使用新版统一入口,保留原 V1 项目版本。
43
+
44
+ 本地接入指针位于 `${XDG_CONFIG_HOME:-~/.config}/openxiangda/support.json`,只保存 profile 和技能安装回执,不保存 Token。普通支持咨询与持续讨论遵循安装的 `openxiangda-support` 技能及用户授权;后台唤醒须有真实宿主监听或定时任务。
45
+
29
46
  ## 项目结构 {#workspace}
30
47
 
31
48
  ```text
@@ -47,6 +64,83 @@ AppSpec 随开发持续维护:测试发布前补齐总纲、关联变更与验
47
64
 
48
65
  纯 CRUD 修改优先使用标准模型、字段和页面;跨模型事务或外部副作用再选择后端。角色、行和字段权限在平台执行。详见[开发流程](./development.md)、[模型与标准 CRUD](./application-foundation.md)和[按需后端](./backend.md)。
49
66
 
67
+ ## 应用源码
68
+
69
+ 平台启用源码托管后,`create` 自动建立应用私有仓库、配置长期 Git 凭据并推送首次提交。
70
+ 应用管理员自动拥有对应仓库管理员权限;无需注册另一套账号。凭据存入系统凭据管理器,
71
+ macOS 使用 Keychain,Windows 使用 Git Credential Manager,Linux 使用已安装的
72
+ Git Credential Manager 或 libsecret。新电脑首次使用需重新执行源码配置。
73
+
74
+ ```bash
75
+ pnpm openxiangda source status
76
+ pnpm openxiangda source setup
77
+ pnpm openxiangda source push -m "完成本轮应用开发"
78
+ ```
79
+
80
+ 创建或推送中断后,在原目录重试。已有外部仓库使用 `source setup --import`,原 `origin`
81
+ 保留为 `external-source`;当前分支的历史随推送保留,不执行强推,也不自动合并冲突。
82
+ 其他分支、标签和 Git LFS 对象需按实际迁移范围另外推送。`source push` 省略 `-m` 时只推送
83
+ 已有提交;带 `-m` 时提交当前所有未忽略更改。源码入口位于平台应用运营的“应用源码”。
84
+
85
+ | 当前场景 | 执行方式 |
86
+ | --- | --- |
87
+ | 新应用 | 正常执行 `create`,平台启用后自动建仓、配置凭据和首次推送 |
88
+ | 已有项目首次交接给另一个 AI | 先读 `context --json` 和 `source status`,沿用项目绑定与版本 |
89
+ | 换电脑或初始化中断 | 在应用目录执行 `pnpm openxiangda source setup`;已有提交及未提交修改会保留 |
90
+ | 从个人远端迁入 | 明确迁入后运行 `source setup --import`,原远端保留为 `external-source` |
91
+ | 本轮修改完成 | 检查差异后运行 `source push -m "AppSpec: <本轮变更ID> 变更说明"`;多个任务共享目录时先精确提交本轮文件,再不带 `-m` 推送 |
92
+ | 准备部署 | 先把本轮提交合入并推送远端默认分支,再从干净且同步的主分支执行 `deploy` |
93
+
94
+ 新仓库默认使用 `main`;导入时,尚无默认分支的空仓库会采用当前分支(例如 `master`)。
95
+ 已有远端默认分支不会因在任务分支运行 setup 而改变。源码操作使用 CLI/终端;
96
+ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MCP 工具。
97
+
98
+ | 错误或状态 | 处理 |
99
+ | --- | --- |
100
+ | `enabled: false` | 平台尚未启用托管,沿用当前工作区;由平台管理员配置后再接入 |
101
+ | `APPLICATION_SOURCE_ORIGIN_CONFLICT` | 核实平台绑定和现有 origin;仅在明确迁入时使用 `--import` |
102
+ | `APPLICATION_SOURCE_CREDENTIAL_HELPER_REQUIRED` / `APPLICATION_SOURCE_CREDENTIAL_NOT_STORED` | 安装或解锁系统凭据管理器,再重试 setup;不把密码写入 URL、项目或明文凭据文件 |
103
+ | 403 / 应用管理权限不足 | 核对当前平台账号及应用管理员资格;由已有应用管理员添加权限 |
104
+ | `APPLICATION_SOURCE_PROVIDER_UNAVAILABLE` / `APPLICATION_SOURCE_PROVIDER_FAILED` | 保留原应用和目录,待 Git 服务恢复后重试同一操作 |
105
+ | 推送被拒绝或主线已前进 | 先 fetch 并查看差异,按项目规则合并解决冲突后重试,不强推覆盖 |
106
+ | `APPLICATION_SOURCE_COMMIT_NOT_PUSHED` | 在绑定仓库推送原提交,核对 source status 和远端 SHA 后重试部署 |
107
+
108
+ 应用创建人和新增应用管理员拥有对应仓库管理权限,不需要另行维护 Git 角色。
109
+ 本期不自动同步权限撤销或删除。不要向应用开发者索要 Forgejo 平台管理员 Token;
110
+ 个人凭据由已登录的平台账号获取,配置成功后可长期复用。
111
+
112
+ ### 从平台和仓库 URL 获取源码
113
+
114
+ 无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
115
+
116
+ ```bash
117
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台> --json
118
+ pnpm dlx openxiangda@2.2.0 source resolve <仓库URL> --base-url <平台> --json
119
+ pnpm dlx openxiangda@2.2.0 source clone <仓库URL> <新目录> --base-url <平台> --json
120
+ ```
121
+
122
+ 登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
123
+ `appCode/name/repository`,不猜应用代码;clone 使用当前账号配置长期凭据后检出源码,
124
+ 返回 `root/baseUrl/appCode/repository/branch/commit`。`--branch <分支>` 可指定分支,
125
+ 省略时使用 Forgejo 实际默认分支。目标目录必须不存在;失败时保留目录供检查。
126
+ 克隆不会加载应用配置、安装依赖、执行应用脚本或递归拉取子模块,并禁用 checkout hooks
127
+ 和全局过滤器;需要 LFS 内容时在审查后单独处理。
128
+
129
+ 既有 `PLATFORM_ADMIN` 平台管理员映射为共享 Forgejo 管理员,可完整管理已有及以后创建的
130
+ 全部仓库,不受应用创建人或应用成员登记限制。平台应用管理员保持对应仓库管理权限。
131
+ 该规则没有新增平台角色,也不扩展普通应用成员的权限。
132
+
133
+ 正式 API 均位于平台 `/service/openxiangda-api/v2` 下,使用现有登录态:
134
+
135
+ | API | 请求及响应 data |
136
+ | --- | --- |
137
+ | `GET /application-source/resolve?repository=<编码后的仓库URL>` | 接受平台登记的 cloneUrl 或 webUrl,返回 `{ appCode, name, repository }`;不返回凭据 |
138
+ | `POST /application-source/credential` | JSON `{ "repository": "仓库URL" }`,返回 `{ appCode, repository, username, password, name, email }`,响应 `Cache-Control: no-store` |
139
+ | `POST /application-source/administrators/reconcile` | 仅平台管理员;同步已有 PLATFORM_ADMIN 到 Git,返回 `{ synchronized }`,不返回凭据 |
140
+
141
+ 使用 CLI 时无需自行调用凭据 API;支持工具不得记录其响应或另建身份体系。
142
+ 未登记的外部仓库先在原工作区执行 `source setup --import`,再使用平台返回的仓库 URL。
143
+
50
144
  ## 检查与交付 {#delivery}
51
145
 
52
146
  只检查时运行 `pnpm openxiangda check`。需要部署测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建;无需再连续重复运行全部脚本。
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "fe1ea2bb3653ab4c2b166e71d7138a9a6c4d2d6d84f3576b790cd04b32db22af"
9
+ "sha256": "7c4703a4135e868ef4c18c04ec62f25bc1067d9e72fb17414babc9f1a567b862"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -96,7 +96,7 @@
96
96
  "id": "delivery",
97
97
  "title": "部署、生产晋级与恢复",
98
98
  "file": "delivery.md",
99
- "sha256": "e2a225019925ffa4855156e7dbde1a70c50a38804e7c0b206e1fbf81008ec078"
99
+ "sha256": "30152536bf782d62ba182272086033fa45596e1f9f42eefe59169c9fa25af68f"
100
100
  },
101
101
  {
102
102
  "id": "upgrading",
@@ -108,7 +108,7 @@
108
108
  "id": "cli",
109
109
  "title": "CLI 命令参考",
110
110
  "file": "reference/cli.md",
111
- "sha256": "05005a0fb268af776510128132b984bc88ca20c5232190aa1ee26070bce796d4"
111
+ "sha256": "f17c3f2bb78bb61382f51ba7ab36a89f228f116e1f020b30ad833a0f4a89d57d"
112
112
  },
113
113
  {
114
114
  "id": "mcp",
@@ -9,6 +9,7 @@
9
9
  | `pnpm openxiangda docs` | 只读 | 按主题和章节读取当前版本中文资料 |
10
10
  | `pnpm openxiangda admin` | 只读 | 只读查看应用管理能力和流程节点运行配置 |
11
11
  | `pnpm openxiangda create` | 远端变更 | 创建、绑定并初始化应用 |
12
+ | `pnpm openxiangda source` | 远端变更 | 配置应用源码仓库、查看状态或提交推送 |
12
13
  | `pnpm openxiangda dev` | 本地写入 | 连接平台测试数据启动本地 Web,按需启动 Nest |
13
14
  | `pnpm openxiangda check` | 本地写入 | 生成契约并在目标平台预检后执行检查、测试和构建 |
14
15
  | `pnpm openxiangda accept` | 远端变更 | 按计划准备可选的真实预发验收身份 |
@@ -15,6 +15,8 @@ description: Identify OpenXiangda V1 or V2 workspaces and select the matching pr
15
15
 
16
16
  ## 升级与说明
17
17
 
18
+ - 平台支持协作读取同次安装的 `$openxiangda-support`。创建和技能刷新默认检查 DWS;`openxiangda support status --json` 查看接入状态,`support bootstrap` 补齐官方全套技能,`support login` 完成 OAuth,`support join` 打开配置链接由用户加入。等待用户或网络恢复不阻塞应用开发。支持命令归统一入口,V1/V2 业务和平台登录绑定保持原有所有者。
19
+
18
20
  - `openxiangda update check` 按项目代际检查稳定更新;离线时可继续使用本地能力。
19
21
  - `openxiangda update install --target workspace` 更新本项目同代工具链。升级后检查源码与锁文件差异,并执行项目验证。
20
22
  - `openxiangda update install --target launcher` 更新全局统一入口,需要 Node.js 24。此操作不转换已有项目。
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: openxiangda-support
3
+ description: 在 OpenXiangda 应用开发中接入内部平台支持协作,咨询平台契约、诊断异常、评审产品与架构,并持续跟进已授权的支持案例。普通应用实现继续使用项目对应代际技能。
4
+ ---
5
+
6
+ # OpenXiangda 支持协作
7
+
8
+ ## 接入与恢复
9
+
10
+ 统一入口的 `support status --json` 返回实际接入状态、精确 DWS profile 和当前通道。新应用创建及技能刷新会执行 bootstrap;离线或待授权不会阻塞应用。缺失时执行 `support bootstrap`,由它安装/复用官方稳定 DWS 和全套 multi 技能。旧 V1 项目通过统一入口使用支持命令,业务仍走项目 V1 引擎;不升级项目代际来取得支持。
11
+
12
+ `support login` 引导用户完成 OAuth;SSH 使用 `--device`。多账号时使用用户指定的 `--profile <corpId:userId>` 或目标组织明确的默认账号,后续复用保存的 profile。不得切换全局默认组织或根据姓名选账号。`support join` 原样打开外置配置的邀请链接,用户在钉钉完成加入;不能打开时展示返回的同一地址。随后运行 `support status` 检查群访问。链接已打开、命令退出成功或群可读不等于独立验证了成员身份或组织限制,以返回的各项状态为准。
13
+
14
+ DWS 命令、参数和实际访问由官方 `dingtalk-chat`、`dingtalk-event` 及其 references 负责;账号问题读取 `dingtalk-misc/references/profile.md`。始终带同一 `--profile` 和 `--format json`,按叶子 Schema/Help 补齐未知参数。授权、加入或服务暂不可用时记录待办,继续独立的应用工作。
15
+
16
+ ## 何时讨论
17
+
18
+ 遇到平台契约不明确、文档与实测冲突、需要平台源码/部署/日志证据、复杂权限与数据处理、通用能力取舍或新需求评审时,先读项目当前资料及证据,再在用户已经授权的协作范围内发问。此技能不会单独授予发送消息、操作服务器、修改数据或发布权限。
19
+
20
+ 提问包含目标、现象、已尝试方法、关键证据、需平台判断的问题;从项目 context 补平台、应用、环境及实际版本。未创建应用的能力咨询标记为平台咨询,不伪造 appCode。内部协作可以讨论相关业务逻辑、源码、日志和完整方案;凭据自动遮蔽,不把维护者 localhost 地址作为其他成员的审阅入口。
21
+
22
+ 需要读取应用源码时,提供平台地址及平台内置仓库 URL。支持侧使用正式 `source resolve <仓库URL> --base-url <平台> --json`,再 `source clone <仓库URL> <新目录> --base-url <平台> --json`;记录实际分支与 commit,首次检出不安装或运行项目脚本。使用平台现有身份和 `PLATFORM_ADMIN` 权限事实,不按某个账号名字开例外。旧项目使用 `source setup --import` 保留原仓库,正常解决 Git 冲突;详细契约以当前版本 getting-started 资料为准。
23
+
24
+ ## 评审与持续协作
25
+
26
+ 产品/架构咨询明确真实问题、已有能力、应用或平台归属、跨项目价值、成本与兼容性、失败恢复和验收方法。可以得出现有能力可满足、应用层实现、证据不足、重复需求、平台候选、已安排实施或拒绝/延期;每项结论附证据及下一步责任方。只有关联真实工程任务及容量才称为已安排实施,不口头承诺发布日期,不把客户业务实体当作平台定义。
27
+
28
+ 首问使用官方群消息入口;后续按真实消息 ID 引用回复,沿同一案例追加证据与验证,不重复创建问题。读取回复后补实现/复现结果,明确解决、暂行规避、候选需求或尚有争议;已回答不等于已解决。复杂或高影响操作先给具体对象、差异、影响、恢复方式,在已有授权范围内推进,超出部分才请求决定。
29
+
30
+ 活跃任务可通过官方事件技能等待增量,恢复任务时读取未完成案例。消息 ID、游标、未决项和下一步责任方保留在项目现有交接记录中,不含凭据。技能不会唤醒结束的 AI 会话;后台继续需要宿主真实监听或定时任务,不宣称所有宿主均已接入。
31
+
32
+ 两个 AI 使用不同真实身份。发送请求成功不是消息投递成功;检查真实投递结果,结果不明先回读,不盲目重发。确认、感谢和重复消息不启动新轮;初始上限 6 轮有效往返,连续 2 轮无新证据时暂停并列未决项。结论引用来源和适用版本交给既有案例/知识流程,不另建一套权威路线图。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -58,13 +58,13 @@
58
58
  "antd-mobile": "5.42.3",
59
59
  "dayjs": "1.11.18",
60
60
  "docx-preview": "0.3.7",
61
- "openxiangda-cli": "2.1.0",
62
- "openxiangda-contracts": "2.1.0",
63
- "openxiangda-devkit-core": "2.1.0",
61
+ "openxiangda-cli": "2.2.1",
62
+ "openxiangda-contracts": "2.2.0",
63
+ "openxiangda-devkit-core": "2.2.0",
64
64
  "openxiangda-legacy": "npm:openxiangda@1.0.268",
65
- "openxiangda-mcp": "2.0.1",
66
- "openxiangda-nest": "2.0.1",
67
- "openxiangda-skill-kit": "2.0.1",
65
+ "openxiangda-mcp": "2.0.2",
66
+ "openxiangda-nest": "2.0.2",
67
+ "openxiangda-skill-kit": "2.0.3",
68
68
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
69
69
  },
70
70
  "peerDependencies": {
@@ -130,21 +130,24 @@
130
130
  },
131
131
  "openxiangdaRelease": {
132
132
  "schemaVersion": "openxiangda.release-notes/v1",
133
- "version": "2.1.0",
134
- "title": "OpenXiangda 平台托管应用镜像上传",
133
+ "version": "2.2.0",
134
+ "title": "OpenXiangda 内部支持协作接入",
135
135
  "status": "reviewed",
136
- "summary": "应用 Nest 后端在本机构建为 OCI 镜像后,通过平台登录态分片上传。平台负责应用归属校验、镜像存储和运行时拉取,开发者无需取得平台维护者个人镜像仓库的推送权限。",
136
+ "summary": "统一入口为 V1/V2 应用开发提供内部支持协作接入,检查官方 DWS 与技能,复用精确账号,并提供授权、打开群邀请和持续案例跟进入口。",
137
137
  "newFeatures": [
138
- "发现并优先使用平台 backendImageUpload 能力,导出 linux/amd64 OCI 镜像并上传有界分片。",
139
- "上传前校验本地镜像大小和摘要,复用已完成的镜像回执,并恢复分片传输中断。"
138
+ "新增 support status、bootstrap、login、join,应用创建和技能刷新后检查支持通道接入状态。",
139
+ "缺少 DWS 时安装官方稳定版 1.0.61,并安装官方完整 multi 技能;已有可用稳定版本可以复用。",
140
+ "增加跨 V1/V2 支持技能,涵盖平台契约咨询、源码诊断上下文、产品架构评审和已授权案例的引用跟进。",
141
+ "使用外置支持通道配置,按原始邀请链接打开钉钉,由用户完成加入。"
140
142
  ],
141
143
  "fixes": [
142
- "启用平台上传后,应用部署不再依赖开发者对平台维护者镜像仓库的推送权限。",
143
- "Docker classic image store 不支持 OCI 导出时,返回明确的 builder 配置指引。"
144
+ "支持接入失败或等待用户操作时保留独立状态,不改变应用创建成功结果;进度写入 stderr,保持引擎 JSON 输出。",
145
+ "使用保存的精确 DWS profile,不按显示姓名或列表顺序猜测账号,不切换平台或 DWS 全局登录身份。",
146
+ "命令发现由统一分发层维护,技能校验显式接收分发命令,保持 V2 核心与两代引擎边界。"
144
147
  ],
145
148
  "affectedUsers": [
146
- "声明可选 Nest 后端并通过 CLI 或 MCP 部署的 OpenXiangda 2.0 应用开发者。",
147
- "维护共享应用运行时及平台镜像存储的平台管理员。"
149
+ "通过统一入口创建应用、初始化工作区或刷新技能的 V1/V2 开发者。",
150
+ "在内部支持通道中继续已授权平台咨询和故障排查的开发 AI。"
148
151
  ],
149
152
  "compatibility": {
150
153
  "node": ">=24",
@@ -152,25 +155,26 @@
152
155
  "v1",
153
156
  "v2"
154
157
  ],
155
- "v1Policy": "V1 应用保持原代际、版本锁定与登录绑定。",
156
- "platformPolicy": "平台托管上传需要匹配的后端、Registry 和网关发布,并启用 backendImageUpload;旧平台继续使用既有构建合同。",
157
- "backendBuild": "可选 Nest 后端仍在开发者电脑上使用 Docker Buildx 构建,需要支持 OCI exporter 的 builder。",
158
+ "v1Policy": "V1 项目保持原代际、版本锁定与登录绑定;支持接入由统一分发入口提供。",
159
+ "platformPolicy": "本次工具发布不要求更新平台镜像;源码诊断仍需目标平台已启用正式源码托管并授予当前账号访问权限。",
160
+ "dws": "最低稳定版 1.0.61;OAuth 与加入群需要用户在官方流程中完成。",
158
161
  "releaseChannels": "latest / stable-v2:V2 正式版;legacy-v1:V1 维护版;alpha:预发布。"
159
162
  },
160
163
  "upgradeSteps": [
161
- "平台管理员先按部署运行手册准备镜像存储与凭据,再部署匹配的后端和网关。",
162
- " Node.js 24 环境安装统一入口:npm install -g openxiangda@2.1.0。",
163
- "更新 V2 项目锁定的工具链并刷新项目资料,核对平台上传能力后运行项目的 openxiangda deploy。"
164
+ "在 Node.js 24 环境安装统一入口:npm install -g openxiangda@2.2.0。",
165
+ "运行 openxiangda support bootstrap 检查安装与完整技能;离线或 CI 初始化可使用 --skip-support,之后再恢复。",
166
+ "运行 openxiangda support status --json 查看实际状态;需要时执行 support login,SSH 可使用 --device。",
167
+ "尚不能访问支持群时运行 support join,在钉钉完成加入后重新检查状态;已有授权案例继续引用原消息跟进。"
164
168
  ],
165
169
  "knownLimitations": [
166
- "当前仅支持 linux/amd64 单平台 OCI 镜像;默认单镜像上限为 1Gi,由平台配置控制。",
167
- "已声明但未启用的平台上传能力会阻止部署,避免回退到个人镜像仓库推送。",
168
- "镜像保留与垃圾回收由平台运维单独管理;现有版本仍可能需要历史镜像进行部署或回滚。",
169
- "托管镜像开始使用后,后端回滚必须保留 Registry、持久卷、凭据和网关读取路由。"
170
+ "可读取群仅证明当前账号的访问能力,不能单独证明群成员身份或组织限制;命令返回独立验证状态。",
171
+ "技能不会唤醒已经结束的开发任务,后台继续需要宿主实际监听或定时任务。",
172
+ "安装、授权和群访问依赖网络及组织配置;新的真实用户入群与双方协作须单独现场验收,替身测试不构成真人验收。",
173
+ "本包提供接入与协作指导,常驻支持服务及知识库由各自私有服务独立部署。"
170
174
  ],
171
175
  "issues": [],
172
- "sha256": "d7f6a21ddc37b3d2439350bf17f5f4575192168944dc33efb15460f7d1854aff",
173
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.1.0"
176
+ "sha256": "638817e7e1d8c9cb99de6b9d41716828d241e31905c9bd0565cb94e43b9ee991",
177
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.2.0"
174
178
  },
175
179
  "scripts": {
176
180
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.1.1",
4
+ "title": "OpenXiangda 应用源码托管",
5
+ "status": "reviewed",
6
+ "summary": "私有化平台通过共享 Forgejo 管理应用源码。创建应用时自动初始化私有仓库并推送代码,应用管理员自动获得仓库管理权限,开发者使用长期 Git 凭据。",
7
+ "newFeatures": [
8
+ "既有 PLATFORM_ADMIN 自动获得共享 Git 服务全部已有和新建仓库的管理权限。",
9
+ "source resolve/clone 支持仅凭平台与仓库 URL、使用当前登录态读取源码,不安装或执行应用脚本。",
10
+ "新增 source status、source setup 和 source push 命令,支持初始化恢复和已有工作区接入。",
11
+ "创建应用后自动提交并推送初始源码,通过系统凭据管理器保存个人 Git 凭据。",
12
+ "配套平台支持首次部署前浏览源码及最近提交,并核验部署关联的提交已经推送。"
13
+ ],
14
+ "fixes": [
15
+ "应用源码可直接保留在平台托管仓库中,减少对开发者个人 Git 服务的依赖。",
16
+ "显式导入保留原 origin 为 external-source,初始化重试保留已有未提交修改,冲突推送不覆盖远端提交。"
17
+ ],
18
+ "affectedUsers": [
19
+ "使用 OpenXiangda 2.0 创建应用或接入平台源码仓库的开发者。",
20
+ "维护内部私有化平台和应用管理员权限的平台管理员。"
21
+ ],
22
+ "compatibility": {
23
+ "node": ">=24",
24
+ "workspaceGenerations": [
25
+ "v1",
26
+ "v2"
27
+ ],
28
+ "v1Policy": "V1 应用保持原代际、版本锁定与登录绑定。",
29
+ "platformPolicy": "自动源码托管需要匹配的平台后端、管理前端和网关,并配置 OPENXIANGDA_SOURCE_*;未启用的站点保持原创建行为。",
30
+ "gitCredential": "支持 macOS Keychain、Windows Git Credential Manager,以及 Linux 已安装的 Git Credential Manager 或 libsecret。",
31
+ "releaseChannels": "latest / stable-v2:V2 正式版;legacy-v1:V1 维护版;alpha:预发布。"
32
+ },
33
+ "upgradeSteps": [
34
+ "平台管理员配置共享 Forgejo、源码入口和专用凭据派生密钥,部署匹配的平台版本;长期保留密钥及 Git 数据。",
35
+ "在 Node.js 24 环境安装统一入口:npm install -g openxiangda@2.1.1。",
36
+ "更新 V2 工作区工具链;新应用自动接入,已有应用运行 openxiangda source setup --import 接入。",
37
+ "完成修改后运行 openxiangda source push -m 描述,再按现有检查和部署流程交付。"
38
+ ],
39
+ "knownLimitations": [
40
+ "当前仅同步应用管理员新增授权,不同步撤销或删除。",
41
+ "导入推送当前分支,不自动迁移全部历史分支、标签及 LFS 对象。",
42
+ "制品仍在本地构建;提交存在性核验不构成服务端构建证明。",
43
+ "新源码功能不依赖 Studio;Studio 旧代码移除属于独立后续工作。"
44
+ ],
45
+ "issues": [],
46
+ "sha256": "f7341eb21e1489c684ee03eed31568063dfad01bfe345f914552bd51b7357f84",
47
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.1.1"
48
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.2.0",
4
+ "title": "OpenXiangda 内部支持协作接入",
5
+ "status": "reviewed",
6
+ "summary": "统一入口为 V1/V2 应用开发提供内部支持协作接入,检查官方 DWS 与技能,复用精确账号,并提供授权、打开群邀请和持续案例跟进入口。",
7
+ "newFeatures": [
8
+ "新增 support status、bootstrap、login、join,应用创建和技能刷新后检查支持通道接入状态。",
9
+ "缺少 DWS 时安装官方稳定版 1.0.61,并安装官方完整 multi 技能;已有可用稳定版本可以复用。",
10
+ "增加跨 V1/V2 支持技能,涵盖平台契约咨询、源码诊断上下文、产品架构评审和已授权案例的引用跟进。",
11
+ "使用外置支持通道配置,按原始邀请链接打开钉钉,由用户完成加入。"
12
+ ],
13
+ "fixes": [
14
+ "支持接入失败或等待用户操作时保留独立状态,不改变应用创建成功结果;进度写入 stderr,保持引擎 JSON 输出。",
15
+ "使用保存的精确 DWS profile,不按显示姓名或列表顺序猜测账号,不切换平台或 DWS 全局登录身份。",
16
+ "命令发现由统一分发层维护,技能校验显式接收分发命令,保持 V2 核心与两代引擎边界。"
17
+ ],
18
+ "affectedUsers": [
19
+ "通过统一入口创建应用、初始化工作区或刷新技能的 V1/V2 开发者。",
20
+ "在内部支持通道中继续已授权平台咨询和故障排查的开发 AI。"
21
+ ],
22
+ "compatibility": {
23
+ "node": ">=24",
24
+ "workspaceGenerations": [
25
+ "v1",
26
+ "v2"
27
+ ],
28
+ "v1Policy": "V1 项目保持原代际、版本锁定与登录绑定;支持接入由统一分发入口提供。",
29
+ "platformPolicy": "本次工具发布不要求更新平台镜像;源码诊断仍需目标平台已启用正式源码托管并授予当前账号访问权限。",
30
+ "dws": "最低稳定版 1.0.61;OAuth 与加入群需要用户在官方流程中完成。",
31
+ "releaseChannels": "latest / stable-v2:V2 正式版;legacy-v1:V1 维护版;alpha:预发布。"
32
+ },
33
+ "upgradeSteps": [
34
+ "在 Node.js 24 环境安装统一入口:npm install -g openxiangda@2.2.0。",
35
+ "运行 openxiangda support bootstrap 检查安装与完整技能;离线或 CI 初始化可使用 --skip-support,之后再恢复。",
36
+ "运行 openxiangda support status --json 查看实际状态;需要时执行 support login,SSH 可使用 --device。",
37
+ "尚不能访问支持群时运行 support join,在钉钉完成加入后重新检查状态;已有授权案例继续引用原消息跟进。"
38
+ ],
39
+ "knownLimitations": [
40
+ "可读取群仅证明当前账号的访问能力,不能单独证明群成员身份或组织限制;命令返回独立验证状态。",
41
+ "技能不会唤醒已经结束的开发任务,后台继续需要宿主实际监听或定时任务。",
42
+ "安装、授权和群访问依赖网络及组织配置;新的真实用户入群与双方协作须单独现场验收,替身测试不构成真人验收。",
43
+ "本包提供接入与协作指导,常驻支持服务及知识库由各自私有服务独立部署。"
44
+ ],
45
+ "issues": [],
46
+ "sha256": "638817e7e1d8c9cb99de6b9d41716828d241e31905c9bd0565cb94e43b9ee991",
47
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.2.0"
48
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "openxiangda-v2",
6
6
  "description": "使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具体变更出发,通过对话发现模块、完成详细产品设计,再开发、检查和交付应用。维护 1.x 应用时使用对应的 1.x 技能。",
7
- "sha256": "3f1a2044254680d480e72ad0fb524967ed471a8c44bb9b0c2b9f226d7e834f43"
7
+ "sha256": "618f4dbe2de1ae08879b04bd192f1feadad24c09b5147f7f222e483e03c8f142"
8
8
  }
9
9
  ]
10
10
  }
@@ -18,10 +18,10 @@ description: 使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具
18
18
  未创建工作区时使用本 Skill 随根包发布的精确版本:
19
19
 
20
20
  ```bash
21
- pnpm dlx openxiangda@2.1.0 auth status --base-url <平台地址> --json
22
- pnpm dlx openxiangda@2.1.0 login --base-url <平台地址>
23
- pnpm dlx openxiangda@2.1.0 create <应用目录> --base-url <同一平台地址>
24
- pnpm dlx openxiangda@2.1.0 skill install --force
21
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
22
+ pnpm dlx openxiangda@2.2.0 login --base-url <平台地址>
23
+ pnpm dlx openxiangda@2.2.0 create <应用目录> --base-url <同一平台地址>
24
+ pnpm dlx openxiangda@2.2.0 skill install --force
25
25
  ```
26
26
 
27
27
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -30,11 +30,14 @@ pnpm dlx openxiangda@2.1.0 skill install --force
30
30
 
31
31
  ## 按任务选择资料
32
32
 
33
+ 平台契约疑问、诊断和产品/架构咨询使用同次分发的 `openxiangda-support` 技能。统一入口创建和技能刷新会检查 DWS;用 `support status --json` 查看待授权、待用户入群或可访问状态,独立应用工作继续推进。实际发送与后续操作遵循用户已有授权。
34
+
33
35
  只读当前任务相关专题。以下参考由中文使用文档生成,与 CLI/MCP 正文同源:
34
36
 
35
37
  | 任务 | 参考 |
36
38
  | --- | --- |
37
39
  | 安装、登录、创建、连接开发 | [开始开发](references/getting-started.md) |
40
+ | 源码仓库、换电脑、旧项目导入、提交推送与重试 | [应用源码](references/getting-started.md#应用源码);先用 `source status` 读取实际绑定 |
38
41
  | 模糊想法、模块发现、PRD、权限与架构设计 | [产品设计](references/product-design.md)、[交互模式](references/interaction-patterns.md) |
39
42
  | 理解需求与选择能力 | [开发流程](references/development.md)、[架构](references/concepts.md) |
40
43
  | 模型、CRUD、字段与移动表单 | [业务模块](references/application-foundation.md)、[字段](references/field-components.md) |
@@ -77,6 +80,12 @@ pnpm exec openxiangda --mcp-stdio --cwd <workspace>
77
80
 
78
81
  开发开始先同步绑定仓库的远端默认主分支。开发完成包括源码、生成契约与必要记录的提交、推送和主线整合;发布从干净且同步的主分支冻结候选。任务分支已推送不代表进入主线。同一工作区保持一个写者,不覆盖其他会话未提交内容。日常 dev/check 可验证未提交源码。
79
82
 
83
+ 平台启用源码托管时,create 自动建仓并首次推送。后续每轮开发完成使用 `pnpm openxiangda source push -m "变更说明"` 提交并推送;已有提交可省略 `-m`。新电脑或初始化中断使用 `source setup`,迁入外部仓库使用 `source setup --import` 并保留原远端。凭据只存入系统凭据管理器,不手写进 URL 或项目文件;冲突按正常 Git 合并处理,不强推覆盖。
84
+
85
+ 已有项目先执行 `source status`;仓库绑定和地址以平台返回为准,不猜测地址或另建个人仓库。提交前检查 Git 差异;`source push -m` 会提交所有未忽略更改,存在其他任务改动时应只提交本轮文件,再不带 `-m` 推送。源码操作使用 CLI,当前没有独立的 source MCP 工具。凭据配置成功后无需每轮重新配置;托管未启用或权限不足时保留原工作区并报告具体原因。
86
+
87
+ 支持排查只有平台地址和仓库 URL 时,先运行 `source resolve <仓库URL> --base-url <平台> --json`,再用 `source clone <仓库URL> <新目录> --base-url <平台> --json` 获取源码,可选 `--branch`。无需预先创建工作区,不猜 appCode,不安装或运行应用脚本;使用当前平台登录账号与系统 Git 凭据。平台既有 `PLATFORM_ADMIN` 可管理共享服务全部仓库,应用管理员只管理对应应用仓库。完整契约见[应用源码](references/getting-started.md#应用源码)。
88
+
80
89
  只验证时运行 check/check_app;授权部署时直接 deploy/deploy_app,它已经包含检查并默认跟踪平台完成,长步骤持续反馈阶段和耗时。生产显式复用成功测试运行。观察中断用 status <运行ID> --watch 或 deployment_status.watch 继续查询原运行。登录、创建及长期 dev 使用 CLI/终端,分别记录本地验证、部署激活和真实业务验收。
81
90
 
82
91
  失败保留错误码、指针与原候选。结果不确定先查平台,不生成新的随机幂等键掩盖原运行;仅执行平台允许的恢复。升级项目后刷新资料并重启旧 MCP 连接。
@@ -9,6 +9,7 @@
9
9
  | `pnpm openxiangda docs` | 只读 | 按主题和章节读取当前版本中文资料 |
10
10
  | `pnpm openxiangda admin` | 只读 | 只读查看应用管理能力和流程节点运行配置 |
11
11
  | `pnpm openxiangda create` | 远端变更 | 创建、绑定并初始化应用 |
12
+ | `pnpm openxiangda source` | 远端变更 | 配置应用源码仓库、查看状态或提交推送 |
12
13
  | `pnpm openxiangda dev` | 本地写入 | 连接平台测试数据启动本地 Web,按需启动 Nest |
13
14
  | `pnpm openxiangda check` | 本地写入 | 生成契约并在目标平台预检后执行检查、测试和构建 |
14
15
  | `pnpm openxiangda accept` | 远端变更 | 按计划准备可选的真实预发验收身份 |
@@ -4,6 +4,10 @@
4
4
 
5
5
  ## 测试部署
6
6
 
7
+ 托管源码应用通过 `pnpm openxiangda source push -m "本轮变更说明"` 完成提交与推送。
8
+ 发布系统在平台绑定仓库中核验精确源码提交,并沿用 AppVersion 的源码和制品摘要关联。
9
+ 本地构建仍是当前交付方式;源码提交存在不等于平台已独立验证制品由该提交构建。
10
+
7
11
  发布前,先将本轮源码、生成契约及必要记录合入并推送仓库的远端默认主分支,然后从干净且同步的主分支工作区发布。工具从 origin 的远端 HEAD 识别主分支,不把任务分支的 upstream 当作主线。未提交、未推送、未合并或落后主线的问题会在构建和上传前返回;工具不会自动合并分支或覆盖其他会话的改动。
8
12
 
9
13
  开发开始时先同步主线并读取项目现状,开发完成包括提交、推送与主线整合。每个工作区保持一个写者;需要并行时使用独立目录并明确各任务范围。日常 dev/check 仍可验证未提交源码。没有 Git 远端的项目应先建立并绑定仓库再发布。
@@ -11,10 +11,10 @@ OpenXiangda 2.0 默认生成 React 应用和共享契约。普通 CRUD、标准
11
11
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
12
12
 
13
13
  ```bash
14
- pnpm dlx openxiangda@2.1.0 skill install --force
15
- pnpm dlx openxiangda@2.1.0 auth status --base-url <平台地址> --json
16
- pnpm dlx openxiangda@2.1.0 login --base-url https://platform.example.com
17
- pnpm dlx openxiangda@2.1.0 create my-app --base-url https://platform.example.com
14
+ pnpm dlx openxiangda@2.2.0 skill install --force
15
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
16
+ pnpm dlx openxiangda@2.2.0 login --base-url https://platform.example.com
17
+ pnpm dlx openxiangda@2.2.0 create my-app --base-url https://platform.example.com
18
18
  cd my-app
19
19
  pnpm openxiangda context --json
20
20
  pnpm openxiangda dev
@@ -26,6 +26,23 @@ pnpm openxiangda dev
26
26
 
27
27
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
28
28
 
29
+ ### 内部支持协作
30
+
31
+ 统一入口在成功创建应用、V1 workspace init/skill bootstrap 和 skill install 后检查 DWS。缺失时安装审核过的官方稳定版和完整 multi 技能(包括 shared、misc/profile 等跨技能引用),复用可用版本;不会自动登录、加入群或发送消息。支持接入结果独立于应用创建,JSON 创建结果保持原结构,接入进度写 stderr。
32
+
33
+ ```bash
34
+ pnpm openxiangda support status --json
35
+ pnpm openxiangda support bootstrap
36
+ pnpm openxiangda support login
37
+ pnpm openxiangda support join
38
+ ```
39
+
40
+ 首次 OAuth 由用户完成,SSH 使用 `support login --device`。支持侧保存 DWS 精确 profile 指针,多账号可传 `--profile <corpId:userId>`;不修改 DWS 或平台默认账号。通道来自外置配置,`join` 原样打开邀请链接,由用户在钉钉完成加入,再执行 `status` 回读。群可访问、成员身份与组织限制分别报告;钉钉负责真实权限判断,不凭链接推定。
41
+
42
+ 等待授权、入群、网络或安装权限时可以继续独立开发。离线/CI 创建和技能安装可加 `--skip-support`,之后运行 `support bootstrap` 恢复。完整官方技能由 DWS 自身安装/备份;自动接入使用 DWS 的 all 目标检测宿主,应用技能安装的 --agent 只决定应用技能位置。`support bootstrap --force --agent <DWS目标宿主>` 可显式指定 DWS 宿主并刷新。`support --help` 查看命令,支持命令无需应用依赖已安装;独立 V1 引擎没有这些分发命令,应使用新版统一入口,保留原 V1 项目版本。
43
+
44
+ 本地接入指针位于 `${XDG_CONFIG_HOME:-~/.config}/openxiangda/support.json`,只保存 profile 和技能安装回执,不保存 Token。普通支持咨询与持续讨论遵循安装的 `openxiangda-support` 技能及用户授权;后台唤醒须有真实宿主监听或定时任务。
45
+
29
46
  ## 项目结构 {#workspace}
30
47
 
31
48
  ```text
@@ -47,6 +64,83 @@ AppSpec 随开发持续维护:测试发布前补齐总纲、关联变更与验
47
64
 
48
65
  纯 CRUD 修改优先使用标准模型、字段和页面;跨模型事务或外部副作用再选择后端。角色、行和字段权限在平台执行。详见[开发流程](development.md)、[模型与标准 CRUD](application-foundation.md)和[按需后端](backend.md)。
49
66
 
67
+ ## 应用源码
68
+
69
+ 平台启用源码托管后,`create` 自动建立应用私有仓库、配置长期 Git 凭据并推送首次提交。
70
+ 应用管理员自动拥有对应仓库管理员权限;无需注册另一套账号。凭据存入系统凭据管理器,
71
+ macOS 使用 Keychain,Windows 使用 Git Credential Manager,Linux 使用已安装的
72
+ Git Credential Manager 或 libsecret。新电脑首次使用需重新执行源码配置。
73
+
74
+ ```bash
75
+ pnpm openxiangda source status
76
+ pnpm openxiangda source setup
77
+ pnpm openxiangda source push -m "完成本轮应用开发"
78
+ ```
79
+
80
+ 创建或推送中断后,在原目录重试。已有外部仓库使用 `source setup --import`,原 `origin`
81
+ 保留为 `external-source`;当前分支的历史随推送保留,不执行强推,也不自动合并冲突。
82
+ 其他分支、标签和 Git LFS 对象需按实际迁移范围另外推送。`source push` 省略 `-m` 时只推送
83
+ 已有提交;带 `-m` 时提交当前所有未忽略更改。源码入口位于平台应用运营的“应用源码”。
84
+
85
+ | 当前场景 | 执行方式 |
86
+ | --- | --- |
87
+ | 新应用 | 正常执行 `create`,平台启用后自动建仓、配置凭据和首次推送 |
88
+ | 已有项目首次交接给另一个 AI | 先读 `context --json` 和 `source status`,沿用项目绑定与版本 |
89
+ | 换电脑或初始化中断 | 在应用目录执行 `pnpm openxiangda source setup`;已有提交及未提交修改会保留 |
90
+ | 从个人远端迁入 | 明确迁入后运行 `source setup --import`,原远端保留为 `external-source` |
91
+ | 本轮修改完成 | 检查差异后运行 `source push -m "AppSpec: <本轮变更ID> 变更说明"`;多个任务共享目录时先精确提交本轮文件,再不带 `-m` 推送 |
92
+ | 准备部署 | 先把本轮提交合入并推送远端默认分支,再从干净且同步的主分支执行 `deploy` |
93
+
94
+ 新仓库默认使用 `main`;导入时,尚无默认分支的空仓库会采用当前分支(例如 `master`)。
95
+ 已有远端默认分支不会因在任务分支运行 setup 而改变。源码操作使用 CLI/终端;
96
+ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MCP 工具。
97
+
98
+ | 错误或状态 | 处理 |
99
+ | --- | --- |
100
+ | `enabled: false` | 平台尚未启用托管,沿用当前工作区;由平台管理员配置后再接入 |
101
+ | `APPLICATION_SOURCE_ORIGIN_CONFLICT` | 核实平台绑定和现有 origin;仅在明确迁入时使用 `--import` |
102
+ | `APPLICATION_SOURCE_CREDENTIAL_HELPER_REQUIRED` / `APPLICATION_SOURCE_CREDENTIAL_NOT_STORED` | 安装或解锁系统凭据管理器,再重试 setup;不把密码写入 URL、项目或明文凭据文件 |
103
+ | 403 / 应用管理权限不足 | 核对当前平台账号及应用管理员资格;由已有应用管理员添加权限 |
104
+ | `APPLICATION_SOURCE_PROVIDER_UNAVAILABLE` / `APPLICATION_SOURCE_PROVIDER_FAILED` | 保留原应用和目录,待 Git 服务恢复后重试同一操作 |
105
+ | 推送被拒绝或主线已前进 | 先 fetch 并查看差异,按项目规则合并解决冲突后重试,不强推覆盖 |
106
+ | `APPLICATION_SOURCE_COMMIT_NOT_PUSHED` | 在绑定仓库推送原提交,核对 source status 和远端 SHA 后重试部署 |
107
+
108
+ 应用创建人和新增应用管理员拥有对应仓库管理权限,不需要另行维护 Git 角色。
109
+ 本期不自动同步权限撤销或删除。不要向应用开发者索要 Forgejo 平台管理员 Token;
110
+ 个人凭据由已登录的平台账号获取,配置成功后可长期复用。
111
+
112
+ ### 从平台和仓库 URL 获取源码
113
+
114
+ 无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
115
+
116
+ ```bash
117
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台> --json
118
+ pnpm dlx openxiangda@2.2.0 source resolve <仓库URL> --base-url <平台> --json
119
+ pnpm dlx openxiangda@2.2.0 source clone <仓库URL> <新目录> --base-url <平台> --json
120
+ ```
121
+
122
+ 登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
123
+ `appCode/name/repository`,不猜应用代码;clone 使用当前账号配置长期凭据后检出源码,
124
+ 返回 `root/baseUrl/appCode/repository/branch/commit`。`--branch <分支>` 可指定分支,
125
+ 省略时使用 Forgejo 实际默认分支。目标目录必须不存在;失败时保留目录供检查。
126
+ 克隆不会加载应用配置、安装依赖、执行应用脚本或递归拉取子模块,并禁用 checkout hooks
127
+ 和全局过滤器;需要 LFS 内容时在审查后单独处理。
128
+
129
+ 既有 `PLATFORM_ADMIN` 平台管理员映射为共享 Forgejo 管理员,可完整管理已有及以后创建的
130
+ 全部仓库,不受应用创建人或应用成员登记限制。平台应用管理员保持对应仓库管理权限。
131
+ 该规则没有新增平台角色,也不扩展普通应用成员的权限。
132
+
133
+ 正式 API 均位于平台 `/service/openxiangda-api/v2` 下,使用现有登录态:
134
+
135
+ | API | 请求及响应 data |
136
+ | --- | --- |
137
+ | `GET /application-source/resolve?repository=<编码后的仓库URL>` | 接受平台登记的 cloneUrl 或 webUrl,返回 `{ appCode, name, repository }`;不返回凭据 |
138
+ | `POST /application-source/credential` | JSON `{ "repository": "仓库URL" }`,返回 `{ appCode, repository, username, password, name, email }`,响应 `Cache-Control: no-store` |
139
+ | `POST /application-source/administrators/reconcile` | 仅平台管理员;同步已有 PLATFORM_ADMIN 到 Git,返回 `{ synchronized }`,不返回凭据 |
140
+
141
+ 使用 CLI 时无需自行调用凭据 API;支持工具不得记录其响应或另建身份体系。
142
+ 未登记的外部仓库先在原工作区执行 `source setup --import`,再使用平台返回的仓库 URL。
143
+
50
144
  ## 检查与交付 {#delivery}
51
145
 
52
146
  只检查时运行 `pnpm openxiangda check`。需要部署测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建;无需再连续重复运行全部脚本。