openxiangda 1.0.170 → 1.0.172

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/lib/sdd.js CHANGED
@@ -155,6 +155,9 @@ function getSddGovernanceConfig(options = {}) {
155
155
  configured && /strictHighRisk\s*:\s*false/.test(block)
156
156
  ? false
157
157
  : enabled;
158
+ const strictDocumentation = Boolean(
159
+ configured && /strictDocumentation\s*:\s*true/.test(block)
160
+ );
158
161
  const pathMatch = block.match(/path\s*:\s*['"]([^'"]+)['"]/);
159
162
  const schemaMatch = block.match(/schemaVersion\s*:\s*['"]([^'"]+)['"]/);
160
163
  const sddPath = normalizeRelPath(pathMatch?.[1] || DEFAULT_SDD_DIR);
@@ -170,6 +173,7 @@ function getSddGovernanceConfig(options = {}) {
170
173
  return {
171
174
  enabled,
172
175
  strictHighRisk,
176
+ strictDocumentation,
173
177
  path: sddPath,
174
178
  rootDir,
175
179
  initialized,
@@ -196,7 +200,9 @@ function buildDefaultConfigYaml() {
196
200
  ' requireApprovalForHighRisk: true',
197
201
  ' requireAffectedCoverage: true',
198
202
  ' requireCoverageManifest: true',
199
- ' requireScenarioEvidence: true',
203
+ ' requireScenarioEvidence: false',
204
+ ' requireChecklistCompletion: false',
205
+ ' releaseModel: publish-from-main-v1',
200
206
  ' archiveMergesDeltaSpecs: true',
201
207
  '',
202
208
  ].join('\n');
@@ -821,6 +827,146 @@ function proposeSddChange(options = {}) {
821
827
  };
822
828
  }
823
829
 
830
+ function createMainlineSddBundle(options = {}) {
831
+ const governance = getSddGovernanceConfig(options);
832
+ if (!governance.initialized) {
833
+ throw new Error('SDD 尚未初始化,不能创建 mainline bundle');
834
+ }
835
+ const changeId = normalizeChangeId(options.changeId);
836
+ const sourceIds = Array.from(
837
+ new Set(splitList(options.changes || options.changeIds).map(normalizeChangeId))
838
+ ).filter(id => id !== changeId);
839
+ if (sourceIds.length === 0) {
840
+ throw new Error('mainline bundle 至少需要一个 --changes <change-a,change-b>');
841
+ }
842
+ const sourceChanges = sourceIds.map(id =>
843
+ loadSddChange({ cwd: options.cwd, configText: options.configText, changeId: id })
844
+ );
845
+ const invalid = sourceChanges.find(
846
+ item => item.meta.status !== 'approved' || !item.meta.approvedAt
847
+ );
848
+ if (invalid) {
849
+ const error = new Error(
850
+ `SDD_MAINLINE_BUNDLE_CHANGE_NOT_APPROVED: ${invalid.changeId} 尚未 approved`
851
+ );
852
+ error.code = 'SDD_MAINLINE_BUNDLE_CHANGE_NOT_APPROVED';
853
+ throw error;
854
+ }
855
+ const nested = sourceChanges.find(item => item.meta.changeMode === 'mainline-bundle');
856
+ if (nested) {
857
+ throw new Error(`mainline bundle 不允许嵌套: ${nested.changeId}`);
858
+ }
859
+
860
+ const affected = mergeAffectedScopes(
861
+ ...sourceChanges.flatMap(item => [
862
+ item.meta.affected,
863
+ normalizeCoverageResources(item.coverage, {}),
864
+ item.release?.targets,
865
+ { files: item.release?.changedFiles || [] },
866
+ ])
867
+ );
868
+ if (affected.functions.length > 0 || affected.automations.length > 0) {
869
+ affected.backend = true;
870
+ }
871
+ const createdAt = nowIso();
872
+ const meta = {
873
+ ...createChangeMetadata(changeId, {
874
+ title: options.title || `Mainline release: ${sourceIds.join(', ')}`,
875
+ affected,
876
+ changeMode: 'mainline-bundle',
877
+ riskLevel: 'medium',
878
+ }),
879
+ status: 'approved',
880
+ approvedAt: createdAt,
881
+ approvedBy: 'mainline-release-coordinator',
882
+ approvalSummary: `Aggregate approved changes on authoritative mainline: ${sourceIds.join(', ')}`,
883
+ bundledChanges: sourceIds,
884
+ createdAt,
885
+ updatedAt: createdAt,
886
+ };
887
+ const coverage = {
888
+ schemaVersion: SDD_SCHEMA_VERSION,
889
+ changeId,
890
+ generatedAt: createdAt,
891
+ resources: affected,
892
+ requirements: sourceChanges.map(item => ({
893
+ id: `bundle-${slug(item.changeId, 'change')}`,
894
+ title: item.meta.title || item.changeId,
895
+ spec: 'specs/app/spec.md',
896
+ scenarios: [
897
+ {
898
+ id: `${slug(item.changeId, 'change')}-merged`,
899
+ title: `${item.changeId} is included in the mainline release`,
900
+ evidence: [],
901
+ },
902
+ ],
903
+ covers: mergeAffectedScopes(
904
+ item.meta.affected,
905
+ normalizeCoverageResources(item.coverage, {}),
906
+ item.release?.targets,
907
+ { files: item.release?.changedFiles || [] }
908
+ ),
909
+ })),
910
+ };
911
+ const release = {
912
+ schemaVersion: SDD_SCHEMA_VERSION,
913
+ changeId,
914
+ mainlinePolicy: 'publish-from-main-v1',
915
+ bundledChanges: sourceIds,
916
+ targets: {
917
+ forms: affected.forms,
918
+ pages: affected.pages,
919
+ functions: affected.functions,
920
+ automations: affected.automations,
921
+ workflows: affected.workflows,
922
+ jsCodeNodes: affected.jsCodeNodes,
923
+ resources: affected.resources,
924
+ runtime: affected.runtime,
925
+ other: [],
926
+ },
927
+ changedFiles: affected.files,
928
+ commands: [],
929
+ verifiedAt: null,
930
+ };
931
+ const changeDir = path.join(governance.rootDir, 'changes', changeId);
932
+ if (fs.existsSync(changeDir) && !options.force) {
933
+ throw new Error(`change 已存在: ${changeId}`);
934
+ }
935
+ if (fs.existsSync(changeDir)) fs.rmSync(changeDir, { recursive: true, force: true });
936
+ ensureDir(path.join(changeDir, 'specs', 'app'));
937
+ writeJsonFile(path.join(changeDir, CHANGE_META_FILE), meta);
938
+ writeJsonFile(path.join(changeDir, COVERAGE_FILE), coverage);
939
+ writeJsonFile(path.join(changeDir, RELEASE_FILE), release);
940
+ fs.writeFileSync(
941
+ path.join(changeDir, 'proposal.md'),
942
+ `# Mainline Release Bundle: ${changeId}\n\nApproved changes: ${sourceIds.join(', ')}.\n\nThe exact structured scope in coverage.json is authoritative.\n`
943
+ );
944
+ fs.writeFileSync(
945
+ path.join(changeDir, 'design.md'),
946
+ '# Design\n\nAll bundled changes are merged and pushed before one staged, atomic application activation.\n'
947
+ );
948
+ fs.writeFileSync(
949
+ path.join(changeDir, 'tasks.md'),
950
+ '# Tasks\n\n## Implementation\n\n- [x] Aggregate approved changes\n\n## Prepublish\n\n- [x] Generate exact structured release scope\n\n## Postpublish\n\n- [ ] Record publish result\n'
951
+ );
952
+ fs.writeFileSync(
953
+ path.join(changeDir, 'evidence.md'),
954
+ '# Evidence\n\n## Verification\n\n- [x] Source changes were approved before bundling\n\n## Release\n\n- [ ] Record atomic activation result\n'
955
+ );
956
+ fs.writeFileSync(
957
+ path.join(changeDir, 'specs', 'app', 'spec.md'),
958
+ `# Mainline Bundle Spec\n\n## ADDED Requirements\n\n### Requirement: Publish ${sourceIds.length} approved changes together\nThe application SHALL activate the exact bundled scope from one authoritative mainline commit.\n\n#### Scenario: Atomic mainline activation\n- **Given** all bundled changes are approved and merged to the remote default branch\n- **When** the release coordinator publishes bundle ${changeId}\n- **Then** changed child releases activate together and unrelated resources remain unchanged\n`
959
+ );
960
+ return {
961
+ schemaVersion: SDD_SCHEMA_VERSION,
962
+ change: meta,
963
+ coverage,
964
+ release,
965
+ bundledChanges: sourceIds,
966
+ dir: path.relative(options.cwd || process.cwd(), changeDir).replace(/\\/g, '/'),
967
+ };
968
+ }
969
+
824
970
  function loadSddChange(options = {}) {
825
971
  const governance = getSddGovernanceConfig(options);
826
972
  const changeId = normalizeChangeId(options.changeId);
@@ -1527,9 +1673,20 @@ function readUncheckedTasks(changeDir, stagePolicy = getSddVerifyStagePolicy('de
1527
1673
  });
1528
1674
  }
1529
1675
 
1530
- function validateCoverageManifest(coverage, meta, targets, highRiskFiles, legacyMode = false) {
1676
+ function validateCoverageManifest(
1677
+ coverage,
1678
+ meta,
1679
+ targets,
1680
+ highRiskFiles,
1681
+ legacyMode = false,
1682
+ options = {}
1683
+ ) {
1531
1684
  const errors = [];
1532
1685
  const warnings = [];
1686
+ const addDocumentationIssue = issue => {
1687
+ if (options.strictDocumentation) errors.push(issue);
1688
+ else warnings.push(issue);
1689
+ };
1533
1690
  if (!coverage || coverage.__invalid) {
1534
1691
  const message = coverage?.__invalid
1535
1692
  ? `coverage.json 无效: ${coverage.error}`
@@ -1545,15 +1702,24 @@ function validateCoverageManifest(coverage, meta, targets, highRiskFiles, legacy
1545
1702
  });
1546
1703
  }
1547
1704
  if (!Array.isArray(coverage.requirements) || coverage.requirements.length === 0) {
1548
- errors.push({ name: 'sdd-coverage-empty', message: 'coverage.json 必须包含 requirements 映射' });
1705
+ addDocumentationIssue({
1706
+ name: 'sdd-coverage-empty',
1707
+ message: 'coverage.json 建议包含 requirements 映射;streamlined 模式仅以精确资源和文件范围作为发布硬门禁',
1708
+ });
1549
1709
  } else {
1550
1710
  for (const requirement of coverage.requirements) {
1551
1711
  if (!requirement.id || !requirement.title || !requirement.spec) {
1552
- errors.push({ name: 'sdd-coverage-requirement-invalid', message: 'coverage requirement 缺少 id/title/spec' });
1712
+ addDocumentationIssue({
1713
+ name: 'sdd-coverage-requirement-invalid',
1714
+ message: 'coverage requirement 缺少 id/title/spec',
1715
+ });
1553
1716
  break;
1554
1717
  }
1555
1718
  if (!Array.isArray(requirement.scenarios) || requirement.scenarios.length === 0) {
1556
- errors.push({ name: 'sdd-coverage-scenarios-missing', message: `coverage requirement ${requirement.id} 缺少 scenarios` });
1719
+ addDocumentationIssue({
1720
+ name: 'sdd-coverage-scenarios-missing',
1721
+ message: `coverage requirement ${requirement.id} 缺少 scenarios`,
1722
+ });
1557
1723
  break;
1558
1724
  }
1559
1725
  }
@@ -1678,6 +1844,9 @@ function buildUpdatedReleaseManifest(loaded, releasePlan, verificationStage = 'd
1678
1844
  const reviewedCommands = Array.isArray(loaded.release?.commands)
1679
1845
  ? loaded.release.commands.filter(command => String(command || '').trim())
1680
1846
  : [];
1847
+ const canonicalCommands = Array.isArray(releasePlan?.commands)
1848
+ ? releasePlan.commands.filter(command => String(command || '').trim())
1849
+ : [];
1681
1850
  return {
1682
1851
  schemaVersion: SDD_SCHEMA_VERSION,
1683
1852
  changeId: loaded.changeId,
@@ -1685,9 +1854,9 @@ function buildUpdatedReleaseManifest(loaded, releasePlan, verificationStage = 'd
1685
1854
  targets: releasePlan?.targets || loaded.release?.targets || {},
1686
1855
  changedFiles: releasePlan?.changedFiles || loaded.release?.changedFiles || [],
1687
1856
  commands:
1688
- reviewedCommands.length > 0
1689
- ? reviewedCommands
1690
- : releasePlan?.commands || [],
1857
+ canonicalCommands.length > 0
1858
+ ? canonicalCommands
1859
+ : reviewedCommands,
1691
1860
  verifiedAt,
1692
1861
  verificationStage,
1693
1862
  verificationStages: {
@@ -1752,7 +1921,7 @@ function recordSddReleaseSourceRevision(options = {}) {
1752
1921
  ...(loaded.release && !loaded.release.__invalid ? loaded.release : {}),
1753
1922
  schemaVersion: SDD_SCHEMA_VERSION,
1754
1923
  changeId: loaded.changeId,
1755
- mainlinePolicy: 'merge-after-publish-v1',
1924
+ mainlinePolicy: 'publish-from-main-v1',
1756
1925
  releaseSourceRevision,
1757
1926
  };
1758
1927
  if (JSON.stringify(release) !== JSON.stringify(loaded.release)) {
@@ -2079,19 +2248,30 @@ function verifySddChange(options = {}) {
2079
2248
  message: `affected 未覆盖高风险文件: ${fileCoverage.missing.join(', ')}`,
2080
2249
  });
2081
2250
  }
2082
- const coverageValidation = validateCoverageManifest(loaded.coverage, loaded.meta, targets, highRiskFiles, legacyMode);
2251
+ const strictDocumentation = Boolean(loaded.governance.strictDocumentation);
2252
+ const coverageValidation = validateCoverageManifest(
2253
+ loaded.coverage,
2254
+ loaded.meta,
2255
+ targets,
2256
+ highRiskFiles,
2257
+ legacyMode,
2258
+ { strictDocumentation }
2259
+ );
2083
2260
  errors.push(...coverageValidation.errors);
2084
2261
  warnings.push(...coverageValidation.warnings);
2085
2262
  const deltaValidation = validateDeltaSpecs(loaded.changeDir);
2086
- errors.push(...deltaValidation.errors);
2263
+ if (strictDocumentation) errors.push(...deltaValidation.errors);
2264
+ else warnings.push(...deltaValidation.errors);
2087
2265
  warnings.push(...deltaValidation.warnings);
2088
2266
  const uncheckedTasks = readUncheckedTasks(loaded.changeDir, stagePolicy);
2089
2267
  if (uncheckedTasks.length > 0) {
2090
- errors.push({
2268
+ const issue = {
2091
2269
  name: 'sdd-unfinished-tasks',
2092
- message: `${uncheckedTasks.length} 个 tasks.md 任务未完成`,
2270
+ message: `${uncheckedTasks.length} 个 tasks.md 任务未完成;streamlined 模式不阻断主线发布`,
2093
2271
  details: uncheckedTasks,
2094
- });
2272
+ };
2273
+ if (strictDocumentation) errors.push(issue);
2274
+ else warnings.push(issue);
2095
2275
  }
2096
2276
  const evidenceFile = path.join(loaded.changeDir, 'evidence.md');
2097
2277
  const uncheckedEvidence = readUncheckedChecklistForStages(evidenceFile, {
@@ -2100,14 +2280,16 @@ function verifySddChange(options = {}) {
2100
2280
  missingMessage: 'evidence.md 缺失',
2101
2281
  });
2102
2282
  if (uncheckedEvidence.length > 0) {
2103
- errors.push({
2283
+ const issue = {
2104
2284
  name: 'sdd-evidence-incomplete',
2105
- message: `${uncheckedEvidence.length} 个 evidence.md 证据项未完成`,
2285
+ message: `${uncheckedEvidence.length} 个 evidence.md 证据项未完成;streamlined 模式不阻断主线发布`,
2106
2286
  details: uncheckedEvidence,
2107
- });
2287
+ };
2288
+ if (strictDocumentation) errors.push(issue);
2289
+ else warnings.push(issue);
2108
2290
  }
2109
2291
  const release =
2110
- options.writeMetadata && options.releasePlan && loaded.release && !loaded.release.__invalid
2292
+ options.releasePlan && loaded.release && !loaded.release.__invalid
2111
2293
  ? buildUpdatedReleaseManifest(loaded, options.releasePlan, verificationStage)
2112
2294
  : loaded.release;
2113
2295
  if (stagePolicy.requireRelease) {
@@ -2116,6 +2298,7 @@ function verifySddChange(options = {}) {
2116
2298
  warnings.push(...releaseValidation.warnings);
2117
2299
  const reviewedPlannedCommands = loaded.meta?.release?.plannedCommands;
2118
2300
  if (
2301
+ strictDocumentation &&
2119
2302
  !legacyMode &&
2120
2303
  Array.isArray(reviewedPlannedCommands) &&
2121
2304
  reviewedPlannedCommands.length > 0
@@ -2151,10 +2334,7 @@ function verifySddChange(options = {}) {
2151
2334
  release: {
2152
2335
  ...(loaded.meta.release || {}),
2153
2336
  plannedCommands:
2154
- Array.isArray(loaded.meta.release?.plannedCommands) &&
2155
- loaded.meta.release.plannedCommands.length > 0
2156
- ? loaded.meta.release.plannedCommands
2157
- : release?.commands || options.releasePlan?.commands || [],
2337
+ release?.commands || options.releasePlan?.commands || [],
2158
2338
  lastVerifiedAt: verifiedAt,
2159
2339
  },
2160
2340
  verification: {
@@ -2474,6 +2654,7 @@ module.exports = {
2474
2654
  buildSddGovernanceReport,
2475
2655
  buildSddStatus,
2476
2656
  createQuickSddChange,
2657
+ createMainlineSddBundle,
2477
2658
  getSddGovernanceConfig,
2478
2659
  getSddChangeScope,
2479
2660
  getSddVerifyStagePolicy,
@@ -185,13 +185,23 @@ function buildNextSteps(targetDir, installedDependencies, bound, runtime) {
185
185
  steps.push(`openxiangda env --profile ${profileArg}`);
186
186
  steps.push('git switch -c codex/<change>');
187
187
  steps.push('openxiangda sdd propose <change>');
188
+ steps.push('完成开发、测试、approve 并提交 codex/<change>,不要从 feature worktree 发布');
189
+ steps.push(
190
+ 'git switch <main-branch> && git merge --no-ff codex/<change> && git push origin <main-branch>'
191
+ );
192
+ steps.push(
193
+ 'openxiangda sdd bundle <release-change> --changes <change-a,change-b>'
194
+ );
195
+ steps.push(
196
+ 'git add openspec/changes/<release-change> && git commit -m "chore: prepare <release-change>" && git push origin <main-branch>'
197
+ );
188
198
  steps.push(
189
199
  runtime === 'react-spa'
190
- ? `openxiangda runtime deploy --no-activate --change <change> --profile ${profileArg}`
191
- : `openxiangda workspace publish --dry-run --only <exact-codes> --change <change> --profile ${profileArg}`
200
+ ? `openxiangda runtime deploy --no-activate --change <release-change> --profile ${profileArg}`
201
+ : `openxiangda workspace publish --dry-run --only <exact-codes> --change <release-change> --profile ${profileArg}`
192
202
  );
193
203
  steps.push(
194
- `发布后将冻结提交 merge/fast-forward 并 push 到主分支,再运行 openxiangda release end --profile ${profileArg}`
204
+ `原子激活后运行 openxiangda release integration-status --profile ${profileArg},再运行 openxiangda release end --profile ${profileArg}`
195
205
  );
196
206
  return steps;
197
207
  }
@@ -26,7 +26,7 @@ This file is a router and safety card. Read only the one or two subskills select
26
26
  ## Scope before work
27
27
 
28
28
  1. Work from the app workspace root and pass `--profile <name>` to every write or release command.
29
- 2. In a shared or dirty checkout, use an isolated Git worktree/branch. `sdd propose|quick|context` automatically claims the current worktree for `CODEX_THREAD_ID`; if another task owns it, stop and move to a separate worktree instead of taking over its files. A release is not closed until its exact frozen commit has been merged (without squash/rebase) and pushed to the authoritative default branch.
29
+ 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.
30
30
  3. Give the task one stable SDD change id. Use `--change <id>` on context, plan, check, verify, publish, and archive commands when supported.
31
31
  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.
32
32
  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.
@@ -60,9 +60,9 @@ openxiangda sdd quick <change> --kind copy --pages <page> --files <file> --summa
60
60
 
61
61
  Function quick changes require the complete `--risk-json` assessment; Automation, Workflow, schema, permissions, auth/public access, migrations, destructive and cross-resource changes cannot use quick mode. Before release run `--stage prepublish`; use `postpublish` only after promotion and `archive` only when all evidence exists.
62
62
 
63
- Keep development lightweight: `sdd verify --stage implementation` checks scope/contracts but does not block on unfinished task/evidence prose. Plain `sdd verify` means prepublish, not all future stages. Do not manufacture postpublish/archive evidence during coding; the strict gates belong at stage/finalize.
63
+ Keep development lightweight: structured approval plus exact resource/file scope are authoritative. By default, unfinished tasks/evidence/spec prose produces warnings at every stage and does not block release; set `strictDocumentation: true` only for a workspace that intentionally wants prose as a gate. Actual publish argv, mainline identity, CAS, lease, destructive scope, and atomic activation remain hard gates.
64
64
 
65
- `sdd verify/sync` preserves non-empty, reviewed `release.json.commands` and `change.json.release.plannedCommands` independently; it must not regenerate either into broader or directly activating commands. The prepublish gate also validates the actual argv, so a safe recorded plan cannot authorize a direct Backend/Form/Runtime activation. Generated React SPA plans use exact Form bundles, one mixed Backend `--stage-only` command, Runtime `--no-activate`, and Root `app-finalize`, with explicit `--profile` and `--change` on every publish command. Each staged command automatically merges its canonical child entry into `.openxiangda/releases/<change>/staged-resources.json`.
65
+ The CLI regenerates one canonical command set from structured coverage instead of asking agents to maintain two command copies. A single Function uses `resource publish function --only <code>`; qualified selectors are used only when Function and Automation are mixed. Generated React SPA plans use exact Form bundles, one Backend `--stage-only` command, Runtime `--no-activate`, and Root `app-finalize`. Actual argv is validated again at write time.
66
66
 
67
67
  ## Safe release recipes
68
68
 
@@ -78,24 +78,22 @@ openxiangda workspace publish --change <change> --profile <name> --only pages/a,
78
78
  React SPA resources/runtime:
79
79
 
80
80
  ```bash
81
- openxiangda resource validate <type> --only <codes> --profile <name>
82
- openxiangda resource plan <type> --only <codes> --profile <name> --json
83
- openxiangda runtime deploy --change <change> --profile <name> --no-activate --json
84
- openxiangda sdd verify <change> --changed --stage prepublish --profile <name>
85
- openxiangda release begin --change <change> --profile <name>
86
- openxiangda resource publish function,automation --only <qualified-codes> --stage-only --change <change> --profile <name>
87
- openxiangda resource publish form-setting --only <form-codes> --change <change> --profile <name>
88
- openxiangda release app-finalize --change <change> --staged-resources-json <JSON|file> --profile <name>
89
- # Merge/fast-forward the frozen release commit into the remote default branch.
81
+ # In task worktrees: implement, test, commit, then merge/fast-forward and push all approved changes.
82
+ # In the clean main checkout:
83
+ openxiangda sdd bundle <release-change> --changes <change-a,change-b>
84
+ # Commit and push the generated bundle on main, then:
85
+ openxiangda sdd verify <release-change> --changed --stage prepublish --profile <name>
86
+ openxiangda release begin --change <release-change> --profile <name>
87
+ # Execute the canonical exact commands printed by workspace plan/verify.
90
88
  openxiangda release integration-status --profile <name>
91
89
  openxiangda release end --profile <name>
92
90
  ```
93
91
 
94
92
  `resource plan` and publish dry-runs are strictly GET/HEAD-only. `READ_ONLY_AUTH_REQUIRED` means the access token expired; run `openxiangda auth refresh --profile <name>` or log in again before retrying. Never add an automatic refresh POST inside a plan.
95
93
 
96
- `release begin --change` separately freezes the change base and the actual publish source: a clean committed `HEAD` on a named branch that already contains the current authoritative remote default branch. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A worktree based on obsolete source fails with `SOURCE_BASE_DIVERGED`/`RELEASE_SOURCE_BEHIND_MAIN`; a selected remote field changed after the baseline fails with `RESOURCE_FIELD_CONFLICT`. Resolve/merge, rebuild, and re-plan instead of forcing the old release. For a one-time historical catch-up only—approved code already merged to main while live resources come from several older release lineages—an exact non-delete publish may add `--adopt-online-baseline --adoption-reason "..."` under the same change/lease. It adopts the server-frozen heads and keeps lease, remote preflight, staged release, and CAS protection; it rejects `--all`, `--force`, `--prune`, and deletes.
94
+ `release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it.
97
95
 
98
- After promotion, immediately merge or fast-forward the exact frozen publish commit into the authoritative remote default branch and push it. `release integration-status` reports the result; normal `release end` refuses with `RELEASE_MAIN_MERGE_PENDING` until the remote mainline contains that exact SHA, so squash/rebase merges do not count. The authoritative target comes only from the live remote default `main`/`master`; local-only refs, overrides, and lease expiry cannot clear this obligation. Archive the SDD change from the updated mainline.
96
+ Because promotion begins from the already-pushed authoritative mainline, `release integration-status` should pass immediately after activation. Run it, then `release end`; no post-release branch merge is required.
99
97
 
100
98
  `runtime deploy` separately freezes the clean committed `HEAD` as `sourceRevision` together with the current active Runtime parent before any build or upload. Any deploy whose source does not descend from the online Runtime fails with `RUNTIME_SOURCE_BASE_DIVERGED`, including `--no-activate`; this prevents staging an old preview for later activation. `--no-build` does not bypass this guard. Only an intentional audited rollback may use `--allow-runtime-rollback --reason "<at least 8 characters>"`, and that flag never permits dirty or non-Git input. SDD evidence under `openspec/` and generated/state paths do not make Runtime source dirty.
101
99
 
@@ -105,7 +103,7 @@ When a Function or Automation is selected because its TypeScript source changed,
105
103
 
106
104
  An App Function may declare metadata-only top-level `secretRefs: [{ name, required }]` only with `function_v2` + `trusted_node_v2`; source resolves values with `await ctx.secrets.get(name)` and uses `ctx.utils.http` for controlled public HTTPS. Create/rotate values through hidden TTY or `openxiangda secret ... --value-stdin --change <change> --profile <name>`. Never put values in arguments, files, manifests, state, plans, logs, errors, or chat. Secret bindings require `backend_release_v2` and whole-app `atomic_staged_children_v2`; a missing capability is fail-closed and never uses the legacy source PATCH. For whole-app activation use exact-scope `resource publish <type> --only <code> --stage-only`, then pass the returned verified `stagedResource` to `release app-finalize`; an active Backend Release is never labeled staged.
107
105
 
108
- The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing/publishing through the same source directory. Different worktrees may keep developing and validating. After promotion, merge/push the frozen SHA and run normal `release end`; an expired lease still retains reconciliation evidence. Use full resource/runtime publish only when the approved dependency closure is app-wide.
106
+ The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing through the same source directory. Different worktrees may keep developing and validating; only the clean synchronized main checkout publishes. Use full resource/runtime publish only when the approved bundle intentionally covers the whole dependency closure.
109
107
 
110
108
  ## Always
111
109
 
@@ -24,9 +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>`. In a shared checkout, create an isolated Git worktree and named feature branch before editing. `sdd propose|quick|context` claims `.openxiangda/worktree-owner.json` for the current `CODEX_THREAD_ID`; a foreign owner is a hard stop. `.openxiangda/state.json` maps profile-local IDs; it is not a collaboration lock. The task is not complete until the exact frozen publish SHA is merged without squash/rebase and pushed to the authoritative default branch.
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.
28
28
 
29
- SDD is permissive during coding and strict at release: `sdd verify --stage implementation` does not require unfinished checklist/evidence prose, and an omitted stage means prepublish rather than postpublish/archive. Exact command, child scope, and mainline gates apply only when staging or activating writes.
29
+ SDD is streamlined by default: unfinished 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
30
 
31
31
  First-class direct configuration writes (form/page/menu/role/permission/workflow/connector/notification/auth/route/public-access/scope/data-view/storage/settings) also require `--change <id>` or a complete stored session created by `openxiangda release begin --change <id>`. Missing context fails locally with `PUBLISH_CONTEXT_REQUIRED` before any HTTP write. Dry-run, schema planning, validation, query/invoke/export/submit/execution/upload, and other data-plane actions do not acquire a lease. Direct Workflow publish/unpublish/delete additionally requires the frozen `--expected-revision`; never fetch the latest revision merely to replay an older payload.
32
32
 
@@ -87,19 +87,16 @@ Do not call `lowcode-workspace publish-all`, `pnpm publish:all`, `pnpm publish:o
87
87
  Forms, Backend resources, and frontend Runtime are staged children of one Root App release:
88
88
 
89
89
  ```bash
90
- openxiangda resource publish form-setting --only <formCode> --change <change> --profile <name>
91
- openxiangda resource plan <type> --only <codes> --profile <name>
92
- openxiangda runtime deploy --change <change> --profile <name> --no-activate --json
93
- openxiangda sdd verify <change> --changed --stage prepublish --profile <name>
94
- openxiangda release begin --change <change> --profile <name>
95
- openxiangda resource publish function,automation --only function:<code>,automation:<code> --stage-only --change <change> --profile <name>
96
- openxiangda release app-finalize --staged-resources-json .openxiangda/releases/<change>/staged-resources.json --change <change> --profile <name>
97
- # Merge/fast-forward the frozen release SHA to the remote default branch.
90
+ openxiangda sdd bundle <release-change> --changes <change-a,change-b>
91
+ # Commit and push the bundle on main/master.
92
+ openxiangda sdd verify <release-change> --changed --stage prepublish --profile <name>
93
+ openxiangda release begin --change <release-change> --profile <name>
94
+ # Run the canonical exact staged commands emitted by the release plan.
98
95
  openxiangda release integration-status --profile <name>
99
96
  openxiangda release end --profile <name>
100
97
  ```
101
98
 
102
- `runtime deploy --no-activate` uploads an immutable preview release from a clean committed `HEAD`. Before any build/upload, every Runtime deploy acquires the app publish lease and freezes that `sourceRevision` plus the current active Runtime parent. Both staged and activating deploys are rejected when the local source does not descend from the online source revision, including with `--no-build`; an intentional rollback requires `--allow-runtime-rollback --reason "<at least 8 characters>"` and remains auditable. SDD evidence in `openspec/` and generated/state directories are excluded from the source-clean check. `release begin --change` obtains app-level promotion ownership, freezes the actual clean publish `HEAD` independently from the earlier change base, and requires it to contain the current remote default branch. Before the first live write, one preflight validates the complete target set. Long asynchronous publish phases run a 30-second lease heartbeat; `PUBLISH_LEASE_LOST` is a hard stop and no later write may run. Synchronous Runtime builds first extend to the platform maximum TTL and must renew successfully before the next write. Do not move Runtime build ahead of lease/upload-plan: direct-storage releases compile the provider-specific `assetBaseUrl` returned by that protected plan, and silently substituting the gateway URL changes chunk/publicPath behavior. Parallel development remains available in other worktrees. After promotion, merge/fast-forward and push the frozen SHA to the authoritative default branch, check `release integration-status`, then run normal `release end`. The target is frozen from the live remote default `main`/`master`; overrides, local-only refs, and lease expiry cannot clear the pending-mainline evidence.
99
+ `runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. The protected upload plan still runs before the actual build because its provider-specific `assetBaseUrl` is compiled into the bundle. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status` is already satisfied after activation and `release end` does not wait for a later merge.
103
100
 
104
101
  `resource plan`, `resource publish`, and `runtime deploy` report coarse phases on stderr and emit a heartbeat after 15 seconds without completion. JSON stdout stays machine-readable and includes `timings`; use it to identify whether time is spent in plan, preflight, build, upload, write, or activate instead of rerunning an opaque command.
105
102
 
@@ -137,8 +134,8 @@ openxiangda workspace bind --profile dev --app-type APP_XXXX
137
134
  - `RUNTIME_SOURCE_DIRTY` or `RUNTIME_SOURCE_LINEAGE_REQUIRED`: commit all build inputs in an auditable Git repository; generated `dist/` and `.openxiangda/` state do not count, and `--no-build` is not an escape hatch.
138
135
  - `PUBLISH_LEASE_HELD`: another change owns promotion; keep developing/testing locally or wait for its release/expiry. Never bypass it with an unleased write.
139
136
  - `PUBLISH_LEASE_LOST`: heartbeat renewal failed or the lease expired during a long phase; stop immediately, inspect the active head, re-plan, and start a new release. Never replay the remaining writes with a newly fetched revision.
140
- - `RELEASE_SOURCE_DIRTY`, `RELEASE_SOURCE_BEHIND_MAIN`, or `RELEASE_PUBLISH_REVISION_CHANGED`: commit all build inputs, merge the current remote mainline into the feature branch, and restart the release from the tested SHA.
141
- - `RELEASE_MAIN_MERGE_PENDING`: the live release SHA is not yet contained in the authoritative remote default branch. Merge without squash/rebase, push, rerun `release integration-status`, then `release end`.
137
+ - `RELEASE_SOURCE_DIRTY` or `RELEASE_PUBLISH_REVISION_CHANGED`: commit all build inputs and restart from a stable mainline SHA.
138
+ - `RELEASE_SOURCE_MAINLINE_REQUIRED` or `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`: merge approved task commits and the generated bundle into the remote default branch, switch to the synchronized local main/master, then begin the release.
142
139
  - `WORKTREE_OWNED_BY_ANOTHER_TASK`: another Codex task owns this source directory; switch to an isolated worktree. Only force-claim with an explicit reason after confirming the original task ended.
143
140
  - Plan unexpectedly includes unrelated resources: do not publish; correct `--change`/`--only` or dependency closure.
144
141
  - A write updates state: let the CLI use its lock/atomic merge; do not hand-edit state or run conflicting legacy clients.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.170",
3
+ "version": "1.0.172",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n strictDocumentation?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
@@ -41,6 +41,7 @@ interface AppWorkspaceConfig {
41
41
  sdd?: {
42
42
  enabled?: boolean;
43
43
  strictHighRisk?: boolean;
44
+ strictDocumentation?: boolean;
44
45
  path?: string;
45
46
  };
46
47
  };
@@ -41,6 +41,7 @@ interface AppWorkspaceConfig {
41
41
  sdd?: {
42
42
  enabled?: boolean;
43
43
  strictHighRisk?: boolean;
44
+ strictDocumentation?: boolean;
44
45
  path?: string;
45
46
  };
46
47
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";AA4CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
1
+ {"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n strictDocumentation?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";AA6CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}