dxcomplete 0.1.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 (121) hide show
  1. package/.env.example +11 -0
  2. package/README.md +215 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +212 -0
  5. package/dist/http/server.d.ts +7 -0
  6. package/dist/http/server.js +236 -0
  7. package/dist/http/service.d.ts +7 -0
  8. package/dist/http/service.js +725 -0
  9. package/dist/init.d.ts +13 -0
  10. package/dist/init.js +128 -0
  11. package/dist/install-manifest.d.ts +25 -0
  12. package/dist/install-manifest.js +96 -0
  13. package/dist/mcp/docs.d.ts +98 -0
  14. package/dist/mcp/docs.js +438 -0
  15. package/dist/mcp/server.d.ts +20 -0
  16. package/dist/mcp/server.js +2345 -0
  17. package/dist/package-root.d.ts +2 -0
  18. package/dist/package-root.js +28 -0
  19. package/dist/runtime/actor.d.ts +14 -0
  20. package/dist/runtime/actor.js +42 -0
  21. package/dist/runtime/auth.d.ts +162 -0
  22. package/dist/runtime/auth.js +394 -0
  23. package/dist/runtime/check.d.ts +7 -0
  24. package/dist/runtime/check.js +16 -0
  25. package/dist/runtime/config.d.ts +17 -0
  26. package/dist/runtime/config.js +93 -0
  27. package/dist/runtime/mongo.d.ts +9 -0
  28. package/dist/runtime/mongo.js +56 -0
  29. package/dist/runtime/records.d.ts +336 -0
  30. package/dist/runtime/records.js +1463 -0
  31. package/dist/runtime/workspace.d.ts +19 -0
  32. package/dist/runtime/workspace.js +102 -0
  33. package/dist/upgrade.d.ts +20 -0
  34. package/dist/upgrade.js +246 -0
  35. package/dist/validate.d.ts +10 -0
  36. package/dist/validate.js +119 -0
  37. package/dist/version.d.ts +3 -0
  38. package/dist/version.js +12 -0
  39. package/docs/codex-integration.md +29 -0
  40. package/docs/cost-model.md +61 -0
  41. package/docs/decision-basis.md +57 -0
  42. package/docs/diagrams.md +31 -0
  43. package/docs/glossary.md +147 -0
  44. package/docs/index.md +60 -0
  45. package/docs/model.md +110 -0
  46. package/docs/open-questions.md +61 -0
  47. package/docs/roles.md +42 -0
  48. package/docs/taxonomy.md +96 -0
  49. package/docs/workflows.md +60 -0
  50. package/package.json +62 -0
  51. package/scripts/check-env-surface.mjs +136 -0
  52. package/scripts/check-public-copy.mjs +263 -0
  53. package/scripts/check-service-boundary.mjs +63 -0
  54. package/scripts/dogfood-work-order.mjs +506 -0
  55. package/scripts/smoke-mcp-http.mjs +3572 -0
  56. package/src/cli.ts +268 -0
  57. package/src/http/server.ts +314 -0
  58. package/src/http/service.ts +934 -0
  59. package/src/init.ts +227 -0
  60. package/src/install-manifest.ts +144 -0
  61. package/src/mcp/docs.ts +557 -0
  62. package/src/mcp/server.ts +3525 -0
  63. package/src/package-root.ts +31 -0
  64. package/src/runtime/actor.ts +61 -0
  65. package/src/runtime/auth.ts +673 -0
  66. package/src/runtime/check.ts +18 -0
  67. package/src/runtime/config.ts +128 -0
  68. package/src/runtime/mongo.ts +89 -0
  69. package/src/runtime/records.ts +2303 -0
  70. package/src/runtime/workspace.ts +155 -0
  71. package/src/upgrade.ts +356 -0
  72. package/src/validate.ts +139 -0
  73. package/src/version.ts +16 -0
  74. package/templates/github/workflows/dxcomplete.yml +16 -0
  75. package/templates/next/pages/api/auth/callback/google.js +12 -0
  76. package/templates/next/pages/api/dxcomplete/[...path].js +12 -0
  77. package/templates/next/pages/api/dxcomplete.js +12 -0
  78. package/templates/next/pages/api/mcp.js +12 -0
  79. package/templates/next/vercel.json +18 -0
  80. package/templates/process/README.md +38 -0
  81. package/templates/process/controls.yml +113 -0
  82. package/templates/process/cost-model.yml +71 -0
  83. package/templates/process/decision-basis.yml +53 -0
  84. package/templates/process/decisions/.gitkeep +1 -0
  85. package/templates/process/diagrams/00-decision-basis.mmd +24 -0
  86. package/templates/process/diagrams/00-overview.mmd +20 -0
  87. package/templates/process/diagrams/01-intake-triage.mmd +20 -0
  88. package/templates/process/diagrams/02-product-definition.mmd +14 -0
  89. package/templates/process/diagrams/03-engineering-execution.mmd +15 -0
  90. package/templates/process/diagrams/04-qa-verification.mmd +12 -0
  91. package/templates/process/diagrams/05-product-validation.mmd +12 -0
  92. package/templates/process/diagrams/06-change-release-control.mmd +16 -0
  93. package/templates/process/diagrams/07-deployment-operations.mmd +16 -0
  94. package/templates/process/diagrams/08-support-incident-management.mmd +16 -0
  95. package/templates/process/diagrams/09-problem-improvement.mmd +14 -0
  96. package/templates/process/diagrams/10-risk-control-management.mmd +14 -0
  97. package/templates/process/diagrams/11-audit-evidence-capture.mmd +13 -0
  98. package/templates/process/evidence/.gitkeep +1 -0
  99. package/templates/process/risks/.gitkeep +1 -0
  100. package/templates/process/roles.yml +96 -0
  101. package/templates/process/taxonomy.yml +514 -0
  102. package/templates/process/workflows.yml +210 -0
  103. package/website/.well-known/oauth-authorization-server +22 -0
  104. package/website/.well-known/oauth-protected-resource/api/dxcomplete/mcp +10 -0
  105. package/website/.well-known/oauth-protected-resource/api/mcp +10 -0
  106. package/website/README.md +12 -0
  107. package/website/app.js +36 -0
  108. package/website/flow.html +85 -0
  109. package/website/glossary.html +280 -0
  110. package/website/index.html +90 -0
  111. package/website/objects.html +287 -0
  112. package/website/outcomes.html +117 -0
  113. package/website/phase-build.html +101 -0
  114. package/website/phase-elicit.html +102 -0
  115. package/website/phase-go-live.html +103 -0
  116. package/website/phase-measure.html +93 -0
  117. package/website/phase-operate.html +102 -0
  118. package/website/phase-orient.html +92 -0
  119. package/website/phase-weigh.html +98 -0
  120. package/website/roles.html +52 -0
  121. package/website/styles.css +1169 -0
