@descix/cli 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/agent-assets/guides/README.md +1 -1
  3. package/agent-assets/guides/dynamic-site-pattern.md +7 -16
  4. package/agent-assets/instructions/app-asset-validation.md +1 -1
  5. package/agent-assets/reference/architecture.md +20 -25
  6. package/agent-assets/reference/local-dev.md +1 -6
  7. package/agent-assets/reference/mcp-integration.md +15 -9
  8. package/agent-assets/reference/platform-api-consumer.md +11 -5
  9. package/agent-assets/reference/workspace-config.md +2 -2
  10. package/bin/descix.js +211 -688
  11. package/lib/command-visibility.js +213 -0
  12. package/lib/commands/corpus.js +2 -5
  13. package/lib/commands/kb.js +2 -2
  14. package/lib/commands/media-upload.js +176 -0
  15. package/lib/commands/retired-kb-sync.js +5 -27
  16. package/lib/commands/status.js +0 -9
  17. package/lib/output.js +31 -0
  18. package/lib/sdk-assets.js +28 -0
  19. package/lib/verb-invokes.generated.json +233 -0
  20. package/lib/workspace-config.js +9 -44
  21. package/package.json +6 -2
  22. package/templates/agent-claude.md +1 -3
  23. package/templates/agent-cline.md +1 -2
  24. package/templates/agent-copilot.md +1 -2
  25. package/templates/agent-cursor.md +1 -2
  26. package/templates/scaffolds/microservice/SERVICE_README_sdk.md +1 -1
  27. package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +2 -3
  28. package/agent-assets/rules/descix_mcp.template.mdc +0 -243
  29. package/lib/commands/briefer/errors.js +0 -88
  30. package/lib/commands/briefer/index.js +0 -322
  31. package/lib/commands/briefer/sources/canonical-sources.js +0 -129
  32. package/lib/commands/briefer/sources/entitlements.js +0 -220
  33. package/lib/commands/briefer/sources/environments.js +0 -205
  34. package/lib/commands/briefer/sources/identifiers.js +0 -214
  35. package/lib/commands/briefer/sources/microservice-deploy.js +0 -267
  36. package/lib/commands/briefer/sources/routing.js +0 -275
  37. package/lib/commands/briefer/sources/what-is-not.js +0 -223
  38. package/lib/commands/briefer/stitcher.js +0 -134
  39. package/lib/commands/briefer/util/source-reader.js +0 -502
  40. package/lib/commands/mcp.js +0 -273
  41. package/lib/commands/update.js +0 -581
  42. package/lib/wizard/setup.js +0 -585
  43. package/templates/descix_mcp.mdc +0 -122
