@north-light/crouter-api 0.3.180 → 0.3.181

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +9 -2
  2. package/dist/{client.d.ts → api/client.d.ts} +10 -1
  3. package/dist/{client.js → api/client.js} +13 -0
  4. package/dist/{dto → api/dto}/broker.d.ts +32 -0
  5. package/dist/{dto → api/dto}/crons.d.ts +17 -0
  6. package/dist/api/dto/memory.d.ts +17 -0
  7. package/dist/api/dto/memory.js +6 -0
  8. package/dist/{dto → api/dto}/messages.d.ts +5 -0
  9. package/dist/{dto → api/dto}/reviews.d.ts +8 -4
  10. package/dist/{index.d.ts → api/index.d.ts} +1 -0
  11. package/dist/{index.js → api/index.js} +1 -0
  12. package/dist/{routes.d.ts → api/routes.d.ts} +2 -0
  13. package/dist/{routes.js → api/routes.js} +4 -0
  14. package/dist/core/memory/doc-link-grammar.d.ts +20 -0
  15. package/dist/core/memory/doc-link-grammar.js +113 -0
  16. package/package.json +13 -7
  17. /package/dist/{__tests__ → api/__tests__}/serial/client.test.d.ts +0 -0
  18. /package/dist/{__tests__ → api/__tests__}/serial/client.test.js +0 -0
  19. /package/dist/{dto → api/dto}/attach.d.ts +0 -0
  20. /package/dist/{dto → api/dto}/attach.js +0 -0
  21. /package/dist/{dto → api/dto}/broker-ops.d.ts +0 -0
  22. /package/dist/{dto → api/dto}/broker-ops.js +0 -0
  23. /package/dist/{dto → api/dto}/broker.js +0 -0
  24. /package/dist/{dto → api/dto}/canvas.d.ts +0 -0
  25. /package/dist/{dto → api/dto}/canvas.js +0 -0
  26. /package/dist/{dto → api/dto}/common.d.ts +0 -0
  27. /package/dist/{dto → api/dto}/common.js +0 -0
  28. /package/dist/{dto → api/dto}/config.d.ts +0 -0
  29. /package/dist/{dto → api/dto}/config.js +0 -0
  30. /package/dist/{dto → api/dto}/crons.js +0 -0
  31. /package/dist/{dto → api/dto}/files.d.ts +0 -0
  32. /package/dist/{dto → api/dto}/files.js +0 -0
  33. /package/dist/{dto → api/dto}/focus.d.ts +0 -0
  34. /package/dist/{dto → api/dto}/focus.js +0 -0
  35. /package/dist/{dto → api/dto}/health.d.ts +0 -0
  36. /package/dist/{dto → api/dto}/health.js +0 -0
  37. /package/dist/{dto → api/dto}/human.d.ts +0 -0
  38. /package/dist/{dto → api/dto}/human.js +0 -0
  39. /package/dist/{dto → api/dto}/inbox.d.ts +0 -0
  40. /package/dist/{dto → api/dto}/inbox.js +0 -0
  41. /package/dist/{dto → api/dto}/lifecycle.d.ts +0 -0
  42. /package/dist/{dto → api/dto}/lifecycle.js +0 -0
  43. /package/dist/{dto → api/dto}/messages.js +0 -0
  44. /package/dist/{dto → api/dto}/modelauth.d.ts +0 -0
  45. /package/dist/{dto → api/dto}/modelauth.js +0 -0
  46. /package/dist/{dto → api/dto}/nodes.d.ts +0 -0
  47. /package/dist/{dto → api/dto}/nodes.js +0 -0
  48. /package/dist/{dto → api/dto}/profiles.d.ts +0 -0
  49. /package/dist/{dto → api/dto}/profiles.js +0 -0
  50. /package/dist/{dto → api/dto}/reports.d.ts +0 -0
  51. /package/dist/{dto → api/dto}/reports.js +0 -0
  52. /package/dist/{dto → api/dto}/review-comments.d.ts +0 -0
  53. /package/dist/{dto → api/dto}/review-comments.js +0 -0
  54. /package/dist/{dto → api/dto}/reviews.js +0 -0
  55. /package/dist/{dto → api/dto}/subscriptions.d.ts +0 -0
  56. /package/dist/{dto → api/dto}/subscriptions.js +0 -0
  57. /package/dist/{dto → api/dto}/worktree.d.ts +0 -0
  58. /package/dist/{dto → api/dto}/worktree.js +0 -0
  59. /package/dist/{errors.d.ts → api/errors.d.ts} +0 -0
  60. /package/dist/{errors.js → api/errors.js} +0 -0