@@ -0,0 +1,19 @@
1
+ export type WorkspaceBootstrapMember = {
2
+ email: string;
3
+ roles: WorkspaceBootstrapRole[];
4
+ role?: "owner" | "member";
5
+ };
6
+ export type WorkspaceConfig = {
7
+ workspaceId: string;
8
+ name: string;
9
+ mode?: WorkspaceMode;
10
+ bootstrapMembers: WorkspaceBootstrapMember[];
11
+ };
12
+ export type WorkspaceMode = "transformation" | "greenfield" | "limited-disclosure";
13
+ export type WorkspaceBootstrapRole = "owner" | "engineer" | "tester" | "operator" | "support_agent" | "end_user";
14
+ export type WorkspaceConfigOptions = {
15
+ cwd?: string;
16
+ configPath?: string;
17
+ };
18
+ export declare function loadWorkspaceConfig(options?: WorkspaceConfigOptions): Promise<WorkspaceConfig>;
19
+ export declare function parseWorkspaceConfig(value: unknown, source?: string): WorkspaceConfig;
@@ -0,0 +1,102 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { normalizeEmail } from "./actor.js";
5
+ const DEFAULT_WORKSPACE_CONFIG = path.join("dxcomplete", "workspace.json");
6
+ export async function loadWorkspaceConfig(options = {}) {
7
+ const cwd = path.resolve(options.cwd ?? process.cwd());
8
+ const configPath = path.resolve(cwd, options.configPath ?? DEFAULT_WORKSPACE_CONFIG);
9
+ if (!(await fileExists(configPath))) {
10
+ throw new Error(`DX Complete workspace config not found: ${configPath}`);
11
+ }
12
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
13
+ return parseWorkspaceConfig(parsed, configPath);
14
+ }
15
+ export function parseWorkspaceConfig(value, source = "workspace config") {
16
+ if (!value || typeof value !== "object") {
17
+ throw new Error(`${source} must be a JSON object.`);
18
+ }
19
+ const input = value;
20
+ const workspaceId = readRequiredString(input.workspaceId, "workspaceId", source);
21
+ const name = readRequiredString(input.name, "name", source);
22
+ const mode = parseWorkspaceMode(input.mode, source);
23
+ const bootstrapMembers = parseBootstrapMembers(input.bootstrapMembers, source);
24
+ return {
25
+ workspaceId,
26
+ name,
27
+ ...(mode ? { mode } : {}),
28
+ bootstrapMembers
29
+ };
30
+ }
31
+ function parseWorkspaceMode(value, source) {
32
+ if (value === undefined) {
33
+ return undefined;
34
+ }
35
+ if (value === "transformation" || value === "greenfield" || value === "limited-disclosure") {
36
+ return value;
37
+ }
38
+ throw new Error(`${source} mode must be transformation, greenfield, or limited-disclosure when provided.`);
39
+ }
40
+ function parseBootstrapMembers(value, source) {
41
+ if (value === undefined) {
42
+ return [];
43
+ }
44
+ if (!Array.isArray(value)) {
45
+ throw new Error(`${source} bootstrapMembers must be an array when provided.`);
46
+ }
47
+ return value.map((entry, index) => {
48
+ if (!entry || typeof entry !== "object") {
49
+ throw new Error(`${source} bootstrapMembers[${index}] must be an object.`);
50
+ }
51
+ const record = entry;
52
+ const email = normalizeEmail(readRequiredString(record.email, `bootstrapMembers[${index}].email`, source));
53
+ const roles = parseBootstrapMemberRoles(record, `bootstrapMembers[${index}]`, source);
54
+ return { email, roles };
55
+ });
56
+ }
57
+ function parseBootstrapMemberRoles(record, keyPrefix, source) {
58
+ if (Array.isArray(record.roles)) {
59
+ const roles = record.roles.map((role, roleIndex) => parseBootstrapRole(role, `${keyPrefix}.roles[${roleIndex}]`, source));
60
+ return uniqueRoles(roles);
61
+ }
62
+ if (record.role === "owner") {
63
+ return ["owner"];
64
+ }
65
+ if (record.role === "member") {
66
+ return ["end_user"];
67
+ }
68
+ throw new Error(`${source} ${keyPrefix}.roles must contain at least one valid workspace role.`);
69
+ }
70
+ function parseBootstrapRole(value, key, source) {
71
+ if (value === "owner" ||
72
+ value === "engineer" ||
73
+ value === "tester" ||
74
+ value === "operator" ||
75
+ value === "support_agent" ||
76
+ value === "end_user") {
77
+ return value;
78
+ }
79
+ throw new Error(`${source} ${key} must be owner, engineer, tester, operator, support_agent, or end_user.`);
80
+ }
81
+ function uniqueRoles(roles) {
82
+ const unique = [...new Set(roles)];
83
+ if (unique.length === 0) {
84
+ throw new Error("workspace config bootstrap member roles must contain at least one role.");
85
+ }
86
+ return unique.sort();
87
+ }
88
+ function readRequiredString(value, key, source) {
89
+ if (typeof value !== "string" || !value.trim()) {
90
+ throw new Error(`${source} ${key} must be a non-empty string.`);
91
+ }
92
+ return value.trim();
93
+ }
94
+ async function fileExists(filePath) {
95
+ try {
96
+ await access(filePath, fsConstants.F_OK);
97
+ return true;
98
+ }
99
+ catch {
100
+ return false;
101
+ }
102
+ }
@@ -0,0 +1,20 @@
1
+ import { type UpgradeManagedFile } from "./install-manifest.js";
2
+ export type UpgradeOptions = {
3
+ targetDir: string;
4
+ apply?: boolean;
5
+ force?: boolean;
6
+ };
7
+ export type UpgradeResult = {
8
+ targetDir: string;
9
+ applied: boolean;
10
+ packageVersion: string;
11
+ workspaceCompatibility: number;
12
+ written: string[];
13
+ planned: string[];
14
+ unchanged: string[];
15
+ manualReview: string[];
16
+ userOwnedDrift: string[];
17
+ manifestWritten: boolean;
18
+ };
19
+ export declare function upgradeProject(options: UpgradeOptions): Promise<UpgradeResult>;
20
+ export declare function getUpgradeManagedFiles(packageRoot: string, targetDir: string): UpgradeManagedFile[];
@@ -0,0 +1,246 @@
1
+ import { copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { INSTALL_MANIFEST_SCHEMA_VERSION, INSTALL_MANIFEST_PATH, hashContent, hashFile, readInstallManifest, writeInstallManifest } from "./install-manifest.js";
4
+ import { fileExists, resolvePackageRoot } from "./package-root.js";
5
+ import { DXCOMPLETE_PACKAGE_VERSION, WORKSPACE_COMPATIBILITY_VERSION } from "./version.js";
6
+ const WELL_KNOWN_REWRITES = [
7
+ {
8
+ source: "/.well-known/oauth-authorization-server",
9
+ destination: "/api/dxcomplete/.well-known/oauth-authorization-server"
10
+ },
11
+ {
12
+ source: "/.well-known/oauth-protected-resource/api/mcp",
13
+ destination: "/api/dxcomplete/.well-known/oauth-protected-resource/api/mcp"
14
+ }
15
+ ];
16
+ const WORKSPACE_CONFIG_PATH = path.join("dxcomplete", "workspace.json");
17
+ export async function upgradeProject(options) {
18
+ const targetDir = path.resolve(options.targetDir);
19
+ const apply = options.apply ?? false;
20
+ const force = options.force ?? false;
21
+ await assertExistingDxcompleteInstall(targetDir);
22
+ const packageRoot = await resolvePackageRoot();
23
+ const managedFiles = getUpgradeManagedFiles(packageRoot, targetDir);
24
+ const manifest = await readInstallManifest(targetDir);
25
+ const result = {
26
+ targetDir,
27
+ applied: apply,
28
+ packageVersion: DXCOMPLETE_PACKAGE_VERSION,
29
+ workspaceCompatibility: WORKSPACE_COMPATIBILITY_VERSION,
30
+ written: [],
31
+ planned: [],
32
+ unchanged: [],
33
+ manualReview: [],
34
+ userOwnedDrift: [],
35
+ manifestWritten: false
36
+ };
37
+ if (apply) {
38
+ const preview = await upgradeProject({ targetDir, apply: false, force });
39
+ if (preview.manualReview.length > 0) {
40
+ return {
41
+ ...preview,
42
+ applied: true,
43
+ written: [],
44
+ planned: [],
45
+ manifestWritten: false
46
+ };
47
+ }
48
+ }
49
+ for (const file of managedFiles) {
50
+ if (file.strategy === "merge-vercel") {
51
+ await upgradeVercelConfig(file, { apply, force, manifest }, result);
52
+ continue;
53
+ }
54
+ await upgradeCopiedFile(file, { apply, force, manifest }, result);
55
+ }
56
+ result.userOwnedDrift.push(...(await collectUserOwnedDrift(packageRoot, targetDir)));
57
+ const manifestNeedsWrite = result.planned.length > 0 || result.written.length > 0 || shouldWriteManifest(manifest, managedFiles);
58
+ if (apply && result.manualReview.length === 0 && manifestNeedsWrite) {
59
+ await writeInstallManifest(targetDir, managedFiles);
60
+ result.written.push(INSTALL_MANIFEST_PATH);
61
+ result.manifestWritten = true;
62
+ }
63
+ else if (!apply && manifestNeedsWrite) {
64
+ result.planned.push(INSTALL_MANIFEST_PATH);
65
+ }
66
+ return result;
67
+ }
68
+ async function assertExistingDxcompleteInstall(targetDir) {
69
+ if (await fileExists(path.join(targetDir, WORKSPACE_CONFIG_PATH))) {
70
+ return;
71
+ }
72
+ throw new Error(`DX Complete install not found in ${targetDir}. Run dxcomplete init before dxcomplete upgrade.`);
73
+ }
74
+ function shouldWriteManifest(manifest, files) {
75
+ if (!manifest) {
76
+ return true;
77
+ }
78
+ if (manifest.schemaVersion !== INSTALL_MANIFEST_SCHEMA_VERSION ||
79
+ manifest.packageVersion !== DXCOMPLETE_PACKAGE_VERSION ||
80
+ manifest.workspaceCompatibility !== WORKSPACE_COMPATIBILITY_VERSION) {
81
+ return true;
82
+ }
83
+ return files.some((file) => !manifest.managedFiles[file.destinationRelative]);
84
+ }
85
+ export function getUpgradeManagedFiles(packageRoot, targetDir) {
86
+ const files = [
87
+ {
88
+ sourceRelative: path.join("templates", "next", "pages", "api", "mcp.js"),
89
+ destinationRelative: path.join("pages", "api", "mcp.js"),
90
+ strategy: "copy"
91
+ },
92
+ {
93
+ sourceRelative: path.join("templates", "next", "pages", "api", "dxcomplete.js"),
94
+ destinationRelative: path.join("pages", "api", "dxcomplete.js"),
95
+ strategy: "copy"
96
+ },
97
+ {
98
+ sourceRelative: path.join("templates", "next", "pages", "api", "dxcomplete", "[...path].js"),
99
+ destinationRelative: path.join("pages", "api", "dxcomplete", "[...path].js"),
100
+ strategy: "copy"
101
+ },
102
+ {
103
+ sourceRelative: path.join("templates", "next", "pages", "api", "auth", "callback", "google.js"),
104
+ destinationRelative: path.join("pages", "api", "auth", "callback", "google.js"),
105
+ strategy: "copy"
106
+ },
107
+ {
108
+ sourceRelative: path.join("templates", "next", "vercel.json"),
109
+ destinationRelative: "vercel.json",
110
+ strategy: "merge-vercel"
111
+ }
112
+ ];
113
+ return files.map((file) => ({
114
+ ...file,
115
+ sourcePath: path.join(packageRoot, file.sourceRelative),
116
+ destinationPath: path.join(targetDir, file.destinationRelative)
117
+ }));
118
+ }
119
+ async function upgradeCopiedFile(file, options, result) {
120
+ const desiredHash = await hashFile(file.sourcePath);
121
+ const exists = await fileExists(file.destinationPath);
122
+ const currentHash = exists ? await hashFile(file.destinationPath) : undefined;
123
+ if (currentHash === desiredHash) {
124
+ result.unchanged.push(file.destinationRelative);
125
+ return;
126
+ }
127
+ const previousHash = options.manifest?.managedFiles[file.destinationRelative]?.sha256;
128
+ if (exists && !options.force && currentHash !== previousHash) {
129
+ result.manualReview.push(previousHash
130
+ ? `${file.destinationRelative} (modified since last DX Complete install)`
131
+ : `${file.destinationRelative} (differs from the current scaffold and no install manifest is available)`);
132
+ return;
133
+ }
134
+ if (!options.apply) {
135
+ result.planned.push(file.destinationRelative);
136
+ return;
137
+ }
138
+ await mkdir(path.dirname(file.destinationPath), { recursive: true });
139
+ await copyFile(file.sourcePath, file.destinationPath);
140
+ result.written.push(file.destinationRelative);
141
+ }
142
+ async function upgradeVercelConfig(file, options, result) {
143
+ const exists = await fileExists(file.destinationPath);
144
+ const templateContent = await readFile(file.sourcePath, "utf8");
145
+ let desiredContent = templateContent;
146
+ if (exists) {
147
+ const currentContent = await readFile(file.destinationPath, "utf8");
148
+ try {
149
+ desiredContent = `${JSON.stringify(mergeVercelConfig(JSON.parse(currentContent)), null, 2)}\n`;
150
+ }
151
+ catch {
152
+ if (!options.force) {
153
+ result.manualReview.push(`${file.destinationRelative} (invalid JSON; cannot merge safely)`);
154
+ return;
155
+ }
156
+ }
157
+ if (hashContent(currentContent) === hashContent(desiredContent)) {
158
+ result.unchanged.push(file.destinationRelative);
159
+ return;
160
+ }
161
+ }
162
+ if (!options.apply) {
163
+ result.planned.push(file.destinationRelative);
164
+ return;
165
+ }
166
+ await writeFile(file.destinationPath, desiredContent, "utf8");
167
+ result.written.push(file.destinationRelative);
168
+ }
169
+ function mergeVercelConfig(value) {
170
+ const config = isPlainObject(value) ? { ...value } : {};
171
+ const functions = isPlainObject(config.functions) ? { ...config.functions } : {};
172
+ const pagesFunctionKey = "pages/api/**/*.js";
173
+ const pagesFunction = isPlainObject(functions[pagesFunctionKey])
174
+ ? { ...functions[pagesFunctionKey] }
175
+ : {};
176
+ pagesFunction.includeFiles = mergeIncludeFiles(pagesFunction.includeFiles, "dxcomplete/workspace.json");
177
+ functions[pagesFunctionKey] = pagesFunction;
178
+ config.functions = functions;
179
+ config.rewrites = mergeRewrites(config.rewrites);
180
+ return config;
181
+ }
182
+ function mergeIncludeFiles(value, requiredFile) {
183
+ if (typeof value === "string") {
184
+ return value === requiredFile ? value : uniqueStrings([value, requiredFile]);
185
+ }
186
+ if (Array.isArray(value)) {
187
+ return uniqueStrings([...value.filter((entry) => typeof entry === "string"), requiredFile]);
188
+ }
189
+ return requiredFile;
190
+ }
191
+ function mergeRewrites(value) {
192
+ const rewrites = Array.isArray(value)
193
+ ? value.filter((entry) => isPlainObject(entry)).map((entry) => ({ ...entry }))
194
+ : [];
195
+ for (const requiredRewrite of WELL_KNOWN_REWRITES) {
196
+ const existing = rewrites.find((rewrite) => rewrite.source === requiredRewrite.source);
197
+ if (existing) {
198
+ existing.destination = requiredRewrite.destination;
199
+ }
200
+ else {
201
+ rewrites.push({ ...requiredRewrite });
202
+ }
203
+ }
204
+ return rewrites;
205
+ }
206
+ async function collectUserOwnedDrift(packageRoot, targetDir) {
207
+ const files = [
208
+ ...(await collectTemplateFiles(path.join(packageRoot, "docs"), path.join(targetDir, "dxcomplete", "docs"), path.join("dxcomplete", "docs"))),
209
+ ...(await collectTemplateFiles(path.join(packageRoot, "templates", "process"), path.join(targetDir, "dxcomplete", "process"), path.join("dxcomplete", "process")))
210
+ ];
211
+ const drift = [];
212
+ for (const file of files) {
213
+ if (!(await fileExists(file.destinationPath))) {
214
+ drift.push(`${file.destinationRelative} (missing user-owned scaffold file)`);
215
+ continue;
216
+ }
217
+ if ((await hashFile(file.sourcePath)) !== (await hashFile(file.destinationPath))) {
218
+ drift.push(`${file.destinationRelative} (differs from current package template)`);
219
+ }
220
+ }
221
+ return drift;
222
+ }
223
+ async function collectTemplateFiles(sourceDir, destinationDir, destinationRootRelative) {
224
+ const entries = await readdir(sourceDir, { withFileTypes: true });
225
+ const files = [];
226
+ for (const entry of entries) {
227
+ const sourcePath = path.join(sourceDir, entry.name);
228
+ const destinationPath = path.join(destinationDir, entry.name);
229
+ const destinationRelative = path.join(destinationRootRelative, entry.name);
230
+ if (entry.isDirectory()) {
231
+ files.push(...(await collectTemplateFiles(sourcePath, destinationPath, destinationRelative)));
232
+ continue;
233
+ }
234
+ if (!entry.isFile()) {
235
+ continue;
236
+ }
237
+ files.push({ sourcePath, destinationPath, destinationRelative });
238
+ }
239
+ return files;
240
+ }
241
+ function isPlainObject(value) {
242
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
243
+ }
244
+ function uniqueStrings(values) {
245
+ return [...new Set(values)];
246
+ }
@@ -0,0 +1,10 @@
1
+ export type ValidateOptions = {
2
+ targetDir: string;
3
+ packageLayout?: boolean;
4
+ };
5
+ export type ValidateResult = {
6
+ targetDir: string;
7
+ ok: boolean;
8
+ missing: string[];
9
+ };
10
+ export declare function validateScaffold(options: ValidateOptions): Promise<ValidateResult>;
@@ -0,0 +1,119 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { access } from "node:fs/promises";
3
+ import path from "node:path";
4
+ const docs = [
5
+ "index.md",
6
+ "decision-basis.md",
7
+ "cost-model.md",
8
+ "glossary.md",
9
+ "model.md",
10
+ "roles.md",
11
+ "taxonomy.md",
12
+ "workflows.md",
13
+ "diagrams.md",
14
+ "codex-integration.md",
15
+ "open-questions.md"
16
+ ];
17
+ const processFiles = [
18
+ "README.md",
19
+ "decision-basis.yml",
20
+ "cost-model.yml",
21
+ "roles.yml",
22
+ "taxonomy.yml",
23
+ "workflows.yml",
24
+ "controls.yml"
25
+ ];
26
+ const diagrams = [
27
+ "00-decision-basis.mmd",
28
+ "00-overview.mmd",
29
+ "01-intake-triage.mmd",
30
+ "02-product-definition.mmd",
31
+ "03-engineering-execution.mmd",
32
+ "04-qa-verification.mmd",
33
+ "05-product-validation.mmd",
34
+ "06-change-release-control.mmd",
35
+ "07-deployment-operations.mmd",
36
+ "08-support-incident-management.mmd",
37
+ "09-problem-improvement.mmd",
38
+ "10-risk-control-management.mmd",
39
+ "11-audit-evidence-capture.mmd"
40
+ ];
41
+ export async function validateScaffold(options) {
42
+ const targetDir = path.resolve(options.targetDir);
43
+ const requiredFiles = options.packageLayout
44
+ ? packageRequiredFiles()
45
+ : installedRequiredFiles();
46
+ const missing = [];
47
+ for (const requiredFile of requiredFiles) {
48
+ if (!(await fileExists(path.join(targetDir, requiredFile)))) {
49
+ missing.push(requiredFile);
50
+ }
51
+ }
52
+ return {
53
+ targetDir,
54
+ ok: missing.length === 0,
55
+ missing
56
+ };
57
+ }
58
+ function packageRequiredFiles() {
59
+ return [
60
+ ...docs.map((file) => path.join("docs", file)),
61
+ ...processFiles.map((file) => path.join("templates", "process", file)),
62
+ ...diagrams.map((file) => path.join("templates", "process", "diagrams", file)),
63
+ ".env.example",
64
+ path.join("api", "mcp.js"),
65
+ path.join("api", "dxcomplete.js"),
66
+ path.join("api", "dxcomplete-service.js"),
67
+ path.join("api", "auth", "callback", "google.js"),
68
+ path.join("dxcomplete", "workspace.json"),
69
+ path.join("scripts", "check-env-surface.mjs"),
70
+ path.join("scripts", "smoke-mcp-http.mjs"),
71
+ path.join("scripts", "dogfood-work-order.mjs"),
72
+ path.join("scripts", "check-public-copy.mjs"),
73
+ path.join("scripts", "check-service-boundary.mjs"),
74
+ path.join("templates", "next", "pages", "api", "mcp.js"),
75
+ path.join("templates", "next", "pages", "api", "dxcomplete.js"),
76
+ path.join("templates", "next", "pages", "api", "dxcomplete", "[...path].js"),
77
+ path.join("templates", "next", "pages", "api", "auth", "callback", "google.js"),
78
+ path.join("templates", "next", "vercel.json"),
79
+ path.join("templates", "github", "workflows", "dxcomplete.yml"),
80
+ path.join("src", "cli.ts"),
81
+ path.join("src", "install-manifest.ts"),
82
+ path.join("src", "init.ts"),
83
+ path.join("src", "package-root.ts"),
84
+ path.join("src", "http", "server.ts"),
85
+ path.join("src", "http", "service.ts"),
86
+ path.join("src", "mcp", "server.ts"),
87
+ path.join("src", "runtime", "auth.ts"),
88
+ path.join("src", "runtime", "check.ts"),
89
+ path.join("src", "runtime", "config.ts"),
90
+ path.join("src", "runtime", "mongo.ts"),
91
+ path.join("src", "runtime", "records.ts"),
92
+ path.join("src", "runtime", "workspace.ts"),
93
+ path.join("src", "upgrade.ts"),
94
+ path.join("src", "version.ts"),
95
+ path.join("src", "validate.ts")
96
+ ];
97
+ }
98
+ function installedRequiredFiles() {
99
+ return [
100
+ path.join("pages", "api", "mcp.js"),
101
+ path.join("pages", "api", "dxcomplete.js"),
102
+ path.join("pages", "api", "dxcomplete", "[...path].js"),
103
+ path.join("pages", "api", "auth", "callback", "google.js"),
104
+ "vercel.json",
105
+ path.join("dxcomplete", "workspace.json"),
106
+ ...docs.map((file) => path.join("dxcomplete", "docs", file)),
107
+ ...processFiles.map((file) => path.join("dxcomplete", "process", file)),
108
+ ...diagrams.map((file) => path.join("dxcomplete", "process", "diagrams", file))
109
+ ];
110
+ }
111
+ async function fileExists(filePath) {
112
+ try {
113
+ await access(filePath, fsConstants.F_OK);
114
+ return true;
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
@@ -0,0 +1,3 @@
1
+ export declare const DXCOMPLETE_PACKAGE_VERSION: string;
2
+ export declare const WORKSPACE_COMPATIBILITY_VERSION = 1;
3
+ export declare const MCP_SURFACE_ID = "dxc-mcp-surface";
@@ -0,0 +1,12 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const packageJson = require("../package.json");
4
+ export const DXCOMPLETE_PACKAGE_VERSION = readPackageVersion(packageJson.version);
5
+ export const WORKSPACE_COMPATIBILITY_VERSION = 1;
6
+ export const MCP_SURFACE_ID = "dxc-mcp-surface";
7
+ function readPackageVersion(value) {
8
+ if (typeof value !== "string" || !value.trim()) {
9
+ throw new Error("DX Complete package.json version must be a non-empty string.");
10
+ }
11
+ return value;
12
+ }
@@ -0,0 +1,29 @@
1
+ # Codex Integration Draft
2
+
3
+ Codex is an instance of a coding-capable model. It is not a role in the DX Complete operating model.
4
+
5
+ The related role is `Engineer`: the person who performs implementation work directly or drives coding-capable tools. Codex may assist the Engineer.
6
+
7
+ ## Current Hypothesis
8
+
9
+ Codex assistance works primarily on `Task` objects. The Engineer defines or accepts the implementation approach, assigns task scope, reviews outputs, and decides whether work is ready for testing.
10
+
11
+ This does not mean Codex can only work on tasks. Codex may also help draft requirements, generate diagrams, update documentation, collect evidence, or propose workflow revisions. Those uses should remain subject to the owning role.
12
+
13
+ ## Draft Operating Pattern
14
+
15
+ 1. Owner defines the requirement and acceptance criteria.
16
+ 2. Engineer may add non-blocking review notes to the expectation or requirement where delivery input should stay visible.
17
+ 3. Engineer refines the committed requirement into requirement detail and tasks.
18
+ 4. Engineer performs implementation on a task, with Codex assistance where appropriate.
19
+ 5. Engineer reviews the implementation output.
20
+ 6. Tester verifies against requirements and acceptance criteria.
21
+ 7. Owner validates the result.
22
+
23
+ ## Guardrails To Resolve
24
+
25
+ - What level of task detail is required before Codex assistance can begin?
26
+ - Who approves Codex-generated changes?
27
+ - What evidence should Codex produce automatically?
28
+ - How should Codex hand off incomplete or uncertain work?
29
+ - Which tasks are not appropriate for Codex assistance?
@@ -0,0 +1,61 @@
1
+ # Draft Cost Model
2
+
3
+ Cost modeling is first-class in DX Complete. Do not reduce the top-level model to OPEX. OPEX/CAPEX-like categories may exist inside the cost model, but the top-level concept should remain Cost Model / Decision Basis.
4
+
5
+ ## Cost Visibility Rule
6
+
7
+ Cost visibility is mandatory. Complete cost data is not mandatory.
8
+
9
+ The system should make the following clear:
10
+
11
+ - What current-state cost data was attempted.
12
+ - What cost data is known.
13
+ - What cost data is estimated.
14
+ - What cost data is unavailable.
15
+ - What cost data is intentionally undisclosed.
16
+ - What assumptions were used.
17
+
18
+ ## Cost Model Distinction
19
+
20
+ Baseline is what the current state costs now, if known.
21
+
22
+ Estimate is what the proposed future state is expected to cost.
23
+
24
+ Actuals are what the delivered future state actually costs after launch, if measured.
25
+
26
+ The structured `Estimate` record is cost-only. It keeps one-time and recurring cost totals separate by period and currency.
27
+
28
+ Expected value belongs in `Benefits`. Benefits may include quantified items with amounts, or qualitative items that are complete without an amount.
29
+
30
+ ## Draft Cost Objects
31
+
32
+ Current-state cost context should be attempted where relevant. It may be complete, partial, unavailable, or limited by disclosure, but it is no longer a separate runtime record in the current model.
33
+
34
+ `Estimate` records itemized cost information for the scope being weighed.
35
+
36
+ `Benefits` records expected value for the scope being weighed. It may contain quantified or qualitative benefit items.
37
+
38
+ Post-launch cost and benefit observations should be captured where useful, but they are not separate runtime records in the current model.
39
+
40
+ ## Possible Cost Categories
41
+
42
+ These categories are examples, not mandatory fields:
43
+
44
+ - Build labor
45
+ - Codex-assisted implementation cost
46
+ - Testing and validation effort
47
+ - Infrastructure
48
+ - Licenses and tools
49
+ - Migration
50
+ - Training and adoption
51
+ - User support load
52
+ - Operational run cost
53
+ - Maintenance
54
+ - Risk mitigation
55
+ - Decommissioning or replacement
56
+
57
+ ## Measurement Rule
58
+
59
+ Actuals should be captured when available. Missing actuals should not block project or service lifecycle closure, but the absence should be visible.
60
+
61
+ Estimate refinement should use actuals where available and record uncertainty where actuals are unavailable.