[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCGMPZXPPESWDTS2720E2X":3,"skill-same:01M6CCGMPZXPPESWDTS2720E2X":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":57,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6CCGMPZXPPESWDTS2720E2X","ts-server-dev-guidelines",null,"House rules for the TypeScript control plane under backend\u002Fservices\u002Fstigmer-server and the shared backend libraries under backend\u002Flibs\u002Fts, covering wire-pinned identifiers, pipeline and extension-point conventions, the error contract, test discipline, and how a core change reaches the cloud edition. Use when adding or changing server code, pipeline steps, extension points, store drivers or Temporal workflows.","stigmer\u002Fstigmer","stigmer","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255479904?v=4","01M6CCFBFXXRZC4ZVNS3JSDV4W","Stigmer","01M6CCFBKY7M8QGR4GH9TT35MY",9,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fts-server-dev-guidelines\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"server","ts server dev guidelines","dev","guidelines","change","libraries","core","code","workflows","control plane","typescript","cloud","services","pipeline","how","Rules","use","backend","discipline","test","2026-11-10 10:12:30",false,"---\nname: ts-server-dev-guidelines\ndescription:\n  House rules for the TypeScript control plane under\n  backend\u002Fservices\u002Fstigmer-server and the shared backend libraries under\n  backend\u002Flibs\u002Fts, covering wire-pinned identifiers, pipeline and\n  extension-point conventions, the error contract, test discipline, and how a\n  core change reaches the cloud edition. Use when adding or changing server\n  code, pipeline steps, extension points, store drivers or Temporal workflows.\npaths:\n  - backend\u002Fservices\u002Fstigmer-server\u002F**\n  - backend\u002Flibs\u002Fts\u002F**\n---\n\n# TypeScript server development guidelines\n\nThe package guide (`backend\u002Fservices\u002Fstigmer-server\u002FAGENTS.md`) carries the\nbinding laws and the read-order; this skill carries the reasoning behind them\nand the conventions that take more than a line. A rule you cannot justify is a\nrule you will eventually violate creatively, so each one here says why. Module\nheaders are the truth nearest the code: when a header and this skill disagree,\nthe header wins and this skill is corrected.\n\n## Parity is the prime directive\n\nThe server is the one implementation of the core for every edition: the cloud\ncomposition pins an exact published release of `@stigmer\u002Fserver`, and\n`test\u002Fconformance\u002F` holds both editions to the same contract. \"Parity\" means\nparity with that suite and with every client already in the field.\n\n- Byte-pinned wire identifiers are never cleaned up. Workflow, activity, queue\n  and signal names, memo keys, id prefixes (`agt_`, `ses_`), refusal copy and\n  error messages are shared with the runner, the cloud composition, the\n  published SDKs and every database a release has written. However inconsistent\n  their casing looks, renaming one is a protocol break the conformance suite\n  catches late and expensively. The names live in one module per domain (for\n  example\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Ftemporal\u002Fagentexecution\u002Fnames.ts`).\n- Deliberate divergences are contract. Where two chains answer the same fault\n  with different codes on purpose, the code carries a comment citing the issue\n  that fixed the behaviour; preserve it. A new cross-edition divergence goes to\n  the maintainers before it ships; nothing diverges silently.\n- A value that looks arbitrary is not therefore tunable. Every semantic timeout,\n  loop cap, TTL and retry policy is a named constant whose rationale rides\n  beside it (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Ftransport\u002Fconstants.ts` is the\n  shape). If you cannot find a value's reason in the module's own comments or in\n  git history, ask; do not normalise it.\n\n## Module shape\n\n- One directory per domain under `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002F`,\n  each owning its steps, its converters, its constants and its tests.\n- Every module opens with an intent header: what the module is for, the\n  trade-offs it embodies, the issues its behaviour traces to, and the\n  conformance suite that proves it. A header is the first thing a stranger\n  reads; write it for them.\n- Tests are co-located in `__tests__\u002F*.test.ts` beside the module, never in a\n  parallel tree, and each test file opens with a header stating what it pins.\n- Functions do one thing. A handler that validates, loads, mutates, persists and\n  formats in one body is what the pipeline exists to prevent.\n\n## Pipeline and composition\n\nEvery domain RPC runs a chain of named steps\n(`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fpipeline.ts`, the shared steps\nunder `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fsteps\u002F`). A composition\nextends a chain only through the registered points; it never patches a step.\n\n- Step names are shared vocabulary (`ValidateProto`, `ResolveSlug`,\n  `CheckDuplicate`, `Persist`, and the rest): never renamed, never given a\n  synonym. A domain-specific step stays domain-local and is promoted to the\n  shared folder only when a second domain needs the identical step.\n- Gate slots are protected vocabulary. The declared slots, their positions in\n  each chain and the reasons for those positions are the header and the literal\n  tuple of `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fextensions\u002Fgate-slots.ts`; read\n  it before touching a chain that carries one. Adding a slot is a design act the\n  maintainers see; renaming one is a wire break. A gate registered into a slot\n  owns its own idempotency, runs wherever its chain runs (in-process callers\n  included), and refuses by throwing a `ConnectError` with the correct code and\n  pinned copy, exactly as a step does.\n- The authorization-tuple lifecycle rides every resource chain. The three shared\n  steps in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fsteps\u002Fauthorization-tuples.ts`\n  are no-ops until a composition registers a lifecycle driver; a new create,\n  delete or update-visibility chain must splice the matching step, and the\n  header there names the one domain that is exempt and why.\n- A store port a composition can drive ships its contract as a kit: a\n  vitest-free case list the OSS adapter's test and a composition's driver test\n  both iterate, so the port is proven per driver and never restated per\n  repository.\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Fidentityaccount\u002Fstore-contract.ts`\n  is the precedent and\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fstore\u002Fport-contract.ts` the runner; the\n  in-package `Store` contract\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fstore\u002F__tests__\u002Fstore-contract.ts`)\n  keeps its vitest shape because no other package runs it.\n- Status-transition hooks fire from every phase-transition persist site, not\n  from one chokepoint. The exhaustive list of sites is the header of\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Frun\u002Fstatus-observers.ts`; a new\n  phase write calls `notifyStatusObservers` and extends that list in the same\n  change. Observers fire only on an actual phase change, after the persist and\n  before any broadcast; an observer's failure is logged, never a failed\n  transition.\n- Caller guards enforce the minting client's contract after the identity stamp,\n  on the serving chain only\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fextensions\u002Fcaller-guards.ts`). A guard\n  is not a verifier and is never folded into one; its refusal is its own wire\n  mapping, and any other throw from it is an infrastructure fault, never\n  softened into a denial. The in-process chain takes no guards by structure, not\n  by a runtime skip.\n- Server code that calls another domain's RPCs rides the in-process transport\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fboot\u002Finprocess.ts`), whose identity\n  position mints the `internal` caller class and whose chain validates, logs and\n  tags the call like an external one. A new server-internal caller adds a\n  narrow, method-segregated edge to the in-process clients and resolves it\n  lazily when it is built before the routes exist. A domain port the composition\n  root injects (the identity-account create path, the grant path, the membership\n  hook) may be called directly, because it is not an RPC. Either way, every\n  `CallerIdentity` is built in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Fauth.ts` or, for an\n  account acting as itself, in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Fidentityaccount\u002Factor.ts`; the\n  server acting for a principal it authenticated itself is `serverActingFor`\n  there; and the trusted-local identity is the wire fallback for an unclaimed\n  request, never a worker's identity.\n- The require-authentication posture has exactly two sources, combined in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fboot\u002Fcompose.ts`: the OSS OIDC issuer\n  setting, and a composed unit's single declaration. Which authorizer runs under\n  each posture is\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fauthorization\u002Fposture.ts`. Under the\n  posture, tokenless requests are refused except where `isAuthenticationExempt`\n  in `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Fauth.ts` says\n  so: methods our protos mark public, and third-party services listed in\n  `AUTHENTICATION_EXEMPT_SERVICES` by name (today the gRPC health service,\n  because Kubernetes probes are tokenless). Registering a new third-party\n  service that must answer anonymous callers adds it to that set in the same\n  change. Never add an environment knob for the posture: a forgotten knob is the\n  failure the two-source rule exists to prevent.\n- The composition root is staged plain functions (config, storage, temporal,\n  controllers, routes, listen). No DI framework: a missing dependency is a\n  compile error or a loud boot throw, never a silent no-op. Lazy providers are\n  for true cycles only.\n- Optional infrastructure is an explicit modelled state, never a nullable.\n  \"Temporal is down\" and \"forgot to wire Temporal\" must be distinguishable.\n\n## Language posture\n\n- Strict TypeScript in the runner's shape (ES2022, NodeNext,\n  `\"type\": \"module\"`). No ESLint here by design: quality is `tsc --noEmit`,\n  Prettier from the repo root, tests and review. No `any`; no `@ts-ignore` or\n  `@ts-expect-error` without a one-line reason; every `switch` over a union\n  closes with a `never` default.\n- No TODO comments. An improvement discovered en route is filed as an issue or\n  fixed; a drift marker is neither.\n- Comments state intent and trade-offs, never narration. An issue number or a\n  file is a verifiable source; \"for compatibility reasons\" is not.\n\n## Errors\n\nErrors are API surface: the CLI, the console and the SDKs render them directly.\n\n- Correct codes (`NotFound`, `AlreadyExists`, `InvalidArgument`,\n  `FailedPrecondition`, `PermissionDenied`), never `Internal` as a catch-all.\n  Messages are in domain terms and name the identifying handle\n  (`agent 'x' not found`), with the copy pinned in one constants module per\n  domain.\n- The store-fault mapping: a typed store not-found (`ResourceNotFoundError`,\n  `AuditNotFoundError`, checked with `instanceof`) maps to `NotFound`. Any other\n  store failure is an infrastructure fault: rethrow it in a pipeline step (the\n  executor answers a sanitized `Internal`) or wrap it with\n  `internalError(cause, \"failed to \u003Cdo thing>\")` from\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Ferrors.ts` in a direct handler.\n  Presenting a locked file or a corrupted page as \"not found\" invites clients to\n  discard real state.\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002F__tests__\u002Fblind-not-found.test.ts`\n  holds the rule: it fails any catch that answers NotFound without testing the\n  error it caught.\n- Quoting: new messages quote identifiers with single quotes (`'${x}'`) and\n  render lists with `quoteJoin` from\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Ferrors.ts`. The existing\n  double-quoted messages are frozen wire constants shared with the cloud\n  edition: contract, not a style to imitate.\n- Log the real error; the wire carries the sanitized message. Never log secrets:\n  the redaction conventions (`***REDACTED***`, the `enc:v1:` prefix) extend to\n  logs and to Temporal payloads.\n- The serving chain's error boundary\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Ferror-boundary.ts`)\n  is a net, not a licence: a raw non-`ConnectError` escaping any handler is\n  converted to the sanitized `Internal` before the wire, and a composed policy\n  may rewrite leak-prone descriptions for anonymous callers. Direct handlers\n  still self-sanitize with `internalError`; authored copy beats the generic net,\n  and the boundary exists for the path someone forgot.\n\n## Tests ship with the feature\n\n- A unit test for every step, converter and validation rule with branching\n  logic; Temporal tests through `@temporalio\u002Ftesting` where a workflow changes;\n  the domain's conformance suite green on the `local` target before the change\n  counts as done.\n- Determinism is non-negotiable: no sleeps (poll with a timeout), no shared\n  mutable state, unique resources per test, failure paths tested on purpose.\n- Verification runs per touched layer: `npm run typecheck && npm test` in this\n  directory always; `make test-conformance` for domain work; the runner's full\n  suite when `backend\u002Flibs\u002Fts\u002Ftemporal-codecs` changes, because the codecs are\n  the wire between the two.\n\n## How a core change reaches the cloud edition\n\nThe cloud repository has no copy of this code; it depends on the published\n`@stigmer\u002Fserver`, `@stigmer\u002Fapis` and `@stigmer\u002Ftemporal-codecs` at one exact\nversion. A change that the cloud needs therefore follows one order:\n\n1. The OSS pull request lands the seam (the new extension point, the new export,\n   the behaviour) here, with its tests and its conformance coverage. While it is\n   in flight, the cloud side is proven against tarballs of this tree\n   (`make stage-server-library`), which publishes nothing.\n2. Once it has merged, a development build of `main` is published\n   (`make publish-dev`, which dispatches `release.dev.yaml` on `main`; it\n   refuses any other ref); its version names the commit it was built from\n   (`X.Y.Z-dev.\u003Cstamp>.g\u003Csha>`). Dev builds go to Stigmer's own npm and image\n   repositories, never npmjs.com or ghcr.io\n   (`.github\u002Fworkflows\u002Fdocs\u002Fdev-publishing.md`).\n3. The cloud pull request pins that build and merges on it, without waiting for\n   a release; the cloud is expected to refuse a build whose named commit is not\n   on `main`.\n4. A release reaches production through the cloud's re-pin to it, which comes\n   before anything deploys.\n\nA cloud-only behaviour never lands here for parity's sake, and a core behaviour\nis never re-implemented in the composition: if no extension point fits, the\nanswer is a new point here, designed as such.\n\n## When surprised\n\nAnything the code or its headers did not anticipate (framework behaviour, an\ninherited nuance without a recorded reason, a failure class nobody named) goes\nto the maintainers before a workaround is adopted, in the pull request or an\nissue, so the next reader does not re-walk the trap.\n","# TypeScript server development guidelines\n\nThe package guide (`backend\u002Fservices\u002Fstigmer-server\u002FAGENTS.md`) carries the\nbinding laws and the read-order; this skill carries the reasoning behind them\nand the conventions that take more than a line. A rule you cannot justify is a\nrule you will eventually violate creatively, so each one here says why. Module\nheaders are the truth nearest the code: when a header and this skill disagree,\nthe header wins and this skill is corrected.\n\n## Parity is the prime directive\n\nThe server is the one implementation of the core for every edition: the cloud\ncomposition pins an exact published release of `@stigmer\u002Fserver`, and\n`test\u002Fconformance\u002F` holds both editions to the same contract. \"Parity\" means\nparity with that suite and with every client already in the field.\n\n- Byte-pinned wire identifiers are never cleaned up. Workflow, activity, queue\n  and signal names, memo keys, id prefixes (`agt_`, `ses_`), refusal copy and\n  error messages are shared with the runner, the cloud composition, the\n  published SDKs and every database a release has written. However inconsistent\n  their casing looks, renaming one is a protocol break the conformance suite\n  catches late and expensively. The names live in one module per domain (for\n  example\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Ftemporal\u002Fagentexecution\u002Fnames.ts`).\n- Deliberate divergences are contract. Where two chains answer the same fault\n  with different codes on purpose, the code carries a comment citing the issue\n  that fixed the behaviour; preserve it. A new cross-edition divergence goes to\n  the maintainers before it ships; nothing diverges silently.\n- A value that looks arbitrary is not therefore tunable. Every semantic timeout,\n  loop cap, TTL and retry policy is a named constant whose rationale rides\n  beside it (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Ftransport\u002Fconstants.ts` is the\n  shape). If you cannot find a value's reason in the module's own comments or in\n  git history, ask; do not normalise it.\n\n## Module shape\n\n- One directory per domain under `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002F`,\n  each owning its steps, its converters, its constants and its tests.\n- Every module opens with an intent header: what the module is for, the\n  trade-offs it embodies, the issues its behaviour traces to, and the\n  conformance suite that proves it. A header is the first thing a stranger\n  reads; write it for them.\n- Tests are co-located in `__tests__\u002F*.test.ts` beside the module, never in a\n  parallel tree, and each test file opens with a header stating what it pins.\n- Functions do one thing. A handler that validates, loads, mutates, persists and\n  formats in one body is what the pipeline exists to prevent.\n\n## Pipeline and composition\n\nEvery domain RPC runs a chain of named steps\n(`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fpipeline.ts`, the shared steps\nunder `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fsteps\u002F`). A composition\nextends a chain only through the registered points; it never patches a step.\n\n- Step names are shared vocabulary (`ValidateProto`, `ResolveSlug`,\n  `CheckDuplicate`, `Persist`, and the rest): never renamed, never given a\n  synonym. A domain-specific step stays domain-local and is promoted to the\n  shared folder only when a second domain needs the identical step.\n- Gate slots are protected vocabulary. The declared slots, their positions in\n  each chain and the reasons for those positions are the header and the literal\n  tuple of `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fextensions\u002Fgate-slots.ts`; read\n  it before touching a chain that carries one. Adding a slot is a design act the\n  maintainers see; renaming one is a wire break. A gate registered into a slot\n  owns its own idempotency, runs wherever its chain runs (in-process callers\n  included), and refuses by throwing a `ConnectError` with the correct code and\n  pinned copy, exactly as a step does.\n- The authorization-tuple lifecycle rides every resource chain. The three shared\n  steps in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Fsteps\u002Fauthorization-tuples.ts`\n  are no-ops until a composition registers a lifecycle driver; a new create,\n  delete or update-visibility chain must splice the matching step, and the\n  header there names the one domain that is exempt and why.\n- A store port a composition can drive ships its contract as a kit: a\n  vitest-free case list the OSS adapter's test and a composition's driver test\n  both iterate, so the port is proven per driver and never restated per\n  repository.\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Fidentityaccount\u002Fstore-contract.ts`\n  is the precedent and\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fstore\u002Fport-contract.ts` the runner; the\n  in-package `Store` contract\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fstore\u002F__tests__\u002Fstore-contract.ts`)\n  keeps its vitest shape because no other package runs it.\n- Status-transition hooks fire from every phase-transition persist site, not\n  from one chokepoint. The exhaustive list of sites is the header of\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Frun\u002Fstatus-observers.ts`; a new\n  phase write calls `notifyStatusObservers` and extends that list in the same\n  change. Observers fire only on an actual phase change, after the persist and\n  before any broadcast; an observer's failure is logged, never a failed\n  transition.\n- Caller guards enforce the minting client's contract after the identity stamp,\n  on the serving chain only\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fextensions\u002Fcaller-guards.ts`). A guard\n  is not a verifier and is never folded into one; its refusal is its own wire\n  mapping, and any other throw from it is an infrastructure fault, never\n  softened into a denial. The in-process chain takes no guards by structure, not\n  by a runtime skip.\n- Server code that calls another domain's RPCs rides the in-process transport\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fboot\u002Finprocess.ts`), whose identity\n  position mints the `internal` caller class and whose chain validates, logs and\n  tags the call like an external one. A new server-internal caller adds a\n  narrow, method-segregated edge to the in-process clients and resolves it\n  lazily when it is built before the routes exist. A domain port the composition\n  root injects (the identity-account create path, the grant path, the membership\n  hook) may be called directly, because it is not an RPC. Either way, every\n  `CallerIdentity` is built in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Fauth.ts` or, for an\n  account acting as itself, in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fdomain\u002Fidentityaccount\u002Factor.ts`; the\n  server acting for a principal it authenticated itself is `serverActingFor`\n  there; and the trusted-local identity is the wire fallback for an unclaimed\n  request, never a worker's identity.\n- The require-authentication posture has exactly two sources, combined in\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fboot\u002Fcompose.ts`: the OSS OIDC issuer\n  setting, and a composed unit's single declaration. Which authorizer runs under\n  each posture is\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fauthorization\u002Fposture.ts`. Under the\n  posture, tokenless requests are refused except where `isAuthenticationExempt`\n  in `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Fauth.ts` says\n  so: methods our protos mark public, and third-party services listed in\n  `AUTHENTICATION_EXEMPT_SERVICES` by name (today the gRPC health service,\n  because Kubernetes probes are tokenless). Registering a new third-party\n  service that must answer anonymous callers adds it to that set in the same\n  change. Never add an environment knob for the posture: a forgotten knob is the\n  failure the two-source rule exists to prevent.\n- The composition root is staged plain functions (config, storage, temporal,\n  controllers, routes, listen). No DI framework: a missing dependency is a\n  compile error or a loud boot throw, never a silent no-op. Lazy providers are\n  for true cycles only.\n- Optional infrastructure is an explicit modelled state, never a nullable.\n  \"Temporal is down\" and \"forgot to wire Temporal\" must be distinguishable.\n\n## Language posture\n\n- Strict TypeScript in the runner's shape (ES2022, NodeNext,\n  `\"type\": \"module\"`). No ESLint here by design: quality is `tsc --noEmit`,\n  Prettier from the repo root, tests and review. No `any`; no `@ts-ignore` or\n  `@ts-expect-error` without a one-line reason; every `switch` over a union\n  closes with a `never` default.\n- No TODO comments. An improvement discovered en route is filed as an issue or\n  fixed; a drift marker is neither.\n- Comments state intent and trade-offs, never narration. An issue number or a\n  file is a verifiable source; \"for compatibility reasons\" is not.\n\n## Errors\n\nErrors are API surface: the CLI, the console and the SDKs render them directly.\n\n- Correct codes (`NotFound`, `AlreadyExists`, `InvalidArgument`,\n  `FailedPrecondition`, `PermissionDenied`), never `Internal` as a catch-all.\n  Messages are in domain terms and name the identifying handle\n  (`agent 'x' not found`), with the copy pinned in one constants module per\n  domain.\n- The store-fault mapping: a typed store not-found (`ResourceNotFoundError`,\n  `AuditNotFoundError`, checked with `instanceof`) maps to `NotFound`. Any other\n  store failure is an infrastructure fault: rethrow it in a pipeline step (the\n  executor answers a sanitized `Internal`) or wrap it with\n  `internalError(cause, \"failed to \u003Cdo thing>\")` from\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Ferrors.ts` in a direct handler.\n  Presenting a locked file or a corrupted page as \"not found\" invites clients to\n  discard real state.\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002F__tests__\u002Fblind-not-found.test.ts`\n  holds the rule: it fails any catch that answers NotFound without testing the\n  error it caught.\n- Quoting: new messages quote identifiers with single quotes (`'${x}'`) and\n  render lists with `quoteJoin` from\n  `backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Ferrors.ts`. The existing\n  double-quoted messages are frozen wire constants shared with the cloud\n  edition: contract, not a style to imitate.\n- Log the real error; the wire carries the sanitized message. Never log secrets:\n  the redaction conventions (`***REDACTED***`, the `enc:v1:` prefix) extend to\n  logs and to Temporal payloads.\n- The serving chain's error boundary\n  (`backend\u002Fservices\u002Fstigmer-server\u002Fsrc\u002Fpipeline\u002Finterceptors\u002Ferror-boundary.ts`)\n  is a net, not a licence: a raw non-`ConnectError` escaping any handler is\n  converted to the sanitized `Internal` before the wire, and a composed policy\n  may rewrite leak-prone descriptions for anonymous callers. Direct handlers\n  still self-sanitize with `internalError`; authored copy beats the generic net,\n  and the boundary exists for the path someone forgot.\n\n## Tests ship with the feature\n\n- A unit test for every step, converter and validation rule with branching\n  logic; Temporal tests through `@temporalio\u002Ftesting` where a workflow changes;\n  the domain's conformance suite green on the `local` target before the change\n  counts as done.\n- Determinism is non-negotiable: no sleeps (poll with a timeout), no shared\n  mutable state, unique resources per test, failure paths tested on purpose.\n- Verification runs per touched layer: `npm run typecheck && npm test` in this\n  directory always; `make test-conformance` for domain work; the runner's full\n  suite when `backend\u002Flibs\u002Fts\u002Ftemporal-codecs` changes, because the codecs are\n  the wire between the two.\n\n## How a core change reaches the cloud edition\n\nThe cloud repository has no copy of this code; it depends on the published\n`@stigmer\u002Fserver`, `@stigmer\u002Fapis` and `@stigmer\u002Ftemporal-codecs` at one exact\nversion. A change that the cloud needs therefore follows one order:\n\n1. The OSS pull request lands the seam (the new extension point, the new export,\n   the behaviour) here, with its tests and its conformance coverage. While it is\n   in flight, the cloud side is proven against tarballs of this tree\n   (`make stage-server-library`), which publishes nothing.\n2. Once it has merged, a development build of `main` is published\n   (`make publish-dev`, which dispatches `release.dev.yaml` on `main`; it\n   refuses any other ref); its version names the commit it was built from\n   (`X.Y.Z-dev.\u003Cstamp>.g\u003Csha>`). Dev builds go to Stigmer's own npm and image\n   repositories, never npmjs.com or ghcr.io\n   (`.github\u002Fworkflows\u002Fdocs\u002Fdev-publishing.md`).\n3. The cloud pull request pins that build and merges on it, without waiting for\n   a release; the cloud is expected to refuse a build whose named commit is not\n   on `main`.\n4. A release reaches production through the cloud's re-pin to it, which comes\n   before anything deploys.\n\nA cloud-only behaviour never lands here for parity's sake, and a core behaviour\nis never re-implemented in the composition: if no extension point fits, the\nanswer is a new point here, designed as such.\n\n## When surprised\n\nAnything the code or its headers did not anticipate (framework behaviour, an\ninherited nuance without a recorded reason, a failure class nobody named) goes\nto the maintainers before a workaround is adopted, in the pull request or an\nissue, so the next reader does not re-walk the trap.\n",{"name":5,"paths":49,"description":7},[50,51],"backend\u002Fservices\u002Fstigmer-server\u002F**","backend\u002Flibs\u002Fts\u002F**","main","49002d2ddc1f123a8d1148f6c6d73d7b385bb0b1",".agents\u002Fskills\u002Fts-server-dev-guidelines",6,"https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer","https:\u002F\u002Fstigmer.ai","https:\u002F\u002Fgithub.com\u002Fstigmer","The open-source backend for the agents in your product.","2026-11-10 20:10:57",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add stigmer\u002Fstigmer --skill ts-server-dev-guidelines",{"tool":67,"command":68},"bunx","bunx skills add stigmer\u002Fstigmer --skill ts-server-dev-guidelines",{"tool":70,"command":71},"pnpm","pnpm dlx skills add stigmer\u002Fstigmer --skill ts-server-dev-guidelines","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fts-server-dev-guidelines\u002FSKILL.md",true,[],[76,103,121,135,149,165],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":93,"publishedAt":44},"01M6CCSERKR4YRSZJ2TB6B0X60","writing-hookify-rules","This skill should be used when the user asks to \"create a hookify rule\", \"write a hook rule\", \"configure hookify\", \"add a hookify rule\", or needs guidance on hookify rule syntax and patterns.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fsupport\u002Ffixtures\u002Fclaude-plugins\u002Fhookify\u002Fskills\u002Fwriting-rules\u002FSKILL.md",[82,85,88],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":86,"categoryName":87,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":89,"categoryName":90,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[94,95,39,96,97,98,99,100,101,102],"writing","Writing Hookify Rules","hookify rules","create","guidance","user","write","configure","syntax",{"skillNo":104,"name":105,"nameZh":6,"description":106,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":107,"categories":108,"keywords":113,"publishedAt":44},"01M6CCSC63RKCGC2S1EZDEMXZE","sql-migrations","Write safe database schema migrations - reversible steps, online index builds, and backfills in batches.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fsql-migrations\u002FSKILL.md",[109,110],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":111,"categoryName":112,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000033","数据分析师",[114,115,116,117,100,118,119,120],"sql","sql migrations","migrations","schema","index","database","safe",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":125,"categories":126,"keywords":127,"publishedAt":44},"01M6CCS9Q8E0R3TYHPZHCPN0JZ","repo-conventions","How to change Go code in this repository - error wrapping, table-driven tests, doc comments, no new dependencies, and running go test before finishing.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Frepo-conventions\u002FSKILL.md",[],[128,129,130,131,43,28,132,31,133,134,38],"repo","repo conventions","table","go","doc comments","repository","doc",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":44},"01M6CCS7F2D1YZ9H3N63XTEV09","incident-postmortem","Write a blameless incident postmortem - timeline, impact, root cause, contributing factors and follow-up actions with owners.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fincident-postmortem\u002FSKILL.md",[],[142,143,100,144,145,146,147,148],"incident postmortem","postmortem","contributing","actions","timeline","owners","up",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":159,"publishedAt":44},"01M6CCS4W1D2K257KHC40HS8JG","data-analysis","Summarise a data export or table - lead with the key finding and give every number its baseline or comparison.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fdata-analysis\u002FSKILL.md",[155,158],{"categoryNo":156,"categoryName":157,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":111,"categoryName":112,"taxonomyType":91,"taxonomyTypeDesc":92},[160,161,162,163,130,164],"analysis","data analysis","data","baseline","export",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":171,"publishedAt":44},"01M6CCS2MNQYK9S24AYHJAVPWF","customer-support","Answer customer questions about orders, returns and refunds from the written policies and the order system, ending with a clear next step.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fcustomer-support\u002FSKILL.md",[],[172,173,174,175,176,177],"support","customer","customer support","system","refunds","returns"]