openxiangda 1.0.176 → 1.0.178

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  function buildTaskStatus(input = {}) {
2
2
  const execution = object(input.execution);
3
+ const directPublish = object(input.directPublish);
3
4
  const steps = Array.isArray(execution?.steps) ? execution.steps : [];
4
5
  const completed = steps.filter(step => step.status === 'completed');
5
6
  const failed = steps.find(step => step.status === 'failed') || null;
@@ -7,12 +8,28 @@ function buildTaskStatus(input = {}) {
7
8
  steps.find(step => ['running', 'write-started'].includes(step.status)) ||
8
9
  null;
9
10
  const pending = steps.filter(step => step.status === 'pending');
10
- const releaseState = execution?.status || 'not-started';
11
+ const releaseState =
12
+ execution?.status ||
13
+ (directPublish?.status === 'completed'
14
+ ? 'direct-publish-completed'
15
+ : 'not-started');
11
16
  const postCommit = object(input.postCommit);
12
17
  const remoteLease = object(input.remoteLease);
13
18
  const taskResult = object(input.taskResult);
14
19
  const integration = object(input.integration);
15
20
  const change = object(input.change);
21
+ const sourceRevision =
22
+ object(input.sourceRevision) ||
23
+ object(directPublish?.sourceRevision) ||
24
+ object(execution?.releaseSourceRevision) ||
25
+ object(execution?.releaseContext?.releaseSourceRevision);
26
+ const changeId =
27
+ input.changeId || change?.id || execution?.changeId || null;
28
+ const leaseBlocked = isRemoteLeaseBlocking({
29
+ remoteLease,
30
+ execution,
31
+ changeId,
32
+ });
16
33
  const phase = inferPhase({
17
34
  releaseState,
18
35
  steps,
@@ -29,14 +46,19 @@ function buildTaskStatus(input = {}) {
29
46
  remoteLease,
30
47
  integration,
31
48
  postCommit,
49
+ leaseBlocked,
32
50
  });
33
51
  const startedAt =
34
52
  execution?.createdAt ||
53
+ directPublish?.completedAt ||
35
54
  change?.createdAt ||
36
55
  taskResult?.recordedAt ||
37
56
  null;
38
57
  const endedAt =
39
58
  execution?.completedAt ||
59
+ (releaseState === 'direct-publish-completed'
60
+ ? directPublish?.completedAt
61
+ : null) ||
40
62
  (phase === 'integration-ready' ? taskResult?.recordedAt : null);
41
63
  const now = Number.isFinite(Number(input.now))
42
64
  ? Number(input.now)
@@ -49,17 +71,18 @@ function buildTaskStatus(input = {}) {
49
71
  running,
50
72
  );
51
73
  const wrotePlatform = Boolean(
52
- execution?.writeAttempted ||
74
+ directPublish?.writeAttempted ||
75
+ execution?.writeAttempted ||
53
76
  execution?.stagedWriteOccurred ||
54
77
  completed.some(step => step.id !== 'lease-and-capture'),
55
78
  );
56
79
  const healthy =
57
- releaseState === 'completed' &&
80
+ ['completed', 'direct-publish-completed'].includes(releaseState) &&
58
81
  (!postCommit || postCommit.status === 'completed');
59
82
 
60
83
  return {
61
84
  schemaVersion: 'openxiangda_task_status_v1',
62
- changeId: input.changeId || change?.id || execution?.changeId || null,
85
+ changeId,
63
86
  phase,
64
87
  healthy,
65
88
  wrotePlatform,
@@ -73,8 +96,8 @@ function buildTaskStatus(input = {}) {
73
96
  },
74
97
  source: {
75
98
  changeStatus: change?.status || null,
76
- taskCommit: taskResult?.commit || null,
77
- taskReady: Boolean(taskResult),
99
+ taskCommit: taskResult?.commit || sourceRevision?.baseCommit || null,
100
+ taskReady: Boolean(taskResult || sourceRevision),
78
101
  integrated: inferIntegrated(integration),
79
102
  },
80
103
  release: {
@@ -86,6 +109,10 @@ function buildTaskStatus(input = {}) {
86
109
  remoteLease?.holder?.changeId ||
87
110
  remoteLease?.holder?.clientSessionId ||
88
111
  remoteLease?.changeId ||
112
+ remoteLease?.clientSessionId ||
113
+ (typeof remoteLease?.holder === 'string'
114
+ ? remoteLease.holder
115
+ : null) ||
89
116
  null,
90
117
  },
91
118
  blocker: blockingLayer
@@ -100,7 +127,7 @@ function buildTaskStatus(input = {}) {
100
127
  message:
101
128
  failed?.error?.message ||
102
129
  failed?.error ||
103
- blockerMessage(blockingLayer),
130
+ blockerMessage(blockingLayer, remoteLease),
104
131
  }
105
132
  : null,
106
133
  nextAction: nextAction({
@@ -111,6 +138,7 @@ function buildTaskStatus(input = {}) {
111
138
  postCommit,
112
139
  taskResult,
113
140
  integration,
141
+ leaseBlocked,
114
142
  }),
115
143
  };
116
144
  }
@@ -124,6 +152,7 @@ function inferPhase(input) {
124
152
  return 'health';
125
153
  }
126
154
  if (input.releaseState === 'completed') return 'healthy';
155
+ if (input.releaseState === 'direct-publish-completed') return 'healthy';
127
156
  if (input.releaseState === 'write-review-required') return 'write-review';
128
157
  if (input.failed) return 'failed';
129
158
  if (input.running) return phaseFromStep(input.running.id);
@@ -163,13 +192,7 @@ function inferBlockingLayer(input) {
163
192
  if (input.postCommit?.status && input.postCommit.status !== 'completed') {
164
193
  return 'post-commit';
165
194
  }
166
- if (
167
- input.remoteLease?.active &&
168
- input.releaseState !== 'running' &&
169
- input.releaseState !== 'staged-resumable'
170
- ) {
171
- return 'lease';
172
- }
195
+ if (input.leaseBlocked) return 'lease';
173
196
  if (input.integration && inferIntegrated(input.integration) === false) {
174
197
  return 'git-mainline';
175
198
  }
@@ -231,13 +254,26 @@ function nextAction(input) {
231
254
  if (input.failed) {
232
255
  return `修复 ${input.failed.id || '失败步骤'} 后重跑同一条 release publish。`;
233
256
  }
234
- if (input.remoteLease?.active && input.releaseState === 'not-started') {
235
- return '等待当前应用发布租约释放;开发与测试可继续并行。';
257
+ if (input.leaseBlocked) {
258
+ const holder =
259
+ input.remoteLease?.changeId ||
260
+ input.remoteLease?.clientSessionId ||
261
+ input.remoteLease?.holder?.changeId ||
262
+ input.remoteLease?.holder?.clientSessionId ||
263
+ input.remoteLease?.holder ||
264
+ '另一发布任务';
265
+ const expiry = input.remoteLease?.expiresAt
266
+ ? `,最晚 ${input.remoteLease.expiresAt} 到期`
267
+ : '';
268
+ return `等待 ${holder} 释放当前应用租约${expiry};本任务尚未写平台,可继续本地开发与测试。`;
236
269
  }
237
270
  if (input.releaseState === 'staged-resumable') {
238
271
  return '重跑同一条 release publish,从已验证的 staged child 继续。';
239
272
  }
240
273
  if (input.releaseState === 'completed') return '执行业务验收并归档 change。';
274
+ if (input.releaseState === 'direct-publish-completed') {
275
+ return '直接发布已完成;执行业务验收并归档 change。';
276
+ }
241
277
  if (input.taskResult && inferIntegrated(input.integration) === false) {
242
278
  return '把 task commit 合并并推送到权威主分支,再创建 mainline bundle。';
243
279
  }
@@ -245,9 +281,11 @@ function nextAction(input) {
245
281
  return '完成实现和聚焦测试,提交后运行 sdd ready。';
246
282
  }
247
283
 
248
- function blockerMessage(layer) {
284
+ function blockerMessage(layer, remoteLease) {
249
285
  const messages = {
250
- lease: '另一个发布任务持有应用租约。',
286
+ lease: remoteLease?.changeId
287
+ ? `发布任务 ${remoteLease.changeId} 持有应用租约。`
288
+ : '另一个发布任务持有应用租约。',
251
289
  'git-mainline': '任务提交尚未完整进入权威主分支。',
252
290
  'post-commit': '发布已激活,提交后动作仍在自动重试。',
253
291
  'write-result': '上次写请求结果不确定,需要只读核对。',
@@ -255,6 +293,31 @@ function blockerMessage(layer) {
255
293
  return messages[layer] || '当前阶段需要处理失败后才能继续。';
256
294
  }
257
295
 
296
+ function isRemoteLeaseBlocking(input = {}) {
297
+ const remote = object(input.remoteLease);
298
+ if (!remote?.active) return false;
299
+ const execution = object(input.execution);
300
+ const context = object(execution?.releaseContext);
301
+ if (
302
+ remote.leaseId &&
303
+ context?.leaseId &&
304
+ String(remote.leaseId) === String(context.leaseId)
305
+ ) {
306
+ return false;
307
+ }
308
+ if (
309
+ remote.clientSessionId &&
310
+ context?.clientSessionId &&
311
+ String(remote.clientSessionId) === String(context.clientSessionId) &&
312
+ (!remote.changeId ||
313
+ !input.changeId ||
314
+ String(remote.changeId) === String(input.changeId))
315
+ ) {
316
+ return false;
317
+ }
318
+ return true;
319
+ }
320
+
258
321
  function object(value) {
259
322
  return value && typeof value === 'object' && !Array.isArray(value)
260
323
  ? value
@@ -10,6 +10,13 @@ OpenXiangda connects an AI coding workspace to the private low-code platform thr
10
10
 
11
11
  This file is a router and safety card. Read only the one or two subskills selected below; do not load every OpenXiangda reference into the same turn.
12
12
 
13
+ ## Keep the agent loop small
14
+
15
+ - Start with one broad CodeGraph exploration that names the complete flow or feature. Use at most one focused follow-up when the first result explicitly omits a required symbol; do not repeat overlapping surveys.
16
+ - Ordinary work uses this router plus one domain subskill. Add a second domain subskill only when the accepted scope genuinely crosses domains; three or more OpenXiangda subskills require stopping and narrowing the task.
17
+ - When the user already supplied concrete requirements and acceptance criteria, record the structured SDD scope and implement. Do not turn a resolved request into a long design essay or ask for a duplicate confirmation.
18
+ - During an unchanged lease/build wait, report the first blocker and then only material state changes. Prefer `openxiangda task status --watch` over repeated narrative updates.
19
+
13
20
  ## Decide the track
14
21
 
15
22
  | Intent | Read next | First action |
@@ -27,7 +34,7 @@ This file is a router and safety card. Read only the one or two subskills select
27
34
  ## Scope before work
28
35
 
29
36
  1. Work from the app workspace root and pass `--profile <name>` to every write or release command.
30
- 2. In a shared or dirty checkout, use an isolated Git worktree/branch for development. Merge approved task commits into the authoritative remote default branch before any live release. Formal publish runs once from a clean local `main`/`master` that exactly equals the remote tip; feature worktrees never publish.
37
+ 2. In a shared app repository, reserve the canonical `main`/`master` checkout for integration and release. Every development task uses an isolated Git worktree/branch from the latest remote mainline; never stash, restore, or overwrite another task's changes to make the canonical checkout publishable. Merge approved task commits into the authoritative remote default branch before any live release. Formal publish runs once from a clean local `main`/`master` that exactly equals the remote tip; feature worktrees never publish.
31
38
  3. Give the task one stable SDD change id. Use `--change <id>` on context, plan, check, verify, publish, and archive commands when supported.
32
39
  4. Plan and publish exact resource codes with `--only <codes>` or `--code <code>`; a type-only or full-resource publish is allowed only when the dependency closure intentionally contains the whole type/application.
33
40
  5. A React SPA page change rebuilds one application runtime. Commit the complete build input first, upload/preview with `runtime deploy --no-activate`, and promote only from a clean merged release head that descends from the online Runtime source revision.
@@ -24,7 +24,9 @@ openxiangda sdd context --change <change> --changed --json
24
24
 
25
25
  Run `openxiangda update check --json` once per substantial task, on a suspected mismatch, or when the cached check is older than one day. If an update is installed, refresh skills once with `openxiangda skill install --force`.
26
26
 
27
- Every write/release command must include `--profile <name>`. Parallel tasks develop in isolated Git worktrees/branches, but do not publish there. Merge approved commits into the authoritative remote default branch, create one `sdd bundle` for the intended changes, commit/push it, and publish once from a clean local `main`/`master` that exactly equals the remote tip.
27
+ Every write/release command must include `--profile <name>`. In a shared app repository the canonical main checkout is integration/release-only; parallel tasks develop in isolated Git worktrees/branches and never stash or restore another task's files. Merge approved commits into the authoritative remote default branch, create one `sdd bundle` for the intended changes, commit/push it, and publish once from a clean local `main`/`master` that exactly equals the remote tip.
28
+
29
+ Keep investigation proportional: one complete CodeGraph survey plus at most one focused follow-up, normally one domain subskill, and no optional prose for a request whose requirements and acceptance criteria are already explicit. Unchanged lease waits should use `task status --watch` and emit only material transitions.
28
30
 
29
31
  SDD is structured-first by default: only `change.json`, `coverage.json`, and `release.json` are created. Generate optional prose with `openxiangda sdd render <change>`; missing checklist/evidence/spec prose is a warning, while approval, exact structured scope, actual argv, mainline identity, child CAS, lease, and atomic activation remain hard gates. Set `strictDocumentation: true` only when prose completion must intentionally block a workspace.
30
32
 
@@ -57,6 +59,8 @@ openxiangda resource publish function --only function_a,function_b --profile <na
57
59
 
58
60
  Selectors apply before manifest parsing, source dependency analysis, and JS_CODE typecheck/build. A scoped Function/Automation plan must touch only the selected targets plus their transitive/shared/ambient dependencies; an unscoped plan intentionally retains full-workspace behavior. Resource commands use the canonical scoped builder packaged with the installed CLI for standard workspaces, so old checked-in builders still receive the optimization; refresh/bootstrap the workspace script only for equivalent manual `pnpm build-js-code` behavior. Nonstandard custom builders remain an explicit compatibility fallback.
59
61
 
62
+ Function/Automation source analysis also extracts statically declared Form filter and order fields. `resource plan` compares them with each bound Form's frozen online schema and reports `formFieldContracts`; publish fails before lease/write when a binding or field is missing. Fix the source or stage the Form schema in the same reviewed release instead of waiting for a production SQL-column error. Dynamic field names remain runtime-validated by the platform and return `FORM_FIELD_NOT_FOUND` as a configuration error.
63
+
60
64
  Source-triggered Function/Automation targets use Backend Release v2 when the platform exposes that capability. One child may mix create, source-only update, and manifest replacement update through explicit per-resource `operation/mode`; the CLI freezes the current Backend Release parent plus Git/change baseline, then runs `prepare -> verify -> activate` or stops verified for `--stage-only`. Activation CAS-checks the entire set and applies all updates in one transaction. `--stage-only` and Secret-bound Function publishing fail closed when Backend Release v2 is unavailable; compatibility fallback is limited to non-staged, non-Secret publishing after an explicit Backend head 404. Existing online bindings, contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged; noops do not advance versions/timestamps. A deliberate whole-definition replacement requires exact `--only/--code` and `--replace-manifest --reason "<why>"`; SDD bypass does not imply replacement authority.
61
65
 
62
66
  Functions with a top-level `secretRefs` field use `backend_release_v2`, including an explicit empty list that removes bindings. This path never falls back to source PATCH. It requires the per-app Secret capability probe to grant `app_function_secrets_v1`, `trusted_node_v2`, `backend_release_v2`, and `atomic_staged_children_v2`; otherwise plan/publish fails closed.
@@ -84,7 +84,7 @@ For a single form edit, still keep the child staged until Root App finalize:
84
84
  openxiangda resource publish form-setting --only <formCode> --change <change> --profile <name>
85
85
  ```
86
86
 
87
- The Form resource bundle carries schema, settings, indexes, data-management, runtime-write, and public-access configuration under one CAS parent. It returns a canonical staged FormRelease and the CLI records it in the change-scoped staged-resources file. `workspace publish --form` is limited to an intentional first-time bootstrap or isolated repair outside a governed multi-resource release; it is not the normal release path.
87
+ The Form resource bundle carries schema, settings, indexes, data-management, runtime-write, public-access configuration, and changed form permission groups under one CAS parent. It returns a canonical staged FormRelease and the CLI records it in the change-scoped staged-resources file. `workspace publish --form` is limited to an intentional first-time bootstrap or isolated repair outside a governed multi-resource release; it is not the normal release path.
88
88
 
89
89
  For Phase 6 React SPA workspaces, `app-workspace.config.ts` should declare
90
90
  `runtimeMode: "react-spa"`. In that mode, `workspace publish --form <code>` is
@@ -110,7 +110,7 @@ Do not use `openxiangda form create` as the normal way to generate a user-facing
110
110
  ## Form Release / CAS Safety
111
111
 
112
112
  - Treat all Form configuration as one immutable release stream. Freeze `revision`, `etag`, and `activeFormReleaseHead` from one snapshot before writing.
113
- - Publish declared schema, packages, settings, indexes, data-management, runtime-write, and public-access configuration through one `resource publish form-setting --only <codes> --change <change>` Form `bundle` mutation. Resource Form bundles are stage-only by default and return immutable `contentHash` plus a canonical `stagedResource` (stable identity is `formUuid`); the CLI merges each entry into `.openxiangda/releases/<change>/staged-resources.json`. Root `app-finalize` atomically switches all child heads plus the App head. Use direct `--activate` only for an intentional form-only repair outside a governed multi-resource release; never use `workspace publish --form` inside an atomic React SPA release.
113
+ - Publish declared schema, packages, settings, indexes, data-management, runtime-write, public-access configuration, and `formPermissionGroups` through one Form `bundle` mutation. `resource publish form-setting,form-permission-group --only <codes> --change <change>` groups changes by `formUuid`, stages one immutable FormRelease per form, and never falls back to direct live permission-group writes. Resource Form bundles return immutable `contentHash` plus a canonical `stagedResource` (stable identity is `formUuid`); the CLI merges each entry into `.openxiangda/releases/<change>/staged-resources.json`. Root `app-finalize` atomically switches all child heads plus the App head. Use direct `--activate` only for an intentional form-only repair outside a governed multi-resource release; never use `workspace publish --form` inside an atomic React SPA release.
114
114
  - Every mutation must carry `If-Match`, `expectedRevision`, `expectedParent`, and a stable artifact ID. Creation starts at revision `0`.
115
115
  - A revision/head conflict is not retryable. Stop with zero later writes, re-plan, and explicitly merge the competing change; never silently adopt the newer live head.
116
116
  - Use `openxiangda form release-head|release-list|release-detail|release-diff` for inspection and `release-activate|release-rollback|release-abort` for controlled recovery. See `docs/openxiangda-form-releases.md` in the OpenXiangda tool repository.
@@ -159,6 +159,14 @@ openxiangda permission form-group-create sales_limited \
159
159
  for real backend read/write field access (`edit`, `readonly`, `hidden`) with
160
160
  `defaultAccess` plus field-ID exceptions.
161
161
 
162
+ For governed application publishing, declare groups under
163
+ `src/resources/formPermissionGroups` and publish them through exact
164
+ `resource publish ... --only ... --change ...` scope. The CLI folds changed
165
+ groups into the owning Form's immutable FormRelease, freezes a permission-group
166
+ parent hash, and keeps live rows unchanged until Root App finalize. The
167
+ low-level `permission form-group-create/update/delete` commands are for
168
+ intentional maintenance and diagnostics, not an atomic multi-resource release.
169
+
162
170
  ## Inspection
163
171
 
164
172
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.176",
3
+ "version": "1.0.178",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -100,6 +100,7 @@
100
100
  "test:publish-lease-heartbeat": "node scripts/publish-lease-heartbeat-smoke.mjs",
101
101
  "test:release-telemetry": "node scripts/release-telemetry-smoke.mjs",
102
102
  "test:release-explain": "node scripts/release-explain-smoke.mjs",
103
+ "test:release-error-classification": "node scripts/release-error-classification-smoke.mjs",
103
104
  "test:task-status": "node scripts/task-status-smoke.mjs",
104
105
  "test:policy": "node scripts/policy-smoke.mjs",
105
106
  "test:release-plan": "node scripts/release-plan-smoke.mjs",
@@ -111,6 +112,7 @@
111
112
  "test:app-release-cli": "node scripts/app-release-cli-smoke.mjs",
112
113
  "test:form-release-cas": "node scripts/form-release-cas-smoke.mjs",
113
114
  "test:source-dependencies": "node scripts/source-dependencies-smoke.mjs",
115
+ "test:form-field-contract": "node scripts/form-field-contract-smoke.mjs",
114
116
  "test:package-runtime-dependencies": "node scripts/package-runtime-dependencies-smoke.mjs",
115
117
  "test:packed-cli": "node scripts/packed-cli-smoke.mjs",
116
118
  "test:runtime-deploy": "node scripts/runtime-deploy-smoke.mjs",
@@ -5,10 +5,11 @@
5
5
 
6
6
  ## 开发原则
7
7
 
8
- - 架构类需求默认只规划、不实现。新应用、复杂页面、登录注册、公开访问、权限数据范围、流程自动化、连接器/通知等需求,先运行 `openxiangda doctor --json` 和 `openxiangda design gates --topic <code> --json`,输出设计并等用户确认;确认前只允许读取、快照、dry-run、提问和写设计文档,不允许改源码、写平台、发布或发送通知。
8
+ - 架构类需求先运行 `openxiangda doctor --json` 和 `openxiangda design gates --topic <code> --json`。只有仍存在会改变实现方向的业务、安全或数据选择时才输出设计并等待确认;用户已经给出具体需求与验收标准时,直接记录结构化 SDD 范围并实现,不再写长篇设计或重复确认。
9
9
  - 先按风险分级:只读/文档/测试为 L0;纯文案样式或单一既有资源绑定等可逆窄改为 L1,可用受限的 `openxiangda sdd quick` 记录精确范围;表单结构、业务函数、自动化/流程、权限、登录/公开访问、数据写入和 runtime/config 为 L2;不可逆、生产迁移或应用级扩权为 L3。L2/L3 必须挂完整 SDD,并由 `coverage.json` 把需求与场景映射到精确的文件、表单、页面及工程资源范围。
10
10
  - 一个开发任务只使用一个显式 change,并从 `openxiangda sdd context --change <change> --changed --json` 开始;默认只维护 change/coverage/release 三份结构化事实源,需要文档时才执行 `openxiangda sdd render <change>`。结构化 approval、资源和文件范围是硬约束,任务/证据/规格文案默认只告警。只有明确配置 `strictDocumentation: true` 才把文案完成度恢复为门禁。
11
- - 多会话开发使用独立 Git worktree/branch,但 feature worktree 不发布。把已批准提交合并并 push 到权威默认主分支后,用 `sdd bundle <release-change> --changes ...` 聚合范围;只从与远端 tip 完全一致的 clean `main`/`master` 一次发布。
11
+ - 多会话开发把规范的 `main`/`master` 检出专用于集成和发布;每个开发任务从最新远端主线使用独立 Git worktree/branch,禁止为了发布 stash/restore 或覆盖其他任务的文件。feature worktree 不发布。把已批准提交合并并 push 到权威默认主分支后,用 `sdd bundle <release-change> --changes ...` 聚合范围;只从与远端 tip 完全一致的 clean `main`/`master` 一次发布。
12
+ - 调查预算默认为一次覆盖完整调用链的 CodeGraph 查询,只有明确缺失符号时再补一次精确查询;普通任务只加载一个领域技能。租约、构建或部署状态未变化时不重复输出相同进度,使用 `openxiangda task status --watch` 等待状态变化。
12
13
  - 账号、角色、权限、数据范围、组织账号、RBAC、查询参数授权需求必须先运行 `openxiangda design gates --topic permissions --json`,选择 `managed-platform-account` / `existing-platform-user-assignment` / `static-role-permission` / `query-param-context`,输出权限矩阵后再实现。
13
14
  - 应用角色只读查询组织账号时声明 `app:organization:read`;创建、修改账号/部门或重置密码时声明 `app:organization:manage`。创建角色、分配成员或维护权限组也必须在角色资源的 `apiPermissionCodes` 声明对应的 `app:role:manage`、`app:page-permission-group:manage`、`app:form-permission-group:manage`。
14
15
  - 默认用户界面保持克制:左侧应用导航、顶部账号信息、首页内容区域。
@@ -8,7 +8,9 @@
8
8
 
9
9
  **所有"发布 / 上线 / 部署 / publish / deploy / ship / release"请求,唯一正确入口是 `openxiangda workspace publish --profile <name>`,不要直接 `pnpm publish:all`。**
10
10
 
11
- **架构类需求默认只规划、不实现。** 新应用、复杂页面、登录注册、公开访问、权限数据范围、流程自动化、连接器/通知等需求,先 `openxiangda doctor --json` + `openxiangda design gates --topic <code> --json`,输出设计并等待用户确认;确认前只允许读取、快照、dry-run、提问和写设计文档,不允许改源码、写平台、发布、部署或发送通知。
11
+ **架构类需求先过设计门。** 新应用、复杂页面、登录注册、公开访问、权限数据范围、流程自动化、连接器/通知等需求,先 `openxiangda doctor --json` + `openxiangda design gates --topic <code> --json`。只有仍存在会改变实现方向的业务、安全或数据选择时才输出设计并等待确认;用户已经给出具体需求与验收标准时,直接记录结构化 SDD 范围并实现,不再写长篇设计或重复确认。
12
+
13
+ 共享应用仓库把规范 `main`/`master` 检出专用于集成和发布;每个开发任务从最新远端主线使用独立 Git worktree/branch,禁止为了发布 stash/restore 或覆盖其他任务文件。调查默认只做一次完整 CodeGraph 查询,明确缺失时最多补一次精确查询;普通任务只加载一个领域技能。等待状态未变化时使用 `openxiangda task status --watch`,不要重复输出相同进度。
12
14
 
13
15
  **按风险分级治理。** 只读/文档/测试为 L0;纯文案样式或单一既有资源绑定等窄小可逆改动为 L1,可用受限的 `openxiangda sdd quick` 记录精确范围;表单结构、业务 Function、Automation/Workflow、权限、登录/公开访问、数据写入和 runtime/config 为 L2;不可逆、生产迁移或应用级扩权为 L3。L2/L3 必须挂完整 SDD,并由 `coverage.json` 把需求与场景映射到精确的文件、表单、页面及工程资源范围。
14
16