package/README.md CHANGED
@@ -41,11 +41,18 @@ autostart hook.
41
41
  canvas, worktree, and human-in-the-loop.
42
42
  - **`routes` / `API_VERSION`** — pure path builders for the `/v1` surface.
43
43
  - **`ApiError` / `ErrorBody` / `isErrorBody`** — the error contract.
44
+ - **`@north-light/crouter-api/doc-link-grammar`** — the `[[canonical/name]]`
45
+ memory-document link grammar (`findDocLinks`, `docLinkNames`,
46
+ `isDocLinkName`). Zero imports and browser-safe, so a UI can scan transcript
47
+ markdown for doc links in the renderer and hand each name to
48
+ `client.resolveMemoryDoc(name, nodeId)`. Import the subpath, not the root, in
49
+ a browser bundle — the root pulls `CrtrClient` and its Node built-ins.
44
50
 
45
51
  ## Source of truth & releases
46
52
 
47
53
  The source lives in the [`crouter`](https://github.com/vallum-security/crouter)
48
- repo at `src/api/`; this package is a build-and-publish mirror of that subtree
49
- (it carries no source of its own). It is published by the `publish-api.yml`
54
+ repo at `src/api/`, plus `src/core/memory/doc-link-grammar.ts` for the subpath
55
+ above; this package is a build-and-publish mirror of those files (it carries no
56
+ source of its own). It is published by the `publish-api.yml`
50
57
  GitHub Action when a tag matching `api-vX.Y.Z` is pushed — the tag version must
51
58
  match this package's `version`.
@@ -5,11 +5,12 @@ import type { PushReportRequest, PushReportResultDTO, ReportDTO, ReportsQuery }
5
5
  import type { CloseRequest, CloseResultDTO, PromoteRequest, RelaunchRootResultDTO, ReviveRequest, ReviveResultDTO, WaitRequest, YieldRequest } from './dto/lifecycle.js';
6
6
  import type { SubscribeRequest, SubscriptionDTO } from './dto/subscriptions.js';
7
7
  import type { FocusDTO, RegisterFocusRequest, SetFocusPaneRequest } from './dto/focus.js';
8
- import { type ArmCronRequest, type CancelCronQuery, type CronDTO, type CronRunDTO, type CronScopeQuery, type CronShowDTO, type ListCronsQuery } from './dto/crons.js';
8
+ import { type ArmCronRequest, type CancelCronQuery, type CronDTO, type CronRunDTO, type CronScopeQuery, type CronShowDTO, type ListCronsQuery, type PokeCronsResult } from './dto/crons.js';
9
9
  import type { NodeConfigPatch } from './dto/config.js';
10
10
  import type { AttachEnsureRequest, AttachEnsureResultDTO } from './dto/attach.js';
11
11
  import type { EnsureProfileRequest, ProfileDTO } from './dto/profiles.js';
12
12
  import type { FilePeekDTO } from './dto/files.js';
13
+ import type { MemoryDocRefDTO } from './dto/memory.js';
13
14
  import type { CredentialRemovalResultDTO, CredentialResultDTO, InstallCredentialRequest, ModelAuthListDTO } from './dto/modelauth.js';
14
15
  import type { CreateHumanBridgeRequest, HumanBridgeResultDTO, HumanCancelRequest, HumanCancelResultDTO, HumanResolveRequest, HumanResolveResultDTO } from './dto/human.js';
15
16
  import type { CancelReviewRequest, CreateReviewRequest, ListReviewsQuery, ReviewCancelResultDTO, ReviewDocumentBaseDTO, ReviewDTO, ReviewListDTO, ReviewSubmitResultDTO } from './dto/reviews.js';
@@ -131,6 +132,10 @@ export declare class CrtrClient {
131
132
  runCron(cronId: string, q?: CronScopeQuery): Promise<CronRunDTO>;
132
133
  /** Cancel one cron (`DELETE /v1/crons/:cronId`, idempotent). */
133
134
  cancelCron(cronId: string, q?: CancelCronQuery): Promise<void>;
135
+ /** Bare eligibility poke (`POST /v1/crons/poke`): re-dues every held active
136
+ * cron now — "something changed; re-check now". Canvas-wide, label-free,
137
+ * idempotent, and free when nothing is held. */
138
+ pokeCrons(): Promise<PokeCronsResult>;
134
139
  ensureAttach(id: string, req?: AttachEnsureRequest): Promise<AttachEnsureResultDTO>;
135
140
  getReports(id: string, q?: ReportsQuery): Promise<ReportDTO[]>;
136
141
  getTranscript(id: string, q?: TranscriptQuery): Promise<TranscriptDTO>;
@@ -143,6 +148,10 @@ export declare class CrtrClient {
143
148
  /** Read an absolute host path as UTF-8 (capped, `truncated` when clipped) for
144
149
  * the browser file-peek panel. */
145
150
  peekFile(path: string): Promise<FilePeekDTO>;
151
+ /** Resolve a `[[name]]` memory-document link to the absolute path the given
152
+ * node would read — the node's own precedence chain, not this process's.
153
+ * Pair with `peekFile` to render the document. */
154
+ resolveMemoryDoc(name: string, nodeId: string): Promise<MemoryDocRefDTO>;
146
155
  ensureProfile(name: string, req?: EnsureProfileRequest): Promise<ProfileDTO>;
147
156
  listProfiles(): Promise<ProfileDTO[]>;
148
157
  getProfile(name: string): Promise<ProfileDTO>;
@@ -227,6 +227,12 @@ export class CrtrClient {
227
227
  async cancelCron(cronId, q) {
228
228
  await this.request('DELETE', withQuery(routes.cron(this.cronPath(cronId)), q));
229
229
  }
230
+ /** Bare eligibility poke (`POST /v1/crons/poke`): re-dues every held active
231
+ * cron now — "something changed; re-check now". Canvas-wide, label-free,
232
+ * idempotent, and free when nothing is held. */
233
+ pokeCrons() {
234
+ return this.request('POST', routes.cronsPoke(), {});
235
+ }
230
236
  ensureAttach(id, req) {
231
237
  return this.request('POST', routes.nodeAttach(this.nodePath(id)), req ?? {});
232
238
  }
@@ -257,6 +263,13 @@ export class CrtrClient {
257
263
  peekFile(path) {
258
264
  return this.request('GET', withQuery(routes.filePeek(), { path }));
259
265
  }
266
+ // ---- Memory documents --------------------------------------------------
267
+ /** Resolve a `[[name]]` memory-document link to the absolute path the given
268
+ * node would read — the node's own precedence chain, not this process's.
269
+ * Pair with `peekFile` to render the document. */
270
+ resolveMemoryDoc(name, nodeId) {
271
+ return this.request('GET', withQuery(routes.memoryResolve(), { name, node: nodeId }));
272
+ }
260
273
  // ---- Profiles ----------------------------------------------------------
261
274
  ensureProfile(name, req) {
262
275
  return this.request('PUT', routes.profile(name), req ?? {});
@@ -28,6 +28,38 @@ export interface BrokerWelcomeSnapshot<M = unknown> {
28
28
  export interface BrokerWelcomeFrame<M = unknown> {
29
29
  type: 'welcome';
30
30
  snapshot?: BrokerWelcomeSnapshot<M>;
31
+ /** The last few id-bearing user-message dispatches from the broker's message
32
+ * ledger, oldest first — so a reattaching follower can key the snapshot's
33
+ * trailing user wakes by id (the id a sender minted via `message_id` on
34
+ * `POST .../messages`) instead of discarding them heuristically. Live-process
35
+ * memory only: a broker restart loses it. Absent on a broker pinned to an
36
+ * older runtime generation. */
37
+ recentUserMessages?: Array<{
38
+ id: string;
39
+ text: string;
40
+ }>;
41
+ }
42
+ /**
43
+ * The additive identity fields the broker sets on a relayed pi `queue_update`:
44
+ * id arrays PARALLEL to pi's text arrays (`steeringIds[i]` identifies
45
+ * `steering[i]`). Sender-minted when the send carried `message_id`,
46
+ * broker-minted otherwise. Absent on a broker pinned to an older runtime
47
+ * generation — consumers fall back to bare texts. Intersect with the pinned pi
48
+ * `queue_update` event shape to read them typed.
49
+ */
50
+ export interface RelayedQueueUpdateIdentity {
51
+ steeringIds?: string[];
52
+ followUpIds?: string[];
53
+ }
54
+ /**
55
+ * The additive identity field the broker sets on a relayed USER-role
56
+ * `message_start`: the message's crouter id — the `message_id` its sender
57
+ * minted, or a broker-minted one. Absent when the message carries none (an
58
+ * engine-command expansion, a pre-ledger replay, an older broker). Intersect
59
+ * with the pinned pi `message_start` event shape to read it typed.
60
+ */
61
+ export interface RelayedUserMessageStartIdentity {
62
+ crtrMessageId?: string;
31
63
  }
32
64
  /**
33
65
  * The broker-control frames a relay consumer reads: `welcome` (catch-up snapshot)
@@ -53,6 +53,14 @@ export interface CronDTO {
53
53
  sink: string | null;
54
54
  tier: string;
55
55
  state: CronStateDTO;
56
+ /** True while the row is parked by the exit-75 owed-gate disposition: the
57
+ * last scheduled run declared "owed but not currently eligible", so the
58
+ * occurrence was not spent. A daemon poke re-dues it now; otherwise a
59
+ * recurring row re-checks at its natural `fire_at` slot (the backstop) and
60
+ * a held one-shot waits for a poke until `expires_at` deletes it. `state`
61
+ * stays honest (active|paused) — renderers derive; an active held row must
62
+ * never present as paused. */
63
+ held: boolean;
56
64
  run_state: CronRunStateDTO;
57
65
  /** Recent health: the most recent settled run, or null if it never ran. */
58
66
  last_run: CronLastRunDTO | null;
@@ -93,6 +101,15 @@ export interface CronRunDTO {
93
101
  /** What the sink did with this run's output, or why it didn't. */
94
102
  delivered: string | null;
95
103
  }
104
+ /** `POST /v1/crons/poke` — the bare daemon-level eligibility poke. Re-dues
105
+ * every held active cron now; paused rows keep their held state. Idempotent
106
+ * and free when nothing is held. */
107
+ export interface PokeCronsResult {
108
+ /** How many held rows were re-dued — for the caller's log line. */
109
+ unparked: number;
110
+ /** The poke receipt instant (UTC). */
111
+ at: IsoTime;
112
+ }
96
113
  /** `GET /v1/crons/:cronId` — one cron with its run-log ring (most recent first). */
97
114
  export interface CronShowDTO {
98
115
  cron: CronDTO;
@@ -0,0 +1,17 @@
1
+ /** `GET /v1/memory/resolve?name=<name>&node=<id>` result — where the named
2
+ * document lives for that node. Resolution runs the node's own precedence
3
+ * chain (its context store, its project stack, its profile, user, builtin), so
4
+ * the same name can answer with different documents for different nodes. */
5
+ export interface MemoryDocRefDTO {
6
+ /** The document's canonical identity (its frontmatter `name`, else its
7
+ * path-derived name) — which may differ from the queried name when the query
8
+ * was a bare leaf or a directory whose INDEX resolved. */
9
+ name: string;
10
+ /** Which store it came from: node | project | profile | user | builtin. */
11
+ scope: string;
12
+ /** Absolute path to the `.md` file. */
13
+ path: string;
14
+ /** The owning plugin's name when the doc is mounted from an installed plugin,
15
+ * absent for a native scope doc. */
16
+ plugin?: string;
17
+ }
@@ -0,0 +1,6 @@
1
+ // Memory-document resolution DTO. Backs `GET /v1/memory/resolve` — a client
2
+ // holding a `[[name]]` link out of a node's transcript turns it into the
3
+ // absolute path of the document that node would read, then peeks that path.
4
+ //
5
+ // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
6
+ export {};
@@ -26,6 +26,11 @@ export interface SendMessageRequest {
26
26
  * body only — rejected with fresh/reopen/situational_context/
27
27
  * output_schema or tier 'deferred'. Absent → durable inbox (unchanged). */
28
28
  delivery?: 'interactive';
29
+ /** Sender-minted message identity for a `delivery:'interactive'` send. The
30
+ * broker mirrors it through pi's queues and echoes it on the relayed user
31
+ * `message_start` (`crtrMessageId`), so an optimistic sender (the gateway)
32
+ * retires its pending entry by exact id. Ignored on the durable-inbox path. */
33
+ message_id?: string;
29
34
  }
30
35
  /** Result of an immediate message send. */
31
36
  export interface MessageResultDTO {
@@ -38,6 +38,9 @@ export interface ReviewDTO {
38
38
  created: IsoTime;
39
39
  /** Daemon open timestamp, after the companion binds. */
40
40
  opened_at?: IsoTime;
41
+ /** When the human submitted a review whose companion was still working. The
42
+ * review remains `open` until the companion goes quiet. */
43
+ submit_requested_at?: IsoTime;
41
44
  /** Daemon source-read status at projection time. */
42
45
  source_missing: boolean;
43
46
  /** Daemon-owned current comment-coordinate source digest. */
@@ -73,12 +76,13 @@ export interface ListReviewsQuery {
73
76
  export interface ReviewListDTO {
74
77
  reviews: ReviewDTO[];
75
78
  }
76
- /** Daemon-derived result of terminal review approval. */
79
+ /** Daemon-derived result of review approval, which may still be waiting on the
80
+ * review's companion. */
77
81
  export interface ReviewSubmitResultDTO {
78
82
  review: ReviewDTO;
79
- /** Immutable approval result. */
80
- result: FeedbackResultDTO;
81
- outcome: 'settled' | 'already_settled';
83
+ /** Immutable approval result; absent while the approval awaits the companion. */
84
+ result?: FeedbackResultDTO;
85
+ outcome: 'settled' | 'already_settled' | 'awaiting_companion';
82
86
  }
83
87
  /** Daemon-derived result of terminal review cancellation. */
84
88
  export interface ReviewCancelResultDTO {
@@ -21,6 +21,7 @@ export * from './dto/canvas.js';
21
21
  export * from './dto/worktree.js';
22
22
  export * from './dto/human.js';
23
23
  export * from './dto/files.js';
24
+ export * from './dto/memory.js';
24
25
  export * from './dto/inbox.js';
25
26
  export * from './dto/reviews.js';
26
27
  export * from './dto/review-comments.js';
@@ -22,6 +22,7 @@ export * from './dto/canvas.js';
22
22
  export * from './dto/worktree.js';
23
23
  export * from './dto/human.js';
24
24
  export * from './dto/files.js';
25
+ export * from './dto/memory.js';
25
26
  export * from './dto/inbox.js';
26
27
  export * from './dto/reviews.js';
27
28
  export * from './dto/review-comments.js';
@@ -46,6 +46,7 @@ export declare const routes: {
46
46
  readonly cronPause: (cronId: string) => string;
47
47
  readonly cronResume: (cronId: string) => string;
48
48
  readonly cronRun: (cronId: string) => string;
49
+ readonly cronsPoke: () => string;
49
50
  readonly canvasAttention: () => string;
50
51
  readonly canvasAttentionCounts: () => string;
51
52
  readonly canvasHistorySearch: () => string;
@@ -81,4 +82,5 @@ export declare const routes: {
81
82
  readonly modelAuths: () => string;
82
83
  readonly modelAuth: (provider: string) => string;
83
84
  readonly filePeek: () => string;
85
+ readonly memoryResolve: () => string;
84
86
  };
@@ -67,6 +67,7 @@ export const routes = {
67
67
  cronPause: (cronId) => `${V}/crons/${cronId}/pause`,
68
68
  cronResume: (cronId) => `${V}/crons/${cronId}/resume`,
69
69
  cronRun: (cronId) => `${V}/crons/${cronId}/run`,
70
+ cronsPoke: () => `${V}/crons/poke`,
70
71
  // Canvas maintenance / reads
71
72
  canvasAttention: () => `${V}/canvas/attention`,
72
73
  canvasAttentionCounts: () => `${V}/canvas/attention/counts`,
@@ -112,4 +113,7 @@ export const routes = {
112
113
  // Host file read (browser file-peek panel). The absolute path rides as a
113
114
  // `path` query param, not a path segment — it is not a single safe segment.
114
115
  filePeek: () => `${V}/files/peek`,
116
+ // Memory-document resolution (a `[[name]]` link in a node's transcript). Both
117
+ // the name and the node it is resolved for ride as query params.
118
+ memoryResolve: () => `${V}/memory/resolve`,
115
119
  };
@@ -0,0 +1,20 @@
1
+ /** One `[[name]]` link occurrence found in a document body. */
2
+ export interface DocLink {
3
+ /** Character index of the first `[` of `[[`. */
4
+ start: number;
5
+ /** Character index just past the closing `]]`. */
6
+ end: number;
7
+ /** The canonical document name between the brackets. */
8
+ name: string;
9
+ }
10
+ /** Is `name` a well-formed canonical doc-link name? */
11
+ export declare function isDocLinkName(name: string): boolean;
12
+ /**
13
+ * Find every `[[name]]` doc link in a markdown body, in order of appearance,
14
+ * skipping fenced code blocks and inline code spans (a link mentioned inside
15
+ * code is an example, not a reference). Occurrences are NOT deduplicated —
16
+ * callers collapse as needed.
17
+ */
18
+ export declare function findDocLinks(body: string): DocLink[];
19
+ /** The distinct link names in a body, first-appearance order. */
20
+ export declare function docLinkNames(body: string): string[];
@@ -0,0 +1,113 @@
1
+ // doc-link-grammar.ts — the single source of truth for the `[[canonical/name]]`
2
+ // memory-document link grammar. Zero imports, pure string ops, browser-safe —
3
+ // any surface that highlights, resolves, or lints doc links imports THIS module
4
+ // rather than re-implementing the bracket scan or the name shape. It ships to
5
+ // external consumers as `@north-light/crouter-api/doc-link-grammar`, so a
6
+ // browser UI rendering a node's transcript scans with the same grammar crtrd
7
+ // validates `/v1/memory/resolve` names with.
8
+ //
9
+ // A doc link is a durable cross-reference written INSIDE a memory document's
10
+ // body, pointing at another memory document by its exact canonical name (the
11
+ // same identifier `crtr memory read <name>` takes; a directory INDEX is linked
12
+ // by its folded bare-directory name). It is distinct from the interactive
13
+ // `/name` inline-reference grammar (`inline-ref-grammar.ts`), which scans a
14
+ // human's live composer text — links live in stored markdown and survive there.
15
+ //
16
+ // Links are POINTERS, never transclusion: nothing in the runtime auto-loads a
17
+ // linked body into context. `crtr memory lint` fails a link that resolves to no
18
+ // document; `crtr memory read` surfaces the resolved links of the doc it loads.
19
+ //
20
+ // This module never resolves anything: `findDocLinks` returns candidate links;
21
+ // a caller decides a link is live by testing `name` against the corpus of
22
+ // resolvable canonical names.
23
+ // A canonical doc name: one or more `[A-Za-z0-9_-]+` segments joined by `/`.
24
+ // Anything else between `[[` `]]` (spaces, `|` aliases, empty segments) is NOT
25
+ // a doc link — the grammar has no alias/label form, by design.
26
+ const NAME_RE = /^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/;
27
+ const LINK_RE = /\[\[([^\[\]\n]+)\]\]/g;
28
+ /** Is `name` a well-formed canonical doc-link name? */
29
+ export function isDocLinkName(name) {
30
+ return NAME_RE.test(name);
31
+ }
32
+ /**
33
+ * Find every `[[name]]` doc link in a markdown body, in order of appearance,
34
+ * skipping fenced code blocks and inline code spans (a link mentioned inside
35
+ * code is an example, not a reference). Occurrences are NOT deduplicated —
36
+ * callers collapse as needed.
37
+ */
38
+ export function findDocLinks(body) {
39
+ const links = [];
40
+ for (const segment of proseSegments(body)) {
41
+ LINK_RE.lastIndex = 0;
42
+ let m;
43
+ while ((m = LINK_RE.exec(segment.text)) !== null) {
44
+ const name = m[1];
45
+ if (!isDocLinkName(name))
46
+ continue;
47
+ links.push({ start: segment.offset + m.index, end: segment.offset + m.index + m[0].length, name });
48
+ }
49
+ }
50
+ return links;
51
+ }
52
+ /** The distinct link names in a body, first-appearance order. */
53
+ export function docLinkNames(body) {
54
+ const seen = new Set();
55
+ const out = [];
56
+ for (const link of findDocLinks(body)) {
57
+ if (seen.has(link.name))
58
+ continue;
59
+ seen.add(link.name);
60
+ out.push(link.name);
61
+ }
62
+ return out;
63
+ }
64
+ const FENCE_RE = /^ {0,3}(`{3,}|~{3,})/;
65
+ function* proseSegments(body) {
66
+ let offset = 0;
67
+ let fence = null; // the opening fence marker while inside a block
68
+ for (const line of body.split('\n')) {
69
+ const lineLen = line.length + 1; // + '\n' (harmless overcount on last line)
70
+ const fenceMatch = line.match(FENCE_RE);
71
+ if (fence !== null) {
72
+ // Inside a fenced block: only a closing fence of the same char and at
73
+ // least the same length ends it. Nothing in here is prose.
74
+ if (fenceMatch && fenceMatch[1][0] === fence[0] && fenceMatch[1].length >= fence.length) {
75
+ fence = null;
76
+ }
77
+ offset += lineLen;
78
+ continue;
79
+ }
80
+ if (fenceMatch) {
81
+ fence = fenceMatch[1];
82
+ offset += lineLen;
83
+ continue;
84
+ }
85
+ // Prose line: split around inline code spans (single-backtick pairs).
86
+ yield* proseParts(line, offset);
87
+ offset += lineLen;
88
+ }
89
+ }
90
+ function* proseParts(line, offset) {
91
+ let i = 0;
92
+ while (i < line.length) {
93
+ const open = line.indexOf('`', i);
94
+ if (open === -1) {
95
+ yield { offset: offset + i, text: line.slice(i) };
96
+ return;
97
+ }
98
+ // Backtick run: an inline span is delimited by runs of equal length.
99
+ let runEnd = open;
100
+ while (runEnd < line.length && line[runEnd] === '`')
101
+ runEnd++;
102
+ const run = line.slice(open, runEnd);
103
+ const close = line.indexOf(run, runEnd);
104
+ if (close === -1) {
105
+ // Unbalanced backticks: treat the rest as prose past the run.
106
+ yield { offset: offset + i, text: line.slice(i, open) };
107
+ i = runEnd;
108
+ continue;
109
+ }
110
+ yield { offset: offset + i, text: line.slice(i, open) };
111
+ i = close + run.length;
112
+ }
113
+ }
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "@north-light/crouter-api",
3
- "version": "0.3.180",
3
+ "version": "0.3.181",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, and the CrtrClient. Zero runtime dependencies.",
5
5
  "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
6
+ "main": "./dist/api/index.js",
7
+ "types": "./dist/api/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js",
12
- "require": "./dist/index.js",
13
- "default": "./dist/index.js"
10
+ "types": "./dist/api/index.d.ts",
11
+ "import": "./dist/api/index.js",
12
+ "require": "./dist/api/index.js",
13
+ "default": "./dist/api/index.js"
14
+ },
15
+ "./doc-link-grammar": {
16
+ "types": "./dist/core/memory/doc-link-grammar.d.ts",
17
+ "import": "./dist/core/memory/doc-link-grammar.js",
18
+ "require": "./dist/core/memory/doc-link-grammar.js",
19
+ "default": "./dist/core/memory/doc-link-grammar.js"
14
20
  }
15
21
  },
16
22
  "files": [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes