Repository navigation
Migrate from npm to pnpm - #6
Merged
Merged
Conversation
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
huangyiirene
marked this pull request as ready for review
January 18, 2026 10:19
Copilot AI
added a commit
that referenced
this pull request
Feb 16, 2026
…fineInterface - Export defineInterface from root index.ts (reviewer suggestion) - Mark ROADMAP Phase A items as complete - Update ROADMAP metrics (199 test files, 5468 tests, 177 schema files) - Update Phase 1 deliverables with Interface/content elements - Add defineInterface() to DX helper list - Update design doc status from "Proposal" to "Phase A Implemented" - Mark design doc Phase A checklist items complete - Add decisions #6-8 to design doc decision log (PageSchema merge, SortItemSchema, branding extend) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
May 22, 2026
Introduces an opt-in path in ObjectStackProtocolImplementation.saveMetaItem that writes overlay metadata through SysMetadataRepository.put instead of the raw engine, so writes append to the change-log and emit HMR seq events. Behavioural changes (all behind options.useRepositoryWritePath / OBJECTSTACK_USE_REPOSITORY_WRITE_PATH=1): - saveMetaItem request gained optional parentVersion (If-Match) and actor fields. ConflictError -> 409 metadata_conflict. - Plural type aliases (views, dashboards, ...) normalized to singular before the repo's overlay-allowlist gate (rubber-duck #5). - Object-registry mutation moved AFTER successful put() so a conflict does not leave the in-memory registry stale (rubber-duck #3 invariant test added). Repo/test-fake fixes uncovered by rubber-duck review: - SysMetadataRepository.put/delete now update/delete by row id because the engine's strict .update requires id or multi:true (rubber-duck #1). - sys_metadata.checksum column widened from 64 -> 71 chars to hold the sha256: prefix produced by hashSpec() (rubber-duck #2). - Three test fake engines extended to support both overlay-tuple and id-based where lookups. 333/333 objectql tests pass. Deferred to PR-10d.4: REST plumbing for parentVersion/actor (rubber-duck #6), race-window retry for omitted parentVersion (rubber-duck #4), default flag flip + legacy path removal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
xuyushun441-sys
pushed a commit
that referenced
this pull request
May 23, 2026
Walking through Studio as a low-code developer surfaced a fundamental gap: it is a beautiful metadata BROWSER but offers no authoring affordances. The #1 reflex of every Airtable / Power Apps user — add a field — has no entry point in our UI. This change adds two authoring touchpoints to the Object Hub > Fields panel that respect Prime Directive #6 (no temporary workarounds) and stay true to metadata-as-code: 1. + Add field button A primary CTA in the toolbar opens a guided dialog (AddFieldDialog) with a type picker (18 supported field types, each with icon + one-line semantics), a derived snake_case machine-name preview, and a live snippet preview. Two actions: • Copy snippet — pastes a defineField-style literal into the clipboard, ready to drop into the fields: { … } block. • Open .object.ts in VS Code — vscode:// deep-link via the existing vscode-objectstack extension. Filesystem writes from the browser are intentionally avoided. When the runtime overlay write-path matures (ADR-0005), the dialog can swap the snippet flow for a real persist call without changing its contract. 2. Click any field row to open a detail drawer Rows are now cursor-pointer and trigger a side Sheet (FieldDetailDrawer) showing the full normalised field spec — all properties, options enumerated, references, formula, validation — plus the same VS Code deep-link and a per-field Copy snippet that emits just this field's literal. The drawer is read-only; users who want to edit follow the VS Code link. The previous behaviour (clicking a row did nothing) was the single biggest dead-end during the persona walkthrough. The drawer is the minimum viable acknowledgement that a field is an interactive object, not a static row of text. Plumbing changes - ObjectSchemaInspector preserves every property of the field spec (spread over the cherry-picked subset) so the drawer has access to schema properties beyond the table columns. - Added a ChevronRight column on the right edge of every row, group-hover translate-x for the same drill-in affordance used on MetadataListPage compact rows. - CopyButton stops propagation so the row click does not fire when copying the field name. Build / tests pnpm --filter @objectstack/studio build — clean. pnpm --filter @objectstack/studio test — 69/69 tests pass; same 2 pre-existing @object-ui/core/dist/evaluator/ExpressionEvaluator module resolution failures in playground-plugins / plugin-system suites, unrelated to this work. Files - apps/studio/src/components/FieldDetailDrawer.tsx (new, ~160 lines) - apps/studio/src/components/AddFieldDialog.tsx (new, ~280 lines) - apps/studio/src/components/ObjectSchemaInspector.tsx · Imports FieldDetailDrawer, AddFieldDialog, Plus, ChevronRight · State for selectedField + addOpen · Preserves full field spec via spread in fieldEntries · Toolbar: + Add field primary CTA · TableRow: cursor-pointer, onClick → setSelectedField · New chevron column on right; colSpan bumped to 7 · Drawer + dialog mounted at end of component · CopyButton stops click propagation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced May 25, 2026
Closed
This was referenced Jun 11, 2026
os-zhuang
added a commit
that referenced
this pull request
Jun 19, 2026
…antics Full hardening of the remaining items from review: - #5 MySQL/length: key _objectstack_sequences by a single key_hash (SHA-256 of object,tenant_id,field,scope) instead of a 4-column natural PK. The natural PK exceeded MySQL's utf8mb4 index-length limit (a certain CREATE TABLE failure) and bounded how long a {field} scope could be. The hash PK keys every dialect uniformly and lets scope be a generous non-indexed column. Legacy 3-column and interim {scope}-column tables are migrated in place; migration fails safe (fixed-prefix keeps working, a per-scope write errors actionably). - #1 scope ambiguity: confirmed NOT fixable by separating adjacent token boundaries — when two records render the same prefix they render the same visible number, so they MUST share a counter to stay unique (a separator would mint duplicates). Documented the semantics + the remedy (delimiter literal in the format), backed by tests. The compile lint already nudges authors toward unambiguous formats. - #6 width overflow: confirmed by-design — the pad width is a MINIMUM, the counter grows past it and never wraps (mainstream autonumber semantics). Documented + regression test, no throw (throwing would break legitimate high-count sequences). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 19, 2026
…ber formats (#2043) * feat(autonumber): date, {field} and per-scope counter reset for formats Tokenize autonumberFormat via a shared pure renderer in @objectstack/spec (parseAutonumberFormat / renderAutonumber) that both the engine fallback and the SQL driver call, so they emit byte-identical numbers (#1603 parity): - date tokens {YYYY}{YY}{MM}{DD}{YYYYMMDD} resolve the calendar day in the request's business timezone (ExecutionContext.timezone, ADR-0053; UTC fallback), threaded through new DriverOptions.timezone - {field} interpolation substitutes record values into the prefix - counter scope = rendered prefix before the sequence slot, so AD{YYYYMMDD}{0000} resets daily, {section}{island_zone}{000} numbers per group, {plan_no}{000} numbers per parent — one mechanism, no separate reset config Fixed-prefix formats (CASE-{0000}) render an empty scope and keep their single global counter. _objectstack_sequences gains a scope column (PK widened to object,tenant_id,field,scope); legacy 3-column tables migrate in place on first use, carrying existing counters to scope=''. * fix(autonumber): drop backtracking lookahead in seed scan (ReDoS) The empty-prefix legacy branch used /(\d+)(?!.*\d)/ to grab the last digit run, whose negative lookahead is a polynomial-ReDoS sink on stored values with many repeated zeros (CodeQL js/polynomial-redos, high). Replace both branches with the linear /\d+/g, preserving the last-digit-run semantics. * fix(autonumber): guard {field} interpolation footguns Add three guardrails on top of the {field}/date/per-scope autonumber work: - Empty interpolated {field} now throws (shared missingFieldValues helper) in both the SQL driver and the engine fallback, instead of silently collapsing the record into the wrong counter scope. - Build-time lint (objectstack compile): unknown / self-referencing {field} fails the build; an optional {field} warns to mark it required. - Legacy _objectstack_sequences PK-widen failure fails safe — fixed-prefix sequences keep working and a per-scope write raises an actionable error rather than an opaque DB primary-key violation at insert time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(autonumber): hash-keyed sequence table; clarify scope & width semantics Full hardening of the remaining items from review: - #5 MySQL/length: key _objectstack_sequences by a single key_hash (SHA-256 of object,tenant_id,field,scope) instead of a 4-column natural PK. The natural PK exceeded MySQL's utf8mb4 index-length limit (a certain CREATE TABLE failure) and bounded how long a {field} scope could be. The hash PK keys every dialect uniformly and lets scope be a generous non-indexed column. Legacy 3-column and interim {scope}-column tables are migrated in place; migration fails safe (fixed-prefix keeps working, a per-scope write errors actionably). - #1 scope ambiguity: confirmed NOT fixable by separating adjacent token boundaries — when two records render the same prefix they render the same visible number, so they MUST share a counter to stay unique (a separator would mint duplicates). Documented the semantics + the remedy (delimiter literal in the format), backed by tests. The compile lint already nudges authors toward unambiguous formats. - #6 width overflow: confirmed by-design — the pad width is a MINIMUM, the counter grows past it and never wraps (mainstream autonumber semantics). Documented + regression test, no throw (throwing would break legitimate high-count sequences). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: os-zhuang <jack@objectstack.ai> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 4, 2026
Merged
This was referenced Aug 26, 2026
This was referenced Aug 30, 2026
os-litant
pushed a commit
that referenced
this pull request
Sep 2, 2026
…02, B-03, C-01) Nine findings, all the same shape: a rule the file already states, or prose re-teaching what the CLI prints for itself. - E-04/E-05 §3.5 becomes a 3-row outcome -> evidence table; the re-quoted error block was already available verbatim from the install. The new probe rule is the E-05 fix: `os validate` loads WITHOUT authoredSource (validate.ts:159; config.ts:224 "Set by `os migrate meta` only"), so a probe calling `define*` throws inside the load and never reaches the gate the recipe is proving. - D-03 the data-gate table is printed by the run itself (meta.ts:106-141, measured: the two `unlocks` strings, the deployment scoping and the dry-run sentence are all emitted). Only the non-CLI half survives -- carry each printed gate into the report as pending, by name. - D-07 the three-layers preamble and Layer table; the section headings and the pipeline line already are the construct. - B-02 the Guardrails section: 4 of 6 restated the boundary verbatim and #6 restated the data-gate rule. The one non-duplicate (one conversionId per commit) moves into the boundary; #6 now lives where the gates are printed. - D-04 the AST-is-lossy rationale, D-05 the 2nd of five statements of "replay from the target major", D-06 two bullets restating the paragraph above them, D-08 a justification of a template row already in the template, B-03 the 3rd copy of the [REMOVED] grep, C-01 routing owned by README and by the domain skills' own frontmatter. B-03 left the .upgrade/ workspace listing a file nothing writes any more; that list now names retired-names.txt, which 2.6 actually produces. Ratchet: 7799 -> 6944 (ceiling 8333). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
This was referenced Sep 2, 2026
This was referenced Sep 4, 2026
This was referenced Sep 6, 2026
This was referenced Sep 11, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…ibeMessageSchema declares (objectstack-ai#17747) Part of objectstack-ai#17184 — the **second half**. The first half (the four undeclared message types) landed as objectstack-ai#17700; this PR does not revisit it. - **Clause-②: no** — this PR puts no new key on any published payload. `packages/spec/src/api/websocket.zod.ts` is **byte-unchanged**: blob `3348dd76745b61e83801e2e8bae48c6756c5a69c` on both sides (`git rev-parse HEAD:<path>` vs `git hash-object <path>`, and an empty `git diff HEAD -- <path>`). Prose only, one file. ## The defect Six subscribe/unsubscribe sites carried every subscription field at the **top level**, in snake_case, where the declared contract nests them and spells them camelCase. `SubscribeMessageSchema` (`websocket.zod.ts:278`–`:281`) declares exactly `type: 'subscribe'` plus a `subscription` object, on top of the three `BaseWebSocketMessage` fields; `EventSubscriptionSchema` (`:102`–`:117`) declares `subscriptionId` / `events` / `objects` / `filters` / `channels`. A fence copied verbatim parses against no schema in the file. The mismatch is **not one thing**, and each axis was verified separately: | page (top level) | declared | axes | |:--|:--|:--| | `subscription_id` | `subscription.subscriptionId` | nesting + snake/camel + **`z.string().uuid()`, so `"sub_1"` does not parse** | | `object`, a string | `subscription.objects`, an array | nesting + singular/plural + scalar/array | | `filter` | `subscription.filters` | nesting + singular/plural | | `events: ["created", …]` | `z.array(EventPatternSchema)` | bare verbs vs patterns — `DataEventType` publishes `data.record.created` | | *absent* | `messageId`, `timestamp` | **both required by `BaseWebSocketMessage`** — an axis not in the card | ### Two page keys map to nothing at all `record_id` and `query` are declared **nowhere** in the protocol. The narrowest scope the contract can express is object + event pattern, so those two sections now say so and move the narrowing to the client rather than showing fields no schema carries. ### Second, independent defect in the same bullet list The page described `filter` as *"Optional filter (same syntax as HTTP API filters)"*. The schema's docblock (`:106`–`:115`) says the opposite in as many words — *"⚠️ **NOT YET ENFORCED** — no runtime evaluates a payload filter. `matchesSubscription` matches on object name and event type only … a subscription carrying `filters` receives every event its patterns match."* The page promised enforcement the contract explicitly disclaims; it now carries that disclaimer. ## Fence census — the round's first deliverable Enumerated on this tree, **not** inherited from the dispatch's page-wide counts. | # | line (pre) | section | type | top-level keys | |--:|--:|:--|:--|:--| | 1 | 256–266 | Subscribe to Object Events | `subscribe` | `type`, `subscription_id`, `object`, `events`, `filter` | | 2 | 379–384 | **Unsubscribe** | `unsubscribe` | `type`, `subscription_id` | | 3 | 404–412 | Subscribe to Specific Record | `subscribe` | `type`, `subscription_id`, `object`, `record_id`, `events` | | 4 | 421–435 | Subscribe to Query Results | `subscribe` | `type`, `subscription_id`, `object`, `query`, `events` | | 5 | 655–662 | Permission Enforcement | `subscribe` | `type`, `subscription_id`, `object`, `events` | | 6 | 679–689 | **Client Reconnection (JS)** | `subscribe` | `type`, `subscription_id` (+ `sub_${Date.now()}`) | ⭐ The card named **four** sections. The census found **six** sites: **Unsubscribe** (objectstack-ai#2) and the **JS client snippet** (objectstack-ai#6) were not on the card's list. objectstack-ai#6 was also missed by a first pass that matched only JSON `"type": "subscribe"` — it uses the single-quoted JS form, and the census was widened to catch it. After the edit all six carry exactly `messageId` / `type` / `timestamp` / `subscription` (or `request` for unsubscribe). ## Fork verdict: **(a) the page is wrong** — every fence Producer evidence on this tree, all six fences landing on the same verdict: - `SubscribeMessageSchema`, `UnsubscribeMessageSchema` and `EventSubscriptionSchema` have **zero** consumers outside `packages/spec`'s own declaration, its unit test and the type-alias pin. - `IRealtimeService.handleUpgrade` is deliberately unimplemented platform-wide — `service-realtime/src/no-channel-route.pin.test.ts:49` asserts it **is not a function**. - `subscription_id` → **0** across **6120** tracked `packages/**` source files (no `dist/`), with four lit controls on the same corpus and the same call: `subscriptionId` **40**, `eventTypes` **27**, `handleUpgrade` **18**, `EventSubscriptionSchema` **13**. - The 660 `record_id` hits are the generic DB-column convention: **zero** files carry both `record_id` and `WebSocket`. Nothing produces or consumes a WebSocket subscribe message, so no top-level field on one can be real. Reading (b) — the schema is incomplete — would need a producer, and there is none. **objectui: reported, not counted.** All WS-subscription probes return 0 there, but the schema-side control is also dark (`subscriptionId` 0, `EventSubscription` 0) — only the generic `useState` (2262) lights, which proves the corpus is real source and nothing more. A zero whose control does not light is not a reading. `cloud` is not checked out here: **NOT MEASURED**. ## Verification - **40/40** derived gates green, on a clean full re-run after the builds. Gate family derived mechanically via `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (it reads the change set from the merge base itself), not hand-listed. - Five gates first returned `PREREQUISITE NOT MET` / "build first" (exit 3 and exit 1) — **NOT MEASURED, not red**. After building `@objectstack/lint...`, `@objectstack/spec` and `@objectstack/client-react...` all five are green. Exit codes captured **before** any pipe. - MDX compiles: `pnpm exec fumadocs-mdx` exit 0. - No generated or tracked artifact moved — `authorable-surface.base.json` included. `git status --porcelain` shows exactly one file. - No control characters: `check:nul-bytes` exit 0, plus a direct `grep -naP` sweep of the edited file. ## `skip-changeset`, and a conflict with the dispatch to flag⚠️ **The dispatch asked for a patch changeset; this PR carries none, and the label instead.** Reporting rather than silently choosing: - `content/docs/**` ships in **no** published package's `files[]` — 0 of 70 publishable packages mention `content/` or `docs/` there, with all 70 shipping `dist` as the positive control. - `@objectstack/docs`, which owns the page, is `private: true` — there is no publishable package to bump. Naming any other package would ship a release note for a package this PR does not touch. - **Precedent on the identical surface**: objectstack-ai#17700, the first half of this very card, merged with no changeset. By the standing criterion — nothing published moves — this is the textbook `skip-changeset` case, which `lint.yml` states in those words. Happy to add a changeset if the maintainer reads it the other way. ## Scope Confined to the subscribe/unsubscribe fences and their Parameters bullet lists. ⛔ Not `websocket.zod.ts`, ⛔ not the message-type sections objectstack-ai#17700 corrected, ⛔ not `content/docs/releases/`, ⛔ no other page. One finding deliberately left out and filed separately: the `type: "event"` fences on this same page carry top-level `subscription_id` / `event` / `data` / `changes` / `reason` where `EventMessageSchema` declares `subscriptionId` / `eventName` / `payload` / `userId` and no `changes` or `reason`. Same defect class, different message family — it is not this round's declared face, and it is not silently widened into. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH --- _Generated by [Claude Code](https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
… throwing (objectstack-ai#18103) Closes objectstack-ai#16910 Clause-②: no The diff is `packages/lint/src/**` plus one changeset. It touches no path under `packages/spec/src/**` — the contract surface `SUSPECT_TIER_GLOBS` declares — adds no schema, no published error code and no new finding id, so the PR-body carrier agrees with the card's. Self-read with `readClause2Line()` against `CLAUSE2_KEY_LINE` taken fresh from `scripts/pm/check-clause2-carriers.mjs` (three capture groups; the value is the last): `{ kind: 'declared', value: 'no' }`. ## What was wrong `lintFlowPatterns` read `.label` off each member of a flow's `edges` list behind nothing but an `Array.isArray` check, which proves the LIST and never its MEMBERS. A YAML `edges:` item left empty deserialises to `null`, so hand-written metadata turned `objectstack validate` into an uncaught `TypeError` out of a function contractually typed `(stack) => FlowLintFinding[]`. Reproduced on `origin/main` at `66e34d14d9` before any edit — the same frame the card names: ``` edges:[null, valid] threw=YES TypeError: Cannot read properties of null (reading 'label') at scanErrorLabelledEdges (lint-flow-patterns.ts:691:28) edges:[undefined, valid] threw=YES TypeError: Cannot read properties of undefined (reading 'label') edges:['a string', valid] threw=NO findings=1 edges:[42, valid] threw=NO findings=1 CONTROL edges:[valid] findings=1 rules=flow-error-label-not-fault ``` A linter that throws instead of reporting takes the whole gate down on exactly the malformed document it exists to catch, and the author gets a stack trace where a diagnostic belongs. ## The semantics chosen, and why it matches objectstack-ai#16751 **The junk member is DROPPED, silently**, through `recordsOf`'s `isRec` filter — the same coercion, from the same one home (`object-graph.ts`), that objectstack-ai#16751 chose for the seven flow-NODE-list readers. Two sibling lists on one flow member now cannot disagree about what a malformed member means. Dropping is not reporting: inventing a finding about an entry no author wrote is the phantom half of this same defect class, which is why every assertion below is an equality against a control and not a lower bound. ## Positive control — the valid edge is still judged Not throwing is half a contract; a guard that abandoned the list would satisfy it and would have traded the crash for silence. Pinned by IDENTITY (rule id and `where`), not by "did not throw", at both addressing depths and for all four bad-member kinds: ``` edges:[null, valid] findings=1 flow-error-label-not-fault where="flow 'crm_flow' · edge 'act' → 'done'" edges:[undefined, valid] findings=1 (identical) edges:['a string', valid] findings=1 (identical) edges:[42, valid] findings=1 (identical) CONTROL edges:[valid] findings=1 (identical) ``` and the whole `lintFlowPatterns(...)` result is asserted `toEqual` the control's, so nothing is invented either. The family sweep adds the same pin over the WHOLE rule table (`AUTHORING_RULES`), for `null` / `undefined` / a string / a number / an array, at both depths — `RESIDUAL_THROWS` and `RESIDUAL_INVENTED` both stay empty. ## The census — list-shaped record reads in `lint-flow-patterns.ts` Triage asked how many exist and how many go through `recordsOf`. Read off the file, not off memory. **Nine sites; four went through `recordsOf` before this PR, six do now, three never did and do not need to.** Zeros reported. | # | Site | Before | After | |---|---|---|---| | 1 | `:1430` `recordsOf(stack.flows)` | `recordsOf` | unchanged | | 2 | `:1441` `recordsOf(flow.nodes)` | `recordsOf` | unchanged | | 3 | `:462` `recordsOf(graph.nodes)` — `findDataNodeAnywhere` | `recordsOf` | unchanged | | 4 | `:1541` `recordsOf(graph.nodes)` — main walk | `recordsOf` | unchanged | | 5 | `:1442` `flow.edges` via `Array.isArray` cast | ⛔ member-blind — **THREW** | **`recordsOf`** | | 6 | `:1542` `graph.edges as unknown as AnyRec[]` | ⛔ member-blind — **THREW** | **`recordsOf`** | | 7 | `:1276` `regionNodesOf` — `Array.isArray(nodes)` cast | not `recordsOf`; **member-safe** | unchanged | | 8 | `:866` `cfg.conditions` via `Array.isArray` cast | not `recordsOf`; **member-safe** | unchanged | | 9 | `:1359` `cfg[slot]` region `branches` list | not `recordsOf`; **member-safe** | unchanged | **The eighth triage predicted is real, and it is objectstack-ai#6.** It is not reachable through the call-site coercion: a nested region's edge list is read out of a container's open `z.record` config by `collectFlowGraphs` behind only `Array.isArray`. Measured on a tree carrying the top-level repair alone, a nested `body.edges` holding `null` still threw from the identical `scanErrorLabelledEdges` frame. Sites 7–9 stay as they are, deliberately, and each was checked rather than assumed: `regionNodesOf`'s only consumer guards with `if (!child || typeof child !== 'object') continue`, `cfg.conditions` is read with optional chaining (`c?.label`), and the `branches` list hands every member to `regionNodesOf`, which refuses a non-object. Re-pointing them at `recordsOf` would change no behaviour and would add copies for `collection-coercion-single-copy.test.ts` to count. ⭐ **A tenth and eleventh site, one file over, and they are in this PR.** `os validate` runs the rule TABLE, so one throwing reader takes every other rule's verdict down with it. The moment `lintFlowPatterns` stopped throwing, the new sweep arm went red on `validateStackExpressions` (`validate-expressions.ts`), which read the same list through the same double cast at `:1405` and handed `flow.edges` on raw out of a `{ ...flow }` spread. Both are re-pointed here. Fixing only the filed rule would have satisfied the card's letter and left the gate down on the same document. ## Ablation — which reader actually carried the crash Each reader reverted one at a time on top of the final commit, the mutation proved on disk (`grep -cF` on both texts plus a `git hash-object` blob-changed check), then restored. ⛔ Not settled by an exit code and ⛔ not by `trap` alone: every restore is settled by `git hash-object` equal to the HEAD blob AND an empty `git diff HEAD`, both printed. | Ablation | Result | |---|---| | A · `lint-flow-patterns.ts` `flow.edges` back to the cast | **519 passed, 0 failed** — green | | B · `lint-flow-patterns.ts` `graph.edges` back to the cast | **10 failed** / 509 passed | | C · `validate-expressions.ts` `graph.edges` back to the cast | **6 failed** / 513 passed | | D · `validate-expressions.ts` `flow.edges` to a member-blind cast | **519 passed, 0 failed** — green | ⭐ **Reported as measured, not as predicted.** Both edge walks read `graph.edges`, because `collectFlowGraphs` re-exposes whatever array it is handed — so B and C are the load-bearing repairs and A and D are **defence in depth, not the fix**. A and D are kept deliberately: they hand the COERCED array to `collectFlowGraphs` instead of the raw one, which is the discipline the node lists already follow, and they keep two sibling lists on one flow member reading the same way. ⛔ Read them as belt and braces, not as one repair written twice.⚠️ D's first run was a no-op — the anchor arguments were inverted, the replacement asserted zero occurrences, and a hole in the landing check (it could not tell "swapped" from "never had the new text") reported it as landed. That reading was **discarded as void**, the check was strengthened with the blob-changed assertion above, and the row in the table is the re-run. ## Reverse-read — which existing sentence does this make false **One sentence changed truth value, and it changed in the direction that is easy to miss.** `findDataNodeAnywhere`'s docblock (`lint-flow-patterns.ts:450-453`) says the arrays handed in "are the ones the caller already coerced through `recordsOf`, so a malformed member cannot make this throw". Its call site at `:1505` passes `nodes, edges` — and before this PR `edges` was NOT coerced, so that sentence was **already false about half its subject**. This change makes it true. It is left unedited because it is now accurate as written. **Nothing became false.** The comments at `:1432-1440` and `:1537-1540` are scoped to the node lists and stay true; `:1531-1533` ("a non-array `nodes` still cannot throw") stays true. The `non-record-object-entry.test.ts` narrative sentences carrying issue numbers and dates are history and were not touched. **A bare present-tense count DID rot, and its ratchet caught it rather than my reading it.** `validate-expressions.test.ts`' read-surface table pinned `flow` as reading exactly `['name', 'nodes']`; the new literal `flow.edges` read makes that `['edges', 'name', 'nodes']`. That table exists to force a deliberate visit when a read is added, so the visit is the update, with the reason recorded beside it. `edges` is declared by `ObjectStackSchema.flows[]`, so the guard's second half (every key read is a key the spec declares) still holds and `TRACKED_UNDECLARED_READS` stays empty. **Tests pinning today's throwing behaviour: ZERO.** Searched `toThrow` across `packages/lint/src/*.test.ts` — no case anywhere asserted that a flow edge list throws, in either file. Nothing was re-judged and nothing was deleted. ## Verification - `pnpm --filter @objectstack/lint test` — **103 files, 3817 passed, 5 skipped, 0 failed** (`VERDICT command-exit 0` under the shared verify lock). - `pnpm --filter @objectstack/lint typecheck` — exit 0, including `check:test-typecheck`. - `pnpm --filter '@objectstack/lint^...' build` — dependency closure built before anything was judged (`VERDICT command-exit 0`). - **Repo-wide lint, not narrowed**: `node --stack-size=4000 eslint . --no-inline-config --format json` ran to completion in the foreground at `4b70081eb8` — **6743 files linted, 0 errors, 0 warnings, exit 0**. The population is read from eslint's own config and the count from its `--format json` output; no narrowing was claimed or needed. - **Derived gate family**, from `scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` over the real change set (it takes the merge base itself), reconciled with `--ran` carrying exit codes: **59 derived, 59 accounted, 56 green, 0 UNRUN.** The 3 non-green are `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt`, each `exit 3` = the gate's own `PREREQUISITE NOT MET` (they read a whole-repo `dist/`). ⛔ Those are NOT MEASURED — neither a pass nor a failure — and CI builds the repo before running them. ## Changeset — measured, not defaulted **A changeset, ⛔ not `skip-changeset`.** `@objectstack/lint` is published (no `private`), its `files[]` is `["dist", "README.md", "CHANGELOG.md"]`, and after `pnpm --filter @objectstack/lint build` the changed modules are present in `dist/index.js` and `dist/runtime.js` with a positive control (`flow-error-label-not-fault` found) and a negative control (the test-only `underNestedRegionEdges` absent). Behaviour on malformed input changes on a shipped path, so `patch`. No `skip-changeset` label applied. **No new published finding id.** No `FLOW_*` rule constant is added and no new diagnostic is emitted; on every well-formed document the output is byte-identical. The only behaviour that changes is on input that previously crashed. ## 验收备注 - **filed as objectstack-ai#18102** — `collectFlowGraphs` (`packages/spec`) filters the NODE members it hands out (objectstack-ai#16752) and forwards `edges` untouched, so `FlowGraph.edges` is declared `readonly FlowEdgeParsed[]` and can hold a non-record. Latent, ⛔ not a live crash: both `graph.edges` consumers are coerced by this PR and the producer never dereferences an edge member. Filed rather than fixed here because it is a different package and would widen this PR's verification surface past the card's declared landing point. Deduped first — the REST search endpoint is 403 by proxy design and the MCP search quota was exhausted, so the documented fallback was used (REST list of `domain:devx` issues, `state=all`, 198 read, local grep) with a positive control: `collectFlowGraphs` hit 2 cards including the known objectstack-ai#16751, while `FlowGraph.edges` and `isRegionDict` hit zero. - **noted, not filed** — `validate-expressions.ts:1200`'s `collectFlowGraphs({ ...flow, nodes, edges })` still spreads `flow`, though the callee reads only `nodes` and `edges`. The spread is now inert. Successor: none — no PR or person is heading for that line, and removing it would be an unmeasured behaviour change for no gain. -⚠️ **Scope declaration**: `validate-expressions.ts` and `validate-expressions.test.ts` are edited beyond the card's named file. Taken in place under the bounded-repair exemption — identical defect class, mechanical repair whose shape is already pinned by objectstack-ai#15793 at `:1279` in that same file, no open PR touches `packages/lint` (5 open PRs checked), and it adds no verification surface: the sweep arm this card required is what caught it and what pins it. The claim comment is the PM's and carries no file list, so this paragraph is the file-surface declaration. --- _Generated by [Claude Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…ferences, TOC the seven long reference files, trim data's description (objectstack-ai#19738) Fixes objectstack-ai#19715 Clause-②: no ## 维护者速读(草稿) **改了什么** — 四个超过 500 行的已发布 `SKILL.md`(platform 1223 → 487 · automation 961 → 448 · data 854 → 469 · upgrade 600 → 488)按标题边界切分:每个被移走的段落与代码块**逐字节**落到该技能自己的 `references/` 下(新增 9 个参考文件),原位只留一行指针;七个超过 300 行的参考文件顶部各加 5 行目录(锚到已有标题);`objectstack-data` 的 `description` 压到 689 字符,触发短语与排除句原样保留、只删括号枚举。零删内容:20 个移动块中 17 个字节相同,3 个仅改了 10 处相对链接路径(逐条列在下文)。 **为什么改** — 决策批次 objectstack-ai#213 第 1 项,维护者裁决「1 2 4 同意」:超长 SKILL.md 会被 `head` 预读截断,示例与运维尾段读起来像第二个技能;切成「路由 + 规则」后入口文件只装规则、决策表与指针,长示例按需跟指针再读。 **风险与代价(含回滚)** — ① 直接读 `SKILL.md` 单文件时,105 个 eval 断言词里有 18 个(从 103 降到 85)现在只经指针可达;整棵技能树(SKILL.md + rules + references)的读数 105/105 前后不变,方法与局限见下文。② 为了到 500 行,platform 的第二部分(插件开发)与 automation 的「状态机与审批」整段下沉,其中含少量决策表——它们仍在树里,入口页保留指向 `rules/*.md` 的路由行。③ 三个门禁脚本的数据行跟着文本走(token 天花板行、role-word 基线两行搬家、scaffold 政策的载体路径),没有新增门禁。④ 令牌总量 +2058(指针行 + 目录行),四个入口行已按落地值下锁,七个目录文件中 4 行按裁决上调、3 行有余量按落地值下锁。回滚:整个 PR 一次 revert 即可,没有生成物或状态迁移。 **席位意见** — **建议批准并合并,但先看一眼「Sections that left SKILL.md」。** 裁决的八条形状逐条核过:零删内容(20 个移动块 17 个字节相同、3 个只改了 8 处相对链接;席位另做了四个技能的整行多重集比对,除链接与 description 外没有任何一行离开树)、四个入口文件全部 ≤ 500、data 的 description 689 字且触发短语与排除句原文保留、七个长参考文件各 5 行目录且锚点全部可达、净 +77 ≤ +80、无新门禁、changeset 按 objectstack-ai#19721 惯例。evals 读数按机械法前后树列 105 / 105 不变;入口页单读 103 → 85,离开的 18 个词都在指针可达处 —— 这是切分的代价,不是丢失。token 棘轮四行入口下锁、九个新文件零余量钉住、四行目录文件按裁决引文上调。第 0 轮 CI 红是本 PR 的:`packages/rest` 的仓级测试钉住 automation 入口页必须写出状态内省路由,切分把唯一拼写搬走了;第 1 轮把拼写放回指针行(净 0 行,不改测试),席位在补丁头上重跑该测试 8 / 8 绿。留给你的一个判断:为了到 500 行,platform 的插件开发与运维两大部分、automation 的状态机与审批、data 的 seeds / 字段组 / lint 整段下沉,其中含少量决策表 —— 它们仍在树里、入口页留有路由行;如认为某段必须留在入口页,点名即可,dev 按行预算换一段下沉。复核记录:PR 评论 5786766593(PASS,在席按服务档渲染);ACCEPT 在卡 objectstack-ai#19715。CI 读数:32 latest-per-name check runs — 25 success, 7 skipped, 0 in progress, 0 other。 **你要做的** — 这是 Tier H(`skills/**`)受管面:确认「哪些规则段可以离开入口页」符合你的意图(见「Sections that left SKILL.md」),然后手工合并;如认为某段必须留在入口页,请点名,我按行预算再换一段下沉。 --- ## What this does The four published `SKILL.md` files over 500 lines are split at heading boundaries into routing + rules. Every moved paragraph and code block lands **verbatim** under that skill's `references/` (nine new files) with a one-line pointer left where it was; the seven reference files over 300 lines gain a 5-line table of contents anchored to their existing headings; `objectstack-data`'s `description` shrinks to 689 characters with the same trigger phrases and exclusion sentence. The token ratchet re-locks the four entry rows at their landed counts, pins the nine new files at theirs, and raises the four TOC'd rows that carried no headroom by exactly the added tokens under the ruling. `skills/README.md` and `content/docs/ai/skills-reference.mdx` are regenerated from the trimmed frontmatter by `gen:skill-docs`. Premise re-measured on `origin/main` `106d4c8dd` before editing: the four line counts (1223 · 961 · 854 · 600) hold; the seven files carried no TOC; data's `description` measured **1003** characters by a YAML folded-scalar parse (the card's 1,023 was a different counting; either way it sat against the 1,024 cap). `premise_still_valid: true`. ## Readings after (head `011dfd603`) | File | Before | After | |:--|--:|--:| | `skills/objectstack-platform/SKILL.md` | 1223 | **487** | | `skills/objectstack-automation/SKILL.md` | 961 | **448** | | `skills/objectstack-data/SKILL.md` | 854 | **469** | | `skills/objectstack-upgrade/SKILL.md` | 600 | **488** | | data `description` (YAML folded, trailing newline excluded) | 1003 chars | **689 chars** | | net lines across `skills/**` (tracked +77 / −1781, new files +1781) | — | **+77** (budget ≤ +80) | ## Diff proof — every moved block (source lines at `106d4c8dd` → destination), verified byte-for-byte against the base blob | # | Source (lines, count) | Heading(s) | Destination (lines) | Bytes | |:-:|:--|:--|:--|:--| | 1 | platform `SKILL.md` 39–68 (30) | `### Minimal Example` | `references/bootstrap.md` 3–32 | verbatim | | 2 | platform 116–157 (42) | `### Map Format (Key → Name)` · `### Barrel Import Pattern` | `references/bootstrap.md` 34–75 | verbatim | | 3 | platform 272–280 (9) | `### Scaffolding Command` | `references/bootstrap.md` 77–85 | verbatim | | 4 | platform 498–527 (30) | `### Plugin Loading Order Matters` · `### Programmatic Bootstrap (Without CLI)` | `references/bootstrap.md` 87–116 | verbatim | | 5 | platform 531–542 (12) | `## Multi-App Composition` | `references/bootstrap.md` 118–129 | verbatim | | 6 | platform 585–644 (60) | `## Complete Working Example` | `references/bootstrap.md` 131–190 | verbatim | | 7 | platform 649–1007 (359) | `# Part 2 — Plugin Development & Kernel Extension` (whole) | `references/plugin-development.md` 1–359 | 6 link paths re-pathed, else verbatim | | 8 | platform 1012–1213 (202) | `# Part 3 — Operations: CLI, Testing, Deployment` (whole) | `references/operations.md` 1–202 | verbatim | | 9 | automation 143–209 (67) | `### Flow Example — Auto-Escalate Overdue Cases` | `references/examples-flows.md` 3–69 | verbatim | | 10 | automation 378–760 (383) | `## State Machines & Approvals` (whole) | `references/state-machines-and-approvals.md` 1–383 | verbatim | | 11 | automation 851–916 (66) | `### Time-relative triggers — scheduled per-record date sweep` | `references/examples-flows.md` 71–136 | verbatim | | 12 | data 184–230 (47) | `## Field Groups (MVP)` | `references/examples-objects.md` 3–49 | verbatim | | 13 | data 300–359 (60) | `## Quick-Start Template` | `references/examples-objects.md` 51–110 | 1 link path re-pathed | | 14 | data 499–525 (27) | `### Lifecycle Hooks` | `references/examples-objects.md` 112–138 | 1 link path re-pathed | | 15 | data 565–622 (58) | `## Metadata Protection (\`protection\`)` | `references/examples-objects.md` 140–197 | verbatim | | 16 | data 626–767 (142) | `## Seed Data & Fixtures (\`defineSeed()\`)` (whole) | `references/seeds.md` 1–142 | verbatim | | 17 | data 771–823 (53) | `## Linting & Generation Quality` | `references/lint-rules.md` 1–53 | verbatim | | 18 | upgrade 269–301 (33) | `### 2.3 A worked R1 — the retired field-mapping \`transform\`` | `references/examples-upgrade.md` 3–35 | verbatim | | 19 | upgrade 449–496 (48) | `### 3.4 The report — the human half` | `references/examples-upgrade.md` 37–84 | verbatim | | 20 | upgrade 540–573 (34) | `## The v17-canonical shapes, compiled` | `references/examples-upgrade.md` 86–119 | verbatim | Method: for each row the base bytes (`git show 106d4c8:path`, the listed lines) were hashed against the destination's listed lines — 17 rows identical, 3 rows differ only on the link-path lines below. The upgrade `a`-tag anchors (`decide-alone-or-ask`, `reverse-check`) stayed in `SKILL.md` because they sit just outside the moved ranges. The four new multi-block files carry a one-line H1; the five single-block files begin with the moved heading itself. **The only non-verbatim bytes — 10 link-path retargets** (a relative link crossing a move boundary would otherwise dangle): - platform `SKILL.md` (staying text): `#verify-your-work`, `#ports--networking`, `#part-3--operations-cli-testing-deployment` → `./references/operations.md#…` (3) - `references/plugin-development.md` (moved text, lines 656/657/658/738/760/791 at base): `./rules/plugin-lifecycle.md` → `../rules/…` (2), `./rules/service-registry.md` → `../rules/…` (2), `./references/plugin-hooks.md` → `./plugin-hooks.md` (2), `../objectstack-data/SKILL.md` → `../../objectstack-data/SKILL.md` (1) - data `SKILL.md` (staying text): `#field-groups-mvp` → `./references/examples-objects.md#field-groups-mvp` (1) - `references/examples-objects.md` (moved text, base lines 359 and 523): `./rules/indexing.md` → `../rules/indexing.md` (1), `./references/data-hooks.md` → `./data-hooks.md` (1) Every intra-file anchor in the 15 touched markdown files resolves (github-slugger, the repo's slug authority; 100 anchors, 0 unresolved) and all 104 relative links under `skills/**` resolve to existing files (the two `./crm_index.md` / `./crm_user_guide.md` spellings in `objectstack-ui/rules/pages.md` are illustrative example text on the base, untouched). ## Sections that left SKILL.md, and why the route had to widen The card's route (platform's ops tail + the two 60-line complete examples) reaches 894 lines on platform — the fenced code in the four files totals 462 / 310 / 223 / 162 lines against the 723 / 461 / 354 / 100 that had to go, so examples alone cannot reach 500 on three of the four. The cut therefore also moves whole self-contained parts: platform Part 2 (plugin development, which already has its own `rules/*.md` and `references/plugin-hooks.md` — the entry keeps a routing line to them), automation's State Machines & Approvals, data's Seed Data & Fixtures, Field Groups and Linting. Decision tables that now live behind a pointer: platform's ObjectKernel vs LiteKernel, Plugin Loading Order, Well-Known Plugin Names, MetadataPlugin boundary, Feature Flags, the ops tables; automation's Approver Types, Node Config, Branching, Best Practices and the state-machine rule; data's seed tables and the lint-rule table. Every pointer names the destination and the moved headings (heading names only — no paraphrase). If the maintainer wants a named section back in the entry, it costs its line count against the 500 ceiling; say which and I swap. ## Evals reading (card item 5) — method, before / after, limit There is no executable runner in this repo: the fixtures `skills/*/evals/*.json` are read by `scripts/check-skills-token-ratchet.mjs` for pricing only (no script consumes `must_contain`). The reading is therefore the mechanical one the dispatch names: for every eval of the four skills, each `must_contain` term's presence (substring, case as written) in that skill's `SKILL.md` alone and in the skill tree (`SKILL.md` + `rules/**` + `references/**`), on `106d4c8dd` and on this head. Script: `eval-reading.mjs` (in the scratchpad, not committed). | Skill | SKILL.md alone, before | SKILL.md alone, after | Tree, before | Tree, after | |:--|--:|--:|--:|--:| | objectstack-platform | 24/24 | 18/24 | 24/24 | 24/24 | | objectstack-automation | 26/26 | 20/26 | 26/26 | 26/26 | | objectstack-data | 35/37 | 31/37 | 37/37 | 37/37 | | objectstack-upgrade | 18/18 | 16/18 | 18/18 | 18/18 | | **total** | **103/105** | **85/105** | **105/105** | **105/105** | The tree column — the reading of record for a skill whose entry routes to references — is unchanged for all 105 terms. The 18 terms that left `SKILL.md` alone, each with the pointer on the eval prompt's path (the two data terms already absent on the base, `schemaMode` and `type: 'secret'`, live in `rules/datasources.md` and `rules/security.md` and are untouched): | Eval | Term | Now in | Pointer in SKILL.md | |:--|:--|:--|:--| | platform objectstack-ai#2 (audit plugin) | `registerService`, `ctx.hook(`, `metadata:reloaded`, `init(`, `destroy` | `references/plugin-development.md` (+ `rules/plugin-lifecycle.md`, `references/plugin-hooks.md`) | the Part 2 pointer, which also names `rules/plugin-lifecycle.md` · `rules/service-registry.md` · `references/plugin-hooks.md` | | platform objectstack-ai#6 (production deploy) | `OS_TRUSTED_ORIGINS` | `references/operations.md` | the Part 3 pointer (names Ports & networking · Deployment targets) | | automation objectstack-ai#1 (nightly sweep) | `defineFlow`, `type: 'schedule'` | `references/examples-flows.md` | the Flow Example pointer; the Flow Types table row `schedule` stays in the entry | | automation objectstack-ai#3 (approval + revise) | `type: 'approval'`, `type: 'back'`, `maxRevisions` | `references/state-machines-and-approvals.md` | the State Machines & Approvals pointer (names Send-back for revision · Node Config); `approval_revise` and `revise` stay in the entry | | automation objectstack-ai#5 (function step) | `timeoutMs` | `references/examples-flows.md` | the Flow Example pointer | | data objectstack-ai#4 (seeds) | `env:`, `daysFromNow` | `references/seeds.md` | the Seed Data pointer (names Dynamic values (CEL)); `defineSeed`, `externalId`, `upsert` stay in the entry | | data objectstack-ai#2 (invoice lines) | `deleteBehavior`, `inlineEdit` | `rules/relationships.md`, `rules/field-types.md`, `references/lint-rules.md` | the Relationship Patterns table's link to `rules/relationships.md` (unchanged) | | upgrade objectstack-ai#2 (conditionalRequired) | `requiredWhen` | `references/examples-upgrade.md` | the v17-canonical shapes pointer | | upgrade objectstack-ai#3 (transform residue) | `never executed` | `references/examples-upgrade.md` | the worked R1 pointer | Limit of the method: substring presence is not a graded run — it cannot say whether an agent given the prompt would follow the pointer. It is the reading the card can have today; no runner was built (item 6). No assertion was tuned. ## Token ratchet (`scripts/check-skills-token-ratchet.mjs`, convention ceil(utf8 bytes / 4)) Bundle total 152,338 → **154,396** (+2,058: the 20 pointer lines, seven 5-line TOCs, four new-file titles, minus the description trim); ratcheted ceiling sum 157,621 → 154,915 (the four entry re-locks bank their shrink). Gate and `--self-test` green on this head (54 authored files within ceilings, 65 self-test cases). | Row | Ceiling before → after | Kind | |:--|:--|:--| | `objectstack-platform/SKILL.md` | 12984 → 5833 | re-lock at landed count | | `objectstack-automation/SKILL.md` | 12768 → 5762 | re-lock | | `objectstack-data/SKILL.md` | 10009 → 6128 | re-lock | | `objectstack-upgrade/SKILL.md` | 8333 → 6193 | re-lock | | `objectstack-data/rules/field-types.md` | 3032 → 3158 | **raise** +126 (TOC; zero headroom) — ruling cited in the row | | `objectstack-ui/rules/dashboards.md` | 6090 → 6252 | **raise** +162 (TOC; zero headroom) | | `objectstack-ui/rules/list-views.md` | 3011 → 3141 | **raise** +130 (TOC +132, 2 absorbed) | | `objectstack-ui/rules/pages.md` | 5501 → 5692 | **raise** +191 (TOC; zero headroom) | | `objectstack-data/references/data-hooks.md` | 12611 → 10066 | re-lock (TOC +182 landed inside 2,727 headroom) | | `objectstack-data/rules/relationships.md` | 3778 → 3676 | re-lock (TOC +154 inside 256 headroom) | | `objectstack-data/rules/validation.md` | 3109 → 2756 | re-lock (TOC +155 inside 508 headroom) | | 9 new rows | `examples-flows` 1436 · `state-machines-and-approvals` 5569 · `examples-objects` 1779 · `lint-rules` 970 · `seeds` 1299 · `bootstrap` 1351 · `operations` 3125 · `plugin-development` 3137 · `examples-upgrade` 1197 | pinned AT landed count, zero headroom | The dispatch's assumption that all seven TOC'd rows sat at zero headroom was false for four of them; those four are re-locked (a lowering, always legitimate) rather than raised. ## Other gate data that followed the moved text (no new gate, ratchet or check script) - `scripts/role-word-baseline.json`: the automation entry's one `role` occurrence (the Approver Types row) and the upgrade entry's one (`role:` in the v17 agent shape) moved with their sections; the two ledger rows moved with them. 44 files / 123 occurrences before and after — a relocation, not an expansion; `check:role-word` green. - `scripts/sync-scaffold-emission-policy.mjs`: the published-restatement row that locates the Complete Working Example's `package.json` fence by heading now points at `references/bootstrap.md`, where the fence lives; `check:scaffold-emission-policy` green (2 carriers). - Generator-owned files: `check:skill-refs` stays green without regeneration — `build-skill-references.ts` owns only `_index.md`, `*.zod.ts` and subfolders under `references/`, so the new top-level hand-written files are inputs it tolerates; `_index.md` lists Zod schemas only. `gen:skill-docs` regenerated `skills/README.md` and `content/docs/ai/skills-reference.mdx` (the one file outside `skills/**` and the gate scripts). - `check-skill-identifier-liveness` leg-2 bindings (`## Seed Data` on platform, `### Access depth …` on data) stayed in their entries. ## Gates (captured exit before any pipe; union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` on `011dfd603`, 70 families, `--ran` reconciles 70/70 with exit codes) All 70 derived families exit 0 on `011dfd603`, plus `pnpm --filter @objectstack/spec check:generated` (15 artifacts up to date after the `origin/main` merge). The ones that read a build were run after `pnpm --filter '@objectstack/lint...' build` and `pnpm --filter '@objectstack/client-react...' build` under the shared verify lock: `check:skill-examples` — "258 prose examples type-check across 3 surface(s)" (the moved os:check blocks included); `check:docs`; `check:docs-transcript-drift`; the two `@objectstack/lint` doc checks. Highlights: `check:skills-token-ratchet` + self-test ✓ · `check:skill-docs` ✓ · `check:skill-refs` ✓ · `check:skill-identifier-liveness` ✓ · `check:skill-top-level-keys` ✓ · `check:skill-compatibility` ✓ · `check:skill-frame-sync` ✓ · `check:doc-authoring` ✓ · `check:role-word` ✓ · `check:scaffold-emission-policy` ✓ · `check:nul-bytes` ✓ · `check:pm-dispatch-gates` (1905 cases) ✓ · `bare-root-worklist --self-test` ✓ (the `skills/*/references/**` liveness/precision pin holds over the enlarged population). `pnpm lint` (repo-wide eslint) is CI's run: the diff touches no `.ts`/`.js` source, only markdown, one JSON ledger and two `.mjs` gate scripts whose own self-tests ran green. Branch merged `origin/main` `170fd836a` (three spec commits, no overlap with this diff) before opening; `packages/spec` was rebuilt after the merge. ## Changeset `skip-changeset`, per the last merged `skills/**` PR (objectstack-ai#19721: `documentation` · `skip-changeset`, Clause-②: no) and measured: no workspace package's `files[]` ships `skills/` (positive control: `packages/cli` ships `bin`). `Clause-②: no`. Tier H — draft, the maintainer merges by hand. ## Acceptance notes - Two data eval terms were already reachable only through `rules/` on the base (`schemaMode`, `type: 'secret'`) — noted, not a card. - `objectstack-ui/rules/pages.md` lines 356/448 carry `./crm_index.md`-style example links that resolve to nothing; they are illustrative package-docs text inside the Docs section, untouched here — noted, not a card. --- _Generated by [Claude Code](https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc)_ --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Converts the project to use pnpm as the package manager.
Changes
package-lock.jsonwithpnpm-lock.yaml, addedpackageManagerfield topackage.jsonprepublishOnlyto use pnpm commandspnpm/action-setup@v4with proper store caching andpnpm install --frozen-lockfileInstall example
Development
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.