@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.
- package/README.md +1 -1
- package/agent-assets/guides/README.md +1 -1
- package/agent-assets/guides/dynamic-site-pattern.md +7 -16
- package/agent-assets/instructions/app-asset-validation.md +1 -1
- package/agent-assets/reference/architecture.md +20 -25
- package/agent-assets/reference/local-dev.md +1 -6
- package/agent-assets/reference/mcp-integration.md +15 -9
- package/agent-assets/reference/platform-api-consumer.md +11 -5
- package/agent-assets/reference/workspace-config.md +2 -2
- package/bin/descix.js +211 -688
- package/lib/command-visibility.js +213 -0
- package/lib/commands/corpus.js +2 -5
- package/lib/commands/kb.js +2 -2
- package/lib/commands/media-upload.js +176 -0
- package/lib/commands/retired-kb-sync.js +5 -27
- package/lib/commands/status.js +0 -9
- package/lib/output.js +31 -0
- package/lib/sdk-assets.js +28 -0
- package/lib/verb-invokes.generated.json +233 -0
- package/lib/workspace-config.js +9 -44
- package/package.json +6 -2
- package/templates/agent-claude.md +1 -3
- package/templates/agent-cline.md +1 -2
- package/templates/agent-copilot.md +1 -2
- package/templates/agent-cursor.md +1 -2
- package/templates/scaffolds/microservice/SERVICE_README_sdk.md +1 -1
- package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +2 -3
- package/agent-assets/rules/descix_mcp.template.mdc +0 -243
- package/lib/commands/briefer/errors.js +0 -88
- package/lib/commands/briefer/index.js +0 -322
- package/lib/commands/briefer/sources/canonical-sources.js +0 -129
- package/lib/commands/briefer/sources/entitlements.js +0 -220
- package/lib/commands/briefer/sources/environments.js +0 -205
- package/lib/commands/briefer/sources/identifiers.js +0 -214
- package/lib/commands/briefer/sources/microservice-deploy.js +0 -267
- package/lib/commands/briefer/sources/routing.js +0 -275
- package/lib/commands/briefer/sources/what-is-not.js +0 -223
- package/lib/commands/briefer/stitcher.js +0 -134
- package/lib/commands/briefer/util/source-reader.js +0 -502
- package/lib/commands/mcp.js +0 -273
- package/lib/commands/update.js +0 -581
- package/lib/wizard/setup.js +0 -585
- package/templates/descix_mcp.mdc +0 -122
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
alwaysApply: true
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# DeSciX SDK Agent Instructions
|
|
6
|
-
|
|
7
|
-
You have SDK agent assets available locally at `{{SDK_ASSETS_PATH}}/`.
|
|
8
|
-
|
|
9
|
-
## Key Architecture Concepts
|
|
10
|
-
|
|
11
|
-
### Configuration: workspace.json is the Config File
|
|
12
|
-
|
|
13
|
-
- **`workspace.json`** at `.descix/workspace.json` is the configuration file
|
|
14
|
-
- **Context auto-detection** - CLI commands detect community/app from current working directory
|
|
15
|
-
|
|
16
|
-
### CLI = Git Mode ONLY
|
|
17
|
-
|
|
18
|
-
- The CLI only supports **git-mode** operations (local processing with Git version control)
|
|
19
|
-
- **Drive-mode** apps are managed via PWA only (server-side processing)
|
|
20
|
-
- All content starts on Drive - developers pull it locally, then manage with Git
|
|
21
|
-
|
|
22
|
-
### App Folder Structure (Convention-Based)
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
my-app/
|
|
26
|
-
├── assets/ # App metadata (icon.png, app_description.md, system_instructions.md)
|
|
27
|
-
├── kb/ # Knowledge base
|
|
28
|
-
│ ├── staging/ # Files to push TO Drive
|
|
29
|
-
│ ├── General/ # Text files pulled FROM Drive
|
|
30
|
-
│ └── chunks/ # Generated JSON chunks (Git-tracked)
|
|
31
|
-
├── site/ # CodeSite static files (optional)
|
|
32
|
-
└── microservice/ # Backend service code (optional)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Post-Setup Initialization
|
|
36
|
-
|
|
37
|
-
**IMPORTANT**: Check `.descix/workspace.json` for `_needsAgentSetup: true`.
|
|
38
|
-
|
|
39
|
-
When this flag is set, the user has just completed `descix mcp quickstart` and needs your help configuring their workspace:
|
|
40
|
-
|
|
41
|
-
1. **Greet the user** and confirm MCP is working: "I can see your DeSciX setup is complete. Let me help you configure your workspace."
|
|
42
|
-
2. **Show their entitlements** from the cached JSON below (communities and apps they own)
|
|
43
|
-
3. **Ask**: "Which app would you like to configure first?" or "Would you like me to scan your folders and suggest mappings?"
|
|
44
|
-
4. **For each app folder**:
|
|
45
|
-
- Analyze the folder structure using heuristics from `{{SDK_ASSETS_PATH}}/instructions/folder-analysis.md`
|
|
46
|
-
- Recommend a packaging type using the decision tree from `{{SDK_ASSETS_PATH}}/instructions/packaging-types.md`
|
|
47
|
-
- Register the app in `workspace.json` with its localPath and absolutePath
|
|
48
|
-
5. **When done**, set `_needsAgentSetup: false` in workspace.json
|
|
49
|
-
|
|
50
|
-
If user asks "Help me configure my workspace" or similar, follow this flow.
|
|
51
|
-
|
|
52
|
-
## Entitlements-First Principle
|
|
53
|
-
|
|
54
|
-
**CRITICAL**: Users purchase apps/communities in the PWA first. Your role is to help configure local folders for apps they already own.
|
|
55
|
-
|
|
56
|
-
### On First Interaction
|
|
57
|
-
|
|
58
|
-
1. Check `.descix/workspace.json` for existing configuration
|
|
59
|
-
2. If `_needsAgentSetup: true`, follow the Post-Setup Initialization flow above
|
|
60
|
-
3. If unconfigured, read `{{SDK_ASSETS_PATH}}/instructions/onboarding-flow.md`
|
|
61
|
-
4. For fresh entitlements: `execute_remote_command({ command: "fetch_my_purchases" })`
|
|
62
|
-
5. Guide user through folder-to-app mapping in `workspace.json`
|
|
63
|
-
|
|
64
|
-
## Local Asset References
|
|
65
|
-
|
|
66
|
-
For quick decisions, read these local files (no network required):
|
|
67
|
-
|
|
68
|
-
| File | Purpose |
|
|
69
|
-
|------|---------|
|
|
70
|
-
| `{{SDK_ASSETS_PATH}}/instructions/packaging-types.md` | App type taxonomy and decision tree |
|
|
71
|
-
| `{{SDK_ASSETS_PATH}}/instructions/folder-analysis.md` | Detect project type from files |
|
|
72
|
-
| `{{SDK_ASSETS_PATH}}/instructions/conversation-guides.md` | Dialogue templates |
|
|
73
|
-
| `{{SDK_ASSETS_PATH}}/instructions/onboarding-flow.md` | Full onboarding workflow |
|
|
74
|
-
|
|
75
|
-
## Template Usage
|
|
76
|
-
|
|
77
|
-
When scaffolding apps, use templates from `{{SDK_ASSETS_PATH}}/templates/`:
|
|
78
|
-
|
|
79
|
-
| Packaging Type | Templates Available |
|
|
80
|
-
|----------------|---------------------|
|
|
81
|
-
| `knowledge-only` | Assets folder only |
|
|
82
|
-
| `static-site` | Site folder scaffold |
|
|
83
|
-
| `microservice` | `manifest.json.template`, `SERVICE_README.template` |
|
|
84
|
-
| `dynamic-site` | Site + microservice scaffolds |
|
|
85
|
-
|
|
86
|
-
Replace `{{PLACEHOLDER}}` values with actual data from user input.
|
|
87
|
-
|
|
88
|
-
## User Entitlements (Cached at Setup)
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{{ENTITLEMENTS_JSON}}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**Primary Community**: {{PRIMARY_COMMUNITY}}
|
|
95
|
-
|
|
96
|
-
Use this cached data for quick checks. For fresh data, call `fetch_my_purchases`.
|
|
97
|
-
|
|
98
|
-
## Tiered Access Pattern
|
|
99
|
-
|
|
100
|
-
Use the appropriate tier based on what you need:
|
|
101
|
-
|
|
102
|
-
### Tier 1: Local Files (Instant)
|
|
103
|
-
```javascript
|
|
104
|
-
// Read instructions directly
|
|
105
|
-
Read("{{SDK_ASSETS_PATH}}/instructions/packaging-types.md")
|
|
106
|
-
```
|
|
107
|
-
**Use for**: Quick lookups, decision trees, templates
|
|
108
|
-
|
|
109
|
-
### Tier 2: tell_me_how (Semantic Discovery)
|
|
110
|
-
```javascript
|
|
111
|
-
tell_me_how({
|
|
112
|
-
question: "How do I scaffold a microservice?",
|
|
113
|
-
scope: "entitlements"
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
**Use for**: Finding the right tool for a task
|
|
117
|
-
|
|
118
|
-
### Tier 3: chat_with_kb (Rich Context)
|
|
119
|
-
```javascript
|
|
120
|
-
chat_with_kb({
|
|
121
|
-
communityId: "daita",
|
|
122
|
-
appId: "docs",
|
|
123
|
-
kbId: "sdk",
|
|
124
|
-
message: "Explain the deployment workflow for dynamic sites"
|
|
125
|
-
})
|
|
126
|
-
```
|
|
127
|
-
**Use for**: Complex questions requiring reasoning
|
|
128
|
-
|
|
129
|
-
### Tier 4: execute_remote_command (Action)
|
|
130
|
-
```javascript
|
|
131
|
-
execute_remote_command({
|
|
132
|
-
command: "scaffold_app_type",
|
|
133
|
-
params: { type: "microservice", app_id: "myapp", ... }
|
|
134
|
-
})
|
|
135
|
-
```
|
|
136
|
-
**Use for**: Actually creating/modifying things
|
|
137
|
-
|
|
138
|
-
## Tool Discovery
|
|
139
|
-
|
|
140
|
-
Use `tell_me_how` to discover platform capabilities:
|
|
141
|
-
|
|
142
|
-
```javascript
|
|
143
|
-
// Discover tools for a task
|
|
144
|
-
tell_me_how({ question: "How do I deploy my app?", scope: "entitlements" })
|
|
145
|
-
|
|
146
|
-
// Then execute the recommended command
|
|
147
|
-
execute_remote_command({ command: "discovered_command", params: {...} })
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
**Scope Options**:
|
|
151
|
-
- `project`: Only tools for apps in workspace.json
|
|
152
|
-
- `entitlements`: Tools for all apps user owns (default)
|
|
153
|
-
- `discovery`: All platform tools (may show purchasable)
|
|
154
|
-
|
|
155
|
-
## App Configuration Workflow
|
|
156
|
-
|
|
157
|
-
When user wants to configure a folder:
|
|
158
|
-
|
|
159
|
-
1. **Analyze folder** using heuristics from `folder-analysis.md`
|
|
160
|
-
2. **Recommend packaging type** using decision tree from `packaging-types.md`
|
|
161
|
-
3. **Present options** using dialogue from `conversation-guides.md`
|
|
162
|
-
4. **Register app** in `workspace.json` with localPath and absolutePath
|
|
163
|
-
5. **Guide CLI commands** based on packaging type
|
|
164
|
-
|
|
165
|
-
## Primary CLI Commands
|
|
166
|
-
|
|
167
|
-
Guide users to these git-mode commands:
|
|
168
|
-
|
|
169
|
-
### Knowledge Base Commands
|
|
170
|
-
| Command | Purpose |
|
|
171
|
-
|---------|---------|
|
|
172
|
-
| `descix drive pull` | Pull text-converted content from Drive |
|
|
173
|
-
| `descix drive push` | Push staging files to Drive |
|
|
174
|
-
| `descix kb corpus sync` | Generate JSON chunks from text files |
|
|
175
|
-
| `descix kb corpus sync` | Push chunks to Pinecone |
|
|
176
|
-
| `descix kb corpus sync` | Full pipeline: pull → chunk → sync |
|
|
177
|
-
| `descix kb corpus status` | Show local vs Pinecone sync status |
|
|
178
|
-
|
|
179
|
-
### Site Commands
|
|
180
|
-
| Command | Purpose |
|
|
181
|
-
|---------|---------|
|
|
182
|
-
| `descix site init` | Copy site scaffold to app |
|
|
183
|
-
| `descix site upload` | Deploy CodeSite to GCS |
|
|
184
|
-
| `descix site servelocal [port]` | Register local dev server port |
|
|
185
|
-
| `descix site list` | List deployed site files |
|
|
186
|
-
| `descix site status` | Get deployment status |
|
|
187
|
-
|
|
188
|
-
### Microservice Commands
|
|
189
|
-
| Command | Purpose |
|
|
190
|
-
|---------|---------|
|
|
191
|
-
| `descix microservice init` | Copy microservice scaffold to app |
|
|
192
|
-
| `descix microservice register` | Register microservice with gateway |
|
|
193
|
-
| `descix microservice vectorize` | Vectorize README for discovery |
|
|
194
|
-
| `descix microservice list` | List registered microservices |
|
|
195
|
-
| `descix microservice health <name>` | Check microservice health |
|
|
196
|
-
| `descix microservice reload` | Reload manifests (admin) |
|
|
197
|
-
|
|
198
|
-
**Context Auto-Detection**: When run from within an app directory, these commands auto-detect community/app from `workspace.json`. Otherwise use `-c` and `-a` flags.
|
|
199
|
-
|
|
200
|
-
## SDK Tools Available (Remote)
|
|
201
|
-
|
|
202
|
-
These tools are auto-registered and discoverable via `tell_me_how`:
|
|
203
|
-
|
|
204
|
-
| Tool | Purpose |
|
|
205
|
-
|------|---------|
|
|
206
|
-
| `scaffold_app_type` | Create boilerplate for chosen packaging type |
|
|
207
|
-
| `analyze_folder_structure` | Detect project type from folder contents |
|
|
208
|
-
| `get_app_sync_status` | Check what's synced (KB, site, service) |
|
|
209
|
-
| `fetch_my_purchases` | Get user's entitlements |
|
|
210
|
-
| `register_service` | Register microservice with gateway |
|
|
211
|
-
| `vectorize_service_readme` | Enable tell_me_how discovery |
|
|
212
|
-
|
|
213
|
-
## Key Principles
|
|
214
|
-
|
|
215
|
-
1. **Never assume** - Always ask before creating mappings
|
|
216
|
-
2. **Entitlements are truth** - Can't configure apps user doesn't own
|
|
217
|
-
3. **Local first** - Read local assets before making network calls
|
|
218
|
-
4. **Guide, don't dictate** - Present options, let user choose
|
|
219
|
-
5. **workspace.json is truth** - All configuration goes here
|
|
220
|
-
|
|
221
|
-
## Error Recovery
|
|
222
|
-
|
|
223
|
-
If commands fail:
|
|
224
|
-
1. Check authentication: Is `wallet.json` valid?
|
|
225
|
-
2. Check entitlements: Does user own the app?
|
|
226
|
-
3. Check configuration: Is app registered in `workspace.json`?
|
|
227
|
-
4. Check location: Are you in the app directory or using `-c/-a` flags?
|
|
228
|
-
5. Use `tell_me_how` to find alternative approaches
|
|
229
|
-
|
|
230
|
-
### Common Errors
|
|
231
|
-
|
|
232
|
-
| Error | Cause | Solution |
|
|
233
|
-
|-------|-------|----------|
|
|
234
|
-
| "Workspace not configured" | No workspace.json | Run `descix init` |
|
|
235
|
-
| "Could not determine app context" | Not in app directory | `cd` to app or use `-c/-a` flags |
|
|
236
|
-
| "ADC not configured" | Missing Google auth | Run `gcloud auth application-default login` |
|
|
237
|
-
| "App not found in workspace" | App not registered | Run `descix mcp quickstart` or add to workspace.json |
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
*Generated by DeSciX SDK Setup*
|
|
242
|
-
*SDK Version: {{SDK_VERSION}}*
|
|
243
|
-
*Workspace: {{WORKSPACE_PATH}}*
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BrieferExtractorError — canonical hard-fail surface for the `descix briefer` CLI.
|
|
3
|
-
*
|
|
4
|
-
* Per WS-DESCIX-BRIEFER-CLI scope doc §2.3 ("Hard-fail philosophy"), every data
|
|
5
|
-
* source has exactly one extraction method and exactly one failure mode:
|
|
6
|
-
* HARD-FAIL with an actionable error message.
|
|
7
|
-
*
|
|
8
|
-
* Per feedback_no-hardcoded-fallbacks: NO fallback values, NO best-effort prose,
|
|
9
|
-
* NO try-catch-and-default. If a source moved or changed shape, the CLI exits
|
|
10
|
-
* non-zero and the canonical briefer file remains untouched.
|
|
11
|
-
*
|
|
12
|
-
* Stable error codes (per scope doc §2.3) — grep-friendly for diagnostics:
|
|
13
|
-
* BRIEFER-SRC-NOT-FOUND Source file missing / line range no longer matches
|
|
14
|
-
* BRIEFER-GCLOUD-AUTH gcloud auth missing or insufficient scope
|
|
15
|
-
* BRIEFER-GCLOUD-FAIL gcloud probe failed (auth/empty/timeout) when --env=demo|prod required it
|
|
16
|
-
* BRIEFER-PARSE-FAIL Source file present but construct unparseable
|
|
17
|
-
* BRIEFER-DRIFT-DETECTED --check diff against canonical briefer non-empty
|
|
18
|
-
* BRIEFER-NEGATIVE-CLAIM Briefer claims X does not exist, but found at Y:Z
|
|
19
|
-
* BRIEFER-FIRESTORE Firestore read via apifront failed
|
|
20
|
-
* BRIEFER-NOT-IMPLEMENTED M1 stub — extractor scaffold-only, real impl in later milestone
|
|
21
|
-
*
|
|
22
|
-
* Per scope doc §2.3, every error message must include:
|
|
23
|
-
* 1. The exact source path + line range that was being read.
|
|
24
|
-
* 2. The construct shape that was expected.
|
|
25
|
-
* 3. What the operator should do to recover.
|
|
26
|
-
* 4. A stable error code from the list above.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
export const BRIEFER_ERROR_CODES = Object.freeze({
|
|
30
|
-
SRC_NOT_FOUND: 'BRIEFER-SRC-NOT-FOUND',
|
|
31
|
-
GCLOUD_AUTH: 'BRIEFER-GCLOUD-AUTH',
|
|
32
|
-
PARSE_FAIL: 'BRIEFER-PARSE-FAIL',
|
|
33
|
-
DRIFT_DETECTED: 'BRIEFER-DRIFT-DETECTED',
|
|
34
|
-
NEGATIVE_CLAIM: 'BRIEFER-NEGATIVE-CLAIM',
|
|
35
|
-
FIRESTORE: 'BRIEFER-FIRESTORE',
|
|
36
|
-
GCLOUD_FAIL: 'BRIEFER-GCLOUD-FAIL',
|
|
37
|
-
NOT_IMPLEMENTED: 'BRIEFER-NOT-IMPLEMENTED',
|
|
38
|
-
WORKSPACE_NOT_FOUND: 'BRIEFER-WORKSPACE-NOT-FOUND'
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
export class BrieferExtractorError extends Error {
|
|
42
|
-
/**
|
|
43
|
-
* @param {object} args
|
|
44
|
-
* @param {string} args.code One of BRIEFER_ERROR_CODES (REQUIRED).
|
|
45
|
-
* @param {string} args.section Briefer section being extracted (e.g., '§3 routing').
|
|
46
|
-
* @param {string} args.source File path or 'gcloud' or 'firestore' (REQUIRED).
|
|
47
|
-
* @param {string} args.expected Construct shape that was expected (REQUIRED).
|
|
48
|
-
* @param {string} args.recovery Concrete next step for the operator (REQUIRED).
|
|
49
|
-
* @param {string} [args.detail] Optional extra context (e.g., parser msg, exit code).
|
|
50
|
-
*/
|
|
51
|
-
constructor({ code, section, source, expected, recovery, detail } = {}) {
|
|
52
|
-
if (!code || !Object.values(BRIEFER_ERROR_CODES).includes(code)) {
|
|
53
|
-
// Hard-fail loud on misuse — caller is constructing a bad error.
|
|
54
|
-
// We do NOT default the code; per feedback_no-hardcoded-fallbacks the
|
|
55
|
-
// absence of a code is itself a misconfiguration we want to surface.
|
|
56
|
-
throw new Error(
|
|
57
|
-
`BrieferExtractorError requires a valid 'code'. Got: ${JSON.stringify(code)}. ` +
|
|
58
|
-
`Valid codes: ${Object.values(BRIEFER_ERROR_CODES).join(', ')}.`
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
if (!source) {
|
|
62
|
-
throw new Error(`BrieferExtractorError requires a 'source' (file path / 'gcloud' / 'firestore').`);
|
|
63
|
-
}
|
|
64
|
-
if (!expected) {
|
|
65
|
-
throw new Error(`BrieferExtractorError requires an 'expected' construct shape description.`);
|
|
66
|
-
}
|
|
67
|
-
if (!recovery) {
|
|
68
|
-
throw new Error(`BrieferExtractorError requires a 'recovery' instruction for the operator.`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const lines = [
|
|
72
|
-
`[${code}] ${section || '(section unspecified)'}`,
|
|
73
|
-
` source: ${source}`,
|
|
74
|
-
` expected: ${expected}`,
|
|
75
|
-
` recovery: ${recovery}`
|
|
76
|
-
];
|
|
77
|
-
if (detail) lines.push(` detail: ${detail}`);
|
|
78
|
-
|
|
79
|
-
super(lines.join('\n'));
|
|
80
|
-
this.name = 'BrieferExtractorError';
|
|
81
|
-
this.code = code;
|
|
82
|
-
this.section = section || null;
|
|
83
|
-
this.source = source;
|
|
84
|
-
this.expected = expected;
|
|
85
|
-
this.recovery = recovery;
|
|
86
|
-
this.detail = detail || null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `descix briefer` — main entry point.
|
|
3
|
-
*
|
|
4
|
-
* Per WS-DESCIX-BRIEFER-CLI scope doc §2 (Architecture / Option A):
|
|
5
|
-
*
|
|
6
|
-
* descix briefer [--env=dev|demo|prod] [--out=PATH] [--check]
|
|
7
|
-
*
|
|
8
|
-
* Reads the per-section data sources defined in scope doc §2.2, dispatches to
|
|
9
|
-
* the per-section extractors in ./sources/*.js, and emits a structured
|
|
10
|
-
* BrieferDoc object. The stitcher composes the markdown; --check mode diffs
|
|
11
|
-
* the regen against the canonical briefer file.
|
|
12
|
-
*
|
|
13
|
-
* HARD-FAIL philosophy (scope doc §2.3 + feedback_no-hardcoded-fallbacks):
|
|
14
|
-
* every extractor that can't find its source data throws BrieferExtractorError
|
|
15
|
-
* with a stable error code. No fallback strings. No try-catch-and-default.
|
|
16
|
-
*
|
|
17
|
-
* M1 SCOPE (this implementation):
|
|
18
|
-
* - CLI subcommand registration in bin/descix.js (separate edit).
|
|
19
|
-
* - This entry orchestrates 7 stub extractors.
|
|
20
|
-
* - Stitcher composes a real (non-empty) markdown with TODO placeholder bodies.
|
|
21
|
-
* - --check runs the full extraction, diffs against canonical, prints a
|
|
22
|
-
* unified-style diff, exits non-zero on drift (which is EXPECTED for M1 —
|
|
23
|
-
* stubs WILL differ from the manually-authored canonical briefer).
|
|
24
|
-
*
|
|
25
|
-
* M2+ SCOPE (NOT IMPLEMENTED HERE):
|
|
26
|
-
* - Real AST/regex parsing of source files.
|
|
27
|
-
* - Live gcloud probes.
|
|
28
|
-
* - Firestore Products counts via apifront.
|
|
29
|
-
* - MustKnow KB partition + auto-sync.
|
|
30
|
-
* - Bootstrap protocol edits (/cos skill, @bootstrap agent).
|
|
31
|
-
* - CI --check gate.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import fs from 'fs/promises';
|
|
35
|
-
import path from 'path';
|
|
36
|
-
import chalk from 'chalk';
|
|
37
|
-
import { fileURLToPath } from 'url';
|
|
38
|
-
|
|
39
|
-
import { BrieferExtractorError, BRIEFER_ERROR_CODES } from './errors.js';
|
|
40
|
-
import { stitchBriefer, extractCitationTrail } from './stitcher.js';
|
|
41
|
-
|
|
42
|
-
import * as identifiers from './sources/identifiers.js';
|
|
43
|
-
import * as environments from './sources/environments.js';
|
|
44
|
-
import * as routing from './sources/routing.js';
|
|
45
|
-
import * as microserviceDeploy from './sources/microservice-deploy.js';
|
|
46
|
-
import * as entitlements from './sources/entitlements.js';
|
|
47
|
-
import * as whatIsNot from './sources/what-is-not.js';
|
|
48
|
-
import * as canonicalSources from './sources/canonical-sources.js';
|
|
49
|
-
|
|
50
|
-
import { WorkspaceConfig } from '../../workspace-config.js';
|
|
51
|
-
|
|
52
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
-
// Extractor dispatch table (scope doc §2.2 ordering)
|
|
54
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
-
export const EXTRACTORS = Object.freeze([
|
|
56
|
-
identifiers,
|
|
57
|
-
environments,
|
|
58
|
-
routing,
|
|
59
|
-
microserviceDeploy,
|
|
60
|
-
entitlements,
|
|
61
|
-
whatIsNot,
|
|
62
|
-
canonicalSources
|
|
63
|
-
]);
|
|
64
|
-
|
|
65
|
-
export const VALID_ENVS = Object.freeze(['dev', 'demo', 'prod']);
|
|
66
|
-
|
|
67
|
-
// Default output path (scope doc §2.1). Resolved relative to workspace root
|
|
68
|
-
// via WorkspaceConfig.getWorkspaceRoot() — never a hardcoded absolute path.
|
|
69
|
-
export const DEFAULT_OUT_RELATIVE = 'DeSciX/V2_docs/architecture/platform-must-know-briefer.md';
|
|
70
|
-
|
|
71
|
-
const MECHANISM_TAG = 'descix briefer v1.0 (M2 — code-grounded extractors)';
|
|
72
|
-
|
|
73
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
74
|
-
// Helpers
|
|
75
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
export function resolveCliPaths() {
|
|
78
|
-
// Resolve the descix-cli root from this file's location, then walk up to
|
|
79
|
-
// DeSciX_Core and the parent repo. M2/M3 extractors that read source files
|
|
80
|
-
// will need these to anchor file reads.
|
|
81
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
82
|
-
const descixCliRoot = path.resolve(here, '../../..'); // .../descix-cli
|
|
83
|
-
const descixCoreRoot = path.resolve(descixCliRoot, '..'); // .../DeSciX_Core
|
|
84
|
-
const desciXRoot = path.resolve(descixCoreRoot, '..'); // .../DeSciX
|
|
85
|
-
const repoRoot = path.resolve(desciXRoot, '..'); // .../Unkamon
|
|
86
|
-
return { descixCliRoot, descixCoreRoot, desciXRoot, repoRoot };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function resolveOutPath(cliOut, workspaceConfig, cliPaths, cwd) {
|
|
90
|
-
// Explicit override always wins.
|
|
91
|
-
if (cliOut) return path.resolve(cliOut);
|
|
92
|
-
|
|
93
|
-
// Default: workspace-root-relative path.
|
|
94
|
-
if (workspaceConfig) {
|
|
95
|
-
const wsRoot = workspaceConfig.getWorkspaceRoot();
|
|
96
|
-
return path.join(wsRoot, DEFAULT_OUT_RELATIVE);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// HARD-FAIL per feedback_no-hardcoded-fallbacks. The previous implementation
|
|
100
|
-
// derived a default from cliPaths.repoRoot (computed from the CLI file
|
|
101
|
-
// location), which silently produced a wrong path like
|
|
102
|
-
// "Unkamon/DeSciX/DeSciX/V2_docs/..." (double DeSciX/) when invoked from
|
|
103
|
-
// inside descix-cli/ with no workspace.json in cwd. No silent path-guessing.
|
|
104
|
-
throw new BrieferExtractorError({
|
|
105
|
-
code: BRIEFER_ERROR_CODES.WORKSPACE_NOT_FOUND,
|
|
106
|
-
section: 'briefer entry',
|
|
107
|
-
source: cwd || '(unknown cwd)',
|
|
108
|
-
expected: 'either .descix/workspace.json reachable by upward search, OR --out=<path>',
|
|
109
|
-
recovery: 'Run from your workspace root (where .descix/workspace.json lives), or pass --out=<path> explicitly.',
|
|
110
|
-
detail: `cliPaths.repoRoot was previously used as a silent fallback (resolved to ${cliPaths.repoRoot}); that fallback is removed.`
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function resolveRegenBy() {
|
|
115
|
-
// Best signal we have without forcing an auth round-trip in M1. M2 will
|
|
116
|
-
// require auth (for apifront/Firestore calls) and can use the wallet email
|
|
117
|
-
// from apiClient.loadCredentials() — at that point this changes to a hard
|
|
118
|
-
// requirement. For M1 scaffolding we just tag the system user.
|
|
119
|
-
return process.env.USER || process.env.USERNAME || 'unknown';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Minimal unified-style diff (line-based). Avoids adding a new npm dependency
|
|
123
|
-
// for the M1 scaffold; M2 may swap in the `diff` package if richer output is
|
|
124
|
-
// needed (currently an unanswered question §9.1 in scope doc — Babel dep).
|
|
125
|
-
function simpleLineDiff(a, b, { aLabel = 'canonical', bLabel = 'regen' } = {}) {
|
|
126
|
-
const aLines = (a || '').split(/\r?\n/);
|
|
127
|
-
const bLines = (b || '').split(/\r?\n/);
|
|
128
|
-
const out = [`--- ${aLabel}`, `+++ ${bLabel}`];
|
|
129
|
-
const max = Math.max(aLines.length, bLines.length);
|
|
130
|
-
let inHunk = false;
|
|
131
|
-
for (let i = 0; i < max; i++) {
|
|
132
|
-
const av = aLines[i];
|
|
133
|
-
const bv = bLines[i];
|
|
134
|
-
if (av === bv) {
|
|
135
|
-
if (inHunk) { out.push(' ' + (av ?? '')); inHunk = false; }
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (!inHunk) {
|
|
139
|
-
out.push(`@@ line ${i + 1} @@`);
|
|
140
|
-
inHunk = true;
|
|
141
|
-
}
|
|
142
|
-
if (av !== undefined) out.push(`-${av}`);
|
|
143
|
-
if (bv !== undefined) out.push(`+${bv}`);
|
|
144
|
-
}
|
|
145
|
-
return out.join('\n');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
149
|
-
// BrieferDoc — the structured object returned by buildBrieferDoc()
|
|
150
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
151
|
-
/**
|
|
152
|
-
* @typedef {object} BrieferSectionResult
|
|
153
|
-
* @property {{number: number, heading: string, sourceFiles: string[]}} section
|
|
154
|
-
* @property {{markdown: string, citations: object[]}} extract
|
|
155
|
-
*/
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @typedef {object} BrieferDoc
|
|
159
|
-
* @property {string} env
|
|
160
|
-
* @property {string} mechanism
|
|
161
|
-
* @property {string} regenBy
|
|
162
|
-
* @property {BrieferSectionResult[]} sections
|
|
163
|
-
* @property {string} markdown Final stitched markdown.
|
|
164
|
-
* @property {string[]} citationTrail Hidden HTML-comment trail (one per line).
|
|
165
|
-
*/
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Run all extractors and return the BrieferDoc.
|
|
169
|
-
*
|
|
170
|
-
* @param {object} opts
|
|
171
|
-
* @param {string} opts.env
|
|
172
|
-
* @param {object} opts.cliPaths
|
|
173
|
-
* @param {object|null} [opts.apiClient] Optional in M1 (stubs don't need it).
|
|
174
|
-
* @param {object|null} [opts.gcloud] Optional in M1 (stubs don't need it).
|
|
175
|
-
* @param {string} opts.regenBy
|
|
176
|
-
* @param {string} [opts.mechanism]
|
|
177
|
-
* @returns {Promise<BrieferDoc>}
|
|
178
|
-
*/
|
|
179
|
-
export async function buildBrieferDoc({ env, cliPaths, apiClient = null, gcloud = null, regenBy, mechanism = MECHANISM_TAG }) {
|
|
180
|
-
const sections = [];
|
|
181
|
-
for (const extractor of EXTRACTORS) {
|
|
182
|
-
const result = await extractor.extract({ env, cliPaths, gcloud, apiClient });
|
|
183
|
-
sections.push({ section: extractor.SECTION, extract: result });
|
|
184
|
-
}
|
|
185
|
-
const markdown = stitchBriefer({ env, mechanism, regenBy, sections });
|
|
186
|
-
const citationTrail = extractCitationTrail(markdown);
|
|
187
|
-
return { env, mechanism, regenBy, sections, markdown, citationTrail };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
191
|
-
// Public CLI entry — invoked by bin/descix.js `briefer` subcommand
|
|
192
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* @param {object} options Commander options object.
|
|
196
|
-
* @param {string} [options.env] dev|demo|prod (default: 'dev' for M1; M2 will hard-fail dev per scope doc §2.1).
|
|
197
|
-
* @param {string} [options.out] Output path override.
|
|
198
|
-
* @param {boolean} [options.check] Drift-detection mode: regen to memory, diff against canonical, exit non-zero on drift.
|
|
199
|
-
* @param {boolean} [options.verbose] Print per-source timing + extractor names.
|
|
200
|
-
*/
|
|
201
|
-
export async function runBriefer(options = {}) {
|
|
202
|
-
const env = (options.env || 'dev').toLowerCase();
|
|
203
|
-
if (!VALID_ENVS.includes(env)) {
|
|
204
|
-
console.error(chalk.red(`\n❌ Invalid --env=${options.env}. Must be one of: ${VALID_ENVS.join(', ')}\n`));
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Per scope §2.1: --env=dev is rejected by the routing extractor specifically
|
|
209
|
-
// (DEV has no LB URL map). Other extractors accept dev because they read
|
|
210
|
-
// only source files (no gcloud probe required). The rejection lives in
|
|
211
|
-
// sources/routing.js; this entry point passes env through unchanged.
|
|
212
|
-
if (options.verbose) {
|
|
213
|
-
console.log(chalk.gray(`[briefer] env=${env}`));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const cliPaths = resolveCliPaths();
|
|
217
|
-
const cwd = process.cwd();
|
|
218
|
-
let workspaceConfig = null;
|
|
219
|
-
try {
|
|
220
|
-
workspaceConfig = await WorkspaceConfig.tryLoad(cwd);
|
|
221
|
-
} catch (error) {
|
|
222
|
-
// tryLoad returns null on a MISSING workspace; it throws only when workspace.json EXISTS
|
|
223
|
-
// and cannot be read. Hard-fail per philosophy — do NOT proceed with a guessed default.
|
|
224
|
-
//
|
|
225
|
-
// The recovery text is the loader's own, not a second opinion invented here: it used to say
|
|
226
|
-
// "run `descix init` to regenerate", which prescribes overwriting the very file that could
|
|
227
|
-
// not be read, and which also contradicts the standing rule against hand-editing it.
|
|
228
|
-
throw new BrieferExtractorError({
|
|
229
|
-
code: BRIEFER_ERROR_CODES.PARSE_FAIL,
|
|
230
|
-
section: 'briefer entry',
|
|
231
|
-
source: '.descix/workspace.json',
|
|
232
|
-
expected: 'valid v2.1 workspace JSON',
|
|
233
|
-
recovery: error.message
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const outPath = resolveOutPath(options.out, workspaceConfig, cliPaths, cwd);
|
|
238
|
-
const regenBy = await resolveRegenBy();
|
|
239
|
-
|
|
240
|
-
if (options.verbose) {
|
|
241
|
-
console.log(chalk.gray(`[briefer] outPath=${outPath}`));
|
|
242
|
-
console.log(chalk.gray(`[briefer] regenBy=${regenBy}`));
|
|
243
|
-
console.log(chalk.gray(`[briefer] extractors=${EXTRACTORS.length}`));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Build the briefer doc. Each extractor is run sequentially; M2 can
|
|
247
|
-
// parallelize if hot-path latency matters (it likely won't for 7 sources).
|
|
248
|
-
let doc;
|
|
249
|
-
try {
|
|
250
|
-
doc = await buildBrieferDoc({ env, cliPaths, regenBy });
|
|
251
|
-
} catch (err) {
|
|
252
|
-
if (err instanceof BrieferExtractorError) {
|
|
253
|
-
console.error(chalk.red(`\n❌ Briefer extraction failed:\n`));
|
|
254
|
-
console.error(chalk.red(err.message));
|
|
255
|
-
console.error('');
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
throw err;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (options.check) {
|
|
262
|
-
return runCheckMode({ doc, outPath, verbose: options.verbose });
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Write mode — overwrite the canonical briefer file.
|
|
266
|
-
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
267
|
-
await fs.writeFile(outPath, doc.markdown, 'utf-8');
|
|
268
|
-
|
|
269
|
-
console.log(chalk.green(`\n✅ Briefer regenerated\n`));
|
|
270
|
-
console.log(chalk.cyan(` Env: ${env}`));
|
|
271
|
-
console.log(chalk.gray (` Mechanism: ${doc.mechanism}`));
|
|
272
|
-
console.log(chalk.gray (` By: ${regenBy}`));
|
|
273
|
-
console.log(chalk.gray (` Out: ${outPath}`));
|
|
274
|
-
console.log(chalk.gray (` Sections: ${doc.sections.length}`));
|
|
275
|
-
console.log(chalk.gray (` Citations: ${doc.citationTrail.length}\n`));
|
|
276
|
-
return doc;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export async function runCheckMode({ doc, outPath, verbose }) {
|
|
280
|
-
let canonical = null;
|
|
281
|
-
try {
|
|
282
|
-
canonical = await fs.readFile(outPath, 'utf-8');
|
|
283
|
-
} catch (err) {
|
|
284
|
-
if (err.code === 'ENOENT') {
|
|
285
|
-
console.error(chalk.red(`\n❌ --check failed: canonical briefer not found at ${outPath}\n`));
|
|
286
|
-
console.error(chalk.gray(' Run `descix briefer` (without --check) to create it.\n'));
|
|
287
|
-
process.exit(2);
|
|
288
|
-
}
|
|
289
|
-
throw err;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (canonical === doc.markdown) {
|
|
293
|
-
console.log(chalk.green(`\n✅ Briefer is in sync — no drift detected.\n`));
|
|
294
|
-
return { drift: false, doc };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Drift detected. Print a unified-style diff and exit non-zero.
|
|
298
|
-
// Per scope doc §2.2 §3, --check also compares the citation trail. For M1
|
|
299
|
-
// we report both prose-drift and citation-drift counts.
|
|
300
|
-
const canonicalTrail = extractCitationTrail(canonical);
|
|
301
|
-
const regenTrail = doc.citationTrail;
|
|
302
|
-
const trailChanged = (canonicalTrail.length !== regenTrail.length) ||
|
|
303
|
-
canonicalTrail.some((c, i) => c !== regenTrail[i]);
|
|
304
|
-
|
|
305
|
-
console.error(chalk.yellow(`\n⚠ [${BRIEFER_ERROR_CODES.DRIFT_DETECTED}] Briefer drift detected.\n`));
|
|
306
|
-
console.error(chalk.gray(` Canonical: ${outPath}`));
|
|
307
|
-
console.error(chalk.gray(` Regen length: ${doc.markdown.length} bytes`));
|
|
308
|
-
console.error(chalk.gray(` Canon length: ${canonical.length} bytes`));
|
|
309
|
-
console.error(chalk.gray(` Citation trail drift: ${trailChanged ? 'YES' : 'no'}\n`));
|
|
310
|
-
|
|
311
|
-
if (verbose) {
|
|
312
|
-
console.error(simpleLineDiff(canonical, doc.markdown, { aLabel: outPath, bLabel: 'regen (in-memory)' }));
|
|
313
|
-
console.error('');
|
|
314
|
-
} else {
|
|
315
|
-
console.error(chalk.gray(' (Run with --verbose for the full diff.)\n'));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// M2: drift means a source-of-truth file changed shape since last regen.
|
|
319
|
-
// Caller should regenerate (run `descix briefer` without --check) and commit
|
|
320
|
-
// the updated canonical briefer.
|
|
321
|
-
process.exit(1);
|
|
322
|
-
}
|