sdx-cli 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,8 @@
19
19
  <p align="center">
20
20
  <a href="#install-from-npm">Install from npm</a> •
21
21
  <a href="#one-command-setup">One-Command Setup</a> •
22
+ <a href="#architecture-pack-org--service-deep-dives">Architecture Pack</a> •
23
+ <a href="#canonical-root-readme-generation">Canonical README</a> •
22
24
  <a href="#daily-workflow">Daily Workflow</a> •
23
25
  <a href="#for-codex-agents">For Codex Agents</a> •
24
26
  <a href="#release-process">Release Process</a>
@@ -131,6 +133,8 @@ From your SDX workspace root:
131
133
 
132
134
  ./scripts/sdx contracts extract --map platform-core
133
135
  ./scripts/sdx docs generate --map platform-core
136
+ ./scripts/sdx architecture generate --map platform-core
137
+ ./scripts/sdx docs readme --map platform-core
134
138
  ```
135
139
 
136
140
  For planning and rollout:
@@ -147,6 +151,96 @@ For Codex:
147
151
  ./scripts/sdx codex run implementation-plan --map platform-core --input ./plans/new-service.md
148
152
  ```
149
153
 
154
+ ### Architecture Pack (Org + Service Deep Dives)
155
+ Generate an executive-ready architecture pack from your initialized consumer workspace:
156
+
157
+ ```bash
158
+ # full pack (org-level + per-service docs/diagrams)
159
+ ./scripts/sdx architecture generate --map platform-core
160
+
161
+ # org-level only
162
+ ./scripts/sdx architecture generate --map platform-core --depth org
163
+
164
+ # targeted service rebuild
165
+ ./scripts/sdx architecture generate --map platform-core --service payments-api
166
+
167
+ # explicit validation pass (override integrity + completeness checks)
168
+ ./scripts/sdx architecture validate --map platform-core
169
+ ```
170
+
171
+ Override source of truth:
172
+ - `maps/<map-id>/architecture-overrides.json`
173
+
174
+ Use overrides to:
175
+ - declare hidden or external dependencies,
176
+ - assert missing relationships,
177
+ - suppress incorrect inferred edges,
178
+ - attach service owner/criticality/business context metadata.
179
+
180
+ ### Canonical Root README Generation
181
+ Generate a complete root `README.md` as the canonical onboarding and architecture overview for your org workspace.
182
+
183
+ ```bash
184
+ # generate/update root README.md
185
+ ./scripts/sdx docs readme --map platform-core
186
+
187
+ # write to a different output file
188
+ ./scripts/sdx docs readme --map platform-core --output ARCHITECTURE.md
189
+
190
+ # check mode for CI (non-zero on stale sources, missing sources, or README drift)
191
+ ./scripts/sdx docs readme --map platform-core --check
192
+
193
+ # dry-run preview with unified diff and freshness summary
194
+ ./scripts/sdx docs readme --map platform-core --dry-run
195
+
196
+ # selective sections
197
+ ./scripts/sdx docs readme --map platform-core \
198
+ --include what_is_this_system,architecture_glance,service_catalog \
199
+ --exclude glossary
200
+ ```
201
+
202
+ Supported section IDs (baseline order):
203
+ - `what_is_this_system`
204
+ - `architecture_glance`
205
+ - `service_catalog`
206
+ - `critical_flows`
207
+ - `event_async_topology`
208
+ - `contracts_index`
209
+ - `repository_index`
210
+ - `environments_deployment`
211
+ - `data_stores_boundaries`
212
+ - `security_compliance`
213
+ - `local_dev_contribution`
214
+ - `runbooks_escalation`
215
+ - `adr_index`
216
+ - `glossary`
217
+ - `changelog_metadata`
218
+
219
+ README config file support (first existing file wins):
220
+ - `.sdx/readme.config.json`
221
+ - `.sdx/readme.config.yaml`
222
+ - `.sdx/readme.config.yml`
223
+
224
+ Config capabilities:
225
+ - section toggles (`sections.include`, `sections.exclude`, `sections.enabled`)
226
+ - repo include/exclude filters (`repos.include`, `repos.exclude`)
227
+ - domain grouping (`domainGroups`)
228
+ - owner/team overrides (`ownerTeamOverrides`)
229
+ - diagram behavior (`diagram.autoGenerateMissing`, `diagram.includeC4Links`)
230
+ - custom intro text (`customIntro`)
231
+ - stale threshold override in hours (`staleThresholdHours`, default `72`)
232
+
233
+ Manual content preservation:
234
+ - generated wrappers: `<!-- SDX:SECTION:<id>:START --> ... <!-- SDX:SECTION:<id>:END -->`
235
+ - preserved manual blocks: `<!-- SDX:SECTION:<id>:MANUAL:START --> ... <!-- SDX:SECTION:<id>:MANUAL:END -->`
236
+
237
+ CI automation example:
238
+ - copy [`docs/examples/readme-refresh.yml`](./docs/examples/readme-refresh.yml) into your consumer workspace repo under `.github/workflows/`.
239
+ - set repo/org variables:
240
+ - `SDX_ORG` (required)
241
+ - `SDX_MAP` (optional, defaults to `all-services` in the workflow)
242
+ - the workflow runs `repo sync`, `map build`, `contracts extract`, `docs generate`, and `docs readme`, then opens a PR.
243
+
150
244
  ## Cross-Repo Tech-Lead PRs (Spec-System Native)
