@treeseed/sdk 0.4.0 → 0.4.1

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.
package/README.md CHANGED
@@ -102,6 +102,19 @@ ctx <target>
102
102
 
103
103
  The old `key=value` graph DSL is no longer supported.
104
104
 
105
+ ## Shared Fixture Support
106
+
107
+ SDK also owns the shared fixture support model used across the Treeseed workspace.
108
+
109
+ That support layer is responsible for:
110
+
111
+ - resolving the canonical shared fixture in `.fixtures/treeseed-fixtures`
112
+ - preparing fixture-local package visibility for package-scoped verification
113
+ - linking real workspace or installed packages into the fixture runtime when available
114
+ - providing the canonical `contracts-only` Agent shim used by packages such as `core` during isolated verification
115
+
116
+ The shared fixture is an integrated Treeseed project, but package verification remains package-scoped. SDK owns the tooling that lets other packages validate their own slice of that project without mutating the fixture itself.
117
+
105
118
  ## Advanced Graph Methods
106
119
 
107
120
  The SDK also exposes lower-level graph primitives such as:
@@ -166,3 +179,9 @@ npm install
166
179
  npm run build
167
180
  npm test
168
181
  ```
182
+
183
+ For fixture-specific work:
184
+
185
+ ```bash
186
+ npm run fixtures:check
187
+ ```
@@ -0,0 +1,24 @@
1
+ export type FixtureInjectionMode = 'workspace-link' | 'installed-link' | 'contracts-only';
2
+ export type FixtureSupportDeclaration = {
3
+ packageName: string;
4
+ modes: readonly FixtureInjectionMode[];
5
+ workspaceDirName?: string;
6
+ entrySpecifier?: string;
7
+ contractsShim?: 'agent';
8
+ };
9
+ export type ResolveSharedFixtureOptions = {
10
+ packageRoot?: string;
11
+ requiredPaths?: string[];
12
+ };
13
+ export type PrepareFixturePackagesOptions = {
14
+ fixtureRoot: string;
15
+ packageRoot?: string;
16
+ declarations: readonly FixtureSupportDeclaration[];
17
+ };
18
+ export declare const DEFAULT_FIXTURE_ID = "treeseed-working-site";
19
+ export declare function resolveRequestedFixtureId(): string;
20
+ export declare function resolveFixturesRepoRoot(options?: ResolveSharedFixtureOptions): string;
21
+ export declare function resolveSharedFixtureRoot(options?: ResolveSharedFixtureOptions): string | null;
22
+ export declare function requireSharedFixtureRoot(options?: ResolveSharedFixtureOptions): string;
23
+ export declare function checkSharedFixture(options?: ResolveSharedFixtureOptions): string;
24
+ export declare function prepareFixturePackages(options: PrepareFixturePackagesOptions): void;
@@ -0,0 +1,337 @@
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const require2 = createRequire(import.meta.url);
6
+ const sdkPackageRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
7
+ const DEFAULT_FIXTURE_ID = "treeseed-working-site";
8
+ function currentPackageRoot(packageRoot) {
9
+ return packageRoot ? resolve(packageRoot) : sdkPackageRoot;
10
+ }
11
+ function requiredFixturePaths(requiredPaths) {
12
+ return requiredPaths && requiredPaths.length > 0 ? requiredPaths : ["src/manifest.yaml", "src/content"];
13
+ }
14
+ function resolveRequestedFixtureId() {
15
+ return process.env.TREESEED_FIXTURE_ID?.trim() || DEFAULT_FIXTURE_ID;
16
+ }
17
+ function resolveFixturesRepoRoot(options = {}) {
18
+ if (process.env.TREESEED_FIXTURES_ROOT?.trim()) {
19
+ return resolve(process.env.TREESEED_FIXTURES_ROOT);
20
+ }
21
+ return resolve(currentPackageRoot(options.packageRoot), ".fixtures", "treeseed-fixtures");
22
+ }
23
+ function fixtureSatisfiesRequiredPaths(root, requiredPaths) {
24
+ return requiredFixturePaths(requiredPaths).every((relativePath) => existsSync(join(root, relativePath)));
25
+ }
26
+ function resolveSharedFixtureRoot(options = {}) {
27
+ const fixturesRepoRoot = resolveFixturesRepoRoot(options);
28
+ const sitesRoot = join(fixturesRepoRoot, "sites");
29
+ if (!existsSync(sitesRoot)) {
30
+ return null;
31
+ }
32
+ const requestedFixtureId = resolveRequestedFixtureId();
33
+ for (const entry of readdirSync(sitesRoot, { withFileTypes: true })) {
34
+ if (!entry.isDirectory()) {
35
+ continue;
36
+ }
37
+ const fixtureRoot = join(sitesRoot, entry.name);
38
+ const manifestPath = join(fixtureRoot, "fixture.manifest.json");
39
+ if (!existsSync(manifestPath)) {
40
+ continue;
41
+ }
42
+ try {
43
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
44
+ if (manifest.id !== requestedFixtureId) {
45
+ continue;
46
+ }
47
+ const root = resolve(fixtureRoot, manifest.root ?? ".");
48
+ if (fixtureSatisfiesRequiredPaths(root, options.requiredPaths)) {
49
+ return root;
50
+ }
51
+ } catch {
52
+ continue;
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ function requireSharedFixtureRoot(options = {}) {
58
+ const fixtureRoot = resolveSharedFixtureRoot(options);
59
+ if (!fixtureRoot) {
60
+ throw new Error(
61
+ `Unable to resolve shared fixture "${resolveRequestedFixtureId()}". Initialize the submodule with "git submodule update --init --recursive".`
62
+ );
63
+ }
64
+ return fixtureRoot;
65
+ }
66
+ function checkSharedFixture(options = {}) {
67
+ const fixtureRoot = requireSharedFixtureRoot(options);
68
+ const missing = requiredFixturePaths(options.requiredPaths).filter((relativePath) => !existsSync(join(fixtureRoot, relativePath)));
69
+ if (missing.length > 0) {
70
+ throw new Error(`Shared fixture is missing required paths at ${fixtureRoot}: ${missing.join(", ")}.`);
71
+ }
72
+ return fixtureRoot;
73
+ }
74
+ function resolveInstalledPackageRoot(packageName, entrySpecifier) {
75
+ const candidates = [
76
+ `${packageName}/package.json`,
77
+ entrySpecifier ?? packageName,
78
+ packageName
79
+ ];
80
+ for (const candidate of candidates) {
81
+ try {
82
+ const resolvedEntry = require2.resolve(candidate);
83
+ let currentDir = dirname(resolvedEntry);
84
+ while (currentDir !== dirname(currentDir)) {
85
+ if (existsSync(resolve(currentDir, "package.json"))) {
86
+ return currentDir;
87
+ }
88
+ currentDir = dirname(currentDir);
89
+ }
90
+ } catch {
91
+ continue;
92
+ }
93
+ }
94
+ return null;
95
+ }
96
+ function resolveWorkspacePackageRoot(packageRoot, workspaceDirName) {
97
+ if (!workspaceDirName) {
98
+ return null;
99
+ }
100
+ const candidate = resolve(packageRoot, "..", workspaceDirName);
101
+ return existsSync(resolve(candidate, "package.json")) ? candidate : null;
102
+ }
103
+ function ensureFixtureLinkedPackage(fixtureRoot, packageName, resolvedPackageRoot) {
104
+ const packageDir = resolve(fixtureRoot, "node_modules", ...packageName.split("/"));
105
+ mkdirSync(dirname(packageDir), { recursive: true });
106
+ rmSync(packageDir, { recursive: true, force: true });
107
+ symlinkSync(resolvedPackageRoot, packageDir, "dir");
108
+ }
109
+ function buildAgentContractsShimPackage(fixtureRoot) {
110
+ const packageDir = resolve(fixtureRoot, "node_modules", "@treeseed", "agent");
111
+ rmSync(packageDir, { recursive: true, force: true });
112
+ mkdirSync(resolve(packageDir, "contracts"), { recursive: true });
113
+ writeFileSync(
114
+ resolve(packageDir, "package.json"),
115
+ JSON.stringify(
116
+ {
117
+ name: "@treeseed/agent",
118
+ type: "module",
119
+ exports: {
120
+ "./runtime-types": {
121
+ types: "./runtime-types.d.js",
122
+ default: "./runtime-types.js"
123
+ },
124
+ "./contracts/messages": {
125
+ types: "./contracts/messages.d.js",
126
+ default: "./contracts/messages.js"
127
+ },
128
+ "./contracts/run": {
129
+ types: "./contracts/run.d.js",
130
+ default: "./contracts/run.js"
131
+ }
132
+ }
133
+ },
134
+ null,
135
+ 2
136
+ ),
137
+ "utf8"
138
+ );
139
+ writeFileSync(resolve(packageDir, "runtime-types.js"), "export {};\n", "utf8");
140
+ writeFileSync(
141
+ resolve(packageDir, "runtime-types.d.ts"),
142
+ [
143
+ "import type { AgentHandlerKind, AgentRunStatus } from '@treeseed/sdk/types/agents';",
144
+ "export interface AgentTriggerInvocation {",
145
+ " kind: 'startup' | 'schedule' | 'message' | 'manual' | 'follow';",
146
+ " source: string;",
147
+ " message?: { id?: string | number; type?: string; payloadJson?: string | null } | null;",
148
+ "}",
149
+ "export interface AgentExecutionResult {",
150
+ " status: AgentRunStatus;",
151
+ " summary: string;",
152
+ " stdout?: string;",
153
+ " stderr?: string;",
154
+ " errorCategory?: import('./contracts/run').AgentErrorCategory | null;",
155
+ " metadata?: Record<string, unknown>;",
156
+ "}",
157
+ "export interface AgentContext {",
158
+ " runId: string;",
159
+ " repoRoot: string;",
160
+ " agent: any;",
161
+ " sdk: any;",
162
+ " trigger: AgentTriggerInvocation;",
163
+ " execution: any;",
164
+ " mutations: any;",
165
+ " repository: any;",
166
+ " verification: any;",
167
+ " notifications: any;",
168
+ " research: any;",
169
+ "}",
170
+ "export interface AgentHandler<TInputs = unknown, TResult = unknown> {",
171
+ " kind: AgentHandlerKind;",
172
+ " resolveInputs(context: AgentContext): Promise<TInputs>;",
173
+ " execute(context: AgentContext, inputs: TInputs): Promise<TResult>;",
174
+ " emitOutputs(context: AgentContext, result: TResult): Promise<AgentExecutionResult>;",
175
+ "}",
176
+ ""
177
+ ].join("\n"),
178
+ "utf8"
179
+ );
180
+ writeFileSync(
181
+ resolve(packageDir, "contracts", "messages.js"),
182
+ [
183
+ "export const AGENT_MESSAGE_TYPES = [];",
184
+ "export function parseAgentMessagePayload(_type, payloadJson) {",
185
+ " return JSON.parse(payloadJson);",
186
+ "}",
187
+ ""
188
+ ].join("\n"),
189
+ "utf8"
190
+ );
191
+ writeFileSync(
192
+ resolve(packageDir, "contracts", "messages.d.ts"),
193
+ [
194
+ "export interface QuestionPriorityUpdatedMessage {",
195
+ " questionId: string;",
196
+ " reason: string;",
197
+ " plannerRunId: string;",
198
+ "}",
199
+ "export interface ObjectivePriorityUpdatedMessage {",
200
+ " objectiveId: string;",
201
+ " reason: string;",
202
+ " plannerRunId: string;",
203
+ "}",
204
+ "export interface ArchitectureUpdatedMessage {",
205
+ " objectiveId: string;",
206
+ " knowledgeId: string;",
207
+ " architectRunId: string;",
208
+ "}",
209
+ "export interface SubscriberNotifiedMessage {",
210
+ " email: string;",
211
+ " itemCount: number;",
212
+ " notifierRunId: string;",
213
+ "}",
214
+ "export interface ResearchStartedMessage {",
215
+ " questionId: string;",
216
+ " researcherRunId: string;",
217
+ "}",
218
+ "export interface ResearchCompletedMessage {",
219
+ " questionId: string;",
220
+ " knowledgeId: string | null;",
221
+ " researcherRunId: string;",
222
+ "}",
223
+ "export interface TaskCompleteMessage {",
224
+ " branchName: string | null;",
225
+ " changedTargets: string[];",
226
+ " engineerRunId: string;",
227
+ "}",
228
+ "export interface TaskWaitingMessage {",
229
+ " blockingReason: string;",
230
+ " engineerRunId: string;",
231
+ "}",
232
+ "export interface TaskFailedMessage {",
233
+ " failureSummary: string;",
234
+ " engineerRunId: string;",
235
+ "}",
236
+ "export interface TaskVerifiedMessage {",
237
+ " branchName: string | null;",
238
+ " reviewerRunId: string;",
239
+ "}",
240
+ "export interface ReviewFailedMessage {",
241
+ " failureSummary: string;",
242
+ " reviewerRunId: string;",
243
+ "}",
244
+ "export interface ReviewWaitingMessage {",
245
+ " blockingReason: string;",
246
+ " reviewerRunId: string;",
247
+ "}",
248
+ "export interface ReleaseStartedMessage {",
249
+ " taskRunId: string | null;",
250
+ " releaserRunId: string;",
251
+ "}",
252
+ "export interface ReleaseCompletedMessage {",
253
+ " releaseSummary: string;",
254
+ " releaserRunId: string;",
255
+ "}",
256
+ "export interface ReleaseFailedMessage {",
257
+ " failureSummary: string;",
258
+ " releaserRunId: string;",
259
+ "}",
260
+ "export interface AgentMessageContracts {",
261
+ " question_priority_updated: QuestionPriorityUpdatedMessage;",
262
+ " objective_priority_updated: ObjectivePriorityUpdatedMessage;",
263
+ " architecture_updated: ArchitectureUpdatedMessage;",
264
+ " subscriber_notified: SubscriberNotifiedMessage;",
265
+ " research_started: ResearchStartedMessage;",
266
+ " research_completed: ResearchCompletedMessage;",
267
+ " task_complete: TaskCompleteMessage;",
268
+ " task_waiting: TaskWaitingMessage;",
269
+ " task_failed: TaskFailedMessage;",
270
+ " task_verified: TaskVerifiedMessage;",
271
+ " review_failed: ReviewFailedMessage;",
272
+ " review_waiting: ReviewWaitingMessage;",
273
+ " release_started: ReleaseStartedMessage;",
274
+ " release_completed: ReleaseCompletedMessage;",
275
+ " release_failed: ReleaseFailedMessage;",
276
+ "}",
277
+ "export type AgentMessageType = keyof AgentMessageContracts;",
278
+ "export type AgentMessagePayload<TType extends AgentMessageType> = AgentMessageContracts[TType];",
279
+ "export declare const AGENT_MESSAGE_TYPES: readonly AgentMessageType[];",
280
+ "export declare function parseAgentMessagePayload<TType extends AgentMessageType>(_type: TType, payloadJson: string): AgentMessagePayload<TType>;",
281
+ ""
282
+ ].join("\n"),
283
+ "utf8"
284
+ );
285
+ writeFileSync(resolve(packageDir, "contracts", "run.js"), "export {};\n", "utf8");
286
+ writeFileSync(
287
+ resolve(packageDir, "contracts", "run.d.ts"),
288
+ [
289
+ "export type AgentErrorCategory = 'execution_error' | 'mutation_error' | 'verification_error' | 'notification_error' | 'research_error' | 'sdk_error' | 'unknown';",
290
+ ""
291
+ ].join("\n"),
292
+ "utf8"
293
+ );
294
+ }
295
+ function prepareFixturePackages(options) {
296
+ const packageRoot = currentPackageRoot(options.packageRoot);
297
+ for (const declaration of options.declarations) {
298
+ let satisfied = false;
299
+ for (const mode of declaration.modes) {
300
+ if (mode === "workspace-link") {
301
+ const workspaceRoot = resolveWorkspacePackageRoot(packageRoot, declaration.workspaceDirName);
302
+ if (!workspaceRoot) {
303
+ continue;
304
+ }
305
+ ensureFixtureLinkedPackage(options.fixtureRoot, declaration.packageName, workspaceRoot);
306
+ satisfied = true;
307
+ break;
308
+ }
309
+ if (mode === "installed-link") {
310
+ const installedRoot = resolveInstalledPackageRoot(declaration.packageName, declaration.entrySpecifier);
311
+ if (!installedRoot) {
312
+ continue;
313
+ }
314
+ ensureFixtureLinkedPackage(options.fixtureRoot, declaration.packageName, installedRoot);
315
+ satisfied = true;
316
+ break;
317
+ }
318
+ if (mode === "contracts-only" && declaration.contractsShim === "agent") {
319
+ buildAgentContractsShimPackage(options.fixtureRoot);
320
+ satisfied = true;
321
+ break;
322
+ }
323
+ }
324
+ if (!satisfied) {
325
+ throw new Error(`Unable to prepare fixture package "${declaration.packageName}" using modes ${declaration.modes.join(", ")}.`);
326
+ }
327
+ }
328
+ }
329
+ export {
330
+ DEFAULT_FIXTURE_ID,
331
+ checkSharedFixture,
332
+ prepareFixturePackages,
333
+ requireSharedFixtureRoot,
334
+ resolveFixturesRepoRoot,
335
+ resolveRequestedFixtureId,
336
+ resolveSharedFixtureRoot
337
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -87,6 +87,10 @@
87
87
  "types": "./dist/verification.d.ts",
88
88
  "default": "./dist/verification.js"
89
89
  },
90
+ "./fixture-support": {
91
+ "types": "./dist/fixture-support.d.ts",
92
+ "default": "./dist/fixture-support.js"
93
+ },
90
94
  "./scripts/verify-driver": "./scripts/verify-driver.mjs",
91
95
  "./workflow-support": {
92
96
  "types": "./dist/workflow-support.d.ts",