@stigmer/cli 3.19.0 → 3.20.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.
- package/commands/apply.d.ts.map +1 -1
- package/commands/apply.js +42 -100
- package/commands/apply.js.map +1 -1
- package/commands/validate.js +3 -4
- package/commands/validate.js.map +1 -1
- package/local/bootstrap.d.ts +1 -2
- package/local/bootstrap.d.ts.map +1 -1
- package/local/bootstrap.js +13 -41
- package/local/bootstrap.js.map +1 -1
- package/local/daemon/launch.d.ts +2 -2
- package/local/daemon/launch.d.ts.map +1 -1
- package/local/daemon/launch.js +2 -2
- package/local/daemon/launch.js.map +1 -1
- package/local/plugins/defaults.js +2 -2
- package/local/plugins/defaults.js.map +1 -1
- package/package.json +7 -7
- package/registry/index.d.ts +2 -2
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +2 -2
- package/registry/index.js.map +1 -1
- package/registry/metadata.d.ts +9 -0
- package/registry/metadata.d.ts.map +1 -1
- package/registry/metadata.js +14 -5
- package/registry/metadata.js.map +1 -1
- package/registry/registry.d.ts +9 -0
- package/registry/registry.d.ts.map +1 -1
- package/registry/registry.js +16 -1
- package/registry/registry.js.map +1 -1
- package/registry/verb-support.d.ts.map +1 -1
- package/registry/verb-support.js +0 -13
- package/registry/verb-support.js.map +1 -1
- package/resources/apply/apply.d.ts +11 -12
- package/resources/apply/apply.d.ts.map +1 -1
- package/resources/apply/apply.js +12 -21
- package/resources/apply/apply.js.map +1 -1
- package/resources/apply/handlers.js +2 -2
- package/resources/apply/handlers.js.map +1 -1
- package/resources/delete.d.ts.map +1 -1
- package/resources/delete.js +0 -1
- package/resources/delete.js.map +1 -1
- package/resources/get-bindings.d.ts.map +1 -1
- package/resources/get-bindings.js +0 -5
- package/resources/get-bindings.js.map +1 -1
- package/resources/list.d.ts.map +1 -1
- package/resources/list.js +0 -1
- package/resources/list.js.map +1 -1
- package/resources/validate.d.ts.map +1 -1
- package/resources/validate.js +0 -2
- package/resources/validate.js.map +1 -1
- package/src/commands/apply.ts +35 -150
- package/src/commands/validate.ts +2 -4
- package/src/local/bootstrap.test.ts +15 -59
- package/src/local/bootstrap.ts +12 -53
- package/src/local/daemon/launch.ts +4 -4
- package/src/local/plugins/defaults.ts +2 -2
- package/src/registry/index.ts +7 -1
- package/src/registry/metadata.ts +15 -5
- package/src/registry/registry.test.ts +1 -7
- package/src/registry/registry.ts +17 -1
- package/src/registry/verb-support.ts +0 -13
- package/src/resources/apply/apply.integration.test.ts +15 -3
- package/src/resources/apply/apply.ts +15 -26
- package/src/resources/apply/handlers.ts +2 -2
- package/src/resources/delete.ts +0 -1
- package/src/resources/get-bindings.ts +0 -5
- package/src/resources/list.ts +0 -1
- package/src/resources/validate.test.ts +0 -1
- package/src/resources/validate.ts +0 -2
- package/local/seedpack-retire.d.ts +0 -63
- package/local/seedpack-retire.d.ts.map +0 -1
- package/local/seedpack-retire.js +0 -361
- package/local/seedpack-retire.js.map +0 -1
- package/resources/apply/declarative.d.ts +0 -106
- package/resources/apply/declarative.d.ts.map +0 -1
- package/resources/apply/declarative.js +0 -397
- package/resources/apply/declarative.js.map +0 -1
- package/resources/apply/synth/project-track.d.ts +0 -21
- package/resources/apply/synth/project-track.d.ts.map +0 -1
- package/resources/apply/synth/project-track.js +0 -102
- package/resources/apply/synth/project-track.js.map +0 -1
- package/resources/apply/synth/reader.d.ts +0 -18
- package/resources/apply/synth/reader.d.ts.map +0 -1
- package/resources/apply/synth/reader.js +0 -55
- package/resources/apply/synth/reader.js.map +0 -1
- package/resources/apply/synth/runtime.d.ts +0 -8
- package/resources/apply/synth/runtime.d.ts.map +0 -1
- package/resources/apply/synth/runtime.js +0 -32
- package/resources/apply/synth/runtime.js.map +0 -1
- package/resources/apply/synth/synthesize.d.ts +0 -43
- package/resources/apply/synth/synthesize.d.ts.map +0 -1
- package/resources/apply/synth/synthesize.js +0 -115
- package/resources/apply/synth/synthesize.js.map +0 -1
- package/src/local/seedpack-retire.test.ts +0 -499
- package/src/local/seedpack-retire.ts +0 -474
- package/src/resources/apply/declarative.integration.test.ts +0 -204
- package/src/resources/apply/declarative.test.ts +0 -132
- package/src/resources/apply/declarative.ts +0 -491
- package/src/resources/apply/synth/capstone.integration.test.ts +0 -134
- package/src/resources/apply/synth/project-track.integration.test.ts +0 -194
- package/src/resources/apply/synth/project-track.ts +0 -126
- package/src/resources/apply/synth/reader.test.ts +0 -90
- package/src/resources/apply/synth/reader.ts +0 -68
- package/src/resources/apply/synth/runtime.test.ts +0 -29
- package/src/resources/apply/synth/runtime.ts +0 -39
- package/src/resources/apply/synth/synthesize.test.ts +0 -109
- package/src/resources/apply/synth/synthesize.ts +0 -161
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
// Capstone end-to-end test — the test the Go feature could never have.
|
|
2
|
-
//
|
|
3
|
-
// A real TypeScript entry point that imports `@stigmer/sdk/synth` is executed by
|
|
4
|
-
// the REAL synthesize() (which spawns `npx tsx`), writing genuine `.pb` files;
|
|
5
|
-
// those are then read and reconciled against an in-process backend. This is the
|
|
6
|
-
// single test that exercises BOTH halves of the feature across the subprocess
|
|
7
|
-
// boundary — producer (SDK) and consumer (CLI) — proving they actually connect.
|
|
8
|
-
//
|
|
9
|
-
// The fixture is created inside this package so the spawned `tsx` resolves
|
|
10
|
-
// `@stigmer/sdk/synth` through the workspace node_modules via upward traversal
|
|
11
|
-
// (a tmp dir under the OS temp root could not resolve the workspace packages).
|
|
12
|
-
// Flakiness mitigations: a generous timeout (first `tsx` run transpiles the SDK)
|
|
13
|
-
// and `prepare` disabled (the fixture borrows the workspace node_modules rather
|
|
14
|
-
// than installing its own).
|
|
15
|
-
|
|
16
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { createServer as createHttp2Server, type Http2Server, type ServerHttp2Session } from "node:http2";
|
|
18
|
-
import type { AddressInfo } from "node:net";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
import { create } from "@bufbuild/protobuf";
|
|
21
|
-
import { type ConnectRouter, createClient } from "@connectrpc/connect";
|
|
22
|
-
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
23
|
-
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
24
|
-
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
25
|
-
import { SkillCommandController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/command_pb";
|
|
26
|
-
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
27
|
-
import type { Project } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
|
|
28
|
-
import { ProjectCommandController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/command_pb";
|
|
29
|
-
import type { Stigmer } from "@stigmer/sdk";
|
|
30
|
-
import { createNodeClient, createNodeTransport, normalizeEndpoint } from "@stigmer/sdk/node";
|
|
31
|
-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
32
|
-
import { detectTrack } from "../declarative.js";
|
|
33
|
-
import type { ControllerFn } from "../handlers.js";
|
|
34
|
-
import { applyProjectTrack } from "./project-track.js";
|
|
35
|
-
|
|
36
|
-
let backend: Http2Server;
|
|
37
|
-
let stigmer: Stigmer;
|
|
38
|
-
let controllerFn: ControllerFn;
|
|
39
|
-
const openSessions = new Set<ServerHttp2Session>();
|
|
40
|
-
let appliedProject: Project | undefined;
|
|
41
|
-
|
|
42
|
-
// A real entry point exercising the public producer API end to end.
|
|
43
|
-
const ENTRY_SOURCE = `import { defineProject } from "@stigmer/sdk/synth";
|
|
44
|
-
|
|
45
|
-
const project = defineProject((ctx) => {
|
|
46
|
-
ctx.skill.fromDir("my-skill");
|
|
47
|
-
ctx.agent({ name: "Reviewer", slug: "reviewer", org: "acme", instructions: "review code" });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
await project.synth();
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
function makeFixture(dir: string): void {
|
|
54
|
-
writeFileSync(
|
|
55
|
-
join(dir, "stigmer.yaml"),
|
|
56
|
-
["kind: Project", "metadata:", " name: Demo", " slug: demo", "spec:", " entry_point: index.ts", ""].join("\n"),
|
|
57
|
-
);
|
|
58
|
-
writeFileSync(join(dir, "index.ts"), ENTRY_SOURCE);
|
|
59
|
-
mkdirSync(join(dir, "my-skill"));
|
|
60
|
-
writeFileSync(join(dir, "my-skill", "SKILL.md"), ["---", "name: my-skill", "---", "# My Skill"].join("\n"));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
beforeAll(async () => {
|
|
64
|
-
const routes = (router: ConnectRouter) => {
|
|
65
|
-
router.service(AgentCommandController, { apply: (req) => req });
|
|
66
|
-
router.service(SkillCommandController, {
|
|
67
|
-
push: (req) =>
|
|
68
|
-
create(SkillSchema, {
|
|
69
|
-
metadata: { id: "skl_1", slug: "my-skill", version: { id: "v1" } },
|
|
70
|
-
spec: { tag: req.tag },
|
|
71
|
-
status: { versionHash: "abc123" },
|
|
72
|
-
}),
|
|
73
|
-
});
|
|
74
|
-
router.service(ProjectCommandController, {
|
|
75
|
-
apply: (req) => {
|
|
76
|
-
appliedProject = req;
|
|
77
|
-
return req;
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
83
|
-
backend.on("session", (session) => {
|
|
84
|
-
openSessions.add(session);
|
|
85
|
-
session.on("close", () => openSessions.delete(session));
|
|
86
|
-
});
|
|
87
|
-
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
88
|
-
const port = (backend.address() as AddressInfo).port;
|
|
89
|
-
const baseUrl = normalizeEndpoint(`127.0.0.1:${port}`);
|
|
90
|
-
stigmer = createNodeClient({ baseUrl });
|
|
91
|
-
const transport = createNodeTransport({ baseUrl });
|
|
92
|
-
controllerFn = (service) => createClient(service, transport);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
afterAll(async () => {
|
|
96
|
-
for (const session of openSessions) session.destroy();
|
|
97
|
-
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe("capstone: real SDK synthesis → CLI reconcile", () => {
|
|
101
|
-
it(
|
|
102
|
-
"runs a real @stigmer/sdk/synth entry point through tsx and reconciles it",
|
|
103
|
-
async () => {
|
|
104
|
-
// Created inside the package so the spawned tsx resolves @stigmer/sdk.
|
|
105
|
-
const dir = mkdtempSync(join(process.cwd(), "capstone-"));
|
|
106
|
-
try {
|
|
107
|
-
makeFixture(dir);
|
|
108
|
-
const detect = detectTrack(dir);
|
|
109
|
-
expect(detect.track).toBe("project");
|
|
110
|
-
|
|
111
|
-
const result = await applyProjectTrack(detect, {
|
|
112
|
-
controller: controllerFn,
|
|
113
|
-
stigmer,
|
|
114
|
-
org: "acme",
|
|
115
|
-
info: () => {},
|
|
116
|
-
warn: () => {},
|
|
117
|
-
// Real spawn (default); only skip the node_modules readiness check —
|
|
118
|
-
// the fixture borrows the workspace install via upward traversal.
|
|
119
|
-
synthesizeDeps: { prepare: () => {} },
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
expect(result.status).toBe("success");
|
|
123
|
-
const members = appliedProject?.spec?.members ?? [];
|
|
124
|
-
const skill = members.find((m) => m.kind === ApiResourceKind.skill);
|
|
125
|
-
const agent = members.find((m) => m.kind === ApiResourceKind.agent);
|
|
126
|
-
expect(skill).toMatchObject({ org: "acme", slug: "my-skill" });
|
|
127
|
-
expect(agent).toMatchObject({ org: "acme", slug: "reviewer" });
|
|
128
|
-
} finally {
|
|
129
|
-
rmSync(dir, { recursive: true, force: true });
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
120_000,
|
|
133
|
-
);
|
|
134
|
-
});
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// In-process integration test for the synthesis (project) track.
|
|
2
|
-
//
|
|
3
|
-
// Stands up a Connect backend (agent + skill + project controllers) and drives
|
|
4
|
-
// applyProjectTrack end to end against a temp project directory whose
|
|
5
|
-
// entry_point is "synthesized" by an injected spawn that writes `.pb` fixtures
|
|
6
|
-
// into STIGMER_OUT_DIR (the same contract the real SDK writer produces). This
|
|
7
|
-
// proves the consumer half — synthesize → read → shared reconciler — without a
|
|
8
|
-
// real subprocess; the capstone test exercises the real `tsx` run.
|
|
9
|
-
|
|
10
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { createServer as createHttp2Server, type Http2Server, type ServerHttp2Session } from "node:http2";
|
|
12
|
-
import type { AddressInfo } from "node:net";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { create, toBinary } from "@bufbuild/protobuf";
|
|
16
|
-
import { type ConnectRouter, createClient } from "@connectrpc/connect";
|
|
17
|
-
import { connectNodeAdapter } from "@connectrpc/connect-node";
|
|
18
|
-
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
19
|
-
import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/command_pb";
|
|
20
|
-
import { SkillSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/api_pb";
|
|
21
|
-
import { SkillCommandController } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/command_pb";
|
|
22
|
-
import { LocalDirSchema, SkillSynthSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/synth_pb";
|
|
23
|
-
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
24
|
-
import type { Project } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/api_pb";
|
|
25
|
-
import { ProjectCommandController } from "@stigmer/protos/ai/stigmer/tenancy/project/v1/command_pb";
|
|
26
|
-
import type { Stigmer } from "@stigmer/sdk";
|
|
27
|
-
import { createNodeClient, createNodeTransport, normalizeEndpoint } from "@stigmer/sdk/node";
|
|
28
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
29
|
-
import { detectTrack } from "../declarative.js";
|
|
30
|
-
import type { ControllerFn } from "../handlers.js";
|
|
31
|
-
import type { SpawnFn } from "./synthesize.js";
|
|
32
|
-
import { applyProjectTrack, previewProjectTrack } from "./project-track.js";
|
|
33
|
-
|
|
34
|
-
let backend: Http2Server;
|
|
35
|
-
let stigmer: Stigmer;
|
|
36
|
-
let controllerFn: ControllerFn;
|
|
37
|
-
const openSessions = new Set<ServerHttp2Session>();
|
|
38
|
-
|
|
39
|
-
let appliedProject: Project | undefined;
|
|
40
|
-
|
|
41
|
-
beforeEach(() => {
|
|
42
|
-
appliedProject = undefined;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// A stigmer.yaml with entry_point makes detectTrack pick the project track; the
|
|
46
|
-
// my-skill dir is the target of the synthesized local SkillSynth.
|
|
47
|
-
function makeProject(dir: string): void {
|
|
48
|
-
writeFileSync(
|
|
49
|
-
join(dir, "stigmer.yaml"),
|
|
50
|
-
[
|
|
51
|
-
"kind: Project",
|
|
52
|
-
"metadata:",
|
|
53
|
-
" name: Demo",
|
|
54
|
-
" slug: demo",
|
|
55
|
-
"spec:",
|
|
56
|
-
" entry_point: index.ts",
|
|
57
|
-
"",
|
|
58
|
-
].join("\n"),
|
|
59
|
-
);
|
|
60
|
-
writeFileSync(join(dir, "index.ts"), "// synthesized by the injected spawn in this test\n");
|
|
61
|
-
mkdirSync(join(dir, "my-skill"));
|
|
62
|
-
writeFileSync(join(dir, "my-skill", "SKILL.md"), ["---", "name: my-skill", "---", "# My Skill"].join("\n"));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Injected spawn standing in for `npx tsx index.ts`: writes the .pb files the
|
|
66
|
-
// SDK writer would produce into STIGMER_OUT_DIR.
|
|
67
|
-
const fakeSpawn: SpawnFn = async (_command, _args, options) => {
|
|
68
|
-
const outDir = options.env.STIGMER_OUT_DIR ?? "";
|
|
69
|
-
writeFileSync(
|
|
70
|
-
join(outDir, "agent-0.pb"),
|
|
71
|
-
toBinary(
|
|
72
|
-
AgentSchema,
|
|
73
|
-
create(AgentSchema, {
|
|
74
|
-
apiVersion: "agentic.stigmer.ai/v1",
|
|
75
|
-
kind: "Agent",
|
|
76
|
-
metadata: { name: "Reviewer", slug: "reviewer" },
|
|
77
|
-
spec: { description: "r" },
|
|
78
|
-
}),
|
|
79
|
-
),
|
|
80
|
-
);
|
|
81
|
-
writeFileSync(
|
|
82
|
-
join(outDir, "skill-0.pb"),
|
|
83
|
-
toBinary(
|
|
84
|
-
SkillSynthSchema,
|
|
85
|
-
create(SkillSynthSchema, {
|
|
86
|
-
source: { case: "local", value: create(LocalDirSchema, { path: "my-skill" }) },
|
|
87
|
-
tag: "latest",
|
|
88
|
-
}),
|
|
89
|
-
),
|
|
90
|
-
);
|
|
91
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
beforeAll(async () => {
|
|
95
|
-
const routes = (router: ConnectRouter) => {
|
|
96
|
-
router.service(AgentCommandController, { apply: (req) => req });
|
|
97
|
-
router.service(SkillCommandController, {
|
|
98
|
-
push: (req) =>
|
|
99
|
-
create(SkillSchema, {
|
|
100
|
-
metadata: { id: "skl_1", slug: "my-skill", version: { id: "v1" } },
|
|
101
|
-
spec: { tag: req.tag },
|
|
102
|
-
status: { versionHash: "abc123" },
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
router.service(ProjectCommandController, {
|
|
106
|
-
apply: (req) => {
|
|
107
|
-
appliedProject = req;
|
|
108
|
-
return req;
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
backend = createHttp2Server(connectNodeAdapter({ routes }));
|
|
114
|
-
backend.on("session", (session) => {
|
|
115
|
-
openSessions.add(session);
|
|
116
|
-
session.on("close", () => openSessions.delete(session));
|
|
117
|
-
});
|
|
118
|
-
await new Promise<void>((resolve) => backend.listen(0, "127.0.0.1", resolve));
|
|
119
|
-
const port = (backend.address() as AddressInfo).port;
|
|
120
|
-
const baseUrl = normalizeEndpoint(`127.0.0.1:${port}`);
|
|
121
|
-
stigmer = createNodeClient({ baseUrl });
|
|
122
|
-
const transport = createNodeTransport({ baseUrl });
|
|
123
|
-
controllerFn = (service) => createClient(service, transport);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
afterAll(async () => {
|
|
127
|
-
for (const session of openSessions) session.destroy();
|
|
128
|
-
await new Promise<void>((resolve) => backend.close(() => resolve()));
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe("applyProjectTrack", () => {
|
|
132
|
-
it("synthesizes, reads, and reconciles project membership", async () => {
|
|
133
|
-
const dir = mkdtempSync(join(tmpdir(), "synth-it-"));
|
|
134
|
-
try {
|
|
135
|
-
makeProject(dir);
|
|
136
|
-
const detect = detectTrack(dir);
|
|
137
|
-
expect(detect.track).toBe("project");
|
|
138
|
-
|
|
139
|
-
const result = await applyProjectTrack(detect, {
|
|
140
|
-
controller: controllerFn,
|
|
141
|
-
stigmer,
|
|
142
|
-
org: "acme",
|
|
143
|
-
info: () => {},
|
|
144
|
-
warn: () => {},
|
|
145
|
-
synthesizeDeps: { spawn: fakeSpawn, prepare: () => {} },
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
expect(result.status).toBe("success");
|
|
149
|
-
|
|
150
|
-
expect(appliedProject).toBeDefined();
|
|
151
|
-
const members = appliedProject?.spec?.members ?? [];
|
|
152
|
-
expect(members).toHaveLength(2);
|
|
153
|
-
|
|
154
|
-
const skill = members.find((m) => m.kind === ApiResourceKind.skill);
|
|
155
|
-
const agent = members.find((m) => m.kind === ApiResourceKind.agent);
|
|
156
|
-
expect(skill).toMatchObject({ org: "acme", slug: "my-skill" });
|
|
157
|
-
expect(agent).toMatchObject({ org: "acme", slug: "reviewer" });
|
|
158
|
-
|
|
159
|
-
expect(appliedProject?.metadata?.org).toBe("acme");
|
|
160
|
-
} finally {
|
|
161
|
-
rmSync(dir, { recursive: true, force: true });
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("dry-run previews the configuration without synthesizing or applying", () => {
|
|
166
|
-
const dir = mkdtempSync(join(tmpdir(), "synth-dry-"));
|
|
167
|
-
try {
|
|
168
|
-
makeProject(dir);
|
|
169
|
-
const detect = detectTrack(dir);
|
|
170
|
-
|
|
171
|
-
const result = previewProjectTrack(detect);
|
|
172
|
-
|
|
173
|
-
expect(result.status).toBe("success");
|
|
174
|
-
expect(appliedProject).toBeUndefined();
|
|
175
|
-
} finally {
|
|
176
|
-
rmSync(dir, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("dry-run warns when the entry point file is missing", () => {
|
|
181
|
-
const dir = mkdtempSync(join(tmpdir(), "synth-dry-missing-"));
|
|
182
|
-
try {
|
|
183
|
-
makeProject(dir);
|
|
184
|
-
rmSync(join(dir, "index.ts"));
|
|
185
|
-
const detect = detectTrack(dir);
|
|
186
|
-
|
|
187
|
-
const result = previewProjectTrack(detect);
|
|
188
|
-
const text = JSON.stringify(result);
|
|
189
|
-
expect(text).toContain("Entry point file not found");
|
|
190
|
-
} finally {
|
|
191
|
-
rmSync(dir, { recursive: true, force: true });
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// Synthesis (project) track: the consumer adapter for `apply entry_point`.
|
|
2
|
-
//
|
|
3
|
-
// Its job mirrors the declarative adapter's — turn a source into resources +
|
|
4
|
-
// skill sources — but the source here is the SDK program rather than YAML
|
|
5
|
-
// files: infer the runtime, run synthesis (subprocess → `.pb`), decode the
|
|
6
|
-
// output, normalize it into the shared shapes, then hand off to the ONE shared
|
|
7
|
-
// reconciler (DD-009 §6). There is no parallel project-apply path.
|
|
8
|
-
//
|
|
9
|
-
// Resources are emitted in dependency-apply order (mcp_server → agent →
|
|
10
|
-
// workflow), matching the declarative track's APPLY_ORDER so parents land before
|
|
11
|
-
// dependents — a deliberate, more-correct ordering than the Go synth path
|
|
12
|
-
// (which applied agents first).
|
|
13
|
-
|
|
14
|
-
import { existsSync } from "node:fs";
|
|
15
|
-
import { isAbsolute, join } from "node:path";
|
|
16
|
-
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
17
|
-
import { UsageError } from "../../../errors/index.js";
|
|
18
|
-
import { CommandResult } from "../../../output/index.js";
|
|
19
|
-
import {
|
|
20
|
-
type DeclarativeDeps,
|
|
21
|
-
type DetectResult,
|
|
22
|
-
type ReconcileResource,
|
|
23
|
-
reconcileProjectMembers,
|
|
24
|
-
type SkillSource,
|
|
25
|
-
} from "../declarative.js";
|
|
26
|
-
import { APPLY_HANDLERS, type ApplyHandler } from "../handlers.js";
|
|
27
|
-
import type { SynthesisResult } from "./reader.js";
|
|
28
|
-
import { readSynthesisOutput } from "./reader.js";
|
|
29
|
-
import { inferRuntime } from "./runtime.js";
|
|
30
|
-
import type { SynthesizeDeps } from "./synthesize.js";
|
|
31
|
-
import { synthesize } from "./synthesize.js";
|
|
32
|
-
|
|
33
|
-
/** Deps for the project track: the reconciler deps plus injectable synthesis. */
|
|
34
|
-
export interface ProjectTrackDeps extends DeclarativeDeps {
|
|
35
|
-
/** Injectable synthesis collaborators (real subprocess by default). */
|
|
36
|
-
readonly synthesizeDeps?: SynthesizeDeps;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Apply an SDK project: synthesize the entry point, read the `.pb` output, and
|
|
41
|
-
* reconcile membership through the shared reconciler. Returns the summary
|
|
42
|
-
* CommandResult for the caller to render.
|
|
43
|
-
*/
|
|
44
|
-
export async function applyProjectTrack(detect: DetectResult, deps: ProjectTrackDeps): Promise<CommandResult> {
|
|
45
|
-
const { project, configDir } = detect;
|
|
46
|
-
if (project === undefined || configDir === undefined) {
|
|
47
|
-
throw new UsageError("project synthesis requires a stigmer.yaml project with an entry_point");
|
|
48
|
-
}
|
|
49
|
-
const entryPoint = project.spec?.entryPoint ?? "";
|
|
50
|
-
if (entryPoint === "") throw new UsageError("project has no entry_point");
|
|
51
|
-
|
|
52
|
-
const runtime = inferRuntime(entryPoint);
|
|
53
|
-
deps.info(`SDK mode: ${entryPoint} (runtime: ${runtime})`);
|
|
54
|
-
|
|
55
|
-
const { outputDir } = await synthesize(
|
|
56
|
-
{ projectDir: configDir, entryPoint, runtime, orgId: deps.org },
|
|
57
|
-
deps.synthesizeDeps,
|
|
58
|
-
);
|
|
59
|
-
const synth = readSynthesisOutput(outputDir);
|
|
60
|
-
deps.info(
|
|
61
|
-
`Synthesis complete: ${synth.agents.length} agent(s), ${synth.workflows.length} workflow(s), ` +
|
|
62
|
-
`${synth.mcpServers.length} MCP server(s), ${synth.skillSynths.length} skill(s)`,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
const resources = toReconcileResources(synth);
|
|
66
|
-
const skillSources = toSkillSources(synth, configDir);
|
|
67
|
-
|
|
68
|
-
return reconcileProjectMembers(project, resources, skillSources, deps);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Dry-run preview: validate the project configuration statically (no synthesis,
|
|
73
|
-
* no backend), warning if the entry point file is missing. Port of Go's
|
|
74
|
-
* executeSDKDryRun.
|
|
75
|
-
*/
|
|
76
|
-
export function previewProjectTrack(detect: DetectResult): CommandResult {
|
|
77
|
-
const { project, configDir } = detect;
|
|
78
|
-
if (project === undefined || configDir === undefined) {
|
|
79
|
-
throw new UsageError("project synthesis requires a stigmer.yaml project with an entry_point");
|
|
80
|
-
}
|
|
81
|
-
const entryPoint = project.spec?.entryPoint ?? "";
|
|
82
|
-
const runtime = inferRuntime(entryPoint);
|
|
83
|
-
|
|
84
|
-
const result = CommandResult.success("Dry run: SDK project configuration is valid");
|
|
85
|
-
const section = result.addSection("Project");
|
|
86
|
-
if (project.metadata?.name) section.field("Name", project.metadata.name);
|
|
87
|
-
section.field("Entry Point", entryPoint);
|
|
88
|
-
section.field("Runtime", runtime);
|
|
89
|
-
|
|
90
|
-
if (!existsSync(join(configDir, entryPoint))) {
|
|
91
|
-
result.addSection("Warning").item(`Entry point file not found: ${entryPoint}`);
|
|
92
|
-
}
|
|
93
|
-
result.hint("Remove --dry-run to execute synthesis and apply resources");
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Normalize decoded protos into ReconcileResources in dependency-apply order.
|
|
98
|
-
function toReconcileResources(synth: SynthesisResult): ReconcileResource[] {
|
|
99
|
-
const resources: ReconcileResource[] = [];
|
|
100
|
-
for (const message of synth.mcpServers) resources.push({ handler: handlerFor(ApiResourceKind.mcp_server), message });
|
|
101
|
-
for (const message of synth.agents) resources.push({ handler: handlerFor(ApiResourceKind.agent), message });
|
|
102
|
-
for (const message of synth.workflows) resources.push({ handler: handlerFor(ApiResourceKind.workflow), message });
|
|
103
|
-
return resources;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Normalize SkillSynths into SkillSources; local paths resolve relative to the
|
|
107
|
-
// project dir (matching Go's pushSkillSynth).
|
|
108
|
-
function toSkillSources(synth: SynthesisResult, configDir: string): SkillSource[] {
|
|
109
|
-
return synth.skillSynths.map((skill) => {
|
|
110
|
-
if (skill.source.case === "local") {
|
|
111
|
-
const path = skill.source.value.path;
|
|
112
|
-
return { kind: "local", dir: isAbsolute(path) ? path : join(configDir, path), tag: skill.tag };
|
|
113
|
-
}
|
|
114
|
-
if (skill.source.case === "git") {
|
|
115
|
-
const git = skill.source.value;
|
|
116
|
-
return { kind: "git", url: git.url, ref: git.ref, subdir: git.subdir, tag: skill.tag };
|
|
117
|
-
}
|
|
118
|
-
throw new UsageError("skill synthesis has no source configured (neither local nor git)");
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function handlerFor(kind: ApiResourceKind): ApplyHandler {
|
|
123
|
-
const handler = APPLY_HANDLERS.get(kind);
|
|
124
|
-
if (handler === undefined) throw new UsageError(`apply not implemented for ${ApiResourceKind[kind]}`);
|
|
125
|
-
return handler;
|
|
126
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// Reader fixtures: encode protos with toBinary into a tmp dir, then assert the
|
|
2
|
-
// reader globs/sorts/decodes them, tolerates a missing dir and a missing
|
|
3
|
-
// dependencies.json, and errors when no resources are present.
|
|
4
|
-
|
|
5
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { create, toBinary } from "@bufbuild/protobuf";
|
|
9
|
-
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
10
|
-
import { McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
11
|
-
import { LocalDirSchema, SkillSynthSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/synth_pb";
|
|
12
|
-
import { WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
13
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
-
import { readSynthesisOutput } from "./reader.js";
|
|
15
|
-
|
|
16
|
-
let dir: string;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
dir = mkdtempSync(join(tmpdir(), "synth-reader-"));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
rmSync(dir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
function writeAgent(name: string, slug: string, file: string): void {
|
|
27
|
-
const agent = create(AgentSchema, { apiVersion: "agentic.stigmer.ai/v1", kind: "Agent", metadata: { name, slug } });
|
|
28
|
-
writeFileSync(join(dir, file), toBinary(AgentSchema, agent));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe("readSynthesisOutput", () => {
|
|
32
|
-
it("decodes every kind and groups by kind", () => {
|
|
33
|
-
writeAgent("Support", "support", "agent-0.pb");
|
|
34
|
-
writeFileSync(
|
|
35
|
-
join(dir, "workflow-0.pb"),
|
|
36
|
-
toBinary(WorkflowSchema, create(WorkflowSchema, { metadata: { name: "Onboard", slug: "onboard" } })),
|
|
37
|
-
);
|
|
38
|
-
writeFileSync(
|
|
39
|
-
join(dir, "mcpserver-0.pb"),
|
|
40
|
-
toBinary(McpServerSchema, create(McpServerSchema, { metadata: { name: "FS", slug: "fs" } })),
|
|
41
|
-
);
|
|
42
|
-
writeFileSync(
|
|
43
|
-
join(dir, "skill-0.pb"),
|
|
44
|
-
toBinary(
|
|
45
|
-
SkillSynthSchema,
|
|
46
|
-
create(SkillSynthSchema, { source: { case: "local", value: create(LocalDirSchema, { path: "./calc" }) } }),
|
|
47
|
-
),
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
const result = readSynthesisOutput(dir);
|
|
51
|
-
|
|
52
|
-
expect(result.agents).toHaveLength(1);
|
|
53
|
-
expect(result.agents[0].metadata?.slug).toBe("support");
|
|
54
|
-
expect(result.workflows[0].metadata?.slug).toBe("onboard");
|
|
55
|
-
expect(result.mcpServers[0].metadata?.slug).toBe("fs");
|
|
56
|
-
expect(result.skillSynths[0].source.case).toBe("local");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("globs and lexically sorts files of the same kind", () => {
|
|
60
|
-
writeAgent("A0", "a0", "agent-0.pb");
|
|
61
|
-
writeAgent("A1", "a1", "agent-1.pb");
|
|
62
|
-
writeAgent("A2", "a2", "agent-2.pb");
|
|
63
|
-
|
|
64
|
-
const result = readSynthesisOutput(dir);
|
|
65
|
-
|
|
66
|
-
expect(result.agents.map((a) => a.metadata?.slug)).toEqual(["a0", "a1", "a2"]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("tolerates a directory with no dependencies.json", () => {
|
|
70
|
-
writeAgent("Solo", "solo", "agent-0.pb");
|
|
71
|
-
// No dependencies.json written — must not throw.
|
|
72
|
-
expect(() => readSynthesisOutput(dir)).not.toThrow();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("ignores unrelated files", () => {
|
|
76
|
-
writeAgent("Solo", "solo", "agent-0.pb");
|
|
77
|
-
writeFileSync(join(dir, "dependencies.json"), "{}");
|
|
78
|
-
writeFileSync(join(dir, "notes.txt"), "hi");
|
|
79
|
-
const result = readSynthesisOutput(dir);
|
|
80
|
-
expect(result.agents).toHaveLength(1);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("throws when the output has no resources", () => {
|
|
84
|
-
expect(() => readSynthesisOutput(dir)).toThrow(/no resources found/);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("throws when the output directory does not exist", () => {
|
|
88
|
-
expect(() => readSynthesisOutput(join(dir, "missing"))).toThrow(/no resources found/);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
// Reader for SDK synthesis output.
|
|
2
|
-
//
|
|
3
|
-
// The SDK writes one binary proto per resource into the out dir:
|
|
4
|
-
// skill-N.pb (SkillSynth — input for skill push)
|
|
5
|
-
// mcpserver-N.pb (McpServer)
|
|
6
|
-
// agent-N.pb (Agent)
|
|
7
|
-
// workflow-N.pb (Workflow)
|
|
8
|
-
// We glob each `<kind>-*.pb`, sort lexically (matching Go's reader so the same
|
|
9
|
-
// bytes read identically on both consumers), and decode with `fromBinary`
|
|
10
|
-
// (DD-009 §3). At least one resource must be present.
|
|
11
|
-
//
|
|
12
|
-
// `dependencies.json` (Go's local-only dependency graph) is deliberately NOT
|
|
13
|
-
// consumed: no dependency-graph validation feature exists, and the TS SDK does
|
|
14
|
-
// not emit it. Its absence is therefore tolerated by construction (DD-009).
|
|
15
|
-
// Port of `internal/cli/synthesis/reader.go`.
|
|
16
|
-
|
|
17
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
import { type DescMessage, fromBinary, type MessageShape } from "@bufbuild/protobuf";
|
|
20
|
-
import { type Agent, AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
21
|
-
import { type McpServer, McpServerSchema } from "@stigmer/protos/ai/stigmer/agentic/mcpserver/v1/api_pb";
|
|
22
|
-
import { type SkillSynth, SkillSynthSchema } from "@stigmer/protos/ai/stigmer/agentic/skill/v1/synth_pb";
|
|
23
|
-
import { type Workflow, WorkflowSchema } from "@stigmer/protos/ai/stigmer/agentic/workflow/v1/api_pb";
|
|
24
|
-
import { UsageError } from "../../../errors/index.js";
|
|
25
|
-
|
|
26
|
-
/** Decoded synthesis output, grouped by kind in read order. */
|
|
27
|
-
export interface SynthesisResult {
|
|
28
|
-
readonly skillSynths: SkillSynth[];
|
|
29
|
-
readonly mcpServers: McpServer[];
|
|
30
|
-
readonly agents: Agent[];
|
|
31
|
-
readonly workflows: Workflow[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Read and decode all synthesized `.pb` files from `outputDir`. Throws a
|
|
36
|
-
* UsageError if the synthesis produced no resources (a program that registered
|
|
37
|
-
* nothing, or wrote to the wrong directory).
|
|
38
|
-
*/
|
|
39
|
-
export function readSynthesisOutput(outputDir: string): SynthesisResult {
|
|
40
|
-
const skillSynths = readProtoFiles(outputDir, "skill-", SkillSynthSchema);
|
|
41
|
-
const mcpServers = readProtoFiles(outputDir, "mcpserver-", McpServerSchema);
|
|
42
|
-
const agents = readProtoFiles(outputDir, "agent-", AgentSchema);
|
|
43
|
-
const workflows = readProtoFiles(outputDir, "workflow-", WorkflowSchema);
|
|
44
|
-
|
|
45
|
-
if (skillSynths.length + mcpServers.length + agents.length + workflows.length === 0) {
|
|
46
|
-
throw new UsageError(
|
|
47
|
-
"no resources found in synthesis output\n\n" +
|
|
48
|
-
"The entry point ran but registered no resources. Register at least one " +
|
|
49
|
-
"agent, workflow, MCP server, or skill, and end with `await project.synth()`.",
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return { skillSynths, mcpServers, agents, workflows };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Glob `<prefix>*.pb` in `dir`, sort lexically (parity with Go's sort.Strings),
|
|
57
|
-
// and decode each. A missing directory yields an empty list (the caller's
|
|
58
|
-
// zero-resource check produces the user-facing error).
|
|
59
|
-
function readProtoFiles<Desc extends DescMessage>(dir: string, prefix: string, schema: Desc): MessageShape<Desc>[] {
|
|
60
|
-
let names: string[];
|
|
61
|
-
try {
|
|
62
|
-
names = readdirSync(dir);
|
|
63
|
-
} catch {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
const matches = names.filter((name) => name.startsWith(prefix) && name.endsWith(".pb")).sort();
|
|
67
|
-
return matches.map((name) => fromBinary(schema, readFileSync(join(dir, name))));
|
|
68
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Runtime inference: every supported extension maps to its runtime; unknown
|
|
2
|
-
// extensions raise actionable guidance listing the supported set.
|
|
3
|
-
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { inferRuntime } from "./runtime.js";
|
|
6
|
-
|
|
7
|
-
describe("inferRuntime", () => {
|
|
8
|
-
it("maps Go, Python, and Node extensions", () => {
|
|
9
|
-
expect(inferRuntime("main.go")).toBe("go");
|
|
10
|
-
expect(inferRuntime("pipelines/main.py")).toBe("python");
|
|
11
|
-
expect(inferRuntime("src/index.ts")).toBe("node");
|
|
12
|
-
expect(inferRuntime("index.js")).toBe("node");
|
|
13
|
-
expect(inferRuntime("index.mts")).toBe("node");
|
|
14
|
-
expect(inferRuntime("index.mjs")).toBe("node");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("is case-insensitive on the extension", () => {
|
|
18
|
-
expect(inferRuntime("Main.GO")).toBe("go");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("throws with the supported extensions when the extension is unknown", () => {
|
|
22
|
-
expect(() => inferRuntime("main.rb")).toThrow(/cannot infer runtime/);
|
|
23
|
-
expect(() => inferRuntime("main.rb")).toThrow(/\.go, \.py, \.ts, \.js, \.mts, \.mjs/);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("throws when there is no extension", () => {
|
|
27
|
-
expect(() => inferRuntime("Makefile")).toThrow(/cannot infer runtime/);
|
|
28
|
-
});
|
|
29
|
-
});
|