@treeseed/dsh-chapters 0.1.0

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 (119) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +644 -0
  3. package/cordis.patch.yml +88 -0
  4. package/docs/architecture.md +460 -0
  5. package/docs/contract.md +375 -0
  6. package/docs/development.md +437 -0
  7. package/docs/host-compaction-seam.md +117 -0
  8. package/docs/knowledge-repo.md +730 -0
  9. package/docs/provider.md +98 -0
  10. package/docs/verify.md +200 -0
  11. package/lib/archive.d.ts +88 -0
  12. package/lib/archive.js +114 -0
  13. package/lib/archive.js.map +1 -0
  14. package/lib/arrival.d.ts +23 -0
  15. package/lib/arrival.js +86 -0
  16. package/lib/arrival.js.map +1 -0
  17. package/lib/artifact-query.d.ts +53 -0
  18. package/lib/artifact-query.js +130 -0
  19. package/lib/artifact-query.js.map +1 -0
  20. package/lib/client.js +234 -0
  21. package/lib/client.js.map +1 -0
  22. package/lib/commands.d.ts +28 -0
  23. package/lib/commands.js +305 -0
  24. package/lib/commands.js.map +1 -0
  25. package/lib/compose.d.ts +30 -0
  26. package/lib/compose.js +131 -0
  27. package/lib/compose.js.map +1 -0
  28. package/lib/continue-core.d.ts +153 -0
  29. package/lib/continue-core.js +252 -0
  30. package/lib/continue-core.js.map +1 -0
  31. package/lib/engine-core.d.ts +219 -0
  32. package/lib/engine-core.js +405 -0
  33. package/lib/engine-core.js.map +1 -0
  34. package/lib/engine.d.ts +211 -0
  35. package/lib/engine.js +571 -0
  36. package/lib/engine.js.map +1 -0
  37. package/lib/enrich-queue.d.ts +66 -0
  38. package/lib/enrich-queue.js +100 -0
  39. package/lib/enrich-queue.js.map +1 -0
  40. package/lib/enrich-store.d.ts +31 -0
  41. package/lib/enrich-store.js +103 -0
  42. package/lib/enrich-store.js.map +1 -0
  43. package/lib/enrich-wire.d.ts +73 -0
  44. package/lib/enrich-wire.js +209 -0
  45. package/lib/enrich-wire.js.map +1 -0
  46. package/lib/enrich.d.ts +73 -0
  47. package/lib/enrich.js +150 -0
  48. package/lib/enrich.js.map +1 -0
  49. package/lib/gitops.d.ts +107 -0
  50. package/lib/gitops.js +539 -0
  51. package/lib/gitops.js.map +1 -0
  52. package/lib/index.d.ts +81 -0
  53. package/lib/index.js +305 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/indexing.d.ts +85 -0
  56. package/lib/indexing.js +212 -0
  57. package/lib/indexing.js.map +1 -0
  58. package/lib/injections.d.ts +66 -0
  59. package/lib/injections.js +83 -0
  60. package/lib/injections.js.map +1 -0
  61. package/lib/notice.d.ts +60 -0
  62. package/lib/notice.js +84 -0
  63. package/lib/notice.js.map +1 -0
  64. package/lib/provider.d.ts +44 -0
  65. package/lib/provider.js +66 -0
  66. package/lib/provider.js.map +1 -0
  67. package/lib/redact.d.ts +24 -0
  68. package/lib/redact.js +85 -0
  69. package/lib/redact.js.map +1 -0
  70. package/lib/registry.d.ts +120 -0
  71. package/lib/registry.js +130 -0
  72. package/lib/registry.js.map +1 -0
  73. package/lib/render.d.ts +40 -0
  74. package/lib/render.js +280 -0
  75. package/lib/render.js.map +1 -0
  76. package/lib/repo.d.ts +78 -0
  77. package/lib/repo.js +184 -0
  78. package/lib/repo.js.map +1 -0
  79. package/lib/rules-commands.d.ts +24 -0
  80. package/lib/rules-commands.js +138 -0
  81. package/lib/rules-commands.js.map +1 -0
  82. package/lib/rules.d.ts +90 -0
  83. package/lib/rules.js +185 -0
  84. package/lib/rules.js.map +1 -0
  85. package/lib/search.d.ts +48 -0
  86. package/lib/search.js +152 -0
  87. package/lib/search.js.map +1 -0
  88. package/lib/signature.d.ts +28 -0
  89. package/lib/signature.js +149 -0
  90. package/lib/signature.js.map +1 -0
  91. package/lib/store.d.ts +130 -0
  92. package/lib/store.js +235 -0
  93. package/lib/store.js.map +1 -0
  94. package/lib/sync.d.ts +183 -0
  95. package/lib/sync.js +719 -0
  96. package/lib/sync.js.map +1 -0
  97. package/lib/tools.d.ts +84 -0
  98. package/lib/tools.js +581 -0
  99. package/lib/tools.js.map +1 -0
  100. package/lib/treedx/client.d.ts +57 -0
  101. package/lib/treedx/client.js +82 -0
  102. package/lib/treedx/client.js.map +1 -0
  103. package/lib/treedx/provider.d.ts +39 -0
  104. package/lib/treedx/provider.js +479 -0
  105. package/lib/treedx/provider.js.map +1 -0
  106. package/lib/treedx/state.d.ts +27 -0
  107. package/lib/treedx/state.js +65 -0
  108. package/lib/treedx/state.js.map +1 -0
  109. package/lib/types.d.ts +100 -0
  110. package/lib/types.js +19 -0
  111. package/lib/types.js.map +1 -0
  112. package/lib/vocabulary.d.ts +41 -0
  113. package/lib/vocabulary.js +118 -0
  114. package/lib/vocabulary.js.map +1 -0
  115. package/package.json +132 -0
  116. package/presets/chapters/agent.cordis.yml +272 -0
  117. package/presets/chapters/preset.yml +3 -0
  118. package/presets/probe-roots/agent.cordis.yml +11 -0
  119. package/presets/probe-roots/preset.yml +3 -0
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The TreeDX HTTP client (provider transport). Thin on purpose: one request
3
+ * shape, bearer auth, bounded timeout, and the sync loop's `{ok, detail, code}`
4
+ * error idiom (§5.3 — transport failures are VALUES, never exceptions).
5
+ *
6
+ * Every response shape consumed here is [doc]-pinned in
7
+ * spikes/treedx/FINDINGS.md, contract-pinned by the stub server, and re-checked
8
+ * live by tests/integration/treedx-live.test.ts. Do not add "convenience"
9
+ * retries here — retry policy is the sync loop's (pull-and-retry, diverged-
10
+ * rebuild) and belongs to the provider verbs.
11
+ *
12
+ * Credential hygiene (record §2.1): the token appears in exactly one place —
13
+ * the request header. No detail/status string is ever built from a request or
14
+ * response that could carry it (TreeDX error envelopes are {code,message,
15
+ * details}; we surface code + message only).
16
+ */
17
+ export interface FetchLike {
18
+ (url: string, init: Record<string, unknown>): Promise<{
19
+ status: number;
20
+ ok: boolean;
21
+ text(): Promise<string>;
22
+ }>;
23
+ }
24
+ export interface TreeDxOk {
25
+ ok: true;
26
+ status: number;
27
+ data: Record<string, unknown>;
28
+ }
29
+ export interface TreeDxFail {
30
+ ok: false;
31
+ status: number;
32
+ /** TreeDX machine code when the envelope carried one (e.g. 'conflict',
33
+ * 'authentication_required', 'not_found'); 'network' / 'timeout' / 'bad_json'
34
+ * are ours. The provider branches on THESE, never on prose (r35 lesson). */
35
+ code: string;
36
+ detail: string;
37
+ }
38
+ export type TreeDxResult<T = Record<string, unknown>> = (TreeDxOk & {
39
+ data: T;
40
+ }) | TreeDxFail;
41
+ export interface TreeDxClientOpts {
42
+ baseUrl: string;
43
+ token?: string;
44
+ /** fetch impl timeout */
45
+ fetchTimeoutMs?: number;
46
+ fetchImpl?: FetchLike;
47
+ }
48
+ export interface TreeDxClient {
49
+ get(path: string): Promise<TreeDxResult>;
50
+ post(path: string, body?: unknown): Promise<TreeDxResult>;
51
+ put(path: string, body?: unknown): Promise<TreeDxResult>;
52
+ del(path: string): Promise<TreeDxResult>;
53
+ }
54
+ /** Unwrap TreeDX's document envelopes: some routes nest under a named key.
55
+ * The provider passes the candidate keys; first present object wins. */
56
+ export declare function pick(data: Record<string, unknown>, ...keys: string[]): Record<string, unknown> | undefined;
57
+ export declare function createTreeDxClient(opts: TreeDxClientOpts): TreeDxClient;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The TreeDX HTTP client (provider transport). Thin on purpose: one request
3
+ * shape, bearer auth, bounded timeout, and the sync loop's `{ok, detail, code}`
4
+ * error idiom (§5.3 — transport failures are VALUES, never exceptions).
5
+ *
6
+ * Every response shape consumed here is [doc]-pinned in
7
+ * spikes/treedx/FINDINGS.md, contract-pinned by the stub server, and re-checked
8
+ * live by tests/integration/treedx-live.test.ts. Do not add "convenience"
9
+ * retries here — retry policy is the sync loop's (pull-and-retry, diverged-
10
+ * rebuild) and belongs to the provider verbs.
11
+ *
12
+ * Credential hygiene (record §2.1): the token appears in exactly one place —
13
+ * the request header. No detail/status string is ever built from a request or
14
+ * response that could carry it (TreeDX error envelopes are {code,message,
15
+ * details}; we surface code + message only).
16
+ */
17
+ /** Unwrap TreeDX's document envelopes: some routes nest under a named key.
18
+ * The provider passes the candidate keys; first present object wins. */
19
+ export function pick(data, ...keys) {
20
+ for (const k of keys) {
21
+ const v = data[k];
22
+ if (v !== null && typeof v === 'object')
23
+ return v;
24
+ }
25
+ return undefined;
26
+ }
27
+ export function createTreeDxClient(opts) {
28
+ const baseUrl = opts.baseUrl.replace(/\/+$/, '');
29
+ const timeoutMs = opts.fetchTimeoutMs ?? 15000;
30
+ const doFetch = opts.fetchImpl ?? ((u, init) => fetch(u, init).then((r) => ({
31
+ status: r.status,
32
+ ok: r.ok,
33
+ text: () => r.text(),
34
+ })));
35
+ const req = async (method, path, body) => {
36
+ const controller = new AbortController();
37
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
38
+ try {
39
+ const headers = { 'content-type': 'application/json' };
40
+ if (opts.token !== undefined && opts.token !== '')
41
+ headers.authorization = `Bearer ${opts.token}`;
42
+ const res = await doFetch(`${baseUrl}${path}`, {
43
+ method,
44
+ headers,
45
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
46
+ signal: controller.signal,
47
+ });
48
+ const text = await res.text();
49
+ let data = {};
50
+ if (text !== '') {
51
+ try {
52
+ const parsed = JSON.parse(text);
53
+ data = parsed !== null && typeof parsed === 'object' ? parsed : { value: parsed };
54
+ }
55
+ catch {
56
+ if (res.ok)
57
+ return { ok: false, status: res.status, code: 'bad_json', detail: `${method} ${path}: non-JSON response (${text.slice(0, 120)})` };
58
+ }
59
+ }
60
+ if (res.ok)
61
+ return { ok: true, status: res.status, data };
62
+ const err = (data.error ?? {});
63
+ const code = String(err.code ?? `http_${res.status}`);
64
+ const message = String(err.message ?? `${method} ${path} failed`);
65
+ return { ok: false, status: res.status, code, detail: `${code} (${res.status}): ${message}`.slice(0, 300) };
66
+ }
67
+ catch (error) {
68
+ const aborted = error?.name === 'AbortError';
69
+ return { ok: false, status: 0, code: aborted ? 'timeout' : 'network', detail: `${method} ${path}: ${aborted ? `timed out after ${timeoutMs}ms` : String(error?.message ?? error)}`.slice(0, 300) };
70
+ }
71
+ finally {
72
+ clearTimeout(timer);
73
+ }
74
+ };
75
+ return {
76
+ get: (p) => req('GET', p),
77
+ post: (p, b) => req('POST', p, b ?? {}),
78
+ put: (p, b) => req('PUT', p, b ?? {}),
79
+ del: (p) => req('DELETE', p),
80
+ };
81
+ }
82
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/treedx/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type SyncProvider } from '../provider.ts';
2
+ import { type TreeDxClient, type FetchLike } from './client.ts';
3
+ export interface TreedxProviderConfig {
4
+ fetchTimeoutMs: number;
5
+ workspaceTtlSeconds: number;
6
+ leaseRetries: number;
7
+ leaseRetryDelayMs: number;
8
+ /** Injectable (stub tests and any future transport instrumentation). */
9
+ fetchImpl?: FetchLike;
10
+ /** Injectable sleep (tests run the retry path without wall-clock cost). */
11
+ sleep?: (ms: number) => Promise<void>;
12
+ now?: () => number;
13
+ }
14
+ /**
15
+ * The live-shaped repository catalog: GET /repos → {ok, repos:[…]} (older
16
+ * docs called it `repositories`); rows carry `repoId` (live-measured) or a
17
+ * bare `id`. One parser for the provider AND the /chapters-link command —
18
+ * this exact shape drift is why the first live run broke.
19
+ */
20
+ export declare function repoCatalog(data: Record<string, unknown>): Array<{
21
+ name: string;
22
+ repoId: string;
23
+ }>;
24
+ /** Resolve a repository's catalog id by name ('' = not found). */
25
+ export declare function resolveRepoId(client: TreeDxClient, name: string): Promise<string>;
26
+ /**
27
+ * Create the provider (registered under kind 'treedx'). One instance per
28
+ * plane (host/engine); the mirror's state file carries everything across
29
+ * calls, so instances remain safe to share across cwds/projects.
30
+ */
31
+ export declare function createTreedxProvider(cfgIn?: Partial<TreedxProviderConfig>): SyncProvider & {
32
+ ensureClone: SyncProvider['ensureClone'];
33
+ };
34
+ /** The default instance registered at module load with production defaults;
35
+ * both planes re-register with user config at boot (src/index.ts, engine). */
36
+ declare const defaultProvider: SyncProvider & {
37
+ ensureClone: SyncProvider["ensureClone"];
38
+ };
39
+ export { defaultProvider as treedxProvider };
@@ -0,0 +1,479 @@
1
+ /**
2
+ * The TreeDX transport provider (src/provider.ts seam; design pinned in
3
+ * spikes/treedx/FINDINGS.md). It implements the sync loop's six transport
4
+ * verbs over TreeDX's no-clone HTTP API, mapping them so runSync's proven
5
+ * control flow — publish → commit → pull → push, degraded to local-only on
6
+ * any remote failure, rebuilt from the store on divergence — keeps working
7
+ * UNCHANGED. The mirror directory is still materialized (§3.1: it is the
8
+ * search/rules/index read surface in every mode); only what fills it changes.
9
+ *
10
+ * Verb mapping (the six, in runSync order):
11
+ * - ensureClone resolve the managed repo, fetch head + corpus into the
12
+ * mirror, write the state file (origin, head, baseline).
13
+ * Origin changed ⇒ 'origin-mismatch' (same code, same
14
+ * rebuild behavior as git mode).
15
+ * - stageAllAndCommit LOCAL workdir-vs-baseline diff, recorded as `staged`
16
+ * (mirrors git's local-commit step: no network here).
17
+ * - pullFastForward head compare vs state. Moved while we hold staged
18
+ * work ⇒ 'diverged' (the rebuild path re-publishes from
19
+ * the store and re-derives the index — safer than an
20
+ * ff refetch could ever be: derived files must not go
21
+ * stale against a corpus they predate). Moved with
22
+ * nothing staged ⇒ plain refetch (ff).
23
+ * - push workspace-create (one writable lease per branch) →
24
+ * overlay-write each staged path (UTF-8 file API; binary
25
+ * -safe blob API) → commit. Lease contention ⇒ 'rejected'
26
+ * (runSync's existing pull-and-retry, plus bounded
27
+ * in-provider waits); moved-base commit ⇒ 'diverged'.
28
+ * - initLocal offline state file (publish/commit/index/search keep
29
+ * working; push deferred — §5.3).
30
+ * - removeMirror inherited from the filesystem reality (mirror is
31
+ * transport, the store is truth).
32
+ *
33
+ * Every failure is a VALUE ({ok:false, code}) — never a throw into the
34
+ * conversation path, and never a prose-matched classification (r35).
35
+ */
36
+ import fs from 'node:fs';
37
+ import path from 'node:path';
38
+ import { parseKnowledgeRemote } from "../repo.js";
39
+ import { registerProvider } from "../provider.js";
40
+ import { createTreeDxClient, pick } from "./client.js";
41
+ import { contentHash, isUtf8Safe, readState, walkMirror, writeState } from "./state.js";
42
+ const DEFAULTS = {
43
+ fetchTimeoutMs: 15000,
44
+ workspaceTtlSeconds: 900,
45
+ leaseRetries: 3,
46
+ leaseRetryDelayMs: 1000,
47
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
48
+ now: () => Date.now(),
49
+ };
50
+ const REF = 'refs/heads/main';
51
+ const sleepOf = (cfg) => cfg.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
52
+ /**
53
+ * The live-shaped repository catalog: GET /repos → {ok, repos:[…]} (older
54
+ * docs called it `repositories`); rows carry `repoId` (live-measured) or a
55
+ * bare `id`. One parser for the provider AND the /chapters-link command —
56
+ * this exact shape drift is why the first live run broke.
57
+ */
58
+ export function repoCatalog(data) {
59
+ const rows = Array.isArray(data.repos) ? data.repos
60
+ : Array.isArray(data.repositories) ? data.repositories : [];
61
+ return rows.map((x) => ({
62
+ name: String(x.repositoryName ?? x.name ?? ''),
63
+ repoId: String(x.repoId ?? x.id ?? ''),
64
+ })).filter((x) => x.repoId !== '');
65
+ }
66
+ /** Resolve a repository's catalog id by name ('' = not found). */
67
+ export async function resolveRepoId(client, name) {
68
+ const list = await client.get('/api/v1/repos');
69
+ if (!list.ok)
70
+ return '';
71
+ return repoCatalog(list.data).find((x) => x.name === name)?.repoId ?? '';
72
+ }
73
+ async function resolve(cfg, remote, state) {
74
+ let target;
75
+ try {
76
+ target = parseKnowledgeRemote(remote.url);
77
+ }
78
+ catch (error) {
79
+ return { ok: false, res: { ok: false, detail: `bad treedx remote: ${String(error?.message ?? error)}` } };
80
+ }
81
+ if (target.treedx === undefined) {
82
+ return { ok: false, res: { ok: false, detail: `remote ${remote.url} is not a treedx+ target` } };
83
+ }
84
+ const client = createTreeDxClient({
85
+ baseUrl: target.treedx.baseUrl,
86
+ ...(remote.token !== undefined ? { token: remote.token } : {}),
87
+ fetchTimeoutMs: cfg.fetchTimeoutMs,
88
+ ...(cfg.fetchImpl !== undefined ? { fetchImpl: cfg.fetchImpl } : {}),
89
+ });
90
+ // Repo id: prefer the state-cached id (stable across passes); otherwise
91
+ // resolve by canonical name through the catalog.
92
+ const list = await client.get('/api/v1/repos');
93
+ if (!list.ok) {
94
+ if (list.code === 'authentication_required' || list.code === 'invalid_token' || list.status === 401 || list.status === 403) {
95
+ return { ok: false, res: { ok: false, code: 'auth', detail: `TreeDX auth rejected (${list.detail}) — re-link the token with /chapters-link` } };
96
+ }
97
+ return { ok: false, res: { ok: false, code: 'network', detail: `repos list: ${list.detail}` } };
98
+ }
99
+ const catalog = repoCatalog(list.data);
100
+ const want = target.treedx.repoName;
101
+ const found = catalog.find((x) => x.name === want);
102
+ const repoId = found !== undefined ? found.repoId : '';
103
+ if (repoId === '' && state?.repoId === undefined) {
104
+ return { ok: false, res: { ok: false, code: 'not_found', detail: `TreeDX has no repository '${want}' at ${target.treedx.baseUrl} (create it or re-link)` } };
105
+ }
106
+ return { ok: true, r: { target, client, repoId: repoId !== '' ? repoId : String(state?.repoId ?? '') } };
107
+ }
108
+ /**
109
+ * Head sha of `refs/heads/main`. LIVE-MEASURED (spikes/treedx/FINDINGS.md):
110
+ * refs entries carry `{kind:'branch', name, target}` — the sha key is
111
+ * `target`; and a managed repository is BORN with `refs/heads/main` (a
112
+ * seeding commit holding `.treedxkeep`), so `null` here only means an
113
+ * unexpected listing failure shape, handled tolerantly. Repo routes address
114
+ * by repoId (name 404s).
115
+ */
116
+ async function headOf(client, repoId) {
117
+ const refs = await client.get(`/api/v1/repos/${encodeURIComponent(repoId)}/refs`);
118
+ if (!refs.ok) {
119
+ if (refs.code === 'not_found')
120
+ return { ok: true, head: null };
121
+ return { ok: false, res: { ok: false, code: refs.code === 'auth' ? 'auth' : 'network', detail: `refs: ${refs.detail}` } };
122
+ }
123
+ const list = Array.isArray(refs.data.refs) ? refs.data.refs : [];
124
+ const main = list.find((x) => String(x.name ?? x.ref ?? '') === REF || String(x.name ?? x.ref ?? '') === 'main');
125
+ const sha = main !== undefined ? String(main.target ?? main.objectId ?? main.sha ?? main.commitSha ?? '') : '';
126
+ return { ok: true, head: sha === '' ? null : sha };
127
+ }
128
+ /**
129
+ * Corpus paths at HEAD. LIVE-MEASURED shape: `{ok, ref, entries:[{path,
130
+ * kind:'blob'|'tree', objectId}]}` — entries include tree rows, so filter to
131
+ * blobs. NO extension filter: the mirror must be a faithful cache of
132
+ * everything the pool holds. Dot-prefixed paths (the birth-seeded
133
+ * `.treedxkeep`) are EXCLUDED on purpose: `walkMirror` never stages them, so
134
+ * a baseline containing one would report a phantom deletion every pass and
135
+ * try to delete the service's own bookkeeping file.
136
+ */
137
+ async function corpusPaths(client, repoId) {
138
+ // PAGINATION IS MANDATORY, not an optimization (live finding 2026-09-23):
139
+ // paths/list answers {entries, page:{limit,hasMore,nextCursor}} capped at
140
+ // 100 — reading one page silently truncated the corpus, and a machine whose
141
+ // twin had pushed the 101st file pulled "up to date" while missing real
142
+ // work. The provider must follow nextCursor until hasMore is false.
143
+ const out = [];
144
+ let cursor;
145
+ for (let page = 0; page < 500; page += 1) {
146
+ const res = await client.post(`/api/v1/repos/${encodeURIComponent(repoId)}/paths/list`, {
147
+ ref: REF,
148
+ ...(cursor !== undefined ? { cursor } : {}),
149
+ });
150
+ if (!res.ok)
151
+ return [];
152
+ const raw = Array.isArray(res.data.entries) ? res.data.entries
153
+ : Array.isArray(res.data.paths) ? res.data.paths : [];
154
+ for (const e of raw) {
155
+ const p = String(typeof e === 'string' ? e : e.path ?? '');
156
+ const kind = typeof e === 'string' ? 'blob' : String(e.kind ?? 'blob');
157
+ if (p !== '' && kind === 'blob' && !p.split('/').some((seg) => seg.startsWith('.')))
158
+ out.push(p);
159
+ }
160
+ const pg = res.data.page;
161
+ if (pg?.hasMore !== true || pg.nextCursor === undefined || pg.nextCursor === cursor)
162
+ return out;
163
+ cursor = pg.nextCursor;
164
+ }
165
+ return out;
166
+ }
167
+ /**
168
+ * Read one corpus file at HEAD. UTF-8 document read; base64 blob fallback.
169
+ * LIVE-MEASURED: a binary path answers `files/read` with 415
170
+ * `unsupported_media_type` — `blobs/read` (→ `blob.contentBase64`) is the
171
+ * binary path back.
172
+ */
173
+ async function readCorpusFile(client, repoId, p) {
174
+ const doc = await client.post(`/api/v1/repos/${encodeURIComponent(repoId)}/files/read`, { ref: REF, path: p, parseFrontmatter: false });
175
+ if (doc.ok) {
176
+ const d = pick(doc.data, 'document', 'file') ?? doc.data;
177
+ const content = d.content;
178
+ if (typeof content === 'string') {
179
+ if (String(d.encoding ?? 'utf8') === 'base64')
180
+ return Buffer.from(content, 'base64');
181
+ return Buffer.from(content, 'utf8');
182
+ }
183
+ return null;
184
+ }
185
+ if (doc.code === 'unsupported_media_type' || doc.status === 415) {
186
+ const blob = await client.post(`/api/v1/repos/${encodeURIComponent(repoId)}/blobs/read`, { ref: REF, path: p });
187
+ if (blob.ok) {
188
+ const b = pick(blob.data, 'blob') ?? blob.data;
189
+ const b64 = String(b.contentBase64 ?? b.content ?? '');
190
+ if (b64 !== '')
191
+ return Buffer.from(b64, 'base64');
192
+ }
193
+ }
194
+ return null;
195
+ }
196
+ async function fetchCorpus(r) {
197
+ const out = new Map();
198
+ for (const p of await corpusPaths(r.client, r.repoId)) {
199
+ const bytes = await readCorpusFile(r.client, r.repoId, p);
200
+ if (bytes !== null)
201
+ out.set(p, bytes);
202
+ }
203
+ return out;
204
+ }
205
+ /**
206
+ * Create the provider (registered under kind 'treedx'). One instance per
207
+ * plane (host/engine); the mirror's state file carries everything across
208
+ * calls, so instances remain safe to share across cwds/projects.
209
+ */
210
+ export function createTreedxProvider(cfgIn = {}) {
211
+ const cfg = { ...DEFAULTS, ...cfgIn };
212
+ const provider = {
213
+ kind: 'treedx',
214
+ describe: (project) => {
215
+ const s = readState(path.join(project.cwd, '.dsh-knowledge'));
216
+ const id = s?.repoId ?? project.repoId ?? '';
217
+ return `treedx · ${id !== '' ? `${id} · ` : ''}${s === null ? 'not cloned yet' : s.offline ? 'offline (push deferred)' : `head ${String(s.head ?? '—').slice(0, 10)}`}`;
218
+ },
219
+ async ensureClone(dir, remote) {
220
+ const state = readState(dir);
221
+ if (state !== null) {
222
+ if (state.origin !== remote.url) {
223
+ return { ok: false, code: 'origin-mismatch', detail: `mirror origin is ${state.origin}, the project's remote is ${remote.url}` };
224
+ }
225
+ return { ok: true, detail: 'already synced (state present)' };
226
+ }
227
+ const res = await resolve(cfg, remote, null);
228
+ if (!res.ok)
229
+ return res.res;
230
+ const r = res.r;
231
+ const head = await headOf(r.client, r.repoId);
232
+ if (!head.ok)
233
+ return head.res;
234
+ // Materialize the corpus into the mirror (the same read surface git mode
235
+ // gives: real files the search/rules/index layers — and the read tool's
236
+ // sibling mirror paths — consume unchanged). A dirty non-empty dir with
237
+ // no state file is dead transport (gitops parity): moving it aside
238
+ // prevents its files being re-pushed as "new" content into a fresh pool.
239
+ const corpus = await fetchCorpus(r);
240
+ if (fs.existsSync(dir)) {
241
+ const strays = fs.readdirSync(dir);
242
+ if (strays.length > 0) {
243
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
244
+ fs.renameSync(dir, `${dir}.stale-${stamp}`);
245
+ }
246
+ }
247
+ fs.mkdirSync(dir, { recursive: true });
248
+ const baseline = {};
249
+ for (const [p, bytes] of corpus) {
250
+ const abs = path.join(dir, p);
251
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
252
+ fs.writeFileSync(abs, bytes);
253
+ baseline[p] = contentHash(bytes);
254
+ }
255
+ writeState(dir, { origin: remote.url, head: head.head, baseline, staged: null, offline: false, repoId: r.repoId });
256
+ return { ok: true, detail: `cloned from TreeDX (${corpus.size} file(s)${head.head === null ? ', empty repo' : ''})`, changed: true };
257
+ },
258
+ async initLocal(dir, remote) {
259
+ if (readState(dir) !== null)
260
+ return { ok: true, detail: 'already synced (state present)' };
261
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length > 0) {
262
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
263
+ fs.renameSync(dir, `${dir}.stale-${stamp}`);
264
+ }
265
+ fs.mkdirSync(dir, { recursive: true });
266
+ writeState(dir, {
267
+ origin: remote?.url ?? '', head: null, baseline: {}, staged: null, offline: true,
268
+ });
269
+ return { ok: true, detail: 'TreeDX offline mirror initialized', changed: true };
270
+ },
271
+ async removeMirror(dir) {
272
+ fs.rmSync(dir, { recursive: true, force: true });
273
+ return { ok: true, detail: 'mirror removed for rebuild' };
274
+ },
275
+ async stageAllAndCommit(dir, message, author) {
276
+ const state = readState(dir);
277
+ if (state === null)
278
+ return { ok: false, detail: 'no TreeDX state — ensureClone/initLocal first' };
279
+ const present = new Set(walkMirror(dir));
280
+ const changed = [];
281
+ for (const p of present) {
282
+ const bytes = fs.readFileSync(path.join(dir, p));
283
+ if (state.baseline[p] !== contentHash(bytes))
284
+ changed.push(p);
285
+ }
286
+ const dropped = Object.keys(state.baseline).filter((p) => !present.has(p));
287
+ if (changed.length === 0 && dropped.length === 0)
288
+ return { ok: true, detail: 'nothing to commit' };
289
+ // git-mode parity: a second stage of identical bytes is 'nothing to
290
+ // commit'. The baseline advances at STAGE time (push success recomputes
291
+ // it wholesale anyway; divergence replaces it), so the staged set is
292
+ // exactly the workdir-vs-last-known delta.
293
+ const baseline = { ...state.baseline };
294
+ for (const p of changed)
295
+ baseline[p] = contentHash(fs.readFileSync(path.join(dir, p)));
296
+ for (const p of dropped)
297
+ delete baseline[p];
298
+ writeState(dir, { ...state, baseline, staged: { message, author, paths: [...changed, ...dropped].sort() } });
299
+ return { ok: true, detail: `staged ${changed.length} path(s)${dropped.length > 0 ? `, ${dropped.length} removal(s)` : ''}`, changed: true };
300
+ },
301
+ async pullFastForward(dir, remote) {
302
+ const state = readState(dir);
303
+ if (state === null)
304
+ return { ok: false, detail: 'no TreeDX state' };
305
+ if (state.offline) {
306
+ // Recovery probe: still down ⇒ degrade again (§5.3). Reachable ⇒ the
307
+ // offline mirror's commits cannot rebase onto the service's history
308
+ // (ours live only in the store, which is the truth), so classify as
309
+ // diverged and let runSync's rebuild republish from the store —
310
+ // exactly the git mode's diverged-offline recovery.
311
+ const probe = await resolve(cfg, remote, state);
312
+ if (!probe.ok)
313
+ return probe.res;
314
+ const head = await headOf(probe.r.client, probe.r.repoId);
315
+ if (!head.ok)
316
+ return head.res;
317
+ return { ok: false, code: 'diverged', detail: 'offline mirror, TreeDX reachable again — rebuilding mirror from remote' };
318
+ }
319
+ const res = await resolve(cfg, remote, state);
320
+ if (!res.ok)
321
+ return res.res;
322
+ const r = res.r;
323
+ const head = await headOf(r.client, r.repoId);
324
+ if (!head.ok)
325
+ return head.res;
326
+ if (head.head === state.head && state.staged === null)
327
+ return { ok: true, detail: 'up to date (nothing to fast-forward)' };
328
+ if (head.head === state.head)
329
+ return { ok: true, detail: 'up to date (local work staged; push will ship it)' };
330
+ // Remote moved since our base. With staged work in hand, a refetch-merge
331
+ // could leave derived files (index/, topics/) built against a stale
332
+ // corpus — divergence REBUILDS from the store (runSync's path), and the
333
+ // rebuild re-publishes + re-derives. Without staged work, a plain ff
334
+ // refetch is all there is.
335
+ if (state.staged !== null) {
336
+ return { ok: false, code: 'diverged', detail: `TreeDX head moved (${String(state.head).slice(0, 10)} → ${String(head.head).slice(0, 10)}) with local work staged — rebuild will recover` };
337
+ }
338
+ const corpus = await fetchCorpus(r);
339
+ const baseline = {};
340
+ for (const [p, bytes] of corpus) {
341
+ const abs = path.join(dir, p);
342
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
343
+ fs.writeFileSync(abs, bytes);
344
+ baseline[p] = contentHash(bytes);
345
+ }
346
+ writeState(dir, { ...state, head: head.head, baseline });
347
+ return { ok: true, detail: `fast-forwarded (TreeDX ${String(head.head).slice(0, 10)}, ${corpus.size} file(s))`, changed: true };
348
+ },
349
+ async push(dir, remote) {
350
+ const state = readState(dir);
351
+ if (state === null)
352
+ return { ok: false, detail: 'no TreeDX state' };
353
+ if (state.staged === null || state.staged.paths.length === 0)
354
+ return { ok: true, detail: 'pushed (nothing new)' };
355
+ const res = await resolve(cfg, remote, state);
356
+ if (!res.ok)
357
+ return res.res;
358
+ const r = res.r;
359
+ let attempt = 0;
360
+ for (;;) {
361
+ const outcome = await onePush(r, dir, state, cfg);
362
+ if (outcome.retryLease === true && attempt < cfg.leaseRetries) {
363
+ attempt += 1;
364
+ await sleepOf(cfg)(cfg.leaseRetryDelayMs * attempt);
365
+ continue;
366
+ }
367
+ return outcome.res;
368
+ }
369
+ },
370
+ };
371
+ return provider;
372
+ }
373
+ /** One push round: workspace → writes → commit. */
374
+ async function onePush(r, dir, state, cfg) {
375
+ const staged = state.staged;
376
+ if (staged === null || staged.paths.length === 0)
377
+ return { res: { ok: true, detail: 'pushed (nothing new)' } };
378
+ // An empty repo has no head to base on (live-gated assumption, FINDINGS):
379
+ // send baseRef only when one exists.
380
+ const createBody = { branchName: REF, mode: 'writable', ttlSeconds: cfg.workspaceTtlSeconds };
381
+ if (state.head !== null)
382
+ createBody.baseRef = REF;
383
+ const wsRes = await r.client.post(`/api/v1/repos/${encodeURIComponent(r.repoId)}/workspaces`, createBody);
384
+ if (!wsRes.ok) {
385
+ if (wsRes.status === 409)
386
+ return { res: { ok: false, code: 'rejected', detail: `workspace lease busy: ${wsRes.detail}` }, retryLease: true };
387
+ if (wsRes.code === 'auth')
388
+ return { res: { ok: false, code: 'auth', detail: wsRes.detail } };
389
+ return { res: { ok: false, code: 'network', detail: `workspace create: ${wsRes.detail}` } };
390
+ }
391
+ const wsData = pick(wsRes.data, 'workspace') ?? wsRes.data;
392
+ const wsId = String(wsData.workspaceId ?? wsData.id ?? '');
393
+ if (wsId === '')
394
+ return { res: { ok: false, detail: 'workspace create returned no id' } };
395
+ // LIVE-MEASURED: a committed workspace releases its lease, but a half-failed
396
+ // one holds it — and the lease survives its TTL until closed (measured:
397
+ // expiresAt passed, create still 409'd, /close released it). Every failure
398
+ // path after create must close, or a broken push poisons the pool's branch
399
+ // for every machine until an operator intervenes.
400
+ const fail = async (res, retryLease) => {
401
+ await r.client.post(`/api/v1/workspaces/${encodeURIComponent(wsId)}/close`, {}).catch(() => undefined);
402
+ return { res, ...(retryLease === true ? { retryLease } : {}) };
403
+ };
404
+ let wrote = 0;
405
+ for (const p of staged.paths) {
406
+ const abs = path.join(dir, p);
407
+ if (!fs.existsSync(abs)) {
408
+ const del = await r.client.del(`/api/v1/workspaces/${encodeURIComponent(wsId)}/files?path=${encodeURIComponent(p)}`);
409
+ if (!del.ok && del.status !== 404)
410
+ return await fail({ ok: false, detail: `overlay delete ${p}: ${del.detail}` });
411
+ wrote += 1;
412
+ continue;
413
+ }
414
+ const bytes = fs.readFileSync(abs);
415
+ if (isUtf8Safe(bytes)) {
416
+ const w = await r.client.put(`/api/v1/workspaces/${encodeURIComponent(wsId)}/files?path=${encodeURIComponent(p)}`, { encoding: 'utf8', content: bytes.toString('utf8') });
417
+ if (!w.ok) {
418
+ if (w.status === 413)
419
+ return await fail({ ok: false, detail: `file ${p} exceeds TreeDX's UTF-8 file limit: ${w.detail}` });
420
+ return await fail({ ok: false, detail: `overlay write ${p}: ${w.detail}` });
421
+ }
422
+ }
423
+ else {
424
+ // live-measured body: `contentBase64` (a `{encoding,content}` body is a 422)
425
+ const b = await r.client.post(`/api/v1/workspaces/${encodeURIComponent(wsId)}/blobs/write`, { path: p, contentBase64: bytes.toString('base64') });
426
+ if (!b.ok) {
427
+ if (b.status === 413)
428
+ return await fail({ ok: false, detail: `blob ${p} exceeds TreeDX's blob limit: ${b.detail}` });
429
+ return await fail({ ok: false, detail: `blob write ${p}: ${b.detail}` });
430
+ }
431
+ }
432
+ wrote += 1;
433
+ }
434
+ const commit = await r.client.post(`/api/v1/workspaces/${encodeURIComponent(wsId)}/commit`, {
435
+ message: staged.message,
436
+ author: staged.author,
437
+ });
438
+ if (!commit.ok) {
439
+ // A 409 here is TreeDX failing closed on a moved ref / revoked lease.
440
+ // Classify by STATUS and CODE only — never by message prose (r35): the
441
+ // head moved underneath us ⇒ diverged-rebuild; lease ⇒ rejected-retry.
442
+ if (commit.status === 409) {
443
+ const head = await headOf(r.client, r.repoId);
444
+ if (head.ok && head.head !== state.head) {
445
+ return await fail({ ok: false, code: 'diverged', detail: `commit refused; TreeDX head moved (${String(state.head).slice(0, 10)} → ${String(head.head).slice(0, 10)}) — rebuild will recover` });
446
+ }
447
+ return await fail({ ok: false, code: 'rejected', detail: `commit conflict: ${commit.detail}` }, true);
448
+ }
449
+ return await fail({ ok: false, code: commit.code === 'auth' ? 'auth' : 'network', detail: `commit: ${commit.detail}` });
450
+ }
451
+ const cData = pick(commit.data, 'commit') ?? commit.data;
452
+ let newHead = (() => {
453
+ const s = cData.commitSha ?? cData.sha;
454
+ return typeof s === 'string' && s !== '' ? s : null;
455
+ })();
456
+ if (newHead === null) {
457
+ const after = await headOf(r.client, r.repoId);
458
+ if (after.ok)
459
+ newHead = after.head;
460
+ }
461
+ const baseline = { ...state.baseline };
462
+ for (const p of staged.paths) {
463
+ const abs = path.join(dir, p);
464
+ if (fs.existsSync(abs))
465
+ baseline[p] = contentHash(fs.readFileSync(abs));
466
+ else
467
+ delete baseline[p];
468
+ }
469
+ writeState(dir, {
470
+ ...state, head: newHead ?? state.head, baseline, staged: null, offline: false,
471
+ });
472
+ return { res: { ok: true, detail: `pushed ${wrote} path(s) to TreeDX`, changed: true } };
473
+ }
474
+ /** The default instance registered at module load with production defaults;
475
+ * both planes re-register with user config at boot (src/index.ts, engine). */
476
+ const defaultProvider = createTreedxProvider();
477
+ registerProvider(defaultProvider);
478
+ export { defaultProvider as treedxProvider };
479
+ //# sourceMappingURL=provider.js.map