agentic-dev 0.2.3 → 0.2.6

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 (153) hide show
  1. package/.agent/prd.json +1 -1
  2. package/.agent/prompt.md +1 -1
  3. package/.claude/CLAUDE.md +1 -1
  4. package/.claude/agents/db-dev.md +1 -1
  5. package/.claude/agents/frontend-dev.md +1 -1
  6. package/.claude/agents/test-dev.md +1 -1
  7. package/.claude/skills/sdd/SKILL.md +9 -9
  8. package/.claude/skills/sdd/references/section-map.md +5 -5
  9. package/.codex/skills/sdd/SKILL.md +9 -9
  10. package/.codex/skills/sdd/references/section-map.md +5 -5
  11. package/.env.example +2 -2
  12. package/AGENTS.md +4 -4
  13. package/README.md +48 -15
  14. package/SDD_SKILL.md +7 -7
  15. package/bin/agentic-dev.mjs +53 -18
  16. package/client/admin/scripts/ui-parity-admin-adapter.mjs +1 -1
  17. package/client/landing/scripts/ui-parity-landing-adapter.mjs +1 -1
  18. package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +1 -1
  19. package/client/{platform → web}/Dockerfile +3 -3
  20. package/client/web/Dockerfile.dev +18 -0
  21. package/client/{platform → web}/README.md +3 -3
  22. package/client/{platform → web}/index.html +1 -1
  23. package/client/{platform → web}/package.json +7 -7
  24. package/client/{platform/scripts/ui-parity-platform-adapter.mjs → web/scripts/ui-parity-web-adapter.mjs} +8 -8
  25. package/client/{platform → web}/src/auth/AuthProvider.tsx +1 -1
  26. package/compose.yml +6 -6
  27. package/infra/compose/.env.dev.example +3 -3
  28. package/infra/compose/.env.prod.example +3 -3
  29. package/infra/compose/README.md +1 -1
  30. package/infra/compose/dev.yml +5 -5
  31. package/infra/compose/prod.yml +6 -6
  32. package/infra/terraform/openstack/dev/terraform.tfvars.example +3 -3
  33. package/infra/terraform/openstack/prod/terraform.tfvars.example +3 -3
  34. package/lib/scaffold.mjs +254 -279
  35. package/package.json +2 -2
  36. package/scripts/dev/audit_sdd_build_ast.py +9 -9
  37. package/sdd/01_planning/01_feature/auth_feature_spec.md +2 -2
  38. package/sdd/01_planning/01_feature/catalog_feature_spec.md +3 -3
  39. package/sdd/01_planning/01_feature/order_feature_spec.md +11 -11
  40. package/sdd/01_planning/02_screen/INDEX.md +2 -2
  41. package/sdd/01_planning/02_screen/README.md +2 -2
  42. package/sdd/01_planning/03_architecture/templates_system_architecture.md +3 -3
  43. package/sdd/01_planning/05_api/templates_api_contract.md +3 -3
  44. package/sdd/01_planning/06_iac/templates_runtime_and_cicd_baseline.md +1 -1
  45. package/sdd/01_planning/07_integration/templates_frontend_api_integration.md +3 -3
  46. package/sdd/01_planning/10_test/templates_test_strategy.md +2 -2
  47. package/sdd/01_planning/INDEX.md +1 -1
  48. package/sdd/02_plan/02_screen/INDEX.md +1 -1
  49. package/sdd/02_plan/02_screen/README.md +1 -1
  50. package/sdd/02_plan/03_architecture/build_ast_runtime_tree_governance.md +1 -1
  51. package/sdd/02_plan/03_architecture/repository_governance.md +2 -2
  52. package/sdd/02_plan/03_architecture/runtime_and_structure_governance.md +1 -1
  53. package/sdd/02_plan/03_architecture/toolchain_governance.md +2 -2
  54. package/sdd/02_plan/06_iac/dev_runtime_delivery.md +1 -1
  55. package/sdd/02_plan/07_integration/frontend_live_integration.md +3 -3
  56. package/sdd/02_plan/10_test/regression_verification.md +2 -2
  57. package/sdd/02_plan/10_test/templates/{ui_parity_platform_contract.template.yaml → ui_parity_web_contract.template.yaml} +2 -2
  58. package/sdd/02_plan/10_test/verification_strategy.md +2 -2
  59. package/sdd/03_build/01_feature/domain/account_and_access.md +1 -1
  60. package/sdd/03_build/01_feature/domain/catalog_and_inventory.md +1 -1
  61. package/sdd/03_build/01_feature/domain/ordering_and_fulfillment.md +1 -1
  62. package/sdd/03_build/01_feature/service/README.md +1 -1
  63. package/sdd/03_build/01_feature/service/{platform_surface.md → web_surface.md} +3 -3
  64. package/sdd/03_build/02_screen/README.md +1 -1
  65. package/sdd/03_build/02_screen/web/README.md +5 -0
  66. package/sdd/03_build/03_architecture/toolchain_governance.md +1 -1
  67. package/sdd/03_build/06_iac/template_runtime_delivery.md +1 -1
  68. package/sdd/03_build/07_integration/frontend_live_integration.md +1 -1
  69. package/sdd/{04_verify → 03_verify}/01_feature/service_verification.md +3 -3
  70. package/sdd/{04_verify → 03_verify}/02_screen/README.md +1 -1
  71. package/sdd/03_verify/02_screen/web/README.md +4 -0
  72. package/sdd/{04_verify → 03_verify}/03_architecture/toolchain_governance.md +2 -2
  73. package/sdd/{04_verify → 03_verify}/06_iac/dev_runtime_delivery.md +1 -1
  74. package/sdd/{04_verify → 03_verify}/06_iac/template_runtime_delivery.md +4 -4
  75. package/sdd/{04_verify → 03_verify}/README.md +3 -3
  76. package/sdd/99_toolchain/01_automation/README.md +2 -2
  77. package/sdd/99_toolchain/01_automation/agentic-dev/assets/repo-contract.template.json +15 -15
  78. package/sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh +1 -1
  79. package/sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json +16 -16
  80. package/sdd/99_toolchain/01_automation/agentic-parity-harness-design.md +9 -9
  81. package/sdd/99_toolchain/01_automation/capture_screen_assets.mjs +4 -4
  82. package/sdd/99_toolchain/01_automation/harness-layout.md +2 -2
  83. package/sdd/99_toolchain/01_automation/parity-execution-tooling-design.md +7 -7
  84. package/sdd/99_toolchain/01_automation/screen_spec_manifest.py +17 -17
  85. package/sdd/99_toolchain/01_automation/ui-parity/README.md +10 -10
  86. package/sdd/99_toolchain/01_automation/ui-parity/cli/materialize-reference-assets.mjs +1 -1
  87. package/sdd/99_toolchain/01_automation/ui-parity/cli/scaffold-contract.mjs +2 -2
  88. package/sdd/99_toolchain/01_automation/ui-parity/core/proof-runner.mjs +1 -1
  89. package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-artifact-layout.md +2 -2
  90. package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-route-gap-interface.md +2 -2
  91. package/sdd/99_toolchain/02_policies/build-ast-governance-policy.md +2 -2
  92. package/sdd/99_toolchain/02_policies/compose-runtime-baseline-policy.md +2 -2
  93. package/sdd/99_toolchain/02_policies/regression-verification-policy.md +1 -1
  94. package/sdd/99_toolchain/03_templates/playwright_exactness_manifest.example.py +1 -1
  95. package/sdd/99_toolchain/README.md +1 -1
  96. package/sdd/README.md +1 -1
  97. package/server/data/README.md +1 -1
  98. package/client/platform/Dockerfile.dev +0 -18
  99. package/sdd/03_build/02_screen/platform/README.md +0 -5
  100. package/sdd/04_verify/02_screen/platform/README.md +0 -4
  101. /package/client/{platform → web}/.dockerignore +0 -0
  102. /package/client/{platform → web}/.env.example +0 -0
  103. /package/client/{platform → web}/postcss.config.js +0 -0
  104. /package/client/{platform → web}/src/api/client.ts +0 -0
  105. /package/client/{platform → web}/src/api/orders.ts +0 -0
  106. /package/client/{platform → web}/src/app/App.tsx +0 -0
  107. /package/client/{platform → web}/src/auth/ProtectedRoute.tsx +0 -0
  108. /package/client/{platform → web}/src/auth/auth-client.ts +0 -0
  109. /package/client/{platform → web}/src/auth/types.ts +0 -0
  110. /package/client/{platform → web}/src/components/AppShell.tsx +0 -0
  111. /package/client/{platform → web}/src/components/ui/button.tsx +0 -0
  112. /package/client/{platform → web}/src/components/ui/card.tsx +0 -0
  113. /package/client/{platform → web}/src/components/ui/input.tsx +0 -0
  114. /package/client/{platform → web}/src/lib/cn.ts +0 -0
  115. /package/client/{platform → web}/src/lib/specRouteCatalog.json +0 -0
  116. /package/client/{platform → web}/src/lib/specScreens.json +0 -0
  117. /package/client/{platform → web}/src/main.tsx +0 -0
  118. /package/client/{platform → web}/src/pages/DashboardPage.tsx +0 -0
  119. /package/client/{platform → web}/src/pages/LoginPage.tsx +0 -0
  120. /package/client/{platform → web}/src/pages/OrdersPage.tsx +0 -0
  121. /package/client/{platform → web}/src/styles/globals.css +0 -0
  122. /package/client/{platform → web}/src/theme-vars.ts +0 -0
  123. /package/client/{platform → web}/src/theme.ts +0 -0
  124. /package/client/{platform → web}/src/vite-env.d.ts +0 -0
  125. /package/client/{platform → web}/tailwind.config.js +0 -0
  126. /package/client/{platform → web}/tsconfig.json +0 -0
  127. /package/client/{platform → web}/vite.config.ts +0 -0
  128. /package/sdd/01_planning/02_screen/{platform_screen_spec.pdf → web_screen_spec.pdf} +0 -0
  129. /package/sdd/{04_verify → 03_verify}/01_feature/README.md +0 -0
  130. /package/sdd/{04_verify → 03_verify}/01_feature/domain_verification.md +0 -0
  131. /package/sdd/{04_verify → 03_verify}/02_screen/_screen_verify_template.md +0 -0
  132. /package/sdd/{04_verify → 03_verify}/02_screen/admin/README.md +0 -0
  133. /package/sdd/{04_verify → 03_verify}/02_screen/landing/README.md +0 -0
  134. /package/sdd/{04_verify → 03_verify}/02_screen/mobile/README.md +0 -0
  135. /package/sdd/{04_verify → 03_verify}/03_architecture/README.md +0 -0
  136. /package/sdd/{04_verify → 03_verify}/03_architecture/architecture_document_governance.md +0 -0
  137. /package/sdd/{04_verify → 03_verify}/03_architecture/build_ast_runtime_tree_governance.md +0 -0
  138. /package/sdd/{04_verify → 03_verify}/03_architecture/repository_governance.md +0 -0
  139. /package/sdd/{04_verify → 03_verify}/06_iac/README.md +0 -0
  140. /package/sdd/{04_verify → 03_verify}/07_integration/README.md +0 -0
  141. /package/sdd/{04_verify → 03_verify}/07_integration/frontend_live_integration.md +0 -0
  142. /package/sdd/{04_verify → 03_verify}/08_nonfunctional/README.md +0 -0
  143. /package/sdd/{04_verify → 03_verify}/08_nonfunctional/repository_hygiene.md +0 -0
  144. /package/sdd/{04_verify → 03_verify}/10_test/README.md +0 -0
  145. /package/sdd/{04_verify → 03_verify}/10_test/regression_verification.md +0 -0
  146. /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/README.md +0 -0
  147. /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/loop_runs/.gitkeep +0 -0
  148. /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/reference/.gitkeep +0 -0
  149. /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/staged_runs/.gitkeep +0 -0
  150. /package/sdd/{04_verify → 03_verify}/10_test/verification_harness.md +0 -0
  151. /package/sdd/99_toolchain/01_automation/assets/{platform_screen_capture → web_screen_capture}/dashboard.png +0 -0
  152. /package/sdd/99_toolchain/01_automation/assets/{platform_screen_capture → web_screen_capture}/login.png +0 -0
  153. /package/sdd/99_toolchain/01_automation/assets/{platform_screen_capture → web_screen_capture}/orders.png +0 -0