151
245
  Use this flow when SDX should create real `CC-*` contract-change PRs in downstream repos that have spec-system initialized.
152
246
 
@@ -204,11 +298,17 @@ Use this minimal runbook when an agent needs architecture context quickly:
204
298
  2. `./scripts/sdx map status <map-id>`
205
299
  3. `./scripts/sdx map build <map-id>`
206
300
  4. `./scripts/sdx contracts extract --map <map-id>`
207
- 5. `./scripts/sdx codex run <task-type> --map <map-id> --input <file>`
301
+ 5. `./scripts/sdx architecture generate --map <map-id>`
302
+ 6. `./scripts/sdx docs readme --map <map-id>`
303
+ 7. `./scripts/sdx codex run <task-type> --map <map-id> --input <file>`
208
304
 
209
305
  Where outputs land:
210
306
  - `maps/<map-id>/service-map.json|md|mmd`
211
307
  - `maps/<map-id>/contracts.json|md`
308
+ - `maps/<map-id>/architecture/model.json|validation.json`
309
+ - `maps/<map-id>/architecture-overrides.json`
310
+ - `docs/architecture/<map-id>/index.md`
311
+ - `docs/architecture/<map-id>/services/*.md`
212
312
  - `codex/context-packs/*.json`
213
313
  - `codex/runs/*.md|json`
214
314
 
@@ -225,8 +325,9 @@ sdx repo add
225
325
  sdx map create|include|exclude|remove-override|status|build
226
326
  sdx prompt
227
327
 
328
+ sdx architecture generate|validate
228
329
  sdx contracts extract
229
- sdx docs generate
330
+ sdx docs generate|readme
230
331
  sdx plan review
231
332
  sdx service propose
