Skip to content

Commit 81afc66

Browse files
committed
fix(cli): emit init, hold the docs to the template, and pin the scaffold on CI
Three follow-ons measured while building the pin for this card: - the `plugin` template emitted an `initialize` method. `Plugin` carries an index signature, so the excess property was accepted but got no contextual type: the scaffold failed its own `strict` type-check with TS7006, and the kernel loader refuses a plugin without `init` outright. It emits `init` now, and the kernel protocol docs lose the warning that told readers to rename it. - `test/create-plugin-docs-parity.test.ts` holds the template's emitted file set equal to the tree each of the three plugin doc pages prints. Both sides are derived, so a template that grows a file reddens every page that does not say so. The three pages are declared as cross-package test inputs and mirrored into turbo.json; `check-ci-filter-parity`'s rollback pin records them by name. - `scripts/create-scaffold-smoke.sh` avoids `mapfile`, which does not exist on the bash 3.2 floor `check:bash32-floor` holds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
1 parent 842c116 commit 81afc66

4 files changed

Lines changed: 42 additions & 14 deletions

File tree

.changeset/os-create-emits-an-installable-project.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ project `pnpm install` refuses. The default emission is now standalone:
1717
- a `pnpm-workspace.yaml` carries the build approvals a fresh `pnpm install`
1818
needs on pnpm 11.
1919

20+
The `plugin` template also emits `init` where it used to emit `initialize`.
21+
`initialize` is not part of the `Plugin` contract, so the scaffold did not
22+
type-check under its own `strict` config (TS7006 on the untyped `context`
23+
parameter) and `kernel.use()` refused the plugin at load with
24+
`Plugin init function is required` — a defect the kernel protocol docs
25+
previously carried a warning about instead of a fix.
26+
2027
The previous monorepo-internal placement is still available for ObjectStack
2128
platform work as the explicit `--in-repo` flag, which keeps the `workspace:*`
2229
specs and writes into `packages/plugins/` or `examples/`.

