Skip to content

fix(create-plugin): take the defaults on cancel and on a non-TTY run (objectui#8786) - #8893

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-8786-create-plugin-cancel-and-nontty
Sep 10, 2026
Merged

huangyiirene merged 1 commit into
mainfrom
claude/issue-8786-create-plugin-cancel-and-nontty

Conversation

@claude

@claude claude Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8786

Cancelling at Plugin description: or Author name: crashed the generator with a TypeError and left an empty packages/plugin-NAME/ behind; and the published README's promise that a non-interactive run "takes MIT and still writes the text" was false — such a run wrote nothing at all and exited 0 in silence. Both are now one rule.

The two premises this card's direction hangs on — measured first

The dispatching seat ruled direction (i) (make the non-TTY claim true) conditional on two premises, either of which being false meant stopping. Both hold:

  • P1 — the README really ships. packages/create-plugin/package.json lists "files": ["dist", "README.md", "CHANGELOG.md", "LICENSE"], so README.md is in the tarball by explicit declaration (and by npm's default inclusion regardless). The false sentence is a published one. ✅
  • P2 — ruling C's fourth clause is a ruling, not a description. objectui#8041 comment 5583986682 carries it in the ruling's own headline — "the scaffolder asks for the licence, defaults to MIT, and writes the matching LICENSE text; non-interactive runs take the default" — and again inside the **Ruled.** paragraph: "--yes / non-TTY runs take MIT and write the text". ✅

What was measured on origin/main (4e3a4f0), driven through a real pty and a pipe

run before after
cancel at Plugin description: TypeError, exit 1, empty dir left 10 files, MIT, exit 0
cancel at Author name: TypeError, exit 1, empty dir left 10 files, MIT, exit 0
cancel at License: (control) 10 files, MIT, exit 0 10 files, MIT, exit 0 — unchanged
all four answered 10 files, exit 0 unchanged
stdin from /dev/null nothing written, exit 0, silent 10 files, MIT, exit 0
stdin from /dev/null, no name nothing written, exit 0, silent nothing written, exit 1, Plugin name is required

The repair

  • prompts returns a short answers object. A cancelled question and every question after it are simply absent, so answers.author was undefined and licenseCopyrightHolder dereferenced .trim() on it. Both text answers are now merged against the same initial their prompt offered — one default written down once — and PluginAnswers types them optional, so reading one raw no longer compiles.
  • A non-TTY stdin is detected before prompts is called, not after. prompts never settles there: readline reports EOF, neither submit nor abort ever fires, and the process runs out of work. There is no result to inspect, which is why the check has to come first.
  • Templating moved ahead of fs.mkdirpSync. Nothing is created on disk until every file's contents exists, so no throw inside buildPluginFiles can leave a half-created directory for the retry to trip over. That makes the "empty directory" half of the card unreachable by construction rather than merely unreached.
  • The plugin name keeps its own behaviour as the one answer with no default — but now says so. Previously a non-TTY run without a name exited 0 having printed nothing.

README.md gains a section stating the single rule, and content/docs/utilities/create-plugin.mdx gains the paragraph its Rule #2 counterpart owes.

Tests — the gate this regression walked through

src/index.ts calls program.parse() at import time, so every existing test in this package stops at buildPluginFiles — and a buildPluginFiles test is green on both sides of this defect. The new src/__tests__/cli-cancel-and-nontty-8786.test.ts therefore drives the built bin: script(1) for a real pty where a cancel exists to be made, an ordinary closed pipe for the non-TTY runs. It rebuilds dist/ unconditionally in beforeAll (scrubbing VITEST, per objectui#8598) so it can never assert about a stale bundle.

At 4ae7e7c6f:

  • pnpm exec vitest run packages/create-plugin/ — Test Files 3 passed (3) · Tests 36 passed (36), the new file contributing 7. templates.test.ts's ten-key set and licenses.test.ts are among them and stay green.
  • pnpm --filter @object-ui/create-plugin type-check — exit 0 (tsc --noEmit && tsc -p tsconfig.test.json, both legs echoed).
  • pnpm --filter @object-ui/create-plugin lint — exit 0; eslint --format json reports 7 files judged, 0 errors, 0 warnings — the whole of this package's TypeScript, the new test file included. The repo-wide pnpm lint is left to CI: this narrowing excludes nothing, because eslint.config.js declares no parserOptions.project and no projectService, so no untouched file's verdict can move on this diff.
  • Gates: check:control-bytes, check:unreferenced-sources, check:doc-fences, check:esm-specifiers, check-changeset-presence, check-changeset-fixed, check-changeset-no-major, check-doc-links, check-type-check-coverage, tsconfig-test-parity-census — all exit 0. Ratchets that judge the new test file — spawned-build-vitest-env-8598, vitest-invocation-guard, one-authority-per-exported-name-6273 — 50 tests passed.

Ablation — the new tests are shown to be able to fail, from the committed tree

Each leg mutated src/index.ts, proved the change landed on disk (anchor counts plus the blob hash), rebuilt, proved it reached dist/index.js, ran, then restored with git checkout HEAD -- and proved the restore by git diff HEAD being empty and the blob matching, with a rebuild proving the mutated form was gone from dist/ too.

leg mutation result
A1a drop the merge: author: answers.author NOT MEASURED — the build refuses it, TS2322: Type 'string | undefined' is not assignable to type 'string'. A reading in its own right: the repair is compiler-enforced, so the exact regression cannot come back silently.
A1b same, cast so it compiles 4 failed / 3 passed — both early cancels and both writing non-TTY runs red with TypeError. Green: the License: cancel (control), the answered run, and the name-required run (it exits before the merge).
A2 ablate the non-TTY detection so every run is treated as interactive 3 failed / 4 passed — only the three non-TTY cases red. Every pty leg, control included, stays green.

Clean rerun after each restore: 7 passed. ⭐ The License: cancel is green in both legs, which is what makes it a control rather than a companion that reds whenever anything breaks.

Position axis (objectui#8875)

Zero class-(A) citations — no in-tree file:line reference is moved by this diff. scripts/cross-file-line-citation-census.mjs --json finds exactly two citations resolving into this package, and both address templates.ts / templates.test.ts, neither of which this diff touches; the census's own totals are unchanged. The line addresses in card #8786's own body (an issue body, not a tree file) do go stale as this lands, by construction. Everything else belongs to objectui#8875 / PR #8881 and is deliberately untouched here, CHANGELOG.md line references included.

Changeset

@object-ui/create-plugin minor — the non-TTY default path is published behaviour that grows. ⛔ Never major (39 packages in one fixed group). The pending .changeset/8041-create-plugin-licence-prompt.md needs no correction: its sentence "A non-interactive run takes MIT and still writes the text" is true as of this PR rather than before it.

验收备注

Out of scope, filed separately: objectui#8892. buildLicenseFile's docblock argues its MIT fallback makes "a manifest claiming a licence with no text beside it" unreachable "by any route, including a future caller that builds PluginTemplateVars by hand", and closes "the manifest is written from the same resolved id, so the two still agree". That last sentence is false: buildPackageJson writes vars.license verbatim and nothing resolves it a second time, so a hand-built { license: 'NOT-A-LICENCE' } yields a manifest naming it beside MIT text. Internal-only today — the package is bin-only and the CLI is the sole caller, passing resolveLicenseId's total output. This PR does not touch either function, so it does not falsify that docblock; it is not carried here, per the dispatching seat's ruling. objectui#8892 records it with three directions and no verdict.

Not reopened: the licence set (four licences, no free text) is the maintainer's call, and objectui#8778 (copyright holder in the four emitted source headers) stays separate — neither is addressed here.

Noted, not filed. content/docs/utilities/create-plugin.mdx still enumerates the prompts as "a description and an author" — the licence question objectui#8041 added as the fourth prompt is absent from that sentence, which predates this card and is objectui#8779's documentation debt, not this one's. It sits directly above the paragraph this PR adds, so it is worth a line rather than silence; ⛔ not repaired here, because establishing that no other claim holds that file costs more than the sentence is worth and the dispatch's scope red line is explicit. Successor: the next PR touching content/docs/utilities/create-plugin.mdx, or a docs pass over objectui#8041's landing.

Seat session for this implementation: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH.

Clause-② is yes, judged on the delivered diff: the emitted file set and the prompt behaviour of the scaffolder are its published behaviour, in ruling C's own words, and this diff changes what both do on two of the runs a user can perform. No new exported symbol (the package is bin-only, with no exports), and no new key on a published payload — the yes rests on the behaviour limb. needs:contract-review is on both carriers and this stays in draft until a CONTRACT_REVIEW_TIER verdict is on record.


Generated by Claude Code


Generated by Claude Code

…(objectui#8786)

`prompts` returns the answers given so far when a question is cancelled, so
`answers.author` was absent after a cancel at `Plugin description:` or `Author
name:`; `licenseCopyrightHolder` then dereferenced `undefined.trim()` and the
run died with a TypeError after `fs.mkdirpSync` had already created the target
directory, leaving an empty `packages/plugin-NAME/` that made the retry fail on
"Directory already exists".

Separately, `prompts` never settles on a stdin that is not a TTY: the process
ran out of work and exited 0 having written nothing, while the published README
promised such a run takes MIT and still writes the text.

One rule now covers both: an unanswered question takes the default its prompt
offered and the complete ten-file set is still written. The plugin name keeps
its own behaviour as the one answer with no default, and now says so instead of
exiting 0 in silence. Templating moved ahead of `fs.mkdirpSync`, so no failure
inside `buildPluginFiles` can leave a half-created directory behind.

The new test drives the built bin — through a real pty for the cancels, through
a plain pipe for the non-TTY runs — because `index.ts` calls `program.parse()`
at import time and a `buildPluginFiles` test is green on both sides of this
defect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests package: create-plugin labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3487.4 KB 3512.7 KB
Main entry chunk (gzip) 144.1 KB 350 KB
Entry file index-1Gls4k7n.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.23KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 200.01KB 55.77KB
fields (index.js) 246.94KB 62.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.54KB 46.34KB
plugin-dashboard (index.js) 131.71KB 34.50KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 252.45KB 65.33KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.26KB 34.13KB
plugin-gantt (index.js) 166.96KB 40.93KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 57.53KB 16.46KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator

Contract review at CONTRACT_REVIEW_TIER — Verdict: PASS (audit reading; director seat, summon #18 segment 6, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00xxZ)

PR objectui#8893 · head 4ae7e7c6f71aea9089e1becda21e1d5b9c13468d (re-read at posting 00:05:45Z; unchanged since 20:32Z) · card objectui#8786.

  • Reviewed-by: isolated claude-fable-5-1 subagent, transcript-verified (55 harness model stamps, all claude-fable-5-1, zero residue; positive control 46 assistant / 34 user role tokens), adopted verbatim below.
  • Implemented-by: session_01611D6ZaRaMmwTNQmSbk8MH · branch claude/issue-8786-create-plugin-cancel-and-nontty (newest Claim: 5607811424, PM-dispatched under os-zhuang). Distinct sessions ⇒ not a self-review.
  • Reading for the seat: PASS, no rework; F6 (mdx prompt enumeration debt) should become a card at ACCEPT, F5(a)/(b) optional follow-ups. ⛔ This seat cleared no carrier and touched no PR state at posting.

Verdict

PASS at CONTRACT_REVIEW_TIER — the contract increment is correctly bounded, the changeset tier matches, every cancel point and the non-TTY path behave as the card's ruled direction (i) requires, and the CLI-level tests pin the contract with a genuine control. Findings below are non-blocking notes for the ACCEPT comment; none warrants REWORK.

Head reviewed

4ae7e7c6f71aea9089e1becda21e1d5b9c13468d — matches the requested prefix; head did not move. Branch claude/issue-8786-create-plugin-cancel-and-nontty, 1 commit over merge-base 4e3a4f07e, base main, draft, mergeable_state: clean. 5 files, +495/−37.

Clause-② reading, claim match, --pair

  • My reading: yes (behaviour limb). No new exported symbol (@object-ui/create-plugin is bin-only, no exports), no new key on a published payload. What changes is the published behaviour of the create-plugin binary on runs a user can perform: cancel at Plugin description:/Author name: goes from TypeError+exit 1+empty dir → 10 files+exit 0; < /dev/null goes from nothing-written+exit 0 → 10 files+exit 0; nameless non-TTY goes from silent exit 0 → Plugin name is required exit 1. README.md is in files[] (published) and gains a section. yes is the conservative and correct call.
  • Claim: card claim comment 5607811424 carries Clause-②: yes in the fixed spelling (dev-appended under PM pre-authorisation, no second claim); PR body says the same in prose. Match.
  • PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 8893 → exit 0 ("readable in the fixed spelling and both carriers agree"); needs:contract-review on both carriers.

Governed surface

node scripts/check-governed-queue-guard.mjs --test <5 paths> → NOT GOVERNED, exit 0. No content/docs/releases/ touched. Normal merge-queue route.

CI on head

33 check-runs on 4ae7e7c6f: 30 success, 3 skipped (coverage lanes + dependabot — push-only). Lint, Type Check, Test (shard 1–4/4), Build & E2E, Changeset Declaration/Bump Policy/Fixed Group, Governed Surface Queue Guard all green. Nothing red ⇒ no causation analysis owed. The new test cannot pass vacuously (build failure or missing script(1) throws/rejects), so shard green is evidence it ran.

Findings

F1 — Published-face delta (all packages/create-plugin/src/index.ts at head).

  • :53-55 hasInteractiveStdin() (internal, process.stdin.isTTY === true), evaluated once at :60.
  • :66-89 name prompt only when interactive; :96-99 a nameless non-TTY run or a cancel at the name prompt now reaches the existing Plugin name is required / exit(1) (previously non-TTY hung prompts and fell off the event loop with exit 0, nothing printed).
  • :127-128 defaults written once; :157-166 prompts only called when interactive, onCancel records and returns undefined (keeps prompts' abort-remaining, return-partial semantics); :193-194 ?? initial merge — the TypeError site. PluginAnswers (:35-39) makes raw reads a TS2322, as the ablation A1a confirms.
  • :168-172 two new stdout lines (No TTY on stdin — …, Cancelled — …) — informational output, asserted by tests as substrings, so they are now pinned text.
  • :210 buildPluginFiles(vars) before :217 fs.mkdirpSync — no directory exists until every file's contents exists.
  • Exit-code set unchanged {0,1}; the mapping changes only on the three runs listed under Clause-②. Files on cancel: 10 at Description/Author/License, 0 at name.
  • README.md:69-91 new section; content/docs/utilities/create-plugin.mdx:57-63 new paragraph. The pre-existing README.md:66-67 sentence is now true instead of false.

F2 — Correctness vs the card: every cancel point, verified against the diff.

  • Name prompt (interactive Ctrl-C): prompts returns {} → :96-99 exit 1, nothing created — pre-existing, unchanged, now documented.
  • Description cancel → answers = {} → both text defaults + MIT; Author cancel → {description} → author '' → holder falls to "the … authors"; License cancel → {description, author} → MIT via resolveLicenseId(undefined). All three write 10 files, exit 0.
  • Non-TTY: {} → all defaults; --author/--description become the written values (:127-128).
  • Directory-exists check (:176-180) still precedes any write; with templating hoisted, no path can leave a half-created directory before the write loop. (The write loop :218-222 itself is still non-atomic against an I/O failure mid-loop — out of this card's scope; the card's defect was pre-write.)
  • Piped stdin with content is now silently ignored in favour of defaults; before the PR it wrote nothing. Not a regression, documented in the README ("a pipe … Nothing is asked"), and the No TTY on stdin line makes it visible.

F3 — Scope: clean. 5 files, all inside packages/create-plugin + its doc page + one changeset. Card item 4 (optional templates.ts docblock) correctly not carried: this diff touches neither buildPackageJson nor buildLicenseFile, so it does not falsify the docblock; filed as objectui#8892. No line-citation (class A) moved: git grep 'index\.ts:[0-9]+' over the package and the doc page finds none on origin/main.

F4 — Changeset: correct. @object-ui/create-plugin: minor — the card's own rule (non-TTY default path added ⇒ minor, never major); package is public. Text matches measured behaviour. Pending .changeset/8041-… needs no correction — its sentence becomes true with this PR.

F5 — Tests pin the contract with a real control. src/__tests__/cli-cancel-and-nontty-8786.test.ts drives the built bin (rebuilt unconditionally in beforeAll, VITEST scrubbed per #8598 — same pattern as packages/cli/src/__tests__/cli-bin.test.ts): asserts exit code, the exact 10-file set, manifest.license, LICENSE holder, and the no-TypeError stderr on each of Description/Author cancels; the License cancel is the ⭐ control (green before and after — confirmed by the PR's A1b/A2 ablation table, where it stays green in both legs); nameless non-TTY asserts exit 1 and files === null. Two gaps, both minor:

  • (a) Interactive cancel at the name prompt (README:80-83 promises exit 1, nothing created) is not pinned — only the non-TTY nameless case is. Suggest a follow-up case; not a REWORK item since the behaviour is unchanged and README-stated.
  • (b) script -qec … /dev/null is util-linux syntax; BSD/macOS script lacks -e/-c, so the four pty cases fail off-Linux with no platform guard, and this is the first pty-driven test in the tree (no spawn('script') precedent). CI is ubuntu-latest, so this is a contributor-ergonomics note, not a gate. Also note stdin.end() right after the last step makes util-linux script inject a VEOF (Ctrl-D) into the pty after the slave drains — benign here because every step list ends the last live prompt, but worth a one-line comment if the harness is reused.

F6 — README/docs: consistent with behaviour. README section and mdx paragraph state one rule and the single name exception, matching index.ts. One adjacent pre-existing defect: create-plugin.mdx:52-53 still says "Everything else — a description and an author — is asked interactively", omitting the licence prompt #8041 added; the dev flagged it as "noted, not filed". Per the checklist (noticed-but-untouched published defect ⇒ file a card), this should become a card at ACCEPT rather than a note; not this PR's falsification.

F7 — PR form. Fixes #8786 is correct: card items 1–3 are fully met and item 4 is filed as #8892; no other closing keyword sits beside an open card number (the word "closes" at the docblock quote is followed by prose, not a reference). Body carries the duplicated bare attribution footer the dev already reported — cosmetic.

Acceptance notes

  • Implemented-by: session session_01611D6ZaRaMmwTNQmSbk8MH, branch claude/issue-8786-create-plugin-cancel-and-nontty (newest Claim: = comment 5607811424, PM dispatch by os-zhuang, dev inherited it; the dev report and PR body name the same session/branch; assignee not rewritten).
  • Reviewed-by: this session session_017Js5kTpTtxieBjPyScgxJ3 (director seat) — distinct from the implementing session ⇒ not a SELF-REVIEW.
  • Premises P1 (README in files[]) and P2 (ruling C fourth clause is a ruling) were re-verified here from origin/main package.json and licenses.ts:18-21; both hold, so direction (i) was the right one to take.
  • Dev's open_questions: []; no flags to answer. Out-of-scope findings: bug(create-plugin): buildLicenseFile's MIT fallback lets the emitted manifest and LICENSE text disagree, against its own docblock #8892 filed; the mdx prompt-enumeration debt (F6) should be turned into a card at ACCEPT; F5(a)/(b) are optional follow-ups, suitable for a comment on the ACCEPT rather than new cards.
  • On PASS: strip needs:contract-review from both carriers in one stroke with a provenance comment citing this verdict, then landing-operations: Lint + Type Check already success on head, all checks green, not governed → flip ready and enqueue.

Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Landing provenance — director seat, summon #18 segment 6 (session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-10T00:14:31Z). Clearing needs:contract-review on both carriers (objectui#8893 + card objectui#8786) on the strength of the contract-review-tier PASS at #8893 (comment) (head 4ae7e7c6f7, unchanged). Landing pre-checks (contract-review.md ①②③): ① tier verdict on the card (pointer posted); ② check-clause2-carriers.mjs --pair 8893 exit 0; ③ 33 check-runs on head, 0 red / 0 in progress; governed-surface test exit 0 (not governed); mergeable_state: clean. Next: ready → auto-merge (SQUASH) → merge-queue entry, per landing-operations.md. Executed under the maintainer's 2026-09-09 13:4xZ order 「把当前的契约复审全部处理完」 precedent; the dispatching seat keeps ACCEPT/landing-window duties (MERGED confirmation + card close-out) if it is back before the queue finishes — otherwise this seat closes out.


Generated by Claude Code

@huangyiirene
huangyiirene marked this pull request as ready for review September 10, 2026 00:15
@huangyiirene
huangyiirene added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 7e247e2 Sep 10, 2026
35 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-8786-create-plugin-cancel-and-nontty branch September 10, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: create-plugin tests

Projects

None yet

2 participants