agentic-dev 0.2.4 → 0.2.7
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/.agent/prd.json +1 -1
- package/.agent/prompt.md +1 -1
- package/.claude/agents/db-dev.md +1 -1
- package/.claude/agents/frontend-dev.md +1 -1
- package/.claude/agents/test-dev.md +1 -1
- package/.claude/skills/sdd/SKILL.md +15 -10
- package/.claude/skills/sdd/references/section-map.md +5 -5
- package/.codex/skills/sdd/SKILL.md +15 -10
- package/.codex/skills/sdd/references/section-map.md +5 -5
- package/AGENTS.md +12 -4
- package/README.md +45 -12
- package/SDD_SKILL.md +7 -7
- package/bin/agentic-dev.mjs +53 -18
- package/client/admin/scripts/ui-parity-admin-adapter.mjs +1 -1
- package/client/landing/scripts/ui-parity-landing-adapter.mjs +1 -1
- package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +1 -1
- package/client/web/scripts/ui-parity-web-adapter.mjs +1 -1
- package/lib/scaffold.mjs +254 -279
- package/package.json +2 -2
- package/sdd/02_plan/03_architecture/repository_governance.md +1 -1
- package/sdd/02_plan/03_architecture/runtime_and_structure_governance.md +1 -1
- package/sdd/02_plan/03_architecture/toolchain_governance.md +2 -2
- package/sdd/02_plan/06_iac/dev_runtime_delivery.md +1 -1
- package/sdd/02_plan/10_test/regression_verification.md +2 -2
- package/sdd/02_plan/10_test/templates/ui_parity_web_contract.template.yaml +1 -1
- package/sdd/02_plan/10_test/verification_strategy.md +2 -2
- package/sdd/03_build/03_architecture/toolchain_governance.md +1 -1
- package/sdd/{04_verify → 03_verify}/01_feature/service_verification.md +1 -1
- package/sdd/{04_verify → 03_verify}/02_screen/README.md +1 -1
- package/sdd/{04_verify → 03_verify}/03_architecture/toolchain_governance.md +2 -2
- package/sdd/{04_verify → 03_verify}/06_iac/dev_runtime_delivery.md +1 -1
- package/sdd/{04_verify → 03_verify}/06_iac/template_runtime_delivery.md +1 -1
- package/sdd/{04_verify → 03_verify}/README.md +3 -3
- package/sdd/99_toolchain/01_automation/README.md +2 -2
- package/sdd/99_toolchain/01_automation/agentic-dev/assets/repo-contract.template.json +5 -5
- package/sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh +1 -1
- package/sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json +6 -6
- package/sdd/99_toolchain/01_automation/agentic-parity-harness-design.md +4 -4
- package/sdd/99_toolchain/01_automation/parity-execution-tooling-design.md +3 -3
- package/sdd/99_toolchain/01_automation/ui-parity/README.md +1 -1
- package/sdd/99_toolchain/01_automation/ui-parity/cli/materialize-reference-assets.mjs +1 -1
- package/sdd/99_toolchain/01_automation/ui-parity/cli/scaffold-contract.mjs +2 -2
- package/sdd/99_toolchain/01_automation/ui-parity/core/proof-runner.mjs +1 -1
- package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-artifact-layout.md +1 -1
- package/sdd/99_toolchain/02_policies/build-ast-governance-policy.md +2 -2
- package/sdd/99_toolchain/02_policies/compose-runtime-baseline-policy.md +2 -2
- package/sdd/99_toolchain/02_policies/regression-verification-policy.md +1 -1
- package/sdd/99_toolchain/README.md +1 -1
- package/sdd/README.md +1 -1
- /package/sdd/{04_verify → 03_verify}/01_feature/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/01_feature/domain_verification.md +0 -0
- /package/sdd/{04_verify → 03_verify}/02_screen/_screen_verify_template.md +0 -0
- /package/sdd/{04_verify → 03_verify}/02_screen/admin/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/02_screen/landing/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/02_screen/mobile/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/02_screen/web/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/03_architecture/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/03_architecture/architecture_document_governance.md +0 -0
- /package/sdd/{04_verify → 03_verify}/03_architecture/build_ast_runtime_tree_governance.md +0 -0
- /package/sdd/{04_verify → 03_verify}/03_architecture/repository_governance.md +0 -0
- /package/sdd/{04_verify → 03_verify}/06_iac/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/07_integration/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/07_integration/frontend_live_integration.md +0 -0
- /package/sdd/{04_verify → 03_verify}/08_nonfunctional/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/08_nonfunctional/repository_hygiene.md +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/regression_verification.md +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/README.md +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/loop_runs/.gitkeep +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/reference/.gitkeep +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/ui_parity/staged_runs/.gitkeep +0 -0
- /package/sdd/{04_verify → 03_verify}/10_test/verification_harness.md +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 {
|
|
5
|
-
|
|
6
|
-
export const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
111
|
+
" agentic-dev",
|
|
112
|
+
" agentic-dev init <target-dir>",
|
|
116
113
|
"",
|
|
117
114
|
"Examples:",
|
|
118
|
-
" npx agentic-dev
|
|
119
|
-
" npx agentic-dev my-app
|
|
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
|
|
123
|
-
" --
|
|
124
|
-
" --
|
|
125
|
-
" --
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
179
|
-
|
|
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
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
337
|
-
|
|
239
|
+
function bootstrapScriptPath(destinationRoot) {
|
|
240
|
+
return path.join(
|
|
338
241
|
destinationRoot,
|
|
339
|
-
"sdd/99_toolchain/01_automation/agentic-dev/
|
|
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
|
-
|
|
354
|
-
|
|
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
|
-
|
|
357
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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
|
-
|
|
309
|
+
runCommand("bash", [bootstrapScript, "."], {
|
|
310
|
+
cwd: destinationRoot,
|
|
311
|
+
label: "Running frontend parity bootstrap",
|
|
312
|
+
});
|
|
364
313
|
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
+
"version": "0.2.7",
|
|
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 web --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",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
- toolchain은 `sdd/99_toolchain/01_automation`, `02_policies`, `03_templates` current split을 따른다.
|
|
62
62
|
- Playwright local exactness는 `run_playwright_exactness.py`와 `playwright_exactness_manifest.py`를 canonical entrypoint로 사용한다.
|
|
63
63
|
- screen 작업은 가능하면 `npx playwright test ...`를 직접 쓰지 않고 toolchain runner를 통해 suite id 기준으로 실행한다.
|
|
64
|
-
- proof artifact는 `sdd/
|
|
64
|
+
- proof artifact는 `sdd/03_verify/10_test/ui_parity/` current path를 기준으로 관리한다.
|
|
65
65
|
- toolchain 정책 문서의 정본은 `sdd/99_toolchain/02_policies`에 둔다.
|
|
66
66
|
- skill/tooling 사용 여부는 개별 dated memo가 아니라 현재 workflow rule로만 유지한다.
|
|
67
67
|
- 화면명세서의 icon/image/logo 등 재사용 가능한 정적 자산은 `spec_asset_builder.py` 또는 wrapper를 먼저 사용해 추출하고, 수동 재작성은 builder가 표현하지 못하는 경우에만 예외로 허용한다.
|
|
@@ -93,6 +93,6 @@
|
|
|
93
93
|
- `sdd/99_toolchain/01_automation/playwright_exactness_manifest.py`
|
|
94
94
|
- `sdd/99_toolchain/01_automation/run_playwright_exactness.py`
|
|
95
95
|
- `sdd/02_plan/10_test/verification_strategy.md`
|
|
96
|
-
- `sdd/
|
|
96
|
+
- `sdd/03_verify/10_test/verification_harness.md`
|
|
97
97
|
- `sdd/99_toolchain/02_policies/regression-verification-policy.md`
|
|
98
98
|
- `sdd/02_plan/10_test/regression_verification.md`
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
## Acceptance Criteria
|
|
16
16
|
|
|
17
17
|
- [x] 회귀 검수는 direct target-only로 끝내지 않는다.
|
|
18
|
-
- [x] selected regression surface는 `sdd/02_plan`, `sdd/03_build`, `sdd/
|
|
18
|
+
- [x] selected regression surface는 `sdd/02_plan`, `sdd/03_build`, `sdd/03_verify`에 current-state로 이어진다.
|
|
19
19
|
- [x] automation gap은 scope 축소가 아니라 residual risk로 기록한다.
|
|
20
20
|
|
|
21
21
|
## Execution Checklist
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
- `AGENTS.md`
|
|
37
37
|
- `.codex/skills/sdd/SKILL.md`
|
|
38
38
|
- `sdd/99_toolchain/01_automation/README.md`
|
|
39
|
-
- `sdd/
|
|
39
|
+
- `sdd/03_verify/10_test/verification_harness.md`
|