@delegance/claude-autopilot 7.3.0 → 7.4.2

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 CHANGED
@@ -2,6 +2,99 @@
2
2
 
3
3
  - v5.6 Phase 7 (docs reconciliation) — pending.
4
4
 
5
+ ## 7.4.2 (2026-05-11)
6
+
7
+ **v7.4.2 — risk-tiered codex pass policy in autopilot skill.**
8
+ Docs-only PR. Codifies finding N2 from the v7.4.1 codex strategic
9
+ review into `skills/autopilot/SKILL.md`.
10
+
11
+ **New policy table** in the skill:
12
+
13
+ | Spec risk | # of codex passes |
14
+ |---|---|
15
+ | **Low** (CLI UX, doc-only, scaffolding, CI tweaks) | 1 |
16
+ | **Medium** (new exec modes, auth, billing, data-access, env vars, API contracts) | 2 |
17
+ | **High** (sandboxing, multi-tenancy, auto-merge, repo-mutation, secrets, RPC/SECURITY DEFINER) | 3 + external review |
18
+
19
+ **Convention:** spec docs declare `risk: low | medium | high` in
20
+ frontmatter. Omitted defaults to **medium** (safer than defaulting
21
+ to low).
22
+
23
+ **v7.x examples** included in the skill text:
24
+ * v7.1.7 (low) — 1 pass, 0 CRITICALs in practice.
25
+ * v7.4.0 (low) — 1 pass, 2 CRITICALs caught pre-impl.
26
+ * v7.0 Phase 6 (high) — 3 passes, would have shipped credential-
27
+ exfiltration vector C3 without all three.
28
+ * v8.0 spec (high) — 2 passes done, needs 3rd before v8 alpha.
29
+
30
+ No code change. Bumping to 7.4.2.
31
+
32
+ ## 7.4.1 (2026-05-11)
33
+
34
+ **v7.4.1 — strategic pivot doc from codex 5.5 review.** Docs-only
35
+ PR. Records the decision to pause v8 daemon implementation pending
36
+ customer discovery, plus 8 other findings from the codex strategic
37
+ review of full project state on 2026-05-11.
38
+
39
+ **Key outcome:** "ship v8 daemon" is NOT the next milestone. The CLI
40
+ chat-session loop is the validated asset; v8 is unvalidated. New
41
+ priority order: (1) customer discovery sprint, (2) hosted beta
42
+ readiness slice (operational), (3) org-tier revocation completion,
43
+ (4) risk-tiered codex pass policy in the autopilot skill.
44
+
45
+ **Process changes adopted:**
46
+
47
+ * **Risk-tiered codex passes** (1 for low-risk CLI UX, 2 for new
48
+ exec/auth/billing/data-access modes, 3 for sandboxing /
49
+ multi-tenancy / repo-mutation).
50
+ * **Strategic codex review every ~10 PRs** (separate from per-spec
51
+ passes — catches "ship more without validating demand" trap).
52
+ * **Bounded benchmark suite gate** (4 repo shapes only, run
53
+ pre-release + after major workflow changes — already in v8 spec).
54
+
55
+ **v8 IF customer discovery validates demand:** local-only alpha
56
+ first (per W5 of codex review). NO hosted workers, NO billing, NO
57
+ auto-merge until alpha demand is proven.
58
+
59
+ Full doc at `docs/strategy/2026-05-11-codex-pivot.md`.
60
+
61
+ ## 7.4.0 (2026-05-11)
62
+
63
+ **v7.4.0 — scaffold per-stack support (Python + FastAPI).** Closes
64
+ the v7.1.6/v7.1.8 benchmark caveat ("n=1, Node 22 ESM only —
65
+ Python/Rust/Go remain v8 follow-ups") and gates v8 spec
66
+ stabilization criteria #2 (4-repo benchmark suite).
67
+
68
+ * **Stack detection precedence** (codex C1): explicit `--stack` >
69
+ FastAPI > Python > Node > detected-but-unsupported > Node fallback.
70
+ FastAPI checked BEFORE Python so FastAPI specs that include
71
+ `pyproject.toml` aren't mis-classified.
72
+ * **FastAPI scaffold completeness** (codex C2): generates a runnable
73
+ `src/<package>/main.py` with `app = FastAPI()`, `/health` route,
74
+ `run()` function, plus `tests/test_main.py` (otherwise the
75
+ `[project.scripts]` entry was dangling).
76
+ * **Name normalization** (codex W1): PEP 503 distribution name +
77
+ valid Python identifier package name. `my-pkg-2` → distribution
78
+ `my-pkg-2`, package `my_pkg_2`. Hatchling explicit `packages`
79
+ config always present.
80
+ * **Detected-but-unsupported** (codex W2): Go/Rust/Ruby specs →
81
+ exit 3 with diagnostic, NOT silent fallback to Node.
82
+ * **Polyglot guard** (codex W3): specs listing both `package.json`
83
+ AND `pyproject.toml` without `--stack` → exit 3.
84
+ * **Narrow dep extraction** (codex W6): 3 patterns only, no inferred
85
+ versions, dedup by PEP 503 normalized name. FastAPI auto-includes
86
+ `fastapi>=0.110` + `uvicorn[standard]>=0.27`.
87
+ * **Module split**: `scaffold.ts` is now the dispatcher;
88
+ per-stack scaffolders live under
89
+ `src/cli/scaffold/{node,python,types}.ts`.
90
+ * **New flags**: `--stack <node|python|fastapi>`, `--list-stacks`.
91
+ * **Integration test** (codex N3): scaffolds FastAPI + creates
92
+ isolated venv (handles PEP 668) + `pip install -e .` + import-
93
+ app. Skipped cleanly when `python3` unavailable.
94
+
95
+ 1563 → 1597 CLI tests; tsc clean; build clean. PR #155 spec +
96
+ #156 impl. Version 7.3.0 → 7.4.0.
97
+
5
98
  ## 7.3.0 (2026-05-10)
6
99
 
7
100
  **v7.3.0 — library export surface for v8 daemon.** Minor bump
@@ -28,7 +28,7 @@ export const HELP_GROUPS = [
28
28
  verbs: [
29
29
  { verb: 'init', summary: 'Scaffold guardrail.config.yaml + auto-detect migrate stack (writes .autopilot/stack.md)' },
30
30
  { verb: 'setup', summary: 'Auto-detect stack, write config, install pre-push hook' },
31
- { verb: 'scaffold', summary: 'Scaffold project skeleton from a spec markdown (--from-spec <path>)' },
31
+ { verb: 'scaffold', summary: 'Scaffold project skeleton from a spec markdown (--from-spec <path> [--stack node|python|fastapi])' },
32
32
  { verb: 'autopilot', summary: 'Multi-phase orchestrator — run scan → spec → plan → implement under one runId (v6.2.0)' },
33
33
  { verb: 'brainstorm', summary: 'Pipeline entry point (Claude Code skill — see /brainstorm)' },
34
34
  { verb: 'spec', summary: 'Spec-writing pointer (Claude Code skill — see /brainstorm)' },
@@ -197,7 +197,7 @@ These are aliases for the flat subcommands; they still work without the 'advance
197
197
  // `run resume` form is handled BEFORE the default `run` -> review dispatch
198
198
  // kicks in (see disambiguation block just below).
199
199
  const SUBCOMMANDS = ['init', 'run', 'runs', 'scan', 'report', 'explain', 'ignore', 'ci', 'pr', 'fix', 'costs', 'watch', 'hook', 'autoregress', 'baseline', 'triage', 'lsp', 'worker', 'mcp', 'test-gen', 'pr-desc', 'doctor', 'preflight', 'setup', 'council', 'migrate-v4', 'migrate', 'migrate-doctor', 'deploy', 'brainstorm', 'spec', 'plan', 'implement', 'review', 'validate', 'autopilot', 'internal', 'help', '--help', '-h'];
200
- const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file', 'path', 'adapter', 'ref', 'sha', 'spec', 'context', 'mode', 'phases', 'budget'];
200
+ const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce', 'ask', 'focus', 'fail-on', 'note', 'reason', 'expires', 'profile', 'severity', 'prompt', 'context-file', 'path', 'adapter', 'ref', 'sha', 'spec', 'context', 'mode', 'phases', 'budget', 'stack'];
201
201
  // Bare invocation — no subcommand, no flags → show welcome guide
202
202
  if (args.length === 0) {
203
203
  const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.GEMINI_API_KEY ||
@@ -933,14 +933,31 @@ switch (subcommand) {
933
933
  }
934
934
  case 'scaffold': {
935
935
  // v7.2.0 — `claude-autopilot scaffold --from-spec <path>`
936
+ // v7.4.0 — `--stack <node|python|fastapi>` + `--list-stacks`.
937
+ if (boolFlag('list-stacks')) {
938
+ const { printStackList } = await import("./scaffold.js");
939
+ printStackList();
940
+ process.exit(0);
941
+ }
936
942
  const fromSpec = flag('from-spec');
937
943
  const dryRun = boolFlag('dry-run');
944
+ const stackArg = flag('stack');
945
+ if (stackArg && !['node', 'python', 'fastapi'].includes(stackArg)) {
946
+ console.error(`\x1b[31m[claude-autopilot] --stack "${stackArg}" not recognized — supported: node, python, fastapi\x1b[0m`);
947
+ console.error(` See: claude-autopilot scaffold --list-stacks`);
948
+ process.exit(3);
949
+ }
938
950
  if (!fromSpec) {
939
951
  console.error(`\x1b[31m[claude-autopilot] scaffold requires --from-spec <path>\x1b[0m`);
940
952
  console.error(` Example: claude-autopilot scaffold --from-spec docs/specs/foo.md`);
953
+ console.error(` Stacks: claude-autopilot scaffold --list-stacks`);
941
954
  process.exit(1);
942
955
  }
943
- await runScaffold({ specPath: fromSpec, dryRun });
956
+ await runScaffold({
957
+ specPath: fromSpec,
958
+ dryRun,
959
+ ...(stackArg ? { stack: stackArg } : {}),
960
+ });
944
961
  process.exit(0);
945
962
  break;
946
963
  }
@@ -0,0 +1,20 @@
1
+ import type { ParsedFiles, ScaffoldResult, ScaffoldRunContext } from './types.ts';
2
+ /**
3
+ * Build a minimal starter package.json. Caller passes in any explicit
4
+ * hints (parsed from spec); we layer Node 22 ESM defaults on top.
5
+ *
6
+ * Note: signature unchanged from v7.2.0 — re-exported through ../scaffold.ts
7
+ * so consumers that imported from the public scaffold module keep working.
8
+ */
9
+ export declare function buildStarterPackageJson(projectName: string, hints: ParsedFiles['packageHints']): Record<string, unknown>;
10
+ /**
11
+ * Node ESM scaffolder. Materializes directories + placeholder files,
12
+ * writes package.json (when listed in spec) and tsconfig.json (when listed),
13
+ * choosing JS vs TS tsconfig flavor based on which extension dominates the
14
+ * other listed paths.
15
+ *
16
+ * Behavior is intentionally byte-identical to v7.2.0 — the existing
17
+ * tests/scaffold.test.ts is the regression bar.
18
+ */
19
+ export declare function scaffoldNode(ctx: ScaffoldRunContext): Promise<ScaffoldResult>;
20
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1,162 @@
1
+ // v7.4.0 — Node ESM scaffolder, extracted from src/cli/scaffold.ts (was the
2
+ // monolithic v7.2.0 implementation). Pure module split: the buildStarterPackageJson
3
+ // + scaffoldNode functions here are byte-identical-in-behavior to v7.2.0; the
4
+ // existing 11 scaffold tests are the regression bar.
5
+ //
6
+ // Why split: v7.4.0 adds Python + FastAPI per-stack scaffolders (see ./python.ts).
7
+ // Keeping each stack in its own module makes the v7.5+ plan (Go, Rust, Ruby) a
8
+ // drop-in pattern: add a new file, register it in the dispatcher inside
9
+ // ../scaffold.ts.
10
+ import * as fs from 'node:fs';
11
+ import * as fsAsync from 'node:fs/promises';
12
+ import * as path from 'node:path';
13
+ const PASS = '\x1b[32m✓\x1b[0m';
14
+ const SKIP = '\x1b[2m·\x1b[0m';
15
+ const DIM = (t) => `\x1b[2m${t}\x1b[0m`;
16
+ /**
17
+ * Build a minimal starter package.json. Caller passes in any explicit
18
+ * hints (parsed from spec); we layer Node 22 ESM defaults on top.
19
+ *
20
+ * Note: signature unchanged from v7.2.0 — re-exported through ../scaffold.ts
21
+ * so consumers that imported from the public scaffold module keep working.
22
+ */
23
+ export function buildStarterPackageJson(projectName, hints) {
24
+ const pkg = {
25
+ name: projectName,
26
+ version: '0.1.0',
27
+ private: true,
28
+ type: hints.type ?? 'module',
29
+ engines: { node: '>=22' },
30
+ scripts: {
31
+ test: 'node --test tests/*.test.js',
32
+ ...hints.scripts,
33
+ },
34
+ };
35
+ if (hints.bin)
36
+ pkg.bin = hints.bin;
37
+ if (hints.dependencies)
38
+ pkg.dependencies = hints.dependencies;
39
+ if (hints.devDependencies)
40
+ pkg.devDependencies = hints.devDependencies;
41
+ return pkg;
42
+ }
43
+ const STARTER_TSCONFIG_JS = {
44
+ compilerOptions: {
45
+ target: 'ES2022',
46
+ module: 'NodeNext',
47
+ moduleResolution: 'NodeNext',
48
+ allowJs: true,
49
+ checkJs: true,
50
+ noEmit: true,
51
+ strict: true,
52
+ esModuleInterop: true,
53
+ skipLibCheck: true,
54
+ types: ['node'],
55
+ },
56
+ include: ['bin/**/*', 'src/**/*', 'tests/**/*'],
57
+ };
58
+ const STARTER_TSCONFIG_TS = {
59
+ compilerOptions: {
60
+ target: 'ES2022',
61
+ module: 'NodeNext',
62
+ moduleResolution: 'NodeNext',
63
+ outDir: 'dist',
64
+ strict: true,
65
+ esModuleInterop: true,
66
+ skipLibCheck: true,
67
+ types: ['node'],
68
+ },
69
+ include: ['bin/**/*', 'src/**/*', 'tests/**/*'],
70
+ };
71
+ /**
72
+ * Node ESM scaffolder. Materializes directories + placeholder files,
73
+ * writes package.json (when listed in spec) and tsconfig.json (when listed),
74
+ * choosing JS vs TS tsconfig flavor based on which extension dominates the
75
+ * other listed paths.
76
+ *
77
+ * Behavior is intentionally byte-identical to v7.2.0 — the existing
78
+ * tests/scaffold.test.ts is the regression bar.
79
+ */
80
+ export async function scaffoldNode(ctx) {
81
+ const { cwd, parsed, dryRun } = ctx;
82
+ const projectName = path.basename(cwd);
83
+ const filesCreated = [];
84
+ const filesSkippedExisting = [];
85
+ const dirsCreated = [];
86
+ let packageJsonAction = 'skipped-exists';
87
+ let tsconfigAction = 'skipped-no-ts';
88
+ // 1) Create directories first.
89
+ const dirs = new Set();
90
+ for (const p of parsed.paths) {
91
+ const d = path.dirname(p);
92
+ if (d && d !== '.')
93
+ dirs.add(d);
94
+ }
95
+ for (const d of dirs) {
96
+ const abs = path.join(cwd, d);
97
+ if (fs.existsSync(abs))
98
+ continue;
99
+ if (!dryRun)
100
+ await fsAsync.mkdir(abs, { recursive: true });
101
+ dirsCreated.push(d);
102
+ console.log(` ${PASS} mkdir ${DIM(d + '/')}`);
103
+ }
104
+ // 2) Create placeholder files (skip ones we'll handle specially).
105
+ const SPECIAL = new Set(['package.json', 'tsconfig.json']);
106
+ for (const p of parsed.paths) {
107
+ if (SPECIAL.has(p))
108
+ continue;
109
+ const abs = path.join(cwd, p);
110
+ if (fs.existsSync(abs)) {
111
+ filesSkippedExisting.push(p);
112
+ console.log(` ${SKIP} exists ${DIM(p)}`);
113
+ continue;
114
+ }
115
+ if (!dryRun) {
116
+ await fsAsync.mkdir(path.dirname(abs), { recursive: true });
117
+ // Touch — empty file. Real content is the agent's job.
118
+ await fsAsync.writeFile(abs, '', 'utf8');
119
+ }
120
+ filesCreated.push(p);
121
+ console.log(` ${PASS} touch ${DIM(p)}`);
122
+ }
123
+ // 3) package.json — only if the spec lists it.
124
+ if (parsed.paths.includes('package.json')) {
125
+ const pkgAbs = path.join(cwd, 'package.json');
126
+ if (fs.existsSync(pkgAbs)) {
127
+ packageJsonAction = 'skipped-exists';
128
+ console.log(` ${SKIP} exists ${DIM('package.json (preserved)')}`);
129
+ }
130
+ else {
131
+ const pkg = buildStarterPackageJson(projectName, parsed.packageHints);
132
+ if (!dryRun) {
133
+ await fsAsync.writeFile(pkgAbs, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
134
+ }
135
+ packageJsonAction = 'created';
136
+ console.log(` ${PASS} write ${DIM('package.json (Node 22 ESM starter)')}`);
137
+ }
138
+ }
139
+ // 4) tsconfig.json — only if the spec lists it. JS-flavor when the
140
+ // other paths are predominantly .js, TS-flavor for .ts.
141
+ if (parsed.paths.includes('tsconfig.json')) {
142
+ const tsAbs = path.join(cwd, 'tsconfig.json');
143
+ if (fs.existsSync(tsAbs)) {
144
+ tsconfigAction = 'skipped-exists';
145
+ console.log(` ${SKIP} exists ${DIM('tsconfig.json (preserved)')}`);
146
+ }
147
+ else {
148
+ const otherPaths = parsed.paths.filter((p) => !SPECIAL.has(p));
149
+ const tsCount = otherPaths.filter((p) => /\.tsx?$/.test(p)).length;
150
+ const jsCount = otherPaths.filter((p) => /\.jsx?$/.test(p)).length;
151
+ const config = tsCount > jsCount ? STARTER_TSCONFIG_TS : STARTER_TSCONFIG_JS;
152
+ tsconfigAction = 'created';
153
+ if (!dryRun) {
154
+ await fsAsync.writeFile(tsAbs, JSON.stringify(config, null, 2) + '\n', 'utf8');
155
+ }
156
+ const flavor = config === STARTER_TSCONFIG_TS ? 'compiled TS to dist/' : 'JS w/ JSDoc + checkJs';
157
+ console.log(` ${PASS} write ${DIM(`tsconfig.json (${flavor})`)}`);
158
+ }
159
+ }
160
+ return { filesCreated, dirsCreated, filesSkippedExisting, packageJsonAction, tsconfigAction };
161
+ }
162
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1,71 @@
1
+ import type { ScaffoldResult, ScaffoldRunContext } from './types.ts';
2
+ /**
3
+ * PEP 503 distribution-name normalization, restricted to what we need
4
+ * here. Lowercase, runs of `[._-]+` collapse to a single `-`, leading +
5
+ * trailing `[._-]` stripped. Empty result falls back to 'app' (so the
6
+ * worst-case `cwd` of `___` still produces a buildable pyproject).
7
+ */
8
+ export declare function normalizeDistributionName(raw: string): string;
9
+ /**
10
+ * Convert a (PEP 503 normalized) distribution name into a valid Python
11
+ * identifier suitable for a top-level package directory:
12
+ * - replace `-` and `.` with `_`
13
+ * - prefix `_` if it starts with a digit (so `2cool` -> `_2cool`)
14
+ *
15
+ * Tests pin both transformations:
16
+ * my-pkg-2 -> my_pkg_2
17
+ * 2cool -> _2cool
18
+ */
19
+ export declare function packageNameFromDistribution(distribution: string): string;
20
+ /**
21
+ * Parse a dependency string (`fastapi`, `fastapi>=0.110`,
22
+ * `uvicorn[standard]`, `pyramid==2.0`) into its PEP 503 normalized
23
+ * "name" portion — used purely as a dedup key. We don't care about the
24
+ * version specifier when keying; first-occurrence wins (per spec
25
+ * "Dedupe by PEP 503 normalized name; first occurrence wins").
26
+ */
27
+ export declare function dependencyNameKey(dep: string): string;
28
+ /**
29
+ * Build the dependency list for the generated pyproject.toml. Honors
30
+ * the narrow contract from spec ("Dependency hint extraction (codex
31
+ * WARNING #6)"): values flow through verbatim, no version inference,
32
+ * deduped by PEP 503 normalized name. For FastAPI we ALSO seed
33
+ * `fastapi>=0.110` and `uvicorn[standard]>=0.27` if not already present.
34
+ */
35
+ export declare function buildPythonDependencies(hintDeps: string[] | undefined, isFastapi: boolean): string[];
36
+ /**
37
+ * Generate the pyproject.toml body. Caller supplies the resolved
38
+ * distribution name, package name, dependency list, and FastAPI flag
39
+ * (only difference: FastAPI adds a `[project.scripts]` block).
40
+ */
41
+ export declare function buildPyproject(opts: {
42
+ distributionName: string;
43
+ packageName: string;
44
+ dependencies: string[];
45
+ isFastapi: boolean;
46
+ }): string;
47
+ /** FastAPI entrypoint — codex CRITICAL #2: must be runnable, not a stub. */
48
+ export declare function buildFastapiMain(packageName: string): string;
49
+ /** Smoke test for the FastAPI scaffold — also auto-included so pytest config isn't dead. */
50
+ export declare function buildFastapiTest(packageName: string): string;
51
+ /**
52
+ * The Python scaffolder. Materializes:
53
+ * - src/<package_name>/__init__.py (empty)
54
+ * - tests/ directory
55
+ * - pyproject.toml (PEP 621 + hatchling)
56
+ * - README.md (only if missing)
57
+ *
58
+ * For FastAPI specs it also writes:
59
+ * - src/<package_name>/main.py (runnable FastAPI app)
60
+ * - tests/test_main.py (smoke test)
61
+ *
62
+ * Files explicitly listed in the spec's `## Files` get touched as empty
63
+ * placeholders if they don't already exist (matches the v7.2.0 Node
64
+ * behavior). The special files above are written with content even when
65
+ * not listed in `## Files` — without them the generated pyproject.toml
66
+ * is invalid (missing package dir) or has dead config (no tests).
67
+ */
68
+ export declare function scaffoldPython(ctx: ScaffoldRunContext, opts: {
69
+ isFastapi: boolean;
70
+ }): Promise<ScaffoldResult>;
71
+ //# sourceMappingURL=python.d.ts.map