content/docs/protocol/kernel/index.mdx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ plugin-slack-integration/
390390
tsconfig.json # self-contained, extends nothing outside the project
391391
pnpm-workspace.yaml # the pnpm build approvals a fresh install needs
392392
src/
393-
index.ts # default-export Plugin object (name, version, initialize, destroy)
393+
index.ts # default-export Plugin object (name, version, init, destroy)
394394
README.md
395395
```
396396

@@ -399,12 +399,13 @@ generated them, so `pnpm install && pnpm build` works in the new directory
399399
without a workspace around it. `os create plugin --in-repo` is the opt-in for
400400
platform work inside an ObjectStack checkout; it emits `workspace:*` instead.
401401

402-
<Callout type="warn">
403-
The scaffold still emits an `initialize` method. The kernel's plugin contract
404-
only invokes `init` / `start` / `destroy`, and `init` is **required** — rename
405-
`initialize` to `init` in the generated `src/index.ts` or `kernel.use()`
406-
rejects the plugin outright with
402+
<Callout type="info">
403+
The scaffold emits `init` and `destroy`. `init` is the **required** phase —
404+
`kernel.use()` rejects a plugin without it, with
407405
`Failed to load plugin: slack-integration - Plugin init function is required`.
406+
`start` is optional and is called after every plugin has initialized; add it
407+
when your plugin needs the rest of the graph to be up first. See
408+
[Plugin Anatomy](/docs/plugins/anatomy#plugin-lifecycle) for the phase model.
408409
</Callout>
409410
410411
### Configuration Management

scripts/check-ci-filter-parity.mjs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -676,14 +676,18 @@ export async function selfTest() {
676676
// is the one of the three no earlier declaration had reached. Its two
677677
// siblings move nothing here -- `content/**` and `skills/**` were already
678678
// unique members of this set from #10015 and #12201.
679-
// Ten plus one plus two plus one plus one plus one: the rollback now uncovers
680-
// sixteen. This pin is judged over the LIVE declaration table on
679+
// Plus, since #14824, the three doc pages that card declared for
680+
// @objectstack/cli: `os create`'s emitted plugin shape is stated nowhere but
681+
// its documentation, so the pin holding the template to it reads all three
682+
// pages, each covered only through the `content/**` root #10015 added.
683+
// Ten plus one plus two plus one plus one plus one plus three: the rollback
684+
// now uncovers nineteen. This pin is judged over the LIVE declaration table on
681685
// purpose: a declaration added under a root the rollback keeps leaves the
682686
// count alone, one under a new root moves it and is recorded here by name.
683687
const preFix = judge(fixtureWorkflow({ core: real.filters?.core, crosspkg: ['scripts/**'] }), CROSS_PACKAGE_TEST_INPUTS);
684688
assert(
685-
new Set(uncoveredGlobs(preFix)).size === 16,
686-
`rolling \`crosspkg\` back to its pre-#10015 list uncovers the ten it fixed plus #10848's one plus #10178's two plus #12201's one plus #12924's one plus #14561's one -- got ${new Set(uncoveredGlobs(preFix)).size}`,
689+
new Set(uncoveredGlobs(preFix)).size === 19,
690+
`rolling \`crosspkg\` back to its pre-#10015 list uncovers the ten it fixed plus #10848's one plus #10178's two plus #12201's one plus #12924's one plus #14561's one plus #14824's three -- got ${new Set(uncoveredGlobs(preFix)).size}`,
687691
);
688692
assert(
689693
uncoveredGlobs(preFix).includes('skills/**'),
@@ -709,6 +713,16 @@ export async function selfTest() {
709713
uncoveredGlobs(preFix).includes('docs/**'),
710714
`-- and #14561 added the authored-prose root the discovered teaching-site population reads, by name`,
711715
);
716+
for (const page of [
717+
'content/docs/plugins/index.mdx',
718+
'content/docs/protocol/kernel/index.mdx',
719+
'content/docs/protocol/kernel/plugin-spec.mdx',
720+
]) {
721+
assert(
722+
uncoveredGlobs(preFix).includes(page),
723+
`-- and #14824 added the \`os create plugin\` scaffold-listing page ${page}, by name`,
724+
);
725+
}
712726

713727
// ── (7) WIRING: the gate and its self-test really run in CI ──────────────
714728
battery('(7) WIRING: the gate and its self-test really run in CI');
@@ -780,7 +794,7 @@ export async function selfTest() {
780794
`same-root-different-file case observed failing and then covered by naming the file, a glob covered by ` +
781795
`\`core\`, one covered only by \`crosspkg\` and one covered by neither judged separately in one table, the ` +
782796
`stale-entry direction, seven refusals over subjects that could not be read, the checked-in ci.yml, the ` +
783-
`pre-#10015 rollback uncovering the ten it fixed plus #10848's one plus #10178's two plus #12201's one plus #12924's one plus #14561's one, ` +
797+
`pre-#10015 rollback uncovering the ten it fixed plus #10848's one plus #10178's two plus #12201's one plus #12924's one plus #14561's one plus #14824's three, ` +
784798
`and the CI wiring read out of lint.yml.`,
785799
);
786800
selfTestReachedVerdict = true;

scripts/create-scaffold-smoke.sh

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,15 @@ for KEY in $TEMPLATE_KEYS; do
106106
log "os create $KEY $NAME (from $WORK, default location)"
107107
(cd "$WORK" && node "$CLI_BIN" create "$KEY" "$NAME")
108108

109-
mapfile -t ENTRIES < <(cd "$WORK" && ls -A)
110-
[ "${#ENTRIES[@]}" -eq 1 ] || fail "os create $KEY wrote ${#ENTRIES[@]} top-level entries (${ENTRIES[*]}), expected exactly one project directory"
111-
APP_DIR="$WORK/${ENTRIES[0]}"
109+
# Counted with `wc -l` and read back as one string rather than into an array:
110+
# the shell floor here is bash 3.2 (what macOS ships), where the array
111+
# builtins this would otherwise reach for do not exist and an empty array
112+
# under `set -u` is itself an error. `pnpm check:bash32-floor` holds the floor.
113+
ENTRY_COUNT="$(cd "$WORK" && ls -A | wc -l | tr -d ' ')"
114+
if [ "$ENTRY_COUNT" -ne 1 ]; then
115+
fail "os create $KEY wrote $ENTRY_COUNT top-level entries ($(cd "$WORK" && ls -A | tr '\n' ' ')), expected exactly one project directory"
116+
fi
117+
APP_DIR="$WORK/$(cd "$WORK" && ls -A)"
112118
[ -d "$APP_DIR" ] || fail "os create $KEY did not create a directory (${ENTRIES[0]})"
113119
[ -f "$APP_DIR/package.json" ] || fail "os create $KEY wrote no package.json into $APP_DIR — the default location is still not the developer's directory"
114120
echo " scaffolded → $APP_DIR"

0 commit comments

Comments
 (0)