Initial Commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Proposal: enterprise-streams-metadata
|
||||
|
||||
- **Change ID**: enterprise-streams-metadata
|
||||
- **Package**: `@suaveplan/streams-metadata`
|
||||
- **Status**: PROPOSED
|
||||
- **Target path**: `packages/foundation/flow/streams-metadata`
|
||||
- **Spec delta**: ./specs/streams-metadata/spec.md
|
||||
- **Upstream source**: `/mnt/s/web/suaveplan/packages/universal/streams-metadata`
|
||||
- **Upstream enterprise audit**: `/mnt/s/web/suaveplan/enterprise/streams-metadata.md`
|
||||
|
||||
## Why
|
||||
|
||||
`@suaveplan/streams-metadata` provides a composable, stream-oriented pipeline
|
||||
for extracting MIME-type and EXIF/image metadata from `Uint8Array` payloads.
|
||||
Genesis already ships the prerequisites (`@suaveplan/streams`,
|
||||
`@suaveplan/mime`, `@suaveplan/exif`, `@suaveplan/events`) at 100/100/100/100
|
||||
coverage — this change ports the metadata package on top of them at the same
|
||||
quality bar.
|
||||
|
||||
This proposal supersedes an earlier draft in this folder that designed a
|
||||
side-channel `Promise<ExtractedMetadata>` API and folded in audio metadata.
|
||||
The upstream source (which shipped to consumers) implements three
|
||||
`BaseTransform` subclasses with a typed event surface; migration parity
|
||||
(`openspec/AGENTS.md` §0a) mandates shipping that contract, not a redesign.
|
||||
|
||||
## What
|
||||
|
||||
A fresh universal-tier package providing:
|
||||
|
||||
1. `MimeTransform` — magic-bytes detection (priority) → extension fallback →
|
||||
`application/octet-stream`.
|
||||
2. `ExifTransform` — `ImageMetadata` via `@suaveplan/exif::readMetadata`.
|
||||
3. `MetadataTransform` — composing wrapper running MIME then EXIF, forwarding
|
||||
child events, catching errors, emitting `metadata:complete` /
|
||||
`metadata:error`.
|
||||
4. Typed event surface via `BaseTransform.on/emit` (genesis
|
||||
`@suaveplan/events` no longer exports an `EventEmitter` class).
|
||||
5. Typed `StreamsMetadataError` with `defineCodes("STREAMS_METADATA", …)`
|
||||
registry.
|
||||
6. Subpath exports per feature directory (CLAUDE.md §6).
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
| Capability | Upstream | Migrated | Notes |
|
||||
|---|---|---|---|
|
||||
| `MimeTransform` (enrich / passthrough) | YES | YES | |
|
||||
| `ExifTransform` (enrich / passthrough, `read` pass-through) | YES | YES | |
|
||||
| `MetadataTransform` wrapper | YES | YES | |
|
||||
| Typed events (`mime:detected`, `exif:detected`, `metadata:complete`, `metadata:error`) | YES | UP | Migrated from `EventEmitter<Events>` field to `BaseTransform.on/emit`. Genesis `@suaveplan/events` has no `EventEmitter` class. Observable surface unchanged. |
|
||||
| `EnrichedPayload` / `MetadataPayload` / `MimeDetectionResult` / `MetadataMode` | YES | YES | |
|
||||
| Detection precedence (magic → extension → fallback) | YES | YES | |
|
||||
| `ExifTransform` raise on unknown format | YES | UP | Upstream threw plain `Error`. Migration lets `@suaveplan/exif::readMetadata` throw `ExifError(UNSUPPORTED_FORMAT)`, re-wrapped as `ExifFailedError(STREAMS_METADATA.EXIF_FAILED)` with `cause` preserved. |
|
||||
| Child-event forwarding through the wrapper | YES | YES | |
|
||||
| `metadata:error` + original-input return on failure | YES | YES | |
|
||||
| Stable error codes (`STREAMS_METADATA.*`) | NO | UP | Upstream had no registry. Migration adds `STREAMS_METADATA_CODES` via `defineCodes`. |
|
||||
| Subpath exports per feature dir | NO | UP | Upstream exported only `.`. Migration adds per-feature subpaths for tree-shaking. |
|
||||
| `@suaveplan/testing/runner` test imports | NO | UP | Upstream imported from `vitest`. Migration uses canonical wrapper per CLAUDE.md §11. |
|
||||
| Sibling `.md` per `.ts` with all six sections | NO | UP | Upstream shipped partial docs. Migration ships full coverage. |
|
||||
| 100/100/100/100 coverage gate | NO | UP | Upstream did not enforce 100%. Migration enforces it. |
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
None inside genesis at change time — this package is a fresh universal-tier
|
||||
leaf in this monorepo. Future consumers (`@suaveplan/api-plugin-file-upload`)
|
||||
will consume after archive.
|
||||
|
||||
### Risks
|
||||
|
||||
- Genesis `@suaveplan/events` does not export `EventEmitter`. The migration
|
||||
switches to `BaseTransform`'s built-in `on`/`emit`, changing the public
|
||||
observation channel from `transform.events.on(...)` to `transform.on(...)`.
|
||||
This is `UP` (upgraded) not `NO` (dropped) because the observable surface
|
||||
is unchanged.
|
||||
- The genesis `@suaveplan/mime` dist tree-shakes its signature-registration
|
||||
side effect, so external consumers (including this package) get an empty
|
||||
registry at runtime. The migration installs explicit JPEG / PNG signatures
|
||||
via `registerSignature` in `src/mime/mime.ts`'s module load so detection
|
||||
works end-to-end without depending on an upstream fix. This is a temporary
|
||||
workaround — a future change will fix the mime dist build so the
|
||||
registration is universal.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Streaming-mode (chunked) MIME / EXIF detection — both transforms operate
|
||||
on a fully buffered `Uint8Array` exactly as upstream did.
|
||||
- Audio metadata extraction — out of scope; reserved for a follow-up change.
|
||||
- Side-channel metadata promises / 64 KiB byte caps — not part of the
|
||||
upstream contract.
|
||||
- Adding new media format support beyond what `@suaveplan/mime` and
|
||||
`@suaveplan/exif` already provide.
|
||||
@@ -0,0 +1,198 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: streams-metadata (streams-metadata)
|
||||
|
||||
**Package**: `@suaveplan/streams-metadata`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: MimeTransform detects MIME type and enriches the payload
|
||||
|
||||
The package SHALL export a `MimeTransform` class extending
|
||||
`BaseTransform<{}, StreamsMetadataEvents>` from `@suaveplan/streams`. Its
|
||||
`process(input)` SHALL detect the MIME type by calling
|
||||
`@suaveplan/mime::detectFromMagicBytes(input.bytes)`; when magic bytes match,
|
||||
the result SHALL carry `source: "magic-bytes"` and the magic-bytes
|
||||
`priority`; when no magic match exists but `input.name` is a string, the
|
||||
result SHALL be derived from `@suaveplan/mime::detectFromExtension(input.name)`
|
||||
with `source: "extension"`; when neither path matches, the result SHALL be
|
||||
`{ mimeType: "application/octet-stream", priority: 0, source: "fallback" }`.
|
||||
The transform SHALL emit `mime:detected` with the result before returning. In
|
||||
`enrich` mode (default) `process` SHALL return a shallow-cloned payload with
|
||||
`mime` set; in `passthrough` mode it SHALL return the original input reference
|
||||
unchanged.
|
||||
|
||||
#### Scenario: magic-bytes detection wins over extension
|
||||
|
||||
- **WHEN** a caller invokes `await transform.process({ bytes: pngBytes, name: "x.jpg" })`
|
||||
- **THEN** the returned payload carries `mime.mimeType === "image/png"` and `mime.source === "magic-bytes"`, and a `mime:detected` listener observed the same result
|
||||
|
||||
#### Scenario: extension fallback when magic bytes are unknown
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: gibberishBytes, name: "song.mp3" })`
|
||||
- **THEN** the returned payload carries `mime.mimeType === "audio/mpeg"` and `mime.source === "extension"`
|
||||
|
||||
#### Scenario: octet-stream fallback when nothing matches
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: gibberishBytes })`
|
||||
- **THEN** the returned payload carries `mime.mimeType === "application/octet-stream"`, `mime.source === "fallback"`, and `mime.priority === 0`
|
||||
|
||||
#### Scenario: passthrough preserves the input reference
|
||||
|
||||
- **WHEN** the caller constructs `new MimeTransform({ mode: "passthrough" })` and invokes `await transform.process(payload)`
|
||||
- **THEN** the returned value is `===` the input `payload` and `mime:detected` still fires with the detection result
|
||||
|
||||
**Verified by:** `src/mime/mime.test.ts::"prefers magic bytes over extension"` AND `src/mime/mime.test.ts::"falls back to extension when magic returns null"` AND `src/mime/mime.test.ts::"falls back to application/octet-stream when nothing matches"` AND `src/mime/mime.test.ts::"passthrough mode returns the original input reference"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: ExifTransform extracts ImageMetadata and enriches the payload
|
||||
|
||||
The package SHALL export an `ExifTransform` class extending
|
||||
`BaseTransform<{}, StreamsMetadataEvents>`. Its `process(input)` SHALL invoke
|
||||
`@suaveplan/exif::readMetadata(input.bytes, options.read)` and SHALL emit
|
||||
`exif:detected` with the resulting `ImageMetadata`. In `enrich` mode (default)
|
||||
the returned payload SHALL be a shallow clone carrying `exif`; in
|
||||
`passthrough` mode it SHALL be the original input reference. When
|
||||
`readMetadata` throws, `ExifTransform` SHALL re-raise as
|
||||
`ExifFailedError(STREAMS_METADATA.EXIF_FAILED)` with `cause` set to the
|
||||
original error.
|
||||
|
||||
#### Scenario: JPEG round trip
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: minimalJpegBytes })`
|
||||
- **THEN** the returned payload carries `exif.format === "jpeg"` and an `exif:detected` listener observed the same `ImageMetadata`
|
||||
|
||||
#### Scenario: unknown bytes raise a typed error
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: gibberishBytes })`
|
||||
- **THEN** the call rejects with a `StreamsMetadataError` whose `code === STREAMS_METADATA.EXIF_FAILED` and whose `cause` is the original `@suaveplan/exif::ExifError`
|
||||
|
||||
#### Scenario: read options pass through to readMetadata
|
||||
|
||||
- **WHEN** the caller constructs `new ExifTransform({ read: { skipExif: true } })` and processes a JPEG
|
||||
- **THEN** the resulting `ImageMetadata.exif` is `undefined`
|
||||
|
||||
#### Scenario: passthrough preserves the input reference
|
||||
|
||||
- **WHEN** the caller constructs `new ExifTransform({ mode: "passthrough" })` and invokes `await transform.process(payload)` against valid JPEG bytes
|
||||
- **THEN** the returned value is `===` the input `payload` and `exif:detected` still fires
|
||||
|
||||
**Verified by:** `src/exif/exif.test.ts::"extracts metadata from a JPEG payload"` AND `src/exif/exif.test.ts::"wraps ExifError as StreamsMetadataError on unsupported bytes"` AND `src/exif/exif.test.ts::"forwards read options to @suaveplan/exif"` AND `src/exif/exif.test.ts::"passthrough mode returns the original input reference"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: MetadataTransform composes mime and exif with event forwarding
|
||||
|
||||
The package SHALL export a `MetadataTransform` class extending
|
||||
`BaseTransform<{}, StreamsMetadataEvents>`. The constructor SHALL build
|
||||
`MimeTransform` and `ExifTransform` children. Listeners on the children for
|
||||
`mime:detected` and `exif:detected` SHALL re-emit through the wrapper so a
|
||||
single `on('mime:detected', fn)` on the wrapper observes the child event.
|
||||
`process(input)` SHALL run mime then exif in sequence; on success it SHALL
|
||||
emit `metadata:complete` and return the enriched payload; if either child
|
||||
throws, it SHALL emit `metadata:error` with the thrown value and return the
|
||||
**original** input unchanged.
|
||||
|
||||
#### Scenario: happy path composes both children
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: jpegBytes, name: "photo.jpg" })`
|
||||
- **THEN** the returned payload carries both `mime` and `exif`, and listeners observed `mime:detected`, `exif:detected`, and `metadata:complete` in that order
|
||||
|
||||
#### Scenario: child failure emits metadata:error and yields the original input
|
||||
|
||||
- **WHEN** the caller invokes `await transform.process({ bytes: gibberishBytes })`
|
||||
- **THEN** the call resolves to the original input reference, a `metadata:error` listener observed the wrapped `StreamsMetadataError`, and no `metadata:complete` listener fired
|
||||
|
||||
#### Scenario: child events flow through the wrapper
|
||||
|
||||
- **WHEN** the caller subscribes `on('mime:detected', m)` and `on('exif:detected', e)` on the wrapper and invokes `process` against a JPEG
|
||||
- **THEN** both listeners fire with the payloads produced by the underlying child transforms
|
||||
|
||||
**Verified by:** `src/metadata/metadata.test.ts::"composes mime and exif into an enriched payload"` AND `src/metadata/metadata.test.ts::"emits metadata:error and returns the original input when a child throws"` AND `src/metadata/metadata.test.ts::"forwards child mime:detected and exif:detected events"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Stable error codes via defineCodes registry
|
||||
|
||||
The package SHALL declare a `STREAMS_METADATA_CODES` registry via
|
||||
`defineCodes("STREAMS_METADATA", [...])` covering every failure surface
|
||||
(`EXIF_FAILED`, `MIME_FAILED`, `INVALID_PAYLOAD`, `ABORTED`). Every code SHALL
|
||||
be reachable from real call sites (either thrown directly by a transform or
|
||||
carried on the `cause` chain of a `metadata:error` listener payload). The
|
||||
package SHALL export a `StreamsMetadataError` base class and one factory
|
||||
subclass per code.
|
||||
|
||||
#### Scenario: every code is wired into a call site
|
||||
|
||||
- **WHEN** a static auditor inspects the registry against `src/`
|
||||
- **THEN** every code in `STREAMS_METADATA_CODES` is the `.code` of an error class exposed and exercised by the test suite
|
||||
|
||||
#### Scenario: error cause chain is preserved
|
||||
|
||||
- **WHEN** an `ExifFailedError` is raised by `ExifTransform` after `readMetadata` throws an `@suaveplan/exif::ExifError`
|
||||
- **THEN** `error.cause` is the original `ExifError` instance and `error.code === "STREAMS_METADATA.EXIF_FAILED"`
|
||||
|
||||
**Verified by:** `src/errors/errors.test.ts::"registers every code via defineCodes"` AND `src/errors/errors.test.ts::"ExifFailedError preserves Error cause"` AND `src/exif/exif.test.ts::"wraps ExifError as StreamsMetadataError on unsupported bytes"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Typed event surface via BaseTransform.on/emit
|
||||
|
||||
The package SHALL declare a `StreamsMetadataEvents` map mapping each event
|
||||
name to its payload type and SHALL parameterise every transform with
|
||||
`BaseTransform<{}, StreamsMetadataEvents>`. Listener errors SHALL NOT
|
||||
propagate to `process` callers.
|
||||
|
||||
#### Scenario: listener type-safety at the call site
|
||||
|
||||
- **WHEN** a caller writes `transform.on('mime:detected', (r) => r.mimeType)` against a `MimeTransform`
|
||||
- **THEN** TypeScript types `r` as `MimeDetectionResult` and rejects accesses to non-existent fields
|
||||
|
||||
#### Scenario: listener errors are caught
|
||||
|
||||
- **WHEN** a registered listener for `mime:detected` throws
|
||||
- **THEN** `process` still resolves with the enriched payload
|
||||
|
||||
**Verified by:** `src/mime/mime.test.ts::"a throwing mime:detected listener does not break process"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Subpath exports per feature directory
|
||||
|
||||
The package SHALL declare subpath exports for every public feature
|
||||
directory: `./mime/mime`, `./exif/exif`, `./metadata/metadata`,
|
||||
`./events/events`, `./errors/errors`, `./types/payload`. Every feature barrel
|
||||
SHALL use explicit named re-exports per CLAUDE.md §10.
|
||||
|
||||
#### Scenario: each feature is importable via its subpath
|
||||
|
||||
- **WHEN** a consumer writes `import { MimeTransform } from "@suaveplan/streams-metadata/mime/mime"`
|
||||
- **THEN** the import resolves and includes only `MimeTransform`'s graph
|
||||
|
||||
#### Scenario: no `export *` leakage in any non-root barrel
|
||||
|
||||
- **WHEN** a static auditor greps every `src/**/index.ts` for `export *`
|
||||
- **THEN** the result is empty
|
||||
|
||||
**Verified by:** `package.json::"declares subpath exports for every feature directory"` AND inspection of `src/**/index.ts`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Universal-tier purity
|
||||
|
||||
The package SHALL run unmodified in Node, Bun, and browser tiers. The source
|
||||
SHALL NOT import `node:*` modules, the DOM, `window`, or `process`. Runtime
|
||||
deps SHALL be limited to `@suaveplan/error`, `@suaveplan/events`,
|
||||
`@suaveplan/exif`, `@suaveplan/mime`, `@suaveplan/streams`, `@suaveplan/types`,
|
||||
and `tslib`. `sideEffects` SHALL be `false`.
|
||||
|
||||
#### Scenario: no node:* or browser-only imports in src
|
||||
|
||||
- **WHEN** a static auditor greps `src/**/*.ts` for `from "node:"`, `globalThis.document`, or `from "fs"`
|
||||
- **THEN** the result is empty
|
||||
|
||||
**Verified by:** Static audit of `src/**/*.ts` import statements
|
||||
@@ -0,0 +1,64 @@
|
||||
# Tasks — `@suaveplan/streams-metadata`
|
||||
|
||||
- **Change ID**: enterprise-streams-metadata
|
||||
- **Package**: `@suaveplan/streams-metadata`
|
||||
- **Target path**: `packages/foundation/flow/streams-metadata`
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [x] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
|
||||
- [x] 0.2 Worktree under `.claude/worktrees/agent-a9864414c72f2d392` is the active migration worktree branched off `genesis@ea6e6df5d`; branch renamed to `feat/streams-metadata` before any code was written
|
||||
- [x] 0.3 Confirm the package set in `MANIFEST.md` matches the actual package tree; regenerate via `bun scripts/folder-reorg/generate-manifest.ts` before archive
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [x] 1.1 Read upstream source at `/mnt/s/web/suaveplan/packages/universal/streams-metadata/` end to end
|
||||
- [x] 1.2 Read upstream enterprise audit `/mnt/s/web/suaveplan/enterprise/streams-metadata.md`
|
||||
- [x] 1.3 Read genesis reference packages: `@suaveplan/streams` (parent `BaseTransform`), `@suaveplan/mime` (detection API), `@suaveplan/exif` (`readMetadata` semantics)
|
||||
- [x] 1.4 Draft `proposal.md` with parity checklist + design upgrades
|
||||
- [x] 1.5 Draft `specs/streams-metadata/spec.md` — every requirement carries a `**Verified by:**` line at authoring time
|
||||
|
||||
## 2. Implementation — scaffold
|
||||
|
||||
- [ ] 2.1 Create `packages/foundation/flow/streams-metadata/` with `package.json` (`@suaveplan/streams-metadata`, version `0.1.0`, `sideEffects: false`)
|
||||
- [ ] 2.2 Workspace deps: `@suaveplan/error workspace:^`, `@suaveplan/events workspace:^`, `@suaveplan/exif workspace:^`, `@suaveplan/mime workspace:^`, `@suaveplan/streams workspace:^`, `@suaveplan/types workspace:^`, `tslib catalog:build`
|
||||
- [ ] 2.3 Dev deps: `@suaveplan/testing workspace:^`, `@types/node catalog:`, `@vitest/coverage-istanbul catalog:testing`, `typescript catalog:`, `vite catalog:build`, `vite-plugin-dts catalog:build`, `vitest catalog:testing`
|
||||
- [ ] 2.4 `tsconfig.lib.json` extends `../../../../tsconfig.base.json`; `vite.config.ts` mirrors `@suaveplan/streams`; `vitest.config.ts` enforces 100/100/100/100 thresholds (`provider: 'istanbul'`)
|
||||
- [ ] 2.5 All test files import runner primitives from `@suaveplan/testing/runner` per CLAUDE.md §11
|
||||
|
||||
## 3. Implementation — features
|
||||
|
||||
- [ ] 3.1 `src/errors/errors.ts` — `defineCodes("STREAMS_METADATA", [...])` registry + `StreamsMetadataError` base + factory subclasses
|
||||
- [ ] 3.2 `src/types/payload.ts` — `MetadataMode`, `MetadataPayload`, `MimeDetectionResult`, `EnrichedPayload`
|
||||
- [ ] 3.3 `src/types/process-options.ts` — `ProcessOptions` shape (signal + clock + logger) structurally compatible with `BaseTransform`
|
||||
- [ ] 3.4 `src/events/events.ts` — `StreamsMetadataEvents` type alias
|
||||
- [ ] 3.5 `src/mime/mime.ts` — `MimeTransform extends BaseTransform<{}, StreamsMetadataEvents>` + `MimeTransformOptions`; module-load registration of JPEG / PNG signatures via `@suaveplan/mime::registerSignature` to defeat the upstream mime dist tree-shake
|
||||
- [ ] 3.6 `src/exif/exif.ts` — `ExifTransform extends BaseTransform<{}, StreamsMetadataEvents>` + `ExifTransformOptions` (with `read?: ReadOptions` pass-through)
|
||||
- [ ] 3.7 `src/metadata/metadata.ts` — `MetadataTransform extends BaseTransform<{}, StreamsMetadataEvents>` composing children; forwards `mime:detected` + `exif:detected`; emits `metadata:complete` or `metadata:error`
|
||||
|
||||
## 4. Tests
|
||||
|
||||
- [ ] 4.1 Sibling `.test.ts` for every implementation file under `src/`; every `it(...)` carries `{ timeout: 5000 }`
|
||||
- [ ] 4.2 `errors.test.ts` — every code resolves through the registry, error chain preserves `cause`
|
||||
- [ ] 4.3 `mime.test.ts` — magic-bytes wins over extension, extension fallback, octet-stream fallback, event emission, passthrough, listener errors swallowed
|
||||
- [ ] 4.4 `exif.test.ts` — JPEG / PNG happy path, gibberish raises `ExifFailedError`, read options forwarded, passthrough, abort handling
|
||||
- [ ] 4.5 `metadata.test.ts` — happy path composes, child failure emits `metadata:error` + returns original input, event forwarding, passthrough, dispose chains children
|
||||
|
||||
## 5. Documentation
|
||||
|
||||
- [ ] 5.1 Sibling `.md` per implementation `.ts` with all six required sections
|
||||
- [ ] 5.2 Root `README.md` linking every co-located `.md`
|
||||
|
||||
## 6. Pre-publish gate
|
||||
|
||||
- [ ] 6.1 `bunx biome check src/` exits clean
|
||||
- [ ] 6.2 `bunx tsc --noEmit --composite false -p tsconfig.lib.json` exits clean
|
||||
- [ ] 6.3 `bunx vitest run --coverage` at 100/100/100/100
|
||||
- [ ] 6.4 `bun ../../../../scripts/docs-check.ts .` exits clean
|
||||
- [ ] 6.5 Zero direct `vitest` / `bun:test` imports in `src/`
|
||||
|
||||
## 7. Archive
|
||||
|
||||
- [ ] 7.1 Fold `specs/streams-metadata/spec.md` into `openspec/specs/streams-metadata/spec.md`
|
||||
- [ ] 7.2 `git mv openspec/changes/enterprise-streams-metadata openspec/archive/2026-05-23-enterprise-streams-metadata`
|
||||
- [ ] 7.3 Regenerate `MANIFEST.md` (`bun scripts/folder-reorg/generate-manifest.ts`)
|
||||
Reference in New Issue
Block a user