@@ -1,214 +0,0 @@
1
- /**
2
- * §1 — Identifiers & invariants extractor (M2 implementation).
3
- *
4
- * Per WS-DESCIX-BRIEFER-CLI scope doc §2.2 §1, this extractor reads:
5
- * - Domain pattern from provision-platform-lb.js:120 (host construction)
6
- * - GCS path pattern from provision-platform-lb.js:123 (pathPrefix)
7
- * - product_id === app_id invariant from hydrateUtils.js
8
- * - Agentic-app / communities-as-apps invariant from the BEAST CEO record
9
- * `apps/unk-beast/kb/Org/agentic-app-invariant-2026-05-26.jsonl`
10
- * - the AI (model, thinking) precedence chain from geminiInteractions.js::resolveModelThinkingPair()
11
- *
12
- * Extraction method: regex+checksum-of-surrounding-lines (NOT AST).
13
- * Per scope §9.1 third-order question, decision documented 2026-05-26: regex
14
- * keeps the CLI dep-light; AST may be added per-extractor later if needed.
15
- *
16
- * HARD-FAIL behavior:
17
- * - Missing source file → BRIEFER-SRC-NOT-FOUND
18
- * - Regex pattern doesn't match → BRIEFER-SRC-NOT-FOUND
19
- * - Pattern matches outside expected line range → BRIEFER-PARSE-FAIL
20
- * - BEAST agentic-app record missing → BRIEFER-SRC-NOT-FOUND (a hard
21
- * invariant — this is the CEO-broadcast canonical record)
22
- */
23
- import {
24
- readSourceFile,
25
- findInLines,
26
- makeCitation,
27
- sliceRange
28
- } from '../util/source-reader.js';
29
- import { BrieferExtractorError, BRIEFER_ERROR_CODES } from '../errors.js';
30
- // The (model, thinking) precedence chain in words, from its ONE owner. The briefer
31
- // QUOTES the served contract rather than paraphrasing it — a briefer paraphrase of a
32
- // chain is exactly how the old five-step list outlived the code it described.
33
- import { MODEL_THINKING_CHAIN } from '@descix/platform-api/mcp-tools';
34
-
35
- export const SECTION = {
36
- number: 1,
37
- heading: '1. Identifiers & invariants',
38
- sourceFiles: [
39
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js',
40
- 'DeSciX/DeSciX_Core/descix-platform-api/src/entitlements/index.js',
41
- 'DeSciX/DeSciX_Cloud/microservice/services/geminiInteractions.js',
42
- 'apps/unk-beast/kb/Org/agentic-app-invariant-2026-05-26.jsonl'
43
- ]
44
- };
45
-
46
- const MESH_FILE = 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js';
47
- const HYDRATE_FILE = 'DeSciX/DeSciX_Core/descix-platform-api/src/entitlements/index.js';
48
- const GEMINI_FILE = 'DeSciX/DeSciX_Cloud/microservice/services/geminiInteractions.js';
49
- const AGENTIC_RECORD = 'apps/unk-beast/kb/Org/agentic-app-invariant-2026-05-26.jsonl';
50
-
51
- export async function extract({ env, cliPaths } = {}) {
52
- if (!env) {
53
- throw new BrieferExtractorError({
54
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
55
- section: `§${SECTION.number} ${SECTION.heading}`,
56
- source: 'extractor argument',
57
- expected: 'env (dev|demo|prod) provided by caller',
58
- recovery: 'Pass {env} from the briefer entry point.'
59
- });
60
- }
61
- if (!cliPaths) {
62
- throw new BrieferExtractorError({
63
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
64
- section: `§${SECTION.number} ${SECTION.heading}`,
65
- source: 'extractor argument',
66
- expected: 'cliPaths provided by caller',
67
- recovery: 'Pass {cliPaths} from the briefer entry point.'
68
- });
69
- }
70
-
71
- // ── Source 1: provision-platform-lb.js — domain + gcsPath patterns ──
72
- const mesh = await readSourceFile({
73
- cliPaths,
74
- relPath: MESH_FILE,
75
- section: `§${SECTION.number} ${SECTION.heading}`
76
- });
77
-
78
- // Host pattern: canonical line shifted to L214 after WS-DESCIX-SINGLETON-PROVISIONER
79
- // (CEO-D-2026-05-26-SINGLETON-PROVISIONER-UNIFIED, 2026-05-26) added the
80
- // SINGLETON_APPS branch + buildSingletonMatcher helper near top of file. The
81
- // line is the canonical UNIFORM host formula (which a singleton override then
82
- // shadows via `effectiveHost` for daita) — briefer extractor still keys on
83
- // this exact construct as the platform routing invariant. Range expanded to
84
- // 100-350 to tolerate NEG helpers + matcher builders in provision-platform-lb.js.
85
- const hostMatch = findInLines({
86
- lines: mesh.lines,
87
- regex: /const\s+host\s*=\s*env\s*===\s*['"]prod['"]\s*\?/,
88
- section: `§${SECTION.number} ${SECTION.heading}`,
89
- source: `${MESH_FILE}:214 (uniform host pattern; CEO-D-2026-05-26-LB-ROUTING-UNIFORM)`,
90
- expected: `const host = env === 'prod' ? ... : ... construct`,
91
- recovery: `Re-locate the host construct in ${MESH_FILE} and update sources/identifiers.js.`,
92
- expectedRange: [100, 350]
93
- });
94
-
95
- // gcsPath pattern: canonical line shifted to L223 after WS-DESCIX-SINGLETON-PROVISIONER.
96
- // This is the non-singleton (uniform) pathPrefix; the singleton override
97
- // (pathPrefixSingletonOverride) is a few lines below. Range matches hostMatch.
98
- const gcsMatch = findInLines({
99
- lines: mesh.lines,
100
- regex: /const\s+pathPrefix\s*=\s*`\/\$\{env\}\/\$\{app\}\/site`/,
101
- section: `§${SECTION.number} ${SECTION.heading}`,
102
- source: `${MESH_FILE}:223 (uniform gcsPath pattern; singleton override at ~L228)`,
103
- expected: 'const pathPrefix = `/${env}/${app}/site` (template literal)',
104
- recovery: `Re-locate the gcsPath construct in ${MESH_FILE}.`,
105
- expectedRange: [100, 350]
106
- });
107
-
108
- // ── Source 2: hydrateUtils.js — product_id===app_id invariant ──
109
- const hydrate = await readSourceFile({
110
- cliPaths,
111
- relPath: HYDRATE_FILE,
112
- section: `§${SECTION.number} ${SECTION.heading}`
113
- });
114
-
115
- const hydrateMatch = findInLines({
116
- lines: hydrate.lines,
117
- regex: /hydrateCommunityIdFromProducts/,
118
- section: `§${SECTION.number} ${SECTION.heading}`,
119
- source: `${HYDRATE_FILE} (hydrateCommunityIdFromProducts definition)`,
120
- expected: 'hydrateCommunityIdFromProducts function declaration',
121
- recovery: `Verify hydrateCommunityIdFromProducts is still exported from ${HYDRATE_FILE}.`
122
- });
123
-
124
- // ── Source 3: geminiInteractions.js — the (model, thinking) pair owner ──
125
- const gemini = await readSourceFile({
126
- cliPaths,
127
- relPath: GEMINI_FILE,
128
- section: `§${SECTION.number} ${SECTION.heading}`
129
- });
130
-
131
- const resolverMatch = findInLines({
132
- lines: gemini.lines,
133
- regex: /export\s+function\s+resolveModelThinkingPair\b/,
134
- section: `§${SECTION.number} ${SECTION.heading}`,
135
- source: `${GEMINI_FILE} (resolveModelThinkingPair)`,
136
- expected: 'export function resolveModelThinkingPair(...)',
137
- recovery: `resolveModelThinkingPair has been moved or renamed. Re-locate and update the extractor.`
138
- });
139
- // Capture the resolver body — from the export to the next closing brace at
140
- // column 0. We'll scan forward a bounded window and slice for the briefer.
141
- const resolverEnd = findResolverEnd(gemini.lines, resolverMatch.lineNumber);
142
- const resolverBody = sliceRange(gemini.lines, resolverMatch.lineNumber, resolverEnd);
143
-
144
- // ── Source 4: BEAST CEO record — agentic-app invariant ──
145
- const beast = await readSourceFile({
146
- cliPaths,
147
- relPath: AGENTIC_RECORD,
148
- section: `§${SECTION.number} ${SECTION.heading}`
149
- });
150
- // The record is JSONL; the canonical claim is one of the question/answer
151
- // chunks. We don't parse the JSONL — we just verify the record EXISTS and
152
- // SHA the whole file for citation purposes. The CEO broadcast title is the
153
- // contract; the briefer prose embeds it verbatim.
154
- const beastSha = makeCitation({
155
- file: AGENTIC_RECORD,
156
- lines: `1-${beast.lines.length}`,
157
- anchor: 'AGENTIC-APP-INHERITANCE-INVARIANT-2026-05-26',
158
- fileLines: beast.lines
159
- }).sha;
160
-
161
- // ── Render the briefer markdown ──
162
- const hostLine = mesh.lines[hostMatch.lineNumber - 1].trim();
163
- const gcsLine = mesh.lines[gcsMatch.lineNumber - 1].trim();
164
-
165
- const markdown = [
166
- `- **Apps are the central unit. Everything is an agentic app.** Communities are themselves agentic apps that share IP with other apps in the same community. The platform is fundamentally an agentic-app store, not a generic SaaS platform with apps bolted on. — \`${AGENTIC_RECORD}\` (CEO broadcast \`AGENTIC-APP-INHERITANCE-INVARIANT-2026-05-26\`, sha=\`${beastSha}\`)`,
167
- `- \`app_id\` is a globally unique slug (e.g., \`daita\`, \`powch\`, \`egpt\`). Same value across DEV, DEMO, PROD. Never contains the environment.`,
168
- `- \`community_id\` is operational metadata stored INSIDE \`Products/{app_id}\`. Never parsed from URL. Never trusted from client.`,
169
- `- \`product_id === app_id\` — the same opaque slug. Server re-derives \`community_id\` from \`Products/{app_id}\` via \`hydrateCommunityIdFromProducts()\`. — \`${HYDRATE_FILE}:${hydrateMatch.lineNumber}\``,
170
- `- Domain pattern: \`{app_id}.{env}.descix.net\` for DEV/DEMO; \`{app_id}.descix.net\` for PROD. — \`${MESH_FILE}:${hostMatch.lineNumber}\``,
171
- ' ```js',
172
- ` ${hostLine}`,
173
- ' ```',
174
- `- GCS path: \`{env}/{app_id}/site/\`. Flat — no community in the path. — \`${MESH_FILE}:${gcsMatch.lineNumber}\``,
175
- ' ```js',
176
- ` ${gcsLine}`,
177
- ' ```',
178
- `- Wildcard TLS cert: \`*.descix.net\` plus per-env wildcards. ONE cert. No per-app cert provisioning.`,
179
- `- **AI (model, thinking) precedence.** ${MODEL_THINKING_CHAIN} Owner: \`resolveModelThinkingPair()\` in \`${GEMINI_FILE}:${resolverMatch.lineNumber}\` — the ONE resolver; the model chain and the thinking chain used to be separate and could assemble a pair no config declared.`,
180
- ' ```js',
181
- indentBlock(resolverBody, ' '),
182
- ' ```'
183
- ].join('\n');
184
-
185
- const citations = [
186
- makeCitation({ file: MESH_FILE, lines: String(hostMatch.lineNumber), anchor: 'host pattern', fileLines: mesh.lines }),
187
- makeCitation({ file: MESH_FILE, lines: String(gcsMatch.lineNumber), anchor: 'gcsPath pattern', fileLines: mesh.lines }),
188
- makeCitation({ file: HYDRATE_FILE, lines: String(hydrateMatch.lineNumber), anchor: 'hydrateCommunityIdFromProducts', fileLines: hydrate.lines }),
189
- makeCitation({ file: GEMINI_FILE, lines: `${resolverMatch.lineNumber}-${resolverEnd}`, anchor: 'resolveModelThinkingPair', fileLines: gemini.lines }),
190
- { file: AGENTIC_RECORD, lines: `1-${beast.lines.length}`, anchor: 'AGENTIC-APP-INHERITANCE-INVARIANT-2026-05-26', sha: beastSha }
191
- ];
192
-
193
- return { markdown, citations };
194
- }
195
-
196
- /**
197
- * Walk forward from the `export function resolveModelThinkingPair` line until we hit
198
- * a closing brace at column 0. Bounded scan window of 80 lines so a runaway
199
- * regex on a refactored file doesn't drag the whole file into the briefer.
200
- */
201
- function findResolverEnd(lines, startLine) {
202
- const MAX = Math.min(lines.length, startLine + 80);
203
- for (let i = startLine; i < MAX; i++) {
204
- if (lines[i] === '}') return i + 1;
205
- }
206
- // If we don't find a clean close, fall back to startLine+30 — we want some
207
- // body in the briefer even if the resolver was reformatted; --check will
208
- // catch the SHA change.
209
- return Math.min(lines.length, startLine + 30);
210
- }
211
-
212
- function indentBlock(block, indent) {
213
- return block.split('\n').map(l => indent + l).join('\n');
214
- }
@@ -1,267 +0,0 @@
1
- /**
2
- * §4 — Microservice deploy extractor (M2 implementation).
3
- *
4
- * Per-app microservice deploy (broker-first model), CONSOLIDATED onto a
5
- * single canonical path for both apps (WS-DEPLOY-HARDENING item 6 —
6
- * Powch's divergent `microservice/scripts/deploy.sh` is now a hard-fail
7
- * deprecation stub, not a second deploy implementation):
8
- * 1. `gcloud run deploy {app}-{env}` — deploy-service-env.sh → lib/cloud-run-deploy.js
9
- * 2. `descix microservice register` — Firestore manifest (or self-register on boot)
10
- *
11
- * No per-app LB/NEG step for non-core apps. Core platform app NEGs at env standup via provision-platform-lb.js.
12
- */
13
- import {
14
- readSourceFile,
15
- findInLines,
16
- makeCitation,
17
- sliceRange,
18
- probeGcloudJson
19
- } from '../util/source-reader.js';
20
- import { BrieferExtractorError, BRIEFER_ERROR_CODES } from '../errors.js';
21
-
22
- export const SECTION = {
23
- number: 4,
24
- heading: '4. Microservice deploy — current state',
25
- sourceFiles: [
26
- 'DeSciX/DeSciX_Powch/microservice/scripts/deploy.sh',
27
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/deploy-service-env.sh',
28
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/lib/cloud-run-deploy.js',
29
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js',
30
- 'DeSciX/DeSciX_Core/descix-cli/bin/descix.js'
31
- ]
32
- };
33
-
34
- const POWCH_DEPLOY = 'DeSciX/DeSciX_Powch/microservice/scripts/deploy.sh';
35
- const CLOUD_DEPLOY = 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/deploy-service-env.sh';
36
- const CLOUD_RUN_LIB = 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/lib/cloud-run-deploy.js';
37
- const LB_FILE = 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js';
38
- const CLI_FILE = 'DeSciX/DeSciX_Core/descix-cli/bin/descix.js';
39
-
40
- export async function extract({ env, cliPaths } = {}) {
41
- if (!env) {
42
- throw new BrieferExtractorError({
43
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
44
- section: `§${SECTION.number} ${SECTION.heading}`,
45
- source: 'extractor argument',
46
- expected: 'env (dev|demo|prod) provided by caller',
47
- recovery: 'Pass {env} from the briefer entry point.'
48
- });
49
- }
50
- if (!cliPaths) {
51
- throw new BrieferExtractorError({
52
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
53
- section: `§${SECTION.number} ${SECTION.heading}`,
54
- source: 'extractor argument',
55
- expected: 'cliPaths provided by caller',
56
- recovery: 'Pass {cliPaths} from the briefer entry point.'
57
- });
58
- }
59
-
60
- // ── Step 1: gcloud run deploy — Cloud canonical lib is the SOLE source of
61
- // this fact. Powch's per-app deploy.sh was RETIRED to a hard-fail
62
- // DEPRECATION stub (WS-DEPLOY-HARDENING item 6) — deploy is now
63
- // CONSOLIDATED onto deploy-service-env.sh → cloud-run-deploy.js for both
64
- // apps. We still read the Powch file, but only to ground-truth its
65
- // retirement marker (fail-loud if the stub itself drifts) — not to
66
- // extract a second `gcloud run deploy` invocation. ──
67
- const powch = await readSourceFile({
68
- cliPaths,
69
- relPath: POWCH_DEPLOY,
70
- section: `§${SECTION.number} ${SECTION.heading}`
71
- });
72
- const powchRun = findInLines({
73
- lines: powch.lines,
74
- regex: /^echo\s+"DEPRECATED:/,
75
- section: `§${SECTION.number} ${SECTION.heading}`,
76
- source: `${POWCH_DEPLOY} (retirement stub marker)`,
77
- expected: 'echo "DEPRECATED: ..." — the WS-DEPLOY-HARDENING item 6 retirement stub',
78
- recovery: `Re-locate the DEPRECATED marker in ${POWCH_DEPLOY}. If the script has been deleted outright, remove this Powch reference from the §4 extractor entirely (single canonical deploy path already covered by CLOUD_RUN_LIB).`
79
- });
80
- const powchDeployEndMatch = findInLines({
81
- lines: powch.lines,
82
- regex: /^exit\s+1/,
83
- section: `§${SECTION.number} ${SECTION.heading}`,
84
- source: `${POWCH_DEPLOY} (retirement stub exit)`,
85
- expected: 'exit 1 — the stub hard-fails rather than deploying',
86
- recovery: `Re-locate the exit 1 in ${POWCH_DEPLOY}.`
87
- });
88
- const powchDeployEnd = powchDeployEndMatch.lineNumber;
89
- const powchDeployBlock = sliceRange(powch.lines, powchRun.lineNumber, powchDeployEnd);
90
-
91
- // Cloud-side deploy: bash script shells out to shared lib (deployToCloudRun)
92
- const cloud = await readSourceFile({
93
- cliPaths,
94
- relPath: CLOUD_DEPLOY,
95
- section: `§${SECTION.number} ${SECTION.heading}`
96
- });
97
- findInLines({
98
- lines: cloud.lines,
99
- regex: /deployToCloudRun/,
100
- section: `§${SECTION.number} ${SECTION.heading}`,
101
- source: `${CLOUD_DEPLOY} (deployToCloudRun import)`,
102
- expected: 'deploy-service-env.sh invokes deployToCloudRun from lib/cloud-run-deploy.js',
103
- recovery: `Re-locate deployToCloudRun in ${CLOUD_DEPLOY}.`
104
- });
105
-
106
- const cloudRunLib = await readSourceFile({
107
- cliPaths,
108
- relPath: CLOUD_RUN_LIB,
109
- section: `§${SECTION.number} ${SECTION.heading}`
110
- });
111
- const cloudRun = findInLines({
112
- lines: cloudRunLib.lines,
113
- regex: /gcloud run deploy \$\{serviceName\}/,
114
- section: `§${SECTION.number} ${SECTION.heading}`,
115
- source: `${CLOUD_RUN_LIB} (gcloud run deploy invocation)`,
116
- expected: 'gcloud run deploy ${serviceName} ... in cloud-run-deploy.js',
117
- recovery: `Re-locate the gcloud run deploy in ${CLOUD_RUN_LIB}.`
118
- });
119
- const cloudDeployEnd = findContinuationEnd(cloudRunLib.lines, cloudRun.lineNumber);
120
- const cloudDeployBlock = sliceRange(cloudRunLib.lines, cloudRun.lineNumber, cloudDeployEnd);
121
-
122
- // Platform LB (env standup only — not per-app)
123
- const lb = await readSourceFile({
124
- cliPaths,
125
- relPath: LB_FILE,
126
- section: `§${SECTION.number} ${SECTION.heading}`
127
- });
128
- const ensureCoreNegMatch = findInLines({
129
- lines: lb.lines,
130
- regex: /function\s+ensureCoreNeg\s*\(/,
131
- section: `§${SECTION.number} ${SECTION.heading}`,
132
- source: `${LB_FILE} (ensureCoreNeg)`,
133
- expected: 'function ensureCoreNeg(envCfg, dryRun)',
134
- recovery: `Re-locate ensureCoreNeg in ${LB_FILE}.`
135
- });
136
-
137
- // ── Step 2: descix microservice register CLI command ──
138
- const cli = await readSourceFile({
139
- cliPaths,
140
- relPath: CLI_FILE,
141
- section: `§${SECTION.number} ${SECTION.heading}`
142
- });
143
- // Per-line scan: find `.command('register')` whose preceding non-empty line
144
- // contains `microserviceCommand`. Disambiguates from program.command('register-delegate')
145
- // which also exists.
146
- let registerLine = -1;
147
- for (let i = 1; i < cli.lines.length; i++) {
148
- if (/\.command\(['"]register['"]\)/.test(cli.lines[i])) {
149
- // Look back up to 3 lines for a `microserviceCommand` anchor.
150
- for (let j = i - 1; j >= Math.max(0, i - 3); j--) {
151
- if (/microserviceCommand/.test(cli.lines[j])) {
152
- registerLine = i + 1;
153
- break;
154
- }
155
- }
156
- if (registerLine !== -1) break;
157
- }
158
- }
159
- if (registerLine === -1) {
160
- throw new BrieferExtractorError({
161
- code: BRIEFER_ERROR_CODES.SRC_NOT_FOUND,
162
- section: `§${SECTION.number} ${SECTION.heading}`,
163
- source: `${CLI_FILE}:3080-3236 (descix microservice register)`,
164
- expected: "microserviceCommand followed by .command('register') in bin/descix.js",
165
- recovery: `Re-locate the microservice register command in ${CLI_FILE}.`,
166
- detail: "Scanned bin/descix.js — no line matches `.command('register')` within 3 lines of `microserviceCommand`."
167
- });
168
- }
169
- const registerMatch = { lineNumber: registerLine };
170
-
171
- // KNOWN GAP toggle: scan for `.command('deploy')` whose preceding 3 lines
172
- // contain a `microserviceCommand` anchor (mirror the register-detect pattern).
173
- // If found, the gap is closed; if absent, gap text is rendered.
174
- let gapToggleClosed = false;
175
- for (let i = 1; i < cli.lines.length; i++) {
176
- if (/\.command\(['"]deploy['"]\)/.test(cli.lines[i])) {
177
- for (let j = i - 1; j >= Math.max(0, i - 3); j--) {
178
- if (/microserviceCommand/.test(cli.lines[j])) {
179
- gapToggleClosed = true;
180
- break;
181
- }
182
- }
183
- if (gapToggleClosed) break;
184
- }
185
- }
186
-
187
- // ── M3: live Cloud Run probe (HARD-FAIL on demo/prod, skipped on dev) ──
188
- const probeCitations = [];
189
- let runProbeNote = '';
190
- if (env === 'dev') {
191
- runProbeNote = '\n_DEV: gcloud probes skipped (local dev — no Cloud Run services to probe)._\n';
192
- } else {
193
- const runProbe = await probeGcloudJson({
194
- command: ['run', 'services', 'list', '--region=us-central1', `--filter=metadata.name~^.*-${env}$`, '--format=json'],
195
- env,
196
- section: `§${SECTION.number} ${SECTION.heading}`,
197
- anchor: 'cloud-run-services-list',
198
- expected: `JSON array of Cloud Run services matching \`-${env}$\` in us-central1`,
199
- recovery: `Run 'gcloud auth login' and 'gcloud config set project descix'. The Cloud Run services cross-check is required for env=${env}.`
200
- });
201
- const services = (runProbe.json || [])
202
- .map(svc => ({
203
- name: (svc.metadata && svc.metadata.name) || '(unknown)',
204
- region: (svc.metadata && svc.metadata.labels && svc.metadata.labels['cloud.googleapis.com/location']) || 'us-central1'
205
- }));
206
- const names = services.map(s => '\`' + s.name + '\`').join(', ');
207
- runProbeNote = `\n**Live Cloud Run state** (probed \`${runProbe.citation.probe}\`, env=\`${env}\`):\n- Services found: \`${services.length}\`\n- Names: ${names || '_(none)_'}\n`;
208
- probeCitations.push(runProbe.citation);
209
- }
210
-
211
- // ── Render the markdown ──
212
- const lines = [
213
- `Today (regen target: \`${env}\`), per-app microservice deploy is **2 steps** (broker-first — no NEG for non-core apps):`,
214
- ``,
215
- `**Step 1.** \`gcloud run deploy {app}-{env} ...\` — deploys the Cloud Run service (via \`deploy-service-env.sh\` → \`lib/cloud-run-deploy.js\`; CLI: \`descix microservice deploy\`). This is now the SOLE deploy path for **both** apps — Powch's own \`deploy.sh\` was retired to a hard-fail deprecation stub (WS-DEPLOY-HARDENING item 6) that points callers at this canonical path.`,
216
- `- Reference (Cloud canonical — sole source of the \`gcloud run deploy\` fact): \`${CLOUD_RUN_LIB}:${cloudRun.lineNumber}\``,
217
- `- Reference (Powch retirement stub, not a second deploy path): \`${POWCH_DEPLOY}:${powchRun.lineNumber}\``,
218
- ``,
219
- 'Verbatim from `' + CLOUD_RUN_LIB + '` (shared deploy lib):',
220
- ``,
221
- '```bash',
222
- cloudDeployBlock,
223
- '```',
224
- ``,
225
- `**Step 2.** \`descix microservice register\` — writes manifest to Firestore \`ServiceManifests\` so Core dispatches via \`proxyToExternalService\`. Self-registration on boot is equivalent. — \`${CLI_FILE}:${registerMatch.lineNumber}\``,
226
- ``,
227
- `**Platform LB (core platform apps at standup):** \`deploy-backend-env.sh\` → \`provision-platform-lb.js\` — daita broker NEG + powch NEG + apex/peer URL map. \`deploy-service-env.sh powch\` re-runs provision for powch NEG. — \`${LB_FILE}:${ensureCoreNegMatch.lineNumber}\``,
228
- ``
229
- ];
230
-
231
- if (gapToggleClosed) {
232
- lines.push(
233
- `**GAP CLOSED:** \`descix microservice deploy\` exists in the CLI as of this regen — it chains the three steps above. (Detected via grep on \`bin/descix.js\`.)`
234
- );
235
- } else {
236
- lines.push(
237
- `**KNOWN GAP:** there is NO \`descix microservice deploy\` command that chains all three. Step 2 today is a manual admin-script invocation, not a CLI subcommand. Closing this gap is part of WS-DESCIX-BRIEFER-CLI scope (or a sibling workstream). _Detected dynamically: grep on \`bin/descix.js\` returned no \`microserviceCommand.command('deploy')\` declaration._`
238
- );
239
- }
240
- if (runProbeNote) lines.push(runProbeNote);
241
-
242
- const markdown = lines.join('\n');
243
-
244
- const citations = [
245
- makeCitation({ file: POWCH_DEPLOY, lines: `${powchRun.lineNumber}-${powchDeployEnd}`, anchor: 'DEPRECATED stub (Powch — consolidated onto Cloud canonical path)', fileLines: powch.lines }),
246
- makeCitation({ file: CLOUD_RUN_LIB, lines: `${cloudRun.lineNumber}-${cloudDeployEnd}`, anchor: 'gcloud run deploy (Cloud canonical lib)', fileLines: cloudRunLib.lines }),
247
- makeCitation({ file: CLOUD_DEPLOY, lines: 'deployToCloudRun', anchor: 'deploy-service-env.sh → lib', fileLines: cloud.lines }),
248
- makeCitation({ file: LB_FILE, lines: String(ensureCoreNegMatch.lineNumber), anchor: 'provision-platform-lb.js ensureCoreNeg', fileLines: lb.lines }),
249
- makeCitation({ file: CLI_FILE, lines: String(registerMatch.lineNumber), anchor: 'descix microservice register', fileLines: cli.lines }),
250
- ...probeCitations
251
- ];
252
-
253
- return { markdown, citations };
254
- }
255
-
256
- /**
257
- * For shell line-continuations (`\\` at line-end), walk forward until the
258
- * continuation chain ends. Returns the line number of the LAST line in the
259
- * block (1-indexed).
260
- */
261
- function findContinuationEnd(lines, startLine) {
262
- let i = startLine - 1;
263
- while (i < lines.length - 1 && /\\\s*$/.test(lines[i])) {
264
- i++;
265
- }
266
- return i + 1;
267
- }