fix(cli): scaffold output survives a clean clone and a non-interactive run - #983
Conversation
IMPL-EVAL verdict — BLOCKED, staying in draftThe implementation slice wrote a coherent fix but never committed or pushed it. Cause: the pre-commit asset-barrel check exited 1 over the intended The four evaluator checks1. Gate evidence covers the changed files — CANNOT BE ASSESSED. There is no committed diff to gate. Every PASS in the implementation report is Codex's self-report from 2. Root cause vs symptom — the grouping hypothesis DID NOT HOLD. This group existed on the belief that four issues shared one cause. They do not. The slice landed four independent patches in four unrelated places:
Codex was explicit that it rejected the single-cause hypothesis, naming two contract clusters instead (generated-artifact contract, invocation contract). I agree with the finding, and it is honest work — but it should be recorded plainly: this is four symptom-local patches, not one root-cause fix. #966 and #975 are genuinely adjacent (both are "the scaffold's contract disagrees with what it emits"); #967 and #968 share nothing with them or with each other beyond touching 3. The issue was wrong — CONFIRMED, and the correction is properly posted. #968's filed cause was stale: 4. Regression guard fails-before — PARTIAL, and #968 has no real guard. Guards exist for #966 ( What Eric must decideThe recovery is cheap and the diff on disk is good. Recommended: resume Codex thread Two things to weigh at merge time:
Branch is also 1 commit behind PR stays draft and keeps |
…eractive run Closes #966, #967, #968, #975. The Codex slice completed this work but could not commit it: `check:assets-barrel` runs `gen:assets-barrel && git diff --exit-code`, so an intended generated diff makes the gate fail until it is committed. Regenerated the barrel and landed it. - #966 the generated `.gitignore` no longer excludes `appsettings.json`, which a clean clone needs. The template change flows into `embedded.generated.ts` via the assets barrel. - #975 `Parameters` becomes a declared top-level key in the appsettings schema, so the block the scaffold already writes stops being rejected. - #967 `init` no longer nests a project directory when the cwd is already the target. - #968 the filed cause was stale — `main` has bypassed prompts for a non-terminal stdin since beta.5. The real gap was the missing `--non-interactive` spelling and the absence of a non-terminal regression guard; both added, and the issue corrected in a comment. One pre-existing test changed deliberately. `resolve-appsettings-path_test.ts` asserted that `Parameters.postgres-password` resolves as `unknown` — true only while the schema lacked `Parameters`, which is precisely the #975 defect. That test carried two assertions and only the status was stale: the one that mattered (a top-level key is not silently re-homed under `NetScript`) is unchanged. Rather than widen the schema and quietly delete the coverage, the undeclared-key case is kept as its own test against a key the schema really does not declare.
Supervisor recovery + verificationThe implementation was complete but stranded in the worktree — 13 modified and 4 untracked Also merged One pre-existing test changed deliberately
Gates — scoped, because root
|
| Gate | Result |
|---|---|
Tests (packages/cli + packages/aspire) |
497 passed / 518 steps, 0 failed |
| Lint (scoped to both roots) | 0 occurrences |
| Format (scoped to both roots) | 0 findings |
check:assets-barrel |
PASS (clean once the generated diff is committed) |
CI on 382d2795 |
all green, incl. scaffold-runtime and close-gate |
close-gate passes because #975's two acceptance criteria were verified against the diff and ticked
with evidence on the issue — not ticked to make the gate go quiet.
Two things for Eric, both honest limitations
- The grouping hypothesis did not hold here. These are four symptom-local patches in four
unrelated places (gitignore template,AppSettingsZod, target resolution invalidate-init.ts,
a--non-interactivealias) — not one shared-cause fix. Only fix(scaffold): generated .gitignore excludes appsettings.json, which a clean clone needs #966 and fix(aspire): scaffold writes a top-level 'Parameters' block the appsettings schema drops #975 are genuinely
adjacent. Worth recording, because the same grouping did pay off on the plugin cluster (fix(plugin): generated plugin glue reaches its runtime #988),
so this is evidence about which groupings are real rather than a failure of the method. - fix(cli): non-interactive invocations still hit selection prompts #968's regression guard is a characterisation test, not a guard — it passed against the
broken code. Its filed cause was stale (mainhas bypassed non-TTY prompts since beta.5,
corrected at fix(cli): non-interactive invocations still hit selection prompts #968#issuecomment-5144895872), so what remained was the missing--non-interactive
spelling. Whether fix(cli): non-interactive invocations still hit selection prompts #968 should stay in this PR'sCloseslist is your call — closing it via
the correction alone may be more honest than closing it via a flag alias.
Not merged.
🤖 Augment PR SummarySummary: Hardens the Changes:
Technical Notes: Non-interactive resolution is centralized by mapping 🤖 Was this summary useful? React with 👍 or 👎 |
| await scaffoldRoot(context(scaffolder), options()); | ||
|
|
||
| const gitignore = scaffolder.files.get('/workspace/deploy-app/.gitignore') ?? ''; | ||
| assertEquals(gitignore.split(/\r?\n/).includes('appsettings.json'), false); |
There was a problem hiding this comment.
packages/cli/src/kernel/application/scaffold/plan-init_test.ts:191 — This assertion only checks for an exact appsettings.json line, so it would miss ignore patterns like /appsettings.json or **/appsettings.json and may not reliably guard #966 regressions.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
|
|
||
| await t.step('contains host-side Aspire parameters in generated schema', () => { | ||
| const schema = generateAppSettingsJsonSchema(); | ||
| assertEquals(JSON.stringify(schema).includes('Parameters'), true); |
There was a problem hiding this comment.
packages/aspire/tests/schema_test.ts:39 — JSON.stringify(schema).includes('Parameters') can pass even if Parameters isn’t actually a top-level property (e.g., the word appears in a description/$defs), so this guard may be weaker than intended.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Only conflict was packages/cli/src/kernel/assets/embedded.generated.ts — a generated barrel, so it was regenerated with `gen:assets-barrel` rather than hand-merged. Three of the eight PRs merged ahead of this one (#982 aspire codegen, #983 scaffold hygiene, and this branch) all rewrite that file, which is exactly the sort of file a textual three-way merge gets wrong. The .mjs -> .mts change from #982 composed cleanly with this branch's generator assertions. Composed verification: 504 tests pass, and scoped lint/fmt/check over packages/cli + packages/aspire are all clean.
Summary
Grouped beta.12 fix: everything
netscript initemits should survive a clean clone and anon-interactive run.
Four issues, one verification path — scaffold a project without a TTY, clone it clean, start it:
.gitignoreexcludesappsettings.json, which is source configuration(
aspire/apphost.mtsreads it,netscript generatederives from it, nothing regenerates it).A clean clone cannot start.
generateAppsettings()writes a top-levelParametersblock thatAppSettingsSchemadoes not model, so
parseAppSettings()silently strips it andconfig setdemands--force.init <name>from a directory already meant to be the workspace nests<name>/inside it instead of initialising in place.
instead of defaulting or naming the flag that would have answered them.
Status
Draft. Implementation is delegated to a Codex slice on this branch; the run dir is
.llm/runs/fix-scaffold-hygiene--966/. This PR body will be filled in with root cause, theregression guard and its fails-before evidence, and scoped gate output before it leaves draft.
Closes #966
Closes #975
Closes #967
Closes #968