package/lib/scaffold.mjs CHANGED
@@ -1,68 +1,56 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import process from "node:process";
4
- import { fileURLToPath } from "node:url";
5
-
6
- export const TEMPLATE_CONFIG = {
7
- landing: {
8
- id: "landing",
9
- serviceName: "client-landing",
10
- composePortVar: "CLIENT_LANDING_PORT",
11
- composeApiVar: "CLIENT_LANDING_VITE_API_BASE_URL",
12
- defaultPort: 3000,
13
- },
14
- platform: {
15
- id: "platform",
16
- serviceName: "client-platform",
17
- composePortVar: "CLIENT_PLATFORM_PORT",
18
- composeApiVar: "CLIENT_PLATFORM_VITE_API_BASE_URL",
19
- defaultPort: 3001,
20
- },
21
- mobile: {
22
- id: "mobile",
23
- serviceName: "client-mobile",
24
- composePortVar: "CLIENT_MOBILE_PORT",
25
- composeApiVar: "CLIENT_MOBILE_VITE_API_BASE_URL",
26
- defaultPort: 3002,
27
- },
28
- admin: {
29
- id: "admin",
30
- serviceName: "client-admin",
31
- composePortVar: "CLIENT_ADMIN_PORT",
32
- composeApiVar: "CLIENT_ADMIN_VITE_API_BASE_URL",
33
- defaultPort: 4000,
34
- },
35
- };
36
-
37
- const ROOT_FILES = [
38
- ".dockerignore",
39
- ".env.example",
40
- ".gitignore",
41
- "AGENTS.md",
42
- "README.md",
43
- "SDD_SKILL.md",
44
- ];
45
-
46
- const ROOT_DIRS = [
47
- ".agent",
48
- ".claude",
49
- ".codex",
50
- "infra",
51
- "scripts",
52
- "sdd",
53
- "server",
54
- ];
55
-
56
- const __filename = fileURLToPath(import.meta.url);
57
- const __dirname = path.dirname(__filename);
58
- const PACKAGE_ROOT = path.resolve(__dirname, "..");
59
-
60
- export function listTemplates() {
61
- return Object.keys(TEMPLATE_CONFIG);
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ export const DEFAULT_TEMPLATE_OWNER = "say828";
8
+
9
+ function runCommand(command, args, { cwd = process.cwd(), label = "" } = {}) {
10
+ if (label) {
11
+ console.log(`\n> ${label}`);
12
+ }
13
+ const result = spawnSync(command, args, {
14
+ cwd,
15
+ stdio: "inherit",
16
+ });
17
+ if (result.status !== 0) {
18
+ throw new Error(`Command failed: ${command} ${args.join(" ")}`);
19
+ }
20
+ }
21
+
22
+ function commandExists(command) {
23
+ const result = spawnSync("bash", ["-lc", `command -v ${command}`], {
24
+ stdio: "ignore",
25
+ });
26
+ return result.status === 0;
27
+ }
28
+
29
+ function copyDirectoryContents(sourceRoot, destinationRoot) {
30
+ for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
31
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
32
+ continue;
33
+ }
34
+ fs.cpSync(path.join(sourceRoot, entry.name), path.join(destinationRoot, entry.name), {
35
+ recursive: true,
36
+ force: true,
37
+ filter: (source) => {
38
+ const baseName = path.basename(source);
39
+ return baseName !== ".git" && baseName !== "node_modules" && baseName !== "dist";
40
+ },
41
+ });
42
+ }
62
43
  }
63
44
 
64
- export function resolvePackageRoot() {
65
- return PACKAGE_ROOT;
45
+ function fetchPage(url) {
46
+ const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.AGENTIC_GITHUB_TOKEN;
47
+ return fetch(url, {
48
+ headers: {
49
+ Accept: "application/vnd.github+json",
50
+ "User-Agent": "agentic-dev",
51
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
52
+ },
53
+ });
66
54
  }
67
55
 
68
56
  export function parseArgs(argv) {
@@ -71,8 +59,10 @@ export function parseArgs(argv) {
71
59
  command: "init",
72
60
  targetDir: "",
73
61
  template: "",
62
+ owner: DEFAULT_TEMPLATE_OWNER,
74
63
  force: false,
75
64
  yes: false,
65
+ skipBootstrap: false,
76
66
  };
77
67
 
78
68
  if (args.length > 0 && !args[0].startsWith("-")) {
@@ -86,6 +76,9 @@ export function parseArgs(argv) {
86
76
  case "-t":
87
77
  options.template = args.shift() ?? "";
88
78
  break;
79
+ case "--owner":
80
+ options.owner = args.shift() ?? DEFAULT_TEMPLATE_OWNER;
81
+ break;
89
82
  case "--force":
90
83
  options.force = true;
91
84
  break;
@@ -93,6 +86,9 @@ export function parseArgs(argv) {
93
86
  case "-y":
94
87
  options.yes = true;
95
88
  break;
89
+ case "--skip-bootstrap":
90
+ options.skipBootstrap = true;
91
+ break;
96
92
  case "--help":
97
93
  case "-h":
98
94
  options.command = "help";
@@ -112,29 +108,24 @@ export function parseArgs(argv) {
112
108
  export function usage() {
113
109
  return [
114
110
  "Usage:",
115
- " agentic-dev init <target-dir> --template <landing|platform|mobile|admin>",
111
+ " agentic-dev",
112
+ " agentic-dev init <target-dir>",
116
113
  "",
117
114
  "Examples:",
118
- " npx agentic-dev init my-app --template platform",
119
- " npx agentic-dev my-app --template mobile",
115
+ " npx agentic-dev",
116
+ " npx agentic-dev init my-app",
117
+ " npx agentic-dev init my-app --template template-web --yes",
120
118
  "",
121
119
  "Options:",
122
- " --template, -t Frontend template to install",
123
- " --force Allow scaffolding into a non-empty directory",
124
- " --yes, -y Skip interactive confirmation",
125
- " --help, -h Show this help",
120
+ " --template, -t Template repo name or suffix to use",
121
+ " --owner GitHub owner for template-* repos (default: say828)",
122
+ " --force Allow scaffolding into a non-empty directory",
123
+ " --yes, -y Skip interactive prompts",
124
+ " --skip-bootstrap Copy the template but skip install/bootstrap",
125
+ " --help, -h Show this help",
126
126
  ].join("\n");
127
127
  }
128
128
 
129
- export function validateTemplate(template) {
130
- if (!TEMPLATE_CONFIG[template]) {
131
- throw new Error(
132
- `Unsupported template: ${template}. Expected one of: ${listTemplates().join(", ")}.`,
133
- );
134
- }
135
- return TEMPLATE_CONFIG[template];
136
- }
137
-
138
129
  export function ensureTargetDir(targetDir, { force = false } = {}) {
139
130
  if (!targetDir) {
140
131
  throw new Error("Missing target directory.");
@@ -150,230 +141,214 @@ export function ensureTargetDir(targetDir, { force = false } = {}) {
150
141
  return resolved;
151
142
  }
152
143
 
153
- function copyPath(relativePath, destinationRoot) {
154
- let sourcePath = path.join(PACKAGE_ROOT, relativePath);
155
- if (!fs.existsSync(sourcePath) && relativePath === ".gitignore") {
156
- const npmIgnorePath = path.join(PACKAGE_ROOT, ".npmignore");
157
- if (fs.existsSync(npmIgnorePath)) {
158
- sourcePath = npmIgnorePath;
144
+ export async function fetchTemplateRepos({ owner = DEFAULT_TEMPLATE_OWNER } = {}) {
145
+ const repos = [];
146
+ let page = 1;
147
+
148
+ while (true) {
149
+ const response = await fetchPage(
150
+ `https://api.github.com/users/${owner}/repos?per_page=100&page=${page}&sort=updated`,
151
+ );
152
+ if (!response.ok) {
153
+ throw new Error(`Failed to fetch template repos from GitHub: ${response.status}`);
159
154
  }
155
+ const payload = await response.json();
156
+ if (!Array.isArray(payload)) {
157
+ throw new Error("Unexpected GitHub response while listing template repos.");
158
+ }
159
+ const publicRepos = payload
160
+ .filter((repo) => !repo.private && typeof repo.name === "string")
161
+ .filter((repo) => repo.name.startsWith("template-"))
162
+ .map((repo) => ({
163
+ name: repo.name,
164
+ description: repo.description ?? "",
165
+ cloneUrl: repo.clone_url,
166
+ htmlUrl: repo.html_url,
167
+ defaultBranch: repo.default_branch || "main",
168
+ }));
169
+ repos.push(...publicRepos);
170
+ if (payload.length < 100) {
171
+ break;
172
+ }
173
+ page += 1;
160
174
  }
161
- const destinationPath = path.join(destinationRoot, relativePath);
162
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
163
- fs.cpSync(sourcePath, destinationPath, {
164
- recursive: true,
165
- force: true,
166
- filter: (source) => {
167
- const baseName = path.basename(source);
168
- return baseName !== ".git" && baseName !== "node_modules" && baseName !== "dist";
169
- },
170
- });
175
+
176
+ repos.sort((a, b) => a.name.localeCompare(b.name));
177
+ return repos;
178
+ }
179
+
180
+ export function resolveTemplateRepo(input, repos) {
181
+ const normalized = (input || "").trim();
182
+ if (!normalized) {
183
+ return null;
184
+ }
185
+
186
+ const exact = repos.find((repo) => repo.name === normalized);
187
+ if (exact) {
188
+ return exact;
189
+ }
190
+
191
+ const normalizedSuffix = normalized.startsWith("template-") ? normalized.slice(9) : normalized;
192
+ const matches = repos.filter((repo) => repo.name === `template-${normalizedSuffix}`);
193
+ if (matches.length === 1) {
194
+ return matches[0];
195
+ }
196
+ if (matches.length > 1) {
197
+ throw new Error(`Ambiguous template repo: ${normalized}`);
198
+ }
199
+ throw new Error(`Template repo not found: ${normalized}`);
171
200
  }
172
201
 
173
- function writeFile(destinationPath, content) {
174
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
175
- fs.writeFileSync(destinationPath, content, "utf8");
202
+ export function selectTemplateRepo(choice, repos) {
203
+ const normalized = (choice || "").trim();
204
+ if (!normalized) {
205
+ throw new Error("Missing template selection.");
206
+ }
207
+
208
+ if (/^\d+$/.test(normalized)) {
209
+ const index = Number.parseInt(normalized, 10) - 1;
210
+ if (index < 0 || index >= repos.length) {
211
+ throw new Error(`Invalid selection: ${normalized}`);
212
+ }
213
+ return repos[index];
214
+ }
215
+
216
+ return resolveTemplateRepo(normalized, repos);
176
217
  }
177
218
 
178
- function renderWorkspace(template) {
179
- return `packages:\n - client/${template}\n`;
219
+ function ensurePnpm() {
220
+ if (commandExists("pnpm")) {
221
+ return ["pnpm"];
222
+ }
223
+ if (commandExists("corepack")) {
224
+ runCommand("corepack", ["enable"], { label: "Enabling corepack" });
225
+ return ["corepack", "pnpm"];
226
+ }
227
+ runCommand("npm", ["install", "-g", "pnpm"], { label: "Installing pnpm globally" });
228
+ return ["pnpm"];
180
229
  }
181
230
 
182
- function renderCompose(template) {
183
- const config = TEMPLATE_CONFIG[template];
184
- return `name: templates
185
-
186
- services:
187
- postgres:
188
- image: postgres:16-alpine
189
- environment:
190
- POSTGRES_DB: \${POSTGRES_DB:-template}
191
- POSTGRES_USER: \${POSTGRES_USER:-template}
192
- POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-template}
193
- ports:
194
- - "\${POSTGRES_PORT:-5432}:5432"
195
- volumes:
196
- - postgres_data:/var/lib/postgresql/data
197
- healthcheck:
198
- test:
199
- [
200
- "CMD-SHELL",
201
- "pg_isready -U \${POSTGRES_USER:-template} -d \${POSTGRES_DB:-template}",
202
- ]
203
- interval: 10s
204
- timeout: 5s
205
- retries: 5
206
- networks:
207
- - template-dev
208
-
209
- mysql:
210
- image: mysql:8.4
211
- profiles: ["mysql"]
212
- environment:
213
- MYSQL_DATABASE: \${MYSQL_DATABASE:-template}
214
- MYSQL_USER: \${MYSQL_USER:-template}
215
- MYSQL_PASSWORD: \${MYSQL_PASSWORD:-template}
216
- MYSQL_ROOT_PASSWORD: \${MYSQL_ROOT_PASSWORD:-template-root}
217
- ports:
218
- - "\${MYSQL_PORT:-3306}:3306"
219
- volumes:
220
- - mysql_data:/var/lib/mysql
221
- healthcheck:
222
- test:
223
- [
224
- "CMD-SHELL",
225
- "mysqladmin ping -h 127.0.0.1 -u\${MYSQL_USER:-template} -p\${MYSQL_PASSWORD:-template}",
226
- ]
227
- interval: 10s
228
- timeout: 5s
229
- retries: 10
230
- start_period: 20s
231
- networks:
232
- - template-dev
233
-
234
- mariadb:
235
- image: mariadb:11.7
236
- profiles: ["mariadb"]
237
- environment:
238
- MARIADB_DATABASE: \${MARIADB_DATABASE:-template}
239
- MARIADB_USER: \${MARIADB_USER:-template}
240
- MARIADB_PASSWORD: \${MARIADB_PASSWORD:-template}
241
- MARIADB_ROOT_PASSWORD: \${MARIADB_ROOT_PASSWORD:-template-root}
242
- ports:
243
- - "\${MARIADB_PORT:-3307}:3306"
244
- volumes:
245
- - mariadb_data:/var/lib/mysql
246
- healthcheck:
247
- test:
248
- [
249
- "CMD-SHELL",
250
- "mariadb-admin ping -h 127.0.0.1 -u\${MARIADB_USER:-template} -p\${MARIADB_PASSWORD:-template}",
251
- ]
252
- interval: 10s
253
- timeout: 5s
254
- retries: 10
255
- start_period: 20s
256
- networks:
257
- - template-dev
258
-
259
- mongo:
260
- image: mongo:8
261
- profiles: ["mongo"]
262
- ports:
263
- - "\${MONGO_PORT:-27017}:27017"
264
- volumes:
265
- - mongo_data:/data/db
266
- networks:
267
- - template-dev
268
-
269
- server:
270
- build:
271
- context: .
272
- dockerfile: server/Dockerfile.dev
273
- network: host
274
- environment:
275
- APP_NAME: \${SERVER_APP_NAME:-Template Server}
276
- ENVIRONMENT: \${SERVER_ENVIRONMENT:-development}
277
- API_PREFIX: \${SERVER_API_PREFIX:-/api/v1}
278
- DATABASE_BACKEND: \${SERVER_DATABASE_BACKEND:-postgres}
279
- POSTGRES_URL: \${SERVER_POSTGRES_URL:-postgresql+psycopg://template:template@postgres:5432/template}
280
- MYSQL_URL: \${SERVER_MYSQL_URL:-mysql+pymysql://template:template@mysql:3306/template}
281
- MARIADB_URL: \${SERVER_MARIADB_URL:-mysql+pymysql://template:template@mariadb:3306/template}
282
- MONGODB_URL: \${SERVER_MONGODB_URL:-mongodb://mongo:27017}
283
- MONGODB_DATABASE: \${SERVER_MONGODB_DATABASE:-template}
284
- JWT_SECRET: \${SERVER_JWT_SECRET:-template-local-dev-secret}
285
- JWT_ALGORITHM: \${SERVER_JWT_ALGORITHM:-HS256}
286
- ACCESS_TOKEN_TTL_MINUTES: \${SERVER_ACCESS_TOKEN_TTL_MINUTES:-120}
287
- BOOTSTRAP_ADMIN_EMAIL: \${SERVER_BOOTSTRAP_ADMIN_EMAIL:-admin@example.com}
288
- BOOTSTRAP_ADMIN_PASSWORD: \${SERVER_BOOTSTRAP_ADMIN_PASSWORD:-change-me-admin}
289
- BOOTSTRAP_ADMIN_NAME: \${SERVER_BOOTSTRAP_ADMIN_NAME:-Template Admin}
290
- BOOTSTRAP_OPERATOR_EMAIL: \${SERVER_BOOTSTRAP_OPERATOR_EMAIL:-operator@example.com}
291
- BOOTSTRAP_OPERATOR_PASSWORD: \${SERVER_BOOTSTRAP_OPERATOR_PASSWORD:-change-me-operator}
292
- BOOTSTRAP_OPERATOR_NAME: \${SERVER_BOOTSTRAP_OPERATOR_NAME:-Template Operator}
293
- CORS_ORIGINS: '\${SERVER_CORS_ORIGINS:-["http://localhost:${config.defaultPort}","http://127.0.0.1:${config.defaultPort}"]}'
294
- SERVER_HTTP_PORT: \${SERVER_HTTP_PORT:-8000}
295
- ports:
296
- - "\${SERVER_HTTP_PORT:-8000}:\${SERVER_HTTP_PORT:-8000}"
297
- volumes:
298
- - ./server:/app/server
299
- depends_on:
300
- postgres:
301
- condition: service_healthy
302
- networks:
303
- - template-dev
304
-
305
- ${config.serviceName}:
306
- build:
307
- context: .
308
- dockerfile: client/${template}/Dockerfile.dev
309
- network: host
310
- environment:
311
- PORT: \${${config.composePortVar}:-${config.defaultPort}}
312
- VITE_API_BASE_URL: \${${config.composeApiVar}:-http://127.0.0.1:8000/api/v1}
313
- CHOKIDAR_USEPOLLING: \${CLIENT_WATCH_USE_POLLING:-false}
314
- ports:
315
- - "\${${config.composePortVar}:-${config.defaultPort}}:\${${config.composePortVar}:-${config.defaultPort}}"
316
- volumes:
317
- - ./client/${template}:/app/client/${template}
318
- depends_on:
319
- server:
320
- condition: service_started
321
- networks:
322
- - template-dev
323
-
324
- networks:
325
- template-dev:
326
- driver: bridge
327
-
328
- volumes:
329
- postgres_data:
330
- mysql_data:
331
- mariadb_data:
332
- mongo_data:
333
- `;
231
+ function maybeCreateEnvFile(destinationRoot) {
232
+ const envExample = path.join(destinationRoot, ".env.example");
233
+ const envFile = path.join(destinationRoot, ".env");
234
+ if (fs.existsSync(envExample) && !fs.existsSync(envFile)) {
235
+ fs.copyFileSync(envExample, envFile);
236
+ }
334
237
  }
335
238
 
336
- function updateRepoContract(destinationRoot, template) {
337
- const contractPath = path.join(
239
+ function bootstrapScriptPath(destinationRoot) {
240
+ return path.join(
338
241
  destinationRoot,
339
- "sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json",
242
+ "sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh",
340
243
  );
341
- const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
342
- const selectedTarget = contract.frontend.targets[template];
343
- contract.frontend.default_target = template;
344
- contract.frontend.targets = {
345
- [template]: selectedTarget,
346
- };
347
- contract.commands.verify_dev = `test -f "$PWD/${selectedTarget.proof_output}" && echo verify-dev-placeholder-ok`;
348
- contract.artifacts.route_gap_output = selectedTarget.route_gap_output;
349
- contract.artifacts.proof_output = selectedTarget.proof_output;
350
- writeFile(contractPath, `${JSON.stringify(contract, null, 2)}\n`);
351
244
  }
352
245
 
353
- export function scaffoldTemplate({ destinationRoot, template }) {
354
- validateTemplate(template);
246
+ function detectClientSurfaces(destinationRoot) {
247
+ const clientDir = path.join(destinationRoot, "client");
248
+ if (!fs.existsSync(clientDir)) {
249
+ return [];
250
+ }
251
+ return fs
252
+ .readdirSync(clientDir, { withFileTypes: true })
253
+ .filter((entry) => entry.isDirectory())
254
+ .map((entry) => entry.name)
255
+ .sort();
256
+ }
257
+
258
+ function validateTemplateRepoContents(destinationRoot, templateRepo) {
259
+ const requiredPaths = [
260
+ ".env.example",
261
+ "pnpm-workspace.yaml",
262
+ "sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh",
263
+ ];
264
+
265
+ const missing = requiredPaths.filter(
266
+ (relativePath) => !fs.existsSync(path.join(destinationRoot, relativePath)),
267
+ );
268
+ if (missing.length > 0) {
269
+ throw new Error(
270
+ `Template repo ${templateRepo.name} is missing required files: ${missing.join(", ")}`,
271
+ );
272
+ }
273
+ }
355
274
 
356
- for (const relativePath of ROOT_FILES) {
357
- copyPath(relativePath, destinationRoot);
275
+ function runInstallSteps(destinationRoot) {
276
+ maybeCreateEnvFile(destinationRoot);
277
+ const clientSurfaces = detectClientSurfaces(destinationRoot);
278
+ if (clientSurfaces.length !== 1) {
279
+ throw new Error(`Expected exactly one client surface in template repo, found ${clientSurfaces.length}.`);
358
280
  }
359
- for (const relativePath of ROOT_DIRS) {
360
- copyPath(relativePath, destinationRoot);
281
+ const clientDir = path.join("client", clientSurfaces[0]);
282
+
283
+ const pnpmCommand = ensurePnpm();
284
+ runCommand(pnpmCommand[0], [...pnpmCommand.slice(1), "install"], {
285
+ cwd: destinationRoot,
286
+ label: "Installing workspace dependencies",
287
+ });
288
+
289
+ runCommand(
290
+ pnpmCommand[0],
291
+ [...pnpmCommand.slice(1), "--dir", clientDir, "exec", "playwright", "install", "chromium"],
292
+ {
293
+ cwd: destinationRoot,
294
+ label: "Installing Playwright Chromium",
295
+ },
296
+ );
297
+
298
+ return ["pnpm install", "pnpm exec playwright install chromium"];
299
+ }
300
+
301
+ function runBootstrapStep(destinationRoot) {
302
+ const bootstrapScript = bootstrapScriptPath(destinationRoot);
303
+ if (!fs.existsSync(bootstrapScript)) {
304
+ throw new Error(
305
+ `Missing bootstrap script in template repo: ${path.relative(destinationRoot, bootstrapScript)}`,
306
+ );
361
307
  }
362
308
 
363
- copyPath(path.join("client", template), destinationRoot);
309
+ runCommand("bash", [bootstrapScript, "."], {
310
+ cwd: destinationRoot,
311
+ label: "Running frontend parity bootstrap",
312
+ });
364
313
 
365
- writeFile(path.join(destinationRoot, "pnpm-workspace.yaml"), renderWorkspace(template));
366
- writeFile(path.join(destinationRoot, "compose.yml"), renderCompose(template));
367
- updateRepoContract(destinationRoot, template);
314
+ return ["bootstrap_frontend_parity.sh ."];
315
+ }
368
316
 
317
+ export function scaffoldFromTemplateRepo({ destinationRoot, templateRepo }) {
318
+ const cloneRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agentic-dev-template-"));
319
+ const repoRoot = path.join(cloneRoot, templateRepo.name);
320
+
321
+ try {
322
+ runCommand("git", ["clone", "--depth", "1", templateRepo.cloneUrl, repoRoot], {
323
+ label: `Cloning ${templateRepo.name}`,
324
+ });
325
+ copyDirectoryContents(repoRoot, destinationRoot);
326
+ } finally {
327
+ fs.rmSync(cloneRoot, { recursive: true, force: true });
328
+ }
329
+ }
330
+
331
+ export function installTemplateRepo({
332
+ destinationRoot,
333
+ templateRepo,
334
+ skipBootstrap = false,
335
+ }) {
336
+ scaffoldFromTemplateRepo({ destinationRoot, templateRepo });
337
+ validateTemplateRepoContents(destinationRoot, templateRepo);
338
+
339
+ const executedSteps = runInstallSteps(destinationRoot);
340
+ if (!skipBootstrap) {
341
+ executedSteps.push(...runBootstrapStep(destinationRoot));
342
+ }
343
+ const clientSurfaces = detectClientSurfaces(destinationRoot);
344
+ const bootstrapHint =
345
+ clientSurfaces.length === 1
346
+ ? `cd client/${clientSurfaces[0]} && npm run ui:parity:bootstrap`
347
+ : "npm run ui:parity:bootstrap";
369
348
  return {
370
349
  destinationRoot,
371
- template,
372
- nextSteps: [
373
- "cp .env.example .env",
374
- "npm install -g pnpm",
375
- "pnpm install",
376
- `cd client/${template} && npm run ui:parity:bootstrap`,
377
- ],
350
+ templateRepo: templateRepo.name,
351
+ executedSteps,
352
+ nextSteps: skipBootstrap ? [bootstrapHint] : [],
378
353
  };
379
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-dev",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "description": "Scaffold an agentic template repo with the selected frontend surface and shared toolchain assets.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.32.0",
@@ -28,7 +28,7 @@
28
28
  "pnpm-workspace.yaml"
29
29
  ],
30
30
  "scripts": {
31
- "smoke:init": "node ./bin/agentic-dev.mjs init ./.tmp-agentic-smoke --template platform --yes --force"
31
+ "smoke:init": "node ./bin/agentic-dev.mjs init ./.tmp-agentic-smoke --template template-web --yes --force"
32
32
  },
33
33
  "keywords": [
34
34
  "agentic",
@@ -12,7 +12,7 @@ ROOT = Path(__file__).resolve().parents[2]
12
12
 
13
13
  SERVICE_DOCS = {
14
14
  "mobile": ROOT / "sdd/03_build/01_feature/service/mobile_surface.md",
15
- "platform": ROOT / "sdd/03_build/01_feature/service/platform_surface.md",
15
+ "web": ROOT / "sdd/03_build/01_feature/service/web_surface.md",
16
16
  "admin": ROOT / "sdd/03_build/01_feature/service/admin_surface.md",
17
17
  "landing": ROOT / "sdd/03_build/01_feature/service/landing_surface.md",
18
18
  }
@@ -59,14 +59,14 @@ def ast_similarity() -> tuple[int, list[str]]:
59
59
  else:
60
60
  findings.append("mobile surface summary does not reflect the real provider/router/shell chain.")
61
61
 
62
- platform = read(SERVICE_DOCS["platform"])
62
+ web = read(SERVICE_DOCS["web"])
63
63
  if contains_all(
64
- platform,
64
+ web,
65
65
  [
66
- "client/platform/src/main.tsx",
66
+ "client/web/src/main.tsx",
67
67
  "AuthProvider",
68
68
  "BrowserRouter",
69
- "client/platform/src/app/App.tsx",
69
+ "client/web/src/app/App.tsx",
70
70
  "ProtectedRoute",
71
71
  "AppShell",
72
72
  "DashboardPage",
@@ -75,7 +75,7 @@ def ast_similarity() -> tuple[int, list[str]]:
75
75
  ):
76
76
  points += 2.5
77
77
  else:
78
- findings.append("platform surface summary does not reflect the real app shell route tree.")
78
+ findings.append("web surface summary does not reflect the real app shell route tree.")
79
79
 
80
80
  admin = read(SERVICE_DOCS["admin"])
81
81
  if contains_all(
@@ -139,9 +139,9 @@ def implementation_traceability() -> tuple[int, list[str]]:
139
139
  else:
140
140
  findings.append("mobile surface summary does not trace route leaves to backend contract leaves.")
141
141
 
142
- platform = read(SERVICE_DOCS["platform"])
142
+ web = read(SERVICE_DOCS["web"])
143
143
  if contains_all(
144
- platform,
144
+ web,
145
145
  [
146
146
  "server/main.py",
147
147
  "server/api/http/app.py",
@@ -153,7 +153,7 @@ def implementation_traceability() -> tuple[int, list[str]]:
153
153
  ):
154
154
  points += 2.0
155
155
  else:
156
- findings.append("platform surface summary does not trace dashboard and orders routes to backend contracts.")
156
+ findings.append("web surface summary does not trace dashboard and orders routes to backend contracts.")
157
157
 
158
158
  admin = read(SERVICE_DOCS["admin"])
159
159
  if contains_all(