232
333
  sdx handoff draft
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const architecture_1 = require("../../lib/architecture");
5
+ const project_1 = require("../../lib/project");
6
+ const workflows_1 = require("../../lib/workflows");
7
+ class ArchitectureGenerateCommand extends core_1.Command {
8
+ static description = 'Generate architecture pack artifacts and diagrams for a map';
9
+ static flags = {
10
+ map: core_1.Flags.string({ required: true, description: 'Map identifier' }),
11
+ depth: core_1.Flags.string({
12
+ required: false,
13
+ options: ['org', 'full'],
14
+ default: 'full',
15
+ description: 'Generation depth: org-only or full (org + per-service packs)',
16
+ }),
17
+ service: core_1.Flags.string({
18
+ required: false,
19
+ description: 'Generate only one service deep-dive (service id/repo name)',
20
+ }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(ArchitectureGenerateCommand);
24
+ if (flags.depth === 'org' && flags.service) {
25
+ throw new Error('Cannot use --service with --depth org. Use --depth full for targeted service generation.');
26
+ }
27
+ const context = (0, project_1.loadProject)(process.cwd());
28
+ const mapArtifacts = (0, workflows_1.buildMapArtifacts)(flags.map, context.db, context.cwd);
29
+ const contractArtifacts = (0, workflows_1.extractContractArtifacts)(flags.map, context.db, context.cwd);
30
+ const docsArtifacts = (0, workflows_1.generateDocsArtifacts)(flags.map, context.db, context.cwd);
31
+ const result = (0, architecture_1.generateArchitecturePack)({
32
+ mapId: flags.map,
33
+ db: context.db,
34
+ cwd: context.cwd,
35
+ depth: flags.depth,
36
+ serviceId: flags.service,
37
+ });
38
+ (0, project_1.recordRun)(context.db, 'architecture_generate', result.validation.valid ? 'ok' : 'error', flags.map, {
39
+ depth: flags.depth,
40
+ service: flags.service,
41
+ generatedServices: result.generatedServices.length,
42
+ validation: result.validation,
43
+ modelPath: result.modelPath,
44
+ indexDocPath: result.indexDocPath,
45
+ baseline: {
46
+ mapArtifacts,
47
+ contractArtifacts,
48
+ docsArtifacts,
49
+ },
50
+ });
51
+ context.db.close();
52
+ this.log(`Generated architecture pack for map '${flags.map}'.`);
53
+ this.log(`Model: ${result.modelPath}`);
54
+ this.log(`Overrides: ${result.overridesPath}`);
55
+ this.log(`Baseline service map: ${result.baselineArtifacts.serviceMapPath}`);
56
+ this.log(`Baseline contracts: ${result.baselineArtifacts.contractsPath}`);
57
+ this.log(`Baseline architecture doc: ${result.baselineArtifacts.architectureDocPath}`);
58
+ if (result.indexDocPath) {
59
+ this.log(`Architecture index: ${result.indexDocPath}`);
60
+ }
61
+ if (result.generatedServices.length > 0) {
62
+ this.log(`Service deep dives: ${result.generatedServices.length}`);
63
+ }
64
+ this.log(`Validation: ${result.validation.valid ? 'pass' : 'fail'} (errors=${result.validation.errors.length}, warnings=${result.validation.warnings.length})`);
65
+ if (!result.validation.valid) {
66
+ this.error('Architecture validation failed. Resolve override/model issues and rerun.', { exit: 1 });
67
+ }
68
+ }
69
+ }
70
+ exports.default = ArchitectureGenerateCommand;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_path_1 = __importDefault(require("node:path"));
7
+ const core_1 = require("@oclif/core");
8
+ const architecture_1 = require("../../lib/architecture");
9
+ const fs_1 = require("../../lib/fs");
10
+ const project_1 = require("../../lib/project");
11
+ class ArchitectureValidateCommand extends core_1.Command {
12
+ static description = 'Validate architecture model completeness and override integrity for a map';
13
+ static flags = {
14
+ map: core_1.Flags.string({ required: true, description: 'Map identifier' }),
15
+ };
16
+ async run() {
17
+ const { flags } = await this.parse(ArchitectureValidateCommand);
18
+ const context = (0, project_1.loadProject)(process.cwd());
19
+ const result = (0, architecture_1.validateArchitecture)({
20
+ mapId: flags.map,
21
+ db: context.db,
22
+ cwd: context.cwd,
23
+ });
24
+ const outDir = node_path_1.default.join(context.cwd, 'maps', flags.map, 'architecture');
25
+ const jsonPath = node_path_1.default.join(outDir, 'validation.json');
26
+ const mdPath = node_path_1.default.join(outDir, 'validation.md');
27
+ (0, fs_1.writeJsonFile)(jsonPath, result);
28
+ const lines = [
29
+ `# Architecture Validation: ${flags.map}`,
30
+ '',
31
+ `- Generated: ${result.generatedAt}`,
32
+ `- Valid: ${result.valid ? 'yes' : 'no'}`,
33
+ `- Errors: ${result.errors.length}`,
34
+ `- Warnings: ${result.warnings.length}`,
35
+ '',
36
+ ];
37
+ if (result.errors.length > 0) {
38
+ lines.push('## Errors');
39
+ lines.push('');
40
+ for (const err of result.errors) {
41
+ lines.push(`- ${err}`);
42
+ }
43
+ lines.push('');
44
+ }
45
+ if (result.warnings.length > 0) {
46
+ lines.push('## Warnings');
47
+ lines.push('');
48
+ for (const warning of result.warnings) {
49
+ lines.push(`- ${warning}`);
50
+ }
51
+ lines.push('');
52
+ }
53
+ (0, fs_1.writeTextFile)(mdPath, `${lines.join('\n')}\n`);
54
+ (0, project_1.recordRun)(context.db, 'architecture_validate', result.valid ? 'ok' : 'error', flags.map, {
55
+ validationPath: jsonPath,
56
+ errorCount: result.errors.length,
57
+ warningCount: result.warnings.length,
58
+ stats: result.stats,
59
+ });
60
+ context.db.close();
61
+ this.log(`Validated architecture for map '${flags.map}'.`);
62
+ this.log(`JSON: ${jsonPath}`);
63
+ this.log(`Markdown: ${mdPath}`);
64
+ this.log(`Result: ${result.valid ? 'pass' : 'fail'}`);
65
+ if (!result.valid) {
66
+ this.error('Architecture validation failed. Resolve errors and rerun.', { exit: 1 });
67
+ }
68
+ }
69
+ }
70
+ exports.default = ArchitectureValidateCommand;
@@ -70,6 +70,7 @@ class BootstrapQuickCommand extends core_1.Command {
70
70
  this.log('- ./scripts/sdx map create all-services --org <org>');
71
71
  this.log('- ./scripts/sdx map build all-services');
72
72
  }
73
+ this.log('- ./scripts/sdx architecture generate --map all-services');
73
74
  if (result.warnings.length > 0) {
74
75
  this.log('');
75
76
  this.log('Warnings:');
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const readme_1 = require("../../lib/readme");
5
+ const project_1 = require("../../lib/project");
6
+ class DocsReadmeCommand extends core_1.Command {
7
+ static description = 'Generate or validate the canonical root README from SDX artifacts';
8
+ static flags = {
9
+ map: core_1.Flags.string({ required: true, description: 'Map identifier' }),
10
+ output: core_1.Flags.string({ required: false, default: 'README.md', description: 'README output path' }),
11
+ check: core_1.Flags.boolean({ required: false, default: false, description: 'Check mode (no writes, non-zero on stale/missing/diff)' }),
12
+ 'dry-run': core_1.Flags.boolean({ required: false, default: false, description: 'Preview mode (no writes, print unified diff + summary)' }),
13
+ include: core_1.Flags.string({
14
+ required: false,
15
+ description: 'Comma-separated section IDs to include (baseline order preserved)',
16
+ }),
17
+ exclude: core_1.Flags.string({
18
+ required: false,
19
+ description: 'Comma-separated section IDs to exclude (applied after include; exclude wins)',
20
+ }),
21
+ };
22
+ async run() {
23
+ const { flags } = await this.parse(DocsReadmeCommand);
24
+ const context = (0, project_1.loadProject)(process.cwd());
25
+ const includeSections = (0, readme_1.parseReadmeSectionList)(flags.include);
26
+ const excludeSections = (0, readme_1.parseReadmeSectionList)(flags.exclude);
27
+ const result = (0, readme_1.generateReadme)({
28
+ mapId: flags.map,
29
+ db: context.db,
30
+ cwd: context.cwd,
31
+ output: flags.output,
32
+ includeSections,
33
+ excludeSections,
34
+ check: flags.check,
35
+ dryRun: flags['dry-run'],
36
+ });
37
+ const status = result.checkPassed ? 'ok' : 'error';
38
+ (0, project_1.recordRun)(context.db, 'docs_readme', status, flags.map, {
39
+ outputPath: result.outputPath,
40
+ sections: result.sections,
41
+ changed: result.changed,
42
+ stale: result.stale,
43
+ staleSources: result.staleSources.map((source) => source.label),
44
+ missingSources: result.missingSources.map((source) => source.label),
45
+ dryRun: flags['dry-run'],
46
+ check: flags.check,
47
+ });
48
+ context.db.close();
49
+ this.log(result.summary);
50
+ if (result.diff && result.diff.trim().length > 0) {
51
+ this.log('');
52
+ this.log(result.diff.trimEnd());
53
+ }
54
+ if (flags.check && !result.checkPassed) {
55
+ this.error('README check failed: stale/missing sources or content drift detected.', { exit: 1 });
56
+ }
57
+ if (!flags.check && !flags['dry-run']) {
58
+ this.log(`Wrote README: ${result.outputPath}`);
59
+ }
60
+ }
61
+ }
62
+ exports.default = DocsReadmeCommand;
@@ -8,6 +8,19 @@ const node_path_1 = __importDefault(require("node:path"));
8
8
  const core_1 = require("@oclif/core");
9
9
  const fs_1 = require("../../lib/fs");
10
10
  const project_1 = require("../../lib/project");
11
+ function copyRecursive(source, target) {
12
+ const stat = node_fs_1.default.statSync(source);
13
+ if (stat.isDirectory()) {
14
+ (0, fs_1.ensureDir)(target);
15
+ const entries = node_fs_1.default.readdirSync(source, { withFileTypes: true });
16
+ for (const entry of entries) {
17
+ copyRecursive(node_path_1.default.join(source, entry.name), node_path_1.default.join(target, entry.name));
18
+ }
19
+ return;
20
+ }
21
+ const body = node_fs_1.default.readFileSync(source, 'utf8');
22
+ (0, fs_1.writeTextFile)(target, body);
23
+ }
11
24
  class PublishWikiCommand extends core_1.Command {
12
25
  static description = 'Export docs-first artifacts to a wiki-friendly directory';
13
26
  static flags = {
@@ -21,6 +34,9 @@ class PublishWikiCommand extends core_1.Command {
21
34
  node_path_1.default.join(context.cwd, 'maps', flags.map, 'contracts.md'),
22
35
  node_path_1.default.join(context.cwd, 'docs', 'architecture', `${flags.map}.md`),
23
36
  ];
37
+ const sourceDirs = [
38
+ node_path_1.default.join(context.cwd, 'docs', 'architecture', flags.map),
39
+ ];
24
40
  const wikiDir = node_path_1.default.join(context.cwd, 'wiki-export', flags.map);
25
41
  (0, fs_1.ensureDir)(wikiDir);
26
42
  for (const source of sourceFiles) {
@@ -28,8 +44,14 @@ class PublishWikiCommand extends core_1.Command {
28
44
  continue;
29
45
  }
30
46
  const target = node_path_1.default.join(wikiDir, node_path_1.default.basename(source));
31
- const body = node_fs_1.default.readFileSync(source, 'utf8');
32
- (0, fs_1.writeTextFile)(target, body);
47
+ copyRecursive(source, target);
48
+ }
49
+ for (const source of sourceDirs) {
50
+ if (!node_fs_1.default.existsSync(source)) {
51
+ continue;
52
+ }
53
+ const target = node_path_1.default.join(wikiDir, node_path_1.default.basename(source));
54
+ copyRecursive(source, target);
33
55
  }
34
56
  (0, project_1.recordRun)(context.db, 'publish_wiki', 'ok', flags.map, { wikiDir });
35
57
  context.db.close();
@@ -29,7 +29,8 @@ class StatusCommand extends core_1.Command {
29
29
  const mapDir = node_path_1.default.join(mapsDir, mapId);
30
30
  const hasScope = (0, fs_1.fileExists)(node_path_1.default.join(mapDir, 'scope.json'));
31
31
  const hasServiceMap = (0, fs_1.fileExists)(node_path_1.default.join(mapDir, 'service-map.json'));
32
- this.log(`- ${mapId}: scope=${hasScope ? 'yes' : 'no'}, service-map=${hasServiceMap ? 'yes' : 'no'}`);
32
+ const hasArchitectureModel = (0, fs_1.fileExists)(node_path_1.default.join(mapDir, 'architecture', 'model.json'));
33
+ this.log(`- ${mapId}: scope=${hasScope ? 'yes' : 'no'}, service-map=${hasServiceMap ? 'yes' : 'no'}, architecture=${hasArchitectureModel ? 'yes' : 'no'}`);
33
34
  }
34
35
  context.db.close();
35
36
  }