dxcomplete 0.2.1 → 0.3.0

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 (89) hide show
  1. package/.env.example +0 -7
  2. package/README.md +68 -103
  3. package/dist/cli.js +2 -24
  4. package/dist/validate.js +10 -26
  5. package/docs/cost-model.md +2 -2
  6. package/docs/decision-basis.md +5 -11
  7. package/docs/diagrams.md +3 -3
  8. package/docs/index.md +25 -39
  9. package/docs/model.md +15 -23
  10. package/docs/open-questions.md +1 -1
  11. package/docs/taxonomy.md +7 -8
  12. package/docs/workflows.md +3 -3
  13. package/package.json +24 -24
  14. package/templates/process/README.md +11 -11
  15. package/templates/process/controls.yml +19 -19
  16. package/templates/process/cost-model.yml +3 -3
  17. package/templates/process/decision-basis.yml +4 -4
  18. package/templates/process/diagrams/00-decision-basis.mmd +1 -1
  19. package/templates/process/diagrams/00-overview.mmd +1 -1
  20. package/templates/process/diagrams/01-intake-triage.mmd +4 -4
  21. package/templates/process/diagrams/02-product-definition.mmd +3 -3
  22. package/templates/process/diagrams/03-engineering-execution.mmd +1 -1
  23. package/templates/process/diagrams/04-qa-verification.mmd +1 -1
  24. package/templates/process/diagrams/05-product-validation.mmd +1 -1
  25. package/templates/process/diagrams/06-change-release-control.mmd +1 -1
  26. package/templates/process/diagrams/07-deployment-operations.mmd +1 -1
  27. package/templates/process/diagrams/08-support-incident-management.mmd +1 -1
  28. package/templates/process/diagrams/09-problem-improvement.mmd +1 -1
  29. package/templates/process/diagrams/10-risk-control-management.mmd +1 -1
  30. package/templates/process/diagrams/11-audit-evidence-capture.mmd +1 -1
  31. package/templates/process/roles.yml +6 -6
  32. package/templates/process/taxonomy.yml +46 -46
  33. package/templates/process/workflows.yml +29 -29
  34. package/website/account.html +57 -0
  35. package/website/app.js +177 -0
  36. package/website/flow.html +4 -0
  37. package/website/glossary.html +4 -0
  38. package/website/index.html +4 -0
  39. package/website/objects.html +4 -0
  40. package/website/operating-guide.html +4 -0
  41. package/website/outcomes.html +4 -0
  42. package/website/phase-build.html +4 -0
  43. package/website/phase-elicit.html +4 -0
  44. package/website/phase-go-live.html +4 -0
  45. package/website/phase-measure.html +4 -0
  46. package/website/phase-operate.html +4 -0
  47. package/website/phase-orient.html +4 -0
  48. package/website/phase-weigh.html +4 -0
  49. package/website/roles.html +4 -0
  50. package/website/styles.css +217 -1
  51. package/dist/http/service.d.ts +0 -7
  52. package/dist/http/service.js +0 -725
  53. package/dist/mcp/docs.d.ts +0 -114
  54. package/dist/mcp/docs.js +0 -626
  55. package/dist/mcp/server.d.ts +0 -20
  56. package/dist/mcp/server.js +0 -3059
  57. package/dist/runtime/auth.d.ts +0 -162
  58. package/dist/runtime/auth.js +0 -394
  59. package/dist/runtime/check.d.ts +0 -7
  60. package/dist/runtime/check.js +0 -16
  61. package/dist/runtime/config.d.ts +0 -17
  62. package/dist/runtime/config.js +0 -93
  63. package/dist/runtime/mongo.d.ts +0 -9
  64. package/dist/runtime/mongo.js +0 -56
  65. package/dist/runtime/records.d.ts +0 -427
  66. package/dist/runtime/records.js +0 -2092
  67. package/scripts/check-env-surface.mjs +0 -136
  68. package/scripts/check-public-copy.mjs +0 -263
  69. package/scripts/check-service-boundary.mjs +0 -63
  70. package/scripts/runtime-work-order.mjs +0 -506
  71. package/scripts/smoke-mcp-http.mjs +0 -4026
  72. package/src/cli.ts +0 -268
  73. package/src/http/server.ts +0 -314
  74. package/src/http/service.ts +0 -934
  75. package/src/init.ts +0 -262
  76. package/src/install-manifest.ts +0 -144
  77. package/src/mcp/docs.ts +0 -777
  78. package/src/mcp/server.ts +0 -4580
  79. package/src/package-root.ts +0 -31
  80. package/src/runtime/actor.ts +0 -61
  81. package/src/runtime/auth.ts +0 -673
  82. package/src/runtime/check.ts +0 -18
  83. package/src/runtime/config.ts +0 -128
  84. package/src/runtime/mongo.ts +0 -89
  85. package/src/runtime/records.ts +0 -3205
  86. package/src/runtime/workspace.ts +0 -155
  87. package/src/upgrade.ts +0 -356
  88. package/src/validate.ts +0 -141
  89. package/src/version.ts +0 -16
@@ -1,136 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
-
7
- const allowedEnvVars = new Map([
8
- ["DXC_MONGODB_URI", "secret"],
9
- ["DXC_DATABASE_NAME", "environment-specific value"],
10
- ["DXC_GOOGLE_CLIENT_ID", "provisioning detail"],
11
- ["DXC_GOOGLE_CLIENT_SECRET", "secret"],
12
- ["DXC_SERVICE_PROVISIONING_SECRET", "secret"],
13
- ["DXC_SERVICE_URL", "environment-specific value"],
14
- ["DXC_SERVICE_CLIENT_ID", "provisioning detail"],
15
- ["DXC_SERVICE_CLIENT_SECRET", "secret"]
16
- ]);
17
-
18
- const allowedClassifications = new Set([
19
- "secret",
20
- "provisioning detail",
21
- "environment-specific value"
22
- ]);
23
-
24
- const scanTargets = [
25
- ".env.example",
26
- "README.md",
27
- "AGENTS.md",
28
- "package.json",
29
- "api",
30
- "scripts",
31
- "src",
32
- "dist",
33
- "docs",
34
- "templates",
35
- "website"
36
- ];
37
-
38
- const docEnvPattern = /\b[A-Z][A-Z0-9]+(?:_[A-Z0-9]+)+\b/g;
39
- const quotedEnvPattern = /["'`]([A-Z][A-Z0-9]+(?:_[A-Z0-9]+)+)["'`]/g;
40
- const failures = [];
41
- const references = new Map();
42
-
43
- for (const [name, classification] of allowedEnvVars) {
44
- if (!allowedClassifications.has(classification)) {
45
- failures.push(`${name}: invalid env classification "${classification}".`);
46
- }
47
- }
48
-
49
- for (const target of scanTargets) {
50
- const targetPath = path.join(rootDir, target);
51
- if (!existsSync(targetPath)) continue;
52
-
53
- for (const filePath of listFiles(targetPath)) {
54
- scanFile(filePath);
55
- }
56
- }
57
-
58
- for (const [name, locations] of references) {
59
- if (!allowedEnvVars.has(name)) {
60
- failures.push(
61
- `${name}: referenced but not allowed. Add it to scripts/check-env-surface.mjs with classification secret, provisioning detail, or environment-specific value, or remove it. First seen at ${locations[0]}.`
62
- );
63
- }
64
- }
65
-
66
- if (failures.length > 0) {
67
- console.error("Environment surface check failed:");
68
- for (const failure of failures) {
69
- console.error(`- ${failure}`);
70
- }
71
- process.exit(1);
72
- }
73
-
74
- console.log("Environment surface check passed.");
75
-
76
- function listFiles(targetPath) {
77
- const stats = statSync(targetPath);
78
- if (stats.isFile()) {
79
- return shouldScan(targetPath) ? [targetPath] : [];
80
- }
81
-
82
- const files = [];
83
- for (const entry of readdirSync(targetPath)) {
84
- if (entry === "node_modules" || entry === ".git") continue;
85
- files.push(...listFiles(path.join(targetPath, entry)));
86
- }
87
- return files;
88
- }
89
-
90
- function shouldScan(filePath) {
91
- const relativePath = path.relative(rootDir, filePath);
92
- if (relativePath === "package-lock.json") return false;
93
-
94
- return [
95
- ".example",
96
- ".html",
97
- ".js",
98
- ".json",
99
- ".md",
100
- ".mjs",
101
- ".ts",
102
- ".yml",
103
- ".yaml"
104
- ].some((extension) => filePath.endsWith(extension));
105
- }
106
-
107
- function scanFile(filePath) {
108
- const relativePath = path.relative(rootDir, filePath);
109
- const content = readFileSync(filePath, "utf8");
110
-
111
- if (relativePath === ".env.example") {
112
- for (const [index, line] of content.split(/\r?\n/).entries()) {
113
- const trimmed = line.trim();
114
- if (!trimmed || trimmed.startsWith("#")) continue;
115
- const key = trimmed.split("=", 1)[0]?.trim();
116
- if (key) addReference(key, `${relativePath}:${index + 1}`);
117
- }
118
- return;
119
- }
120
-
121
- const isSourceLike = /\.(?:js|json|mjs|ts)$/.test(filePath);
122
- const pattern = isSourceLike ? quotedEnvPattern : docEnvPattern;
123
- pattern.lastIndex = 0;
124
-
125
- for (const match of content.matchAll(pattern)) {
126
- const name = isSourceLike ? match[1] : match[0];
127
- addReference(name, relativePath);
128
- }
129
- }
130
-
131
- function addReference(name, location) {
132
- if (!references.has(name)) {
133
- references.set(name, []);
134
- }
135
- references.get(name).push(location);
136
- }
@@ -1,263 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
- const websiteDir = path.join(rootDir, "website");
7
-
8
- const forbiddenPatterns = [
9
- { pattern: /\bMermaid\b/i, reason: "Mermaid/source details are not public-facing copy." },
10
- { pattern: /\bflowchart\b/i, reason: "Diagram source terms are not public-facing copy." },
11
- { pattern: /\bdiagram source\b/i, reason: "Source labels are not public-facing copy." },
12
- { pattern: /\bCLI\b/, reason: "Command-line internals do not belong in public website copy." },
13
- { pattern: /\bMCP\b/, reason: "MCP internals do not belong in public website copy." },
14
- { pattern: /\bMongoDB\b/, reason: "Storage internals do not belong in public website copy." },
15
- { pattern: /\bnpx\b/i, reason: "Install command details do not belong in public website copy." },
16
- { pattern: /\bcommand-line\b/i, reason: "Command-line internals do not belong in public website copy." },
17
- { pattern: /\bbootstrap\b/i, reason: "Implementation language does not belong in public website copy." },
18
- { pattern: /\bscaffold(?:ing)?\b/i, reason: "Scaffold language is maintainer-facing." },
19
- { pattern: /\btemplates?\b/i, reason: "Template language is maintainer-facing." },
20
- { pattern: /\bpackage\b/i, reason: "Package details are maintainer-facing." },
21
- { pattern: /\bimplementation\b/i, reason: "Implementation mechanics are maintainer-facing." },
22
- { pattern: /\bconfiguration\b/i, reason: "Configuration mechanics are maintainer-facing." },
23
- { pattern: /\bdeveloper\b/i, reason: "Developer-facing language should stay out of public pages." },
24
- { pattern: /\binternal\b/i, reason: "Internal-facing language should stay out of public pages." },
25
- { pattern: /\bTODO\b/i, reason: "TODOs should not appear in public website copy." },
26
- { pattern: /\bdraft\b/i, reason: "Draft labels should not appear in public website copy." },
27
- { pattern: /\bplaceholder\b/i, reason: "Placeholder labels should not appear in public website copy." }
28
- ];
29
-
30
- const htmlFiles = readdirSync(websiteDir)
31
- .filter((file) => file.endsWith(".html"))
32
- .sort();
33
-
34
- const jsFiles = readdirSync(websiteDir)
35
- .filter((file) => file.endsWith(".js"))
36
- .sort();
37
-
38
- const mdFiles = readdirSync(websiteDir)
39
- .filter((file) => file.endsWith(".md"))
40
- .sort();
41
-
42
- const failures = [];
43
- const alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
44
-
45
- function visibleTextFromHtml(html) {
46
- return html
47
- .replace(/<script[\s\S]*?<\/script>/gi, " ")
48
- .replace(/<style[\s\S]*?<\/style>/gi, " ")
49
- .replace(/<!--[\s\S]*?-->/g, " ")
50
- .replace(/<[^>]+>/g, " ")
51
- .replace(/&nbsp;/g, " ")
52
- .replace(/&amp;/g, "&")
53
- .replace(/&lt;/g, "<")
54
- .replace(/&gt;/g, ">")
55
- .replace(/&quot;/g, '"')
56
- .replace(/&#39;/g, "'")
57
- .replace(/\s+/g, " ")
58
- .trim();
59
- }
60
-
61
- function addCopyFailures(file, text) {
62
- for (const { pattern, reason } of forbiddenPatterns) {
63
- const match = text.match(pattern);
64
- if (match) {
65
- failures.push({
66
- file,
67
- reason,
68
- detail: `matched "${match[0]}"`
69
- });
70
- }
71
- }
72
- }
73
-
74
- function addBrokenLinkFailures(file, html) {
75
- const linkPattern = /href=["']\.\/([^"'#?]+\.html)(?:[?#][^"']*)?["']/g;
76
- for (const match of html.matchAll(linkPattern)) {
77
- const linkedFile = match[1];
78
- const linkedPath = path.join(websiteDir, linkedFile);
79
- if (!existsSync(linkedPath)) {
80
- failures.push({
81
- file,
82
- reason: "Local website link points to a missing page.",
83
- detail: linkedFile
84
- });
85
- }
86
- }
87
- }
88
-
89
- function plainText(html) {
90
- return html
91
- .replace(/<[^>]+>/g, " ")
92
- .replace(/&nbsp;/g, " ")
93
- .replace(/&amp;/g, "&")
94
- .replace(/&lt;/g, "<")
95
- .replace(/&gt;/g, ">")
96
- .replace(/&quot;/g, '"')
97
- .replace(/&#39;/g, "'")
98
- .replace(/\s+/g, " ")
99
- .trim();
100
- }
101
-
102
- function normalizeTerm(term) {
103
- return term.toLowerCase().replace(/\s+/g, " ").trim();
104
- }
105
-
106
- function compareTerms(left, right) {
107
- return left.localeCompare(right, "en", { sensitivity: "base" });
108
- }
109
-
110
- function addGlossaryFailures() {
111
- const glossaryFile = "glossary.html";
112
- const glossarySource = readFileSync(path.join(websiteDir, glossaryFile), "utf8");
113
- const sectionPattern = /<section id="([a-z])" class="glossary-letter">([\s\S]*?)<\/section>/g;
114
- const sections = [...glossarySource.matchAll(sectionPattern)].map((match) => ({
115
- id: match[1],
116
- body: match[2]
117
- }));
118
- const sectionIds = sections.map((section) => section.id);
119
- const expectedSectionIds = [...sectionIds].sort();
120
-
121
- if (sectionIds.join("") !== expectedSectionIds.join("")) {
122
- failures.push({
123
- file: `website/${glossaryFile}`,
124
- reason: "Glossary letter sections are not in alphabetical order.",
125
- detail: sectionIds.join(", ")
126
- });
127
- }
128
-
129
- const terms = [];
130
- for (const section of sections) {
131
- const sectionTerms = [...section.body.matchAll(/<dt>([\s\S]*?)<span>/g)]
132
- .map((match) => plainText(match[1]));
133
- const expectedTerms = [...sectionTerms].sort(compareTerms);
134
-
135
- if (sectionTerms.join("||") !== expectedTerms.join("||")) {
136
- failures.push({
137
- file: `website/${glossaryFile}`,
138
- reason: `Glossary terms under ${section.id.toUpperCase()} are not alphabetical.`,
139
- detail: sectionTerms.join(", ")
140
- });
141
- }
142
-
143
- for (const term of sectionTerms) {
144
- if (term.charAt(0).toLowerCase() !== section.id) {
145
- failures.push({
146
- file: `website/${glossaryFile}`,
147
- reason: "Glossary term appears under the wrong letter.",
148
- detail: `${term} under ${section.id.toUpperCase()}`
149
- });
150
- }
151
- terms.push(term);
152
- }
153
- }
154
-
155
- const normalizedTerms = terms.map(normalizeTerm);
156
- const duplicateTerms = normalizedTerms.filter((term, index) => normalizedTerms.indexOf(term) !== index);
157
- for (const term of [...new Set(duplicateTerms)]) {
158
- failures.push({
159
- file: `website/${glossaryFile}`,
160
- reason: "Glossary contains a duplicate term.",
161
- detail: term
162
- });
163
- }
164
-
165
- const linkLetters = [...glossarySource.matchAll(/<a href="#([a-z])">[A-Z]<\/a>/g)].map((match) => match[1]);
166
- const disabledLetters = [...glossarySource.matchAll(/<span aria-disabled="true">([A-Z])<\/span>/g)].map((match) => match[1].toLowerCase());
167
- const indexLetters = [...linkLetters, ...disabledLetters].sort();
168
-
169
- if (indexLetters.join("") !== alphabet.join("")) {
170
- failures.push({
171
- file: `website/${glossaryFile}`,
172
- reason: "Glossary A-Z index must include every letter exactly once.",
173
- detail: indexLetters.join("")
174
- });
175
- }
176
-
177
- for (const sectionId of sectionIds) {
178
- if (!linkLetters.includes(sectionId)) {
179
- failures.push({
180
- file: `website/${glossaryFile}`,
181
- reason: "Glossary A-Z index is missing a link for a populated letter.",
182
- detail: sectionId.toUpperCase()
183
- });
184
- }
185
- }
186
-
187
- for (const disabledLetter of disabledLetters) {
188
- if (sectionIds.includes(disabledLetter)) {
189
- failures.push({
190
- file: `website/${glossaryFile}`,
191
- reason: "Glossary A-Z index disables a populated letter.",
192
- detail: disabledLetter.toUpperCase()
193
- });
194
- }
195
- }
196
-
197
- const glossarySet = new Set(normalizedTerms);
198
- for (const requiredTerm of publicTermsThatNeedGlossaryEntries()) {
199
- if (!glossarySet.has(normalizeTerm(requiredTerm))) {
200
- failures.push({
201
- file: `website/${glossaryFile}`,
202
- reason: "Glossary is missing a term used by the public website.",
203
- detail: requiredTerm
204
- });
205
- }
206
- }
207
- }
208
-
209
- function publicTermsThatNeedGlossaryEntries() {
210
- const terms = new Set();
211
-
212
- const recordsSource = readFileSync(path.join(websiteDir, "objects.html"), "utf8");
213
- for (const match of recordsSource.matchAll(/<span class="record-name">([\s\S]*?)<\/span>/g)) {
214
- terms.add(plainText(match[1]));
215
- }
216
- for (const match of recordsSource.matchAll(/<dt>([\s\S]*?)<\/dt>/g)) {
217
- terms.add(plainText(match[1]));
218
- }
219
-
220
- for (const file of htmlFiles.filter((file) => file.startsWith("phase-"))) {
221
- const source = readFileSync(path.join(websiteDir, file), "utf8");
222
- const recordsMatch = source.match(/<h2>Records<\/h2>[\s\S]*?<ul>([\s\S]*?)<\/ul>/);
223
- if (!recordsMatch) continue;
224
- for (const match of recordsMatch[1].matchAll(/<li>([\s\S]*?)<\/li>/g)) {
225
- terms.add(plainText(match[1]));
226
- }
227
- }
228
-
229
- const rolesSource = readFileSync(path.join(websiteDir, "roles.html"), "utf8");
230
- for (const match of rolesSource.matchAll(/<button[^>]*>([\s\S]*?)<\/button>/g)) {
231
- terms.add(plainText(match[1]));
232
- }
233
-
234
- return [...terms].sort(compareTerms);
235
- }
236
-
237
- for (const file of htmlFiles) {
238
- const source = readFileSync(path.join(websiteDir, file), "utf8");
239
- addCopyFailures(`website/${file}`, visibleTextFromHtml(source));
240
- addBrokenLinkFailures(`website/${file}`, source);
241
- }
242
-
243
- for (const file of jsFiles) {
244
- const source = readFileSync(path.join(websiteDir, file), "utf8");
245
- addCopyFailures(`website/${file}`, source);
246
- }
247
-
248
- for (const file of mdFiles) {
249
- const source = readFileSync(path.join(websiteDir, file), "utf8");
250
- addCopyFailures(`website/${file}`, source);
251
- }
252
-
253
- addGlossaryFailures();
254
-
255
- if (failures.length > 0) {
256
- console.error("Public website copy check failed:");
257
- for (const failure of failures) {
258
- console.error(`- ${failure.file}: ${failure.reason} (${failure.detail})`);
259
- }
260
- process.exit(1);
261
- }
262
-
263
- console.log("Public website copy check passed.");
@@ -1,63 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
- const workspaceBoundaryFiles = [
7
- "src/http/server.ts",
8
- "api/mcp.js",
9
- "api/dxcomplete.js",
10
- "api/auth/callback/google.js",
11
- "templates/next/pages/api/mcp.js",
12
- "templates/next/pages/api/dxcomplete.js",
13
- "templates/next/pages/api/dxcomplete/[...path].js",
14
- "templates/next/pages/api/auth/callback/google.js"
15
- ];
16
- const bannedPatterns = [
17
- {
18
- pattern: /\bconnectRuntime\b/,
19
- reason: "workspace MCP code must not open the runtime database"
20
- },
21
- {
22
- pattern: /runtime\/mongo|runtime\\mongo|\.\.\/runtime\/mongo|\.\.\/dist\/runtime\/mongo/,
23
- reason: "workspace MCP code must not import the Mongo runtime"
24
- },
25
- {
26
- pattern: /runtime\/records|runtime\\records|\.\.\/runtime\/records|\.\.\/dist\/runtime\/records/,
27
- reason: "workspace MCP code must not import runtime record modules"
28
- },
29
- {
30
- pattern: /runtime\/auth|runtime\\auth|\.\.\/runtime\/auth|\.\.\/dist\/runtime\/auth/,
31
- reason: "workspace MCP code must not import central auth/storage modules"
32
- },
33
- {
34
- pattern: /\bDXC_MONGODB_URI\b/,
35
- reason: "workspace MCP code must not reference Mongo credentials"
36
- },
37
- {
38
- pattern: /\bDXC_GOOGLE_CLIENT_ID\b|\bDXC_GOOGLE_CLIENT_SECRET\b/,
39
- reason: "workspace MCP code must not reference Google OAuth provisioning secrets"
40
- }
41
- ];
42
- const failures = [];
43
-
44
- for (const relativePath of workspaceBoundaryFiles) {
45
- const absolutePath = path.join(rootDir, relativePath);
46
- const content = readFileSync(absolutePath, "utf8");
47
-
48
- for (const { pattern, reason } of bannedPatterns) {
49
- if (pattern.test(content)) {
50
- failures.push(`${relativePath}: ${reason}.`);
51
- }
52
- }
53
- }
54
-
55
- if (failures.length > 0) {
56
- console.error("Service boundary check failed:");
57
- for (const failure of failures) {
58
- console.error(`- ${failure}`);
59
- }
60
- process.exit(1);
61
- }
62
-
63
- console.log("Service boundary check passed.");