@delegance/claude-autopilot 7.2.1 → 7.4.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.
- package/CHANGELOG.md +41 -0
- package/dist/src/cli/help-text.js +1 -1
- package/dist/src/cli/index.js +19 -2
- package/dist/src/cli/scaffold/node.d.ts +20 -0
- package/dist/src/cli/scaffold/node.js +162 -0
- package/dist/src/cli/scaffold/python.d.ts +71 -0
- package/dist/src/cli/scaffold/python.js +338 -0
- package/dist/src/cli/scaffold/types.d.ts +68 -0
- package/dist/src/cli/scaffold/types.js +6 -0
- package/dist/src/cli/scaffold.d.ts +43 -29
- package/dist/src/cli/scaffold.js +245 -174
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +33 -1
- package/package.json +3 -2
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// v7.4.0 — Python + FastAPI scaffolder.
|
|
2
|
+
//
|
|
3
|
+
// Two scaffold flavors share most of this module:
|
|
4
|
+
// - bare Python (pyproject.toml / requirements.txt detected)
|
|
5
|
+
// - FastAPI (Python + a main.py + a `fastapi` mention in the spec)
|
|
6
|
+
//
|
|
7
|
+
// FastAPI auto-includes `fastapi>=0.110` and `uvicorn[standard]>=0.27` in
|
|
8
|
+
// dependencies (deduped by PEP 503 normalized name) and emits a runnable
|
|
9
|
+
// `src/<package>/main.py` + `tests/test_main.py` so the generated
|
|
10
|
+
// `[project.scripts]` entrypoint actually resolves on `pip install -e .`
|
|
11
|
+
// — not a dangling stub. This was codex CRITICAL #2 on the v7.4.0 spec.
|
|
12
|
+
//
|
|
13
|
+
// All naming follows the deterministic algorithm described in the spec
|
|
14
|
+
// ("Name normalization (codex WARNING #1)"):
|
|
15
|
+
// - distribution_name = PEP 503 normalize(basename(cwd))
|
|
16
|
+
// - package_name = distribution_name with `-`/`.` -> `_`,
|
|
17
|
+
// prefix `_` if it would start with a digit.
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import * as fsAsync from 'node:fs/promises';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
const PASS = '\x1b[32m✓\x1b[0m';
|
|
22
|
+
const SKIP = '\x1b[2m·\x1b[0m';
|
|
23
|
+
const DIM = (t) => `\x1b[2m${t}\x1b[0m`;
|
|
24
|
+
/**
|
|
25
|
+
* PEP 503 distribution-name normalization, restricted to what we need
|
|
26
|
+
* here. Lowercase, runs of `[._-]+` collapse to a single `-`, leading +
|
|
27
|
+
* trailing `[._-]` stripped. Empty result falls back to 'app' (so the
|
|
28
|
+
* worst-case `cwd` of `___` still produces a buildable pyproject).
|
|
29
|
+
*/
|
|
30
|
+
export function normalizeDistributionName(raw) {
|
|
31
|
+
const lower = raw.toLowerCase();
|
|
32
|
+
const collapsed = lower.replace(/[._-]+/g, '-');
|
|
33
|
+
const stripped = collapsed.replace(/^[-]+/, '').replace(/[-]+$/, '');
|
|
34
|
+
return stripped.length > 0 ? stripped : 'app';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Convert a (PEP 503 normalized) distribution name into a valid Python
|
|
38
|
+
* identifier suitable for a top-level package directory:
|
|
39
|
+
* - replace `-` and `.` with `_`
|
|
40
|
+
* - prefix `_` if it starts with a digit (so `2cool` -> `_2cool`)
|
|
41
|
+
*
|
|
42
|
+
* Tests pin both transformations:
|
|
43
|
+
* my-pkg-2 -> my_pkg_2
|
|
44
|
+
* 2cool -> _2cool
|
|
45
|
+
*/
|
|
46
|
+
export function packageNameFromDistribution(distribution) {
|
|
47
|
+
const replaced = distribution.replace(/[-.]/g, '_');
|
|
48
|
+
return /^[0-9]/.test(replaced) ? `_${replaced}` : replaced;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse a dependency string (`fastapi`, `fastapi>=0.110`,
|
|
52
|
+
* `uvicorn[standard]`, `pyramid==2.0`) into its PEP 503 normalized
|
|
53
|
+
* "name" portion — used purely as a dedup key. We don't care about the
|
|
54
|
+
* version specifier when keying; first-occurrence wins (per spec
|
|
55
|
+
* "Dedupe by PEP 503 normalized name; first occurrence wins").
|
|
56
|
+
*/
|
|
57
|
+
export function dependencyNameKey(dep) {
|
|
58
|
+
// Strip extras + version specifier. Match the leading
|
|
59
|
+
// identifier (PEP 508 names are `[A-Za-z0-9._-]+`).
|
|
60
|
+
const m = /^[A-Za-z0-9._-]+/.exec(dep.trim());
|
|
61
|
+
if (!m)
|
|
62
|
+
return dep.trim().toLowerCase();
|
|
63
|
+
return normalizeDistributionName(m[0]);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build the dependency list for the generated pyproject.toml. Honors
|
|
67
|
+
* the narrow contract from spec ("Dependency hint extraction (codex
|
|
68
|
+
* WARNING #6)"): values flow through verbatim, no version inference,
|
|
69
|
+
* deduped by PEP 503 normalized name. For FastAPI we ALSO seed
|
|
70
|
+
* `fastapi>=0.110` and `uvicorn[standard]>=0.27` if not already present.
|
|
71
|
+
*/
|
|
72
|
+
export function buildPythonDependencies(hintDeps, isFastapi) {
|
|
73
|
+
const out = [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const push = (raw) => {
|
|
76
|
+
const key = dependencyNameKey(raw);
|
|
77
|
+
if (seen.has(key))
|
|
78
|
+
return;
|
|
79
|
+
seen.add(key);
|
|
80
|
+
out.push(raw);
|
|
81
|
+
};
|
|
82
|
+
for (const d of hintDeps ?? [])
|
|
83
|
+
push(d);
|
|
84
|
+
if (isFastapi) {
|
|
85
|
+
// Only auto-add when not already supplied. If the spec listed
|
|
86
|
+
// `fastapi==0.115`, we keep that pin and don't override it with our
|
|
87
|
+
// default lower bound.
|
|
88
|
+
push('fastapi>=0.110');
|
|
89
|
+
push('uvicorn[standard]>=0.27');
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a TOML string array, one entry per line. Used for the
|
|
95
|
+
* `dependencies = [...]` block in pyproject.toml. Strings are quoted
|
|
96
|
+
* with double quotes; we escape backslashes + double quotes.
|
|
97
|
+
*/
|
|
98
|
+
function tomlStringArray(values) {
|
|
99
|
+
if (values.length === 0)
|
|
100
|
+
return '[]';
|
|
101
|
+
const lines = values.map(v => ` "${v.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`);
|
|
102
|
+
return `[\n${lines.join(',\n')},\n]`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Generate the pyproject.toml body. Caller supplies the resolved
|
|
106
|
+
* distribution name, package name, dependency list, and FastAPI flag
|
|
107
|
+
* (only difference: FastAPI adds a `[project.scripts]` block).
|
|
108
|
+
*/
|
|
109
|
+
export function buildPyproject(opts) {
|
|
110
|
+
const { distributionName, packageName, dependencies, isFastapi } = opts;
|
|
111
|
+
const lines = [];
|
|
112
|
+
lines.push('[project]');
|
|
113
|
+
lines.push(`name = "${distributionName}"`);
|
|
114
|
+
lines.push('version = "0.1.0"');
|
|
115
|
+
lines.push('requires-python = ">=3.11"');
|
|
116
|
+
lines.push(`dependencies = ${tomlStringArray(dependencies)}`);
|
|
117
|
+
lines.push('');
|
|
118
|
+
if (isFastapi) {
|
|
119
|
+
lines.push('[project.scripts]');
|
|
120
|
+
lines.push(`${distributionName}-server = "${packageName}.main:run"`);
|
|
121
|
+
lines.push('');
|
|
122
|
+
}
|
|
123
|
+
lines.push('[build-system]');
|
|
124
|
+
lines.push('requires = ["hatchling"]');
|
|
125
|
+
lines.push('build-backend = "hatchling.build"');
|
|
126
|
+
lines.push('');
|
|
127
|
+
// codex W1 — explicit packages list, no auto-discovery.
|
|
128
|
+
lines.push('[tool.hatch.build.targets.wheel]');
|
|
129
|
+
lines.push(`packages = ["src/${packageName}"]`);
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push('[tool.pytest.ini_options]');
|
|
132
|
+
lines.push('testpaths = ["tests"]');
|
|
133
|
+
lines.push('');
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
/** FastAPI entrypoint — codex CRITICAL #2: must be runnable, not a stub. */
|
|
137
|
+
export function buildFastapiMain(packageName) {
|
|
138
|
+
return `"""FastAPI entrypoint — auto-scaffolded by claude-autopilot.
|
|
139
|
+
Override the prose docstring + add real routes; keep \`app\` and
|
|
140
|
+
\`run()\` exported so the [project.scripts] entry stays valid.
|
|
141
|
+
"""
|
|
142
|
+
from fastapi import FastAPI
|
|
143
|
+
import uvicorn
|
|
144
|
+
|
|
145
|
+
app = FastAPI()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.get("/health")
|
|
149
|
+
def health() -> dict[str, str]:
|
|
150
|
+
return {"status": "ok"}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def run() -> None:
|
|
154
|
+
uvicorn.run("${packageName}.main:app", host="0.0.0.0", port=8000)
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
/** Smoke test for the FastAPI scaffold — also auto-included so pytest config isn't dead. */
|
|
158
|
+
export function buildFastapiTest(packageName) {
|
|
159
|
+
return `from fastapi.testclient import TestClient
|
|
160
|
+
from ${packageName}.main import app
|
|
161
|
+
|
|
162
|
+
client = TestClient(app)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_health() -> None:
|
|
166
|
+
response = client.get("/health")
|
|
167
|
+
assert response.status_code == 200
|
|
168
|
+
assert response.json() == {"status": "ok"}
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
/** Generic Python README placeholder. Never overwrites an existing README. */
|
|
172
|
+
function buildReadme(distributionName, isFastapi) {
|
|
173
|
+
const stackLabel = isFastapi ? 'FastAPI' : 'Python';
|
|
174
|
+
return `# ${distributionName}
|
|
175
|
+
|
|
176
|
+
${stackLabel} project scaffolded by \`claude-autopilot scaffold --from-spec\`.
|
|
177
|
+
|
|
178
|
+
## Install
|
|
179
|
+
|
|
180
|
+
\`\`\`bash
|
|
181
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
182
|
+
pip install -e .
|
|
183
|
+
\`\`\`
|
|
184
|
+
|
|
185
|
+
## Test
|
|
186
|
+
|
|
187
|
+
\`\`\`bash
|
|
188
|
+
pytest
|
|
189
|
+
\`\`\`
|
|
190
|
+
`;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The Python scaffolder. Materializes:
|
|
194
|
+
* - src/<package_name>/__init__.py (empty)
|
|
195
|
+
* - tests/ directory
|
|
196
|
+
* - pyproject.toml (PEP 621 + hatchling)
|
|
197
|
+
* - README.md (only if missing)
|
|
198
|
+
*
|
|
199
|
+
* For FastAPI specs it also writes:
|
|
200
|
+
* - src/<package_name>/main.py (runnable FastAPI app)
|
|
201
|
+
* - tests/test_main.py (smoke test)
|
|
202
|
+
*
|
|
203
|
+
* Files explicitly listed in the spec's `## Files` get touched as empty
|
|
204
|
+
* placeholders if they don't already exist (matches the v7.2.0 Node
|
|
205
|
+
* behavior). The special files above are written with content even when
|
|
206
|
+
* not listed in `## Files` — without them the generated pyproject.toml
|
|
207
|
+
* is invalid (missing package dir) or has dead config (no tests).
|
|
208
|
+
*/
|
|
209
|
+
export async function scaffoldPython(ctx, opts) {
|
|
210
|
+
const { cwd, parsed, dryRun } = ctx;
|
|
211
|
+
const { isFastapi } = opts;
|
|
212
|
+
const distributionName = normalizeDistributionName(path.basename(cwd));
|
|
213
|
+
const packageName = packageNameFromDistribution(distributionName);
|
|
214
|
+
const filesCreated = [];
|
|
215
|
+
const filesSkippedExisting = [];
|
|
216
|
+
const dirsCreated = [];
|
|
217
|
+
// We treat these as "managed" — we generate them with content, not
|
|
218
|
+
// empty placeholders, so the spec's bullet-list entries for them
|
|
219
|
+
// don't get touched first.
|
|
220
|
+
const MANAGED_FILES = new Set([
|
|
221
|
+
'pyproject.toml',
|
|
222
|
+
'requirements.txt', // we don't generate this, but if listed we leave it
|
|
223
|
+
`src/${packageName}/__init__.py`,
|
|
224
|
+
`src/${packageName}/main.py`,
|
|
225
|
+
'tests/test_main.py',
|
|
226
|
+
'README.md',
|
|
227
|
+
]);
|
|
228
|
+
// 1) Create directories. Always include the package + tests dirs;
|
|
229
|
+
// plus any dirs implied by spec paths.
|
|
230
|
+
const dirs = new Set([`src/${packageName}`, 'tests']);
|
|
231
|
+
for (const p of parsed.paths) {
|
|
232
|
+
const d = path.dirname(p);
|
|
233
|
+
if (d && d !== '.')
|
|
234
|
+
dirs.add(d);
|
|
235
|
+
}
|
|
236
|
+
for (const d of dirs) {
|
|
237
|
+
const abs = path.join(cwd, d);
|
|
238
|
+
if (fs.existsSync(abs))
|
|
239
|
+
continue;
|
|
240
|
+
if (!dryRun)
|
|
241
|
+
await fsAsync.mkdir(abs, { recursive: true });
|
|
242
|
+
dirsCreated.push(d);
|
|
243
|
+
console.log(` ${PASS} mkdir ${DIM(d + '/')}`);
|
|
244
|
+
}
|
|
245
|
+
// 2) Touch placeholder files for any spec paths we don't manage.
|
|
246
|
+
for (const p of parsed.paths) {
|
|
247
|
+
if (MANAGED_FILES.has(p))
|
|
248
|
+
continue;
|
|
249
|
+
const abs = path.join(cwd, p);
|
|
250
|
+
if (fs.existsSync(abs)) {
|
|
251
|
+
filesSkippedExisting.push(p);
|
|
252
|
+
console.log(` ${SKIP} exists ${DIM(p)}`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (!dryRun) {
|
|
256
|
+
await fsAsync.mkdir(path.dirname(abs), { recursive: true });
|
|
257
|
+
await fsAsync.writeFile(abs, '', 'utf8');
|
|
258
|
+
}
|
|
259
|
+
filesCreated.push(p);
|
|
260
|
+
console.log(` ${PASS} touch ${DIM(p)}`);
|
|
261
|
+
}
|
|
262
|
+
// 3) src/<package_name>/__init__.py
|
|
263
|
+
const initRel = `src/${packageName}/__init__.py`;
|
|
264
|
+
const initAbs = path.join(cwd, initRel);
|
|
265
|
+
if (fs.existsSync(initAbs)) {
|
|
266
|
+
filesSkippedExisting.push(initRel);
|
|
267
|
+
console.log(` ${SKIP} exists ${DIM(initRel)}`);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
if (!dryRun)
|
|
271
|
+
await fsAsync.writeFile(initAbs, '', 'utf8');
|
|
272
|
+
filesCreated.push(initRel);
|
|
273
|
+
console.log(` ${PASS} touch ${DIM(initRel)}`);
|
|
274
|
+
}
|
|
275
|
+
// 4) FastAPI-only: main.py + tests/test_main.py
|
|
276
|
+
if (isFastapi) {
|
|
277
|
+
const mainRel = `src/${packageName}/main.py`;
|
|
278
|
+
const mainAbs = path.join(cwd, mainRel);
|
|
279
|
+
if (fs.existsSync(mainAbs)) {
|
|
280
|
+
filesSkippedExisting.push(mainRel);
|
|
281
|
+
console.log(` ${SKIP} exists ${DIM(mainRel)}`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
if (!dryRun)
|
|
285
|
+
await fsAsync.writeFile(mainAbs, buildFastapiMain(packageName), 'utf8');
|
|
286
|
+
filesCreated.push(mainRel);
|
|
287
|
+
console.log(` ${PASS} write ${DIM(`${mainRel} (FastAPI app + /health + run())`)}`);
|
|
288
|
+
}
|
|
289
|
+
const testRel = 'tests/test_main.py';
|
|
290
|
+
const testAbs = path.join(cwd, testRel);
|
|
291
|
+
if (fs.existsSync(testAbs)) {
|
|
292
|
+
filesSkippedExisting.push(testRel);
|
|
293
|
+
console.log(` ${SKIP} exists ${DIM(testRel)}`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
if (!dryRun)
|
|
297
|
+
await fsAsync.writeFile(testAbs, buildFastapiTest(packageName), 'utf8');
|
|
298
|
+
filesCreated.push(testRel);
|
|
299
|
+
console.log(` ${PASS} write ${DIM(`${testRel} (smoke test for /health)`)}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// 5) pyproject.toml
|
|
303
|
+
const pyprojectAbs = path.join(cwd, 'pyproject.toml');
|
|
304
|
+
if (fs.existsSync(pyprojectAbs)) {
|
|
305
|
+
filesSkippedExisting.push('pyproject.toml');
|
|
306
|
+
console.log(` ${SKIP} exists ${DIM('pyproject.toml (preserved)')}`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
const dependencies = buildPythonDependencies(parsed.packageHints.pythonDeps, isFastapi);
|
|
310
|
+
const body = buildPyproject({ distributionName, packageName, dependencies, isFastapi });
|
|
311
|
+
if (!dryRun)
|
|
312
|
+
await fsAsync.writeFile(pyprojectAbs, body, 'utf8');
|
|
313
|
+
filesCreated.push('pyproject.toml');
|
|
314
|
+
const flavor = isFastapi ? 'PEP 621 + hatchling + FastAPI deps' : 'PEP 621 + hatchling';
|
|
315
|
+
console.log(` ${PASS} write ${DIM(`pyproject.toml (${flavor})`)}`);
|
|
316
|
+
}
|
|
317
|
+
// 6) README.md — codex NOTE #1 (always create, never overwrite).
|
|
318
|
+
const readmeAbs = path.join(cwd, 'README.md');
|
|
319
|
+
if (fs.existsSync(readmeAbs)) {
|
|
320
|
+
filesSkippedExisting.push('README.md');
|
|
321
|
+
console.log(` ${SKIP} exists ${DIM('README.md (preserved)')}`);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
if (!dryRun)
|
|
325
|
+
await fsAsync.writeFile(readmeAbs, buildReadme(distributionName, isFastapi), 'utf8');
|
|
326
|
+
filesCreated.push('README.md');
|
|
327
|
+
console.log(` ${PASS} write ${DIM('README.md')}`);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
filesCreated,
|
|
331
|
+
dirsCreated,
|
|
332
|
+
filesSkippedExisting,
|
|
333
|
+
// Node-shape fields — we never touch package.json / tsconfig in Python.
|
|
334
|
+
packageJsonAction: 'skipped-exists',
|
|
335
|
+
tsconfigAction: 'skipped-no-ts',
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=python.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Supported `--stack` values. v7.5+ will add 'go', 'rust', 'ruby'. */
|
|
2
|
+
export type Stack = 'node' | 'python' | 'fastapi';
|
|
3
|
+
/**
|
|
4
|
+
* Stacks we can DETECT but cannot scaffold yet. Detection still warns +
|
|
5
|
+
* exits 3 so the operator gets a clear "v7.5" diagnostic instead of a
|
|
6
|
+
* silent fallback to Node, which would generate a wrong-language skeleton.
|
|
7
|
+
*/
|
|
8
|
+
export type UnsupportedStack = 'go' | 'rust' | 'ruby';
|
|
9
|
+
export interface ParsedFiles {
|
|
10
|
+
/** Raw paths extracted from the `## Files` section bullets. */
|
|
11
|
+
paths: string[];
|
|
12
|
+
/** Loosely-parsed package.json hints found anywhere in the section. */
|
|
13
|
+
packageHints: {
|
|
14
|
+
bin?: Record<string, string>;
|
|
15
|
+
type?: 'module' | 'commonjs';
|
|
16
|
+
dependencies?: Record<string, string>;
|
|
17
|
+
devDependencies?: Record<string, string>;
|
|
18
|
+
scripts?: Record<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort stack hint from prose ("uses fastapi", "Python 3.12",
|
|
21
|
+
* "Node 22 ESM"). Used as a tie-breaker when path heuristics are
|
|
22
|
+
* ambiguous between Python and FastAPI.
|
|
23
|
+
*/
|
|
24
|
+
stackHint?: 'node' | 'python' | 'fastapi';
|
|
25
|
+
/**
|
|
26
|
+
* Extra Python dependency strings extracted via the narrow contract
|
|
27
|
+
* documented in the spec ("Dependency hint extraction"):
|
|
28
|
+
* - explicit `dependencies: [...]` block in spec prose
|
|
29
|
+
* - backticked package names with extras (`uvicorn[standard]`)
|
|
30
|
+
* - phrase `depends on <name>`
|
|
31
|
+
*
|
|
32
|
+
* Stored verbatim — never version-inferred. Deduped by
|
|
33
|
+
* PEP 503 normalized name in the Python scaffolder.
|
|
34
|
+
*/
|
|
35
|
+
pythonDeps?: string[];
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export interface ScaffoldOptions {
|
|
39
|
+
cwd?: string;
|
|
40
|
+
specPath: string;
|
|
41
|
+
/** When true, log what would happen but don't write anything. */
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Explicit stack override. When provided, skips path-based detection
|
|
45
|
+
* (but still validates the value: unknown → exit 3). v7.4.0 ships
|
|
46
|
+
* 'node' | 'python' | 'fastapi'.
|
|
47
|
+
*/
|
|
48
|
+
stack?: Stack;
|
|
49
|
+
}
|
|
50
|
+
export interface ScaffoldResult {
|
|
51
|
+
filesCreated: string[];
|
|
52
|
+
dirsCreated: string[];
|
|
53
|
+
filesSkippedExisting: string[];
|
|
54
|
+
/** Node-only metadata; Python scaffolder leaves these as 'skipped-no-ts' / 'skipped-exists'. */
|
|
55
|
+
packageJsonAction: 'created' | 'merged' | 'skipped-exists';
|
|
56
|
+
tsconfigAction: 'created' | 'skipped-exists' | 'skipped-no-ts';
|
|
57
|
+
/** v7.4.0 — which stack was used. Useful for tests and for the CLI banner. */
|
|
58
|
+
stack?: Stack;
|
|
59
|
+
/** Names of files explicitly skipped because of `--stack` filtering (codex W5). */
|
|
60
|
+
ignoredOtherStackFiles?: string[];
|
|
61
|
+
}
|
|
62
|
+
/** Per-stack scaffolders share this small context. */
|
|
63
|
+
export interface ScaffoldRunContext {
|
|
64
|
+
cwd: string;
|
|
65
|
+
parsed: ParsedFiles;
|
|
66
|
+
dryRun: boolean;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// v7.4.0 — shared types for per-stack scaffolders. Lives in its own file
|
|
2
|
+
// (rather than ../scaffold.ts) so that node.ts and python.ts can both import
|
|
3
|
+
// from it without creating a circular dependency back to the public entry
|
|
4
|
+
// module.
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -1,39 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
dirsCreated: string[];
|
|
10
|
-
filesSkippedExisting: string[];
|
|
11
|
-
packageJsonAction: 'created' | 'merged' | 'skipped-exists';
|
|
12
|
-
tsconfigAction: 'created' | 'skipped-exists' | 'skipped-no-ts';
|
|
13
|
-
}
|
|
14
|
-
interface ParsedFiles {
|
|
15
|
-
/** Raw paths extracted from the `## Files` section bullets. */
|
|
16
|
-
paths: string[];
|
|
17
|
-
/** Loosely-parsed package.json hints found anywhere in the section. */
|
|
18
|
-
packageHints: {
|
|
19
|
-
bin?: Record<string, string>;
|
|
20
|
-
type?: 'module' | 'commonjs';
|
|
21
|
-
dependencies?: Record<string, string>;
|
|
22
|
-
devDependencies?: Record<string, string>;
|
|
23
|
-
scripts?: Record<string, string>;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
1
|
+
import { buildStarterPackageJson } from './scaffold/node.ts';
|
|
2
|
+
import type { ParsedFiles, ScaffoldOptions, ScaffoldResult, Stack, UnsupportedStack } from './scaffold/types.ts';
|
|
3
|
+
export { buildStarterPackageJson };
|
|
4
|
+
export type { ScaffoldOptions, ScaffoldResult, ParsedFiles, Stack };
|
|
5
|
+
/** Valid `--stack` argument values. v7.5+ adds 'go', 'rust', 'ruby'. */
|
|
6
|
+
export declare const SUPPORTED_STACKS: readonly Stack[];
|
|
7
|
+
/** Stacks we DETECT-but-don't-support yet. Mapped to spec exit-3 messages. */
|
|
8
|
+
export declare const UNSUPPORTED_STACK_FILES: Record<UnsupportedStack, string>;
|
|
26
9
|
/**
|
|
27
10
|
* Parse the `## Files` (or `## files`) section of a spec markdown file.
|
|
28
11
|
* Tolerant: missing section returns `null`; malformed bullets are skipped
|
|
29
12
|
* silently. Returns extracted file paths + best-effort package-hint blob.
|
|
13
|
+
*
|
|
14
|
+
* v7.4.0 also extracts:
|
|
15
|
+
* - `stackHint` — first prose mention of `fastapi` / `python` / `node`
|
|
16
|
+
* (case-insensitive). Used as a tie-breaker when path heuristics are
|
|
17
|
+
* ambiguous between Python and FastAPI.
|
|
18
|
+
* - `pythonDeps` — narrow extraction per spec ("Dependency hint
|
|
19
|
+
* extraction"): explicit `dependencies: [...]` block, backticked
|
|
20
|
+
* package names with extras, and the phrase `depends on <name>`.
|
|
30
21
|
*/
|
|
31
22
|
export declare function parseSpecFiles(markdown: string): ParsedFiles | null;
|
|
32
23
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
24
|
+
* Result of stack detection. `kind` is one of:
|
|
25
|
+
* - 'resolved' — `stack` is set; proceed.
|
|
26
|
+
* - 'unsupported' — detected an unsupported stack file (Go/Rust/Ruby).
|
|
27
|
+
* Caller exits 3 with `message`.
|
|
28
|
+
* - 'polyglot' — both Node + Python markers present without --stack.
|
|
29
|
+
* Caller exits 3 with `message`.
|
|
35
30
|
*/
|
|
36
|
-
export
|
|
31
|
+
export type StackDetection = {
|
|
32
|
+
kind: 'resolved';
|
|
33
|
+
stack: Stack;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'unsupported';
|
|
36
|
+
stack: UnsupportedStack;
|
|
37
|
+
message: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'polyglot';
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Apply the precedence ladder documented at the top of this file. Pure
|
|
44
|
+
* function — no I/O — so it's directly unit-testable.
|
|
45
|
+
*/
|
|
46
|
+
export declare function detectStack(parsed: ParsedFiles, explicit?: Stack): StackDetection;
|
|
47
|
+
/**
|
|
48
|
+
* Print the `--list-stacks` output (codex NOTE #2). Three sections:
|
|
49
|
+
* Supported, Auto-detected, Recognized-but-unsupported.
|
|
50
|
+
*/
|
|
51
|
+
export declare function printStackList(): void;
|
|
37
52
|
export declare function runScaffold(opts: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
38
|
-
export {};
|
|
39
53
|
//# sourceMappingURL=scaffold.d.ts.map
|