ggaction 0.0.8 → 0.0.10

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 (35) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +34 -6
  3. package/knowledge/action-cards.json +10941 -0
  4. package/knowledge/extension-authoring.md +248 -0
  5. package/knowledge/intent-taxonomy.json +183 -0
  6. package/knowledge/mcp-resources.json +95 -0
  7. package/knowledge/task-packet.schema.json +159 -0
  8. package/knowledge/task-resolver.js +1230 -0
  9. package/package.json +11 -1
  10. package/src/actions/basic.js +3 -3
  11. package/src/actions/coordinates/actions.js +4 -0
  12. package/src/actions/encodings/position/policies/line.js +5 -1
  13. package/src/actions/guides/legends/categorical/index.js +85 -2
  14. package/src/actions/guides/legends/categorical/symbols.js +2 -76
  15. package/src/actions/index.js +1 -1
  16. package/src/actions/primitives/index.js +27 -0
  17. package/src/actions/primitives/semantic.js +22 -185
  18. package/src/actions/primitives/semanticAction.js +188 -0
  19. package/src/actions/primitives/semanticValidation/dataset.js +7 -3
  20. package/src/actions/primitives/semanticValidation/index.js +28 -15
  21. package/src/actions/primitives/semanticValidation/layer.js +20 -16
  22. package/src/core/action.js +20 -5
  23. package/src/core/extensionRegistry.js +104 -0
  24. package/src/extension.js +8 -1
  25. package/src/grammar/transformTopology.js +18 -0
  26. package/src/grammar/transforms.js +21 -20
  27. package/src/materialization/dataProvenance.js +2 -2
  28. package/src/materialization/marks/pathOrder.js +2 -2
  29. package/src/mcp/adapter.js +206 -0
  30. package/src/mcp/cli.js +11 -0
  31. package/src/mcp/server.js +101 -0
  32. package/types/extension.d.ts +19 -0
  33. package/types/program.d.ts +4 -0
  34. package/src/actions/coordinates/index.js +0 -5
  35. package/src/actions/primitives/semanticValue.js +0 -1
@@ -0,0 +1,206 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { searchGgaction } from "../../knowledge/task-resolver.js";
6
+
7
+ const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
8
+ const cardsArtifact = JSON.parse(readFileSync(
9
+ path.join(packageRoot, "knowledge/action-cards.json"),
10
+ "utf8"
11
+ ));
12
+ const resourcesArtifact = JSON.parse(readFileSync(
13
+ path.join(packageRoot, "knowledge/mcp-resources.json"),
14
+ "utf8"
15
+ ));
16
+
17
+ const cards = new Map(cardsArtifact.cards.map(card => [card.name, card]));
18
+ const recipes = new Map(resourcesArtifact.recipes.map(recipe => [recipe.id, recipe]));
19
+ const docs = new Map(resourcesArtifact.docs.map(section => [section.id, section]));
20
+
21
+ function unique(values) {
22
+ return [...new Set(values)];
23
+ }
24
+
25
+ export const SEARCH_TOOL_NAME = "search_ggaction";
26
+ export const OVERVIEW_URI = "ggaction://overview";
27
+
28
+ export const SEARCH_TOOL = Object.freeze({
29
+ name: SEARCH_TOOL_NAME,
30
+ title: "Search ggaction authoring knowledge",
31
+ description: "Resolve one complete ggaction chart request into a bounded ordered task packet with exact imports, authoring prerequisites, executable immutable steps, terminal unsupported constraints, and explicit unresolved decisions.",
32
+ inputSchema: Object.freeze({
33
+ type: "object",
34
+ additionalProperties: false,
35
+ required: ["query"],
36
+ properties: Object.freeze({
37
+ query: Object.freeze({
38
+ type: "string",
39
+ minLength: 1,
40
+ maxLength: 500,
41
+ description: "The exact user chart-authoring request, including chart, encodings, guides, layout, and output format. Do not append dataset contents, code scaffolding, or evaluator instructions."
42
+ })
43
+ })
44
+ }),
45
+ annotations: Object.freeze({
46
+ readOnlyHint: true,
47
+ destructiveHint: false,
48
+ idempotentHint: true,
49
+ openWorldHint: false
50
+ })
51
+ });
52
+
53
+ export class KnowledgeResourceError extends Error {
54
+ constructor(message) {
55
+ super(message);
56
+ this.name = "KnowledgeResourceError";
57
+ }
58
+ }
59
+
60
+ function jsonResource(uri, value) {
61
+ return Object.freeze({
62
+ uri,
63
+ mimeType: "application/json",
64
+ text: JSON.stringify(value)
65
+ });
66
+ }
67
+
68
+ function markdownResource(uri, value) {
69
+ return Object.freeze({
70
+ uri,
71
+ mimeType: "text/markdown",
72
+ text: `${value.text}\n\nCanonical public route: ${value.route}`
73
+ });
74
+ }
75
+
76
+ function encodedUri(owner, id) {
77
+ return `ggaction://${owner}/${encodeURIComponent(id)}`;
78
+ }
79
+
80
+ function parseResourceUri(uri) {
81
+ let parsed;
82
+ try {
83
+ parsed = new URL(uri);
84
+ } catch {
85
+ throw new KnowledgeResourceError(`Invalid knowledge resource URI: ${uri}`);
86
+ }
87
+ if (parsed.protocol !== "ggaction:" || parsed.search || parsed.hash) {
88
+ throw new KnowledgeResourceError(`Unsupported knowledge resource URI: ${uri}`);
89
+ }
90
+ const segments = parsed.pathname.split("/").filter(Boolean);
91
+ return { owner: parsed.hostname, segments };
92
+ }
93
+
94
+ export function searchGgactionText(query) {
95
+ return JSON.stringify(searchGgaction(query));
96
+ }
97
+
98
+ export function docsFallbackResources(packet) {
99
+ if (!packet || !Array.isArray(packet.unresolved) || packet.unresolved.length === 0) {
100
+ return [];
101
+ }
102
+ for (const entry of packet.unresolved) {
103
+ if (!Array.isArray(entry.resources) || entry.resources.length === 0) {
104
+ throw new KnowledgeResourceError(
105
+ `Unresolved constraint ${entry.constraint ?? "unknown"} has no explicit documentation resource.`
106
+ );
107
+ }
108
+ }
109
+ const resources = unique(packet.unresolved.flatMap(entry => entry.resources));
110
+ return resources.map(uri => {
111
+ const { owner, segments } = parseResourceUri(uri);
112
+ if (owner !== "docs" || segments.length !== 1) {
113
+ throw new KnowledgeResourceError(`Invalid unresolved documentation resource: ${uri}`);
114
+ }
115
+ const id = decodeURIComponent(segments[0]);
116
+ const section = docs.get(id);
117
+ if (!section) {
118
+ throw new KnowledgeResourceError(`Unknown unresolved documentation resource: ${uri}`);
119
+ }
120
+ return Object.freeze({
121
+ uri,
122
+ name: section.title,
123
+ description: `Read only for the unresolved constraint; public route ${section.route}.`,
124
+ mimeType: "text/markdown"
125
+ });
126
+ });
127
+ }
128
+
129
+ export function listKnowledgeResources() {
130
+ return [
131
+ Object.freeze({
132
+ uri: OVERVIEW_URI,
133
+ name: resourcesArtifact.overview.title,
134
+ description: "Small MCP-first routing instructions; no complete documentation preload.",
135
+ mimeType: "application/json"
136
+ }),
137
+ ...resourcesArtifact.recipes.map(recipe => Object.freeze({
138
+ uri: encodedUri("recipes", recipe.id),
139
+ name: recipe.title,
140
+ description: recipe.summary,
141
+ mimeType: "application/json"
142
+ }))
143
+ ];
144
+ }
145
+
146
+ export function listKnowledgeResourceTemplates() {
147
+ return [
148
+ Object.freeze({
149
+ uriTemplate: "ggaction://actions/{name}",
150
+ name: "Exact compact action card",
151
+ description: "Read one exact action card by current public action name.",
152
+ mimeType: "application/json"
153
+ }),
154
+ Object.freeze({
155
+ uriTemplate: "ggaction://docs/{section}",
156
+ name: "Unresolved-only bounded documentation section",
157
+ description: "Read only a section recommended by the latest search_ggaction unresolved result.",
158
+ mimeType: "text/markdown"
159
+ })
160
+ ];
161
+ }
162
+
163
+ export function readKnowledgeResource(uri, { allowedDocs = [] } = {}) {
164
+ if (uri === OVERVIEW_URI) {
165
+ return jsonResource(uri, {
166
+ schemaVersion: resourcesArtifact.schemaVersion,
167
+ title: resourcesArtifact.overview.title,
168
+ text: resourcesArtifact.overview.text
169
+ });
170
+ }
171
+
172
+ const { owner, segments } = parseResourceUri(uri);
173
+ if (segments.length !== 1) {
174
+ throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
175
+ }
176
+ const id = decodeURIComponent(segments[0]);
177
+ if (owner === "actions") {
178
+ const card = cards.get(id);
179
+ if (!card) throw new KnowledgeResourceError(`Unknown ggaction action: ${id}`);
180
+ return jsonResource(uri, card);
181
+ }
182
+ if (owner === "recipes") {
183
+ const recipe = recipes.get(id);
184
+ if (!recipe) throw new KnowledgeResourceError(`Unknown ggaction recipe: ${id}`);
185
+ return jsonResource(uri, {
186
+ schemaVersion: resourcesArtifact.schemaVersion,
187
+ ...recipe,
188
+ packet: searchGgaction(recipe.query)
189
+ });
190
+ }
191
+ if (owner === "docs") {
192
+ if (!new Set(allowedDocs).has(uri)) {
193
+ throw new KnowledgeResourceError(
194
+ "Documentation resources are available only when recommended by the latest unresolved search result."
195
+ );
196
+ }
197
+ const section = docs.get(id);
198
+ if (!section) throw new KnowledgeResourceError(`Unknown docs fallback section: ${id}`);
199
+ return markdownResource(uri, section);
200
+ }
201
+ throw new KnowledgeResourceError(`Unknown knowledge resource URI: ${uri}`);
202
+ }
203
+
204
+ if (cardsArtifact.schemaVersion !== 1 || resourcesArtifact.schemaVersion !== 2) {
205
+ throw new Error("MCP action cards must use schemaVersion 1 and resources schemaVersion 2.");
206
+ }
package/src/mcp/cli.js ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runGgactionMcpServer } from "./server.js";
4
+
5
+ try {
6
+ await runGgactionMcpServer();
7
+ } catch (error) {
8
+ const message = error instanceof Error ? error.message : String(error);
9
+ process.stderr.write(`ggaction MCP failed: ${message}\n`);
10
+ process.exitCode = 1;
11
+ }
@@ -0,0 +1,101 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ErrorCode,
6
+ ListResourcesRequestSchema,
7
+ ListResourceTemplatesRequestSchema,
8
+ ListToolsRequestSchema,
9
+ McpError,
10
+ ReadResourceRequestSchema
11
+ } from "@modelcontextprotocol/sdk/types.js";
12
+
13
+ import {
14
+ docsFallbackResources,
15
+ KnowledgeResourceError,
16
+ listKnowledgeResources,
17
+ listKnowledgeResourceTemplates,
18
+ readKnowledgeResource,
19
+ SEARCH_TOOL,
20
+ SEARCH_TOOL_NAME,
21
+ searchGgactionText
22
+ } from "./adapter.js";
23
+
24
+ const SERVER_INFO = Object.freeze({ name: "ggaction", version: "1.0.0" });
25
+
26
+ function toolError(error) {
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ return {
29
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
30
+ isError: true
31
+ };
32
+ }
33
+
34
+ function validateToolArguments(args) {
35
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
36
+ throw new TypeError("search_ggaction arguments must be an object with one query string.");
37
+ }
38
+ const names = Object.keys(args);
39
+ if (names.length !== 1 || names[0] !== "query") {
40
+ throw new TypeError("search_ggaction accepts exactly one argument: query.");
41
+ }
42
+ return args.query;
43
+ }
44
+
45
+ export function createGgactionMcpServer() {
46
+ const server = new Server(SERVER_INFO, {
47
+ capabilities: { tools: {}, resources: {} },
48
+ instructions: "Call search_ggaction once with only the exact user task; do not append dataset contents, code scaffolding, or evaluator instructions. Use authoring imports, initialization, prerequisites, and immutable steps in order. Treat unsupported entries as terminal limitations. Read only the resource URIs attached to unresolved entries. The server is read-only and does not execute chart code."
49
+ });
50
+ let allowedDocs = new Set();
51
+
52
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
53
+ tools: [SEARCH_TOOL]
54
+ }));
55
+
56
+ server.setRequestHandler(CallToolRequestSchema, async request => {
57
+ if (request.params.name !== SEARCH_TOOL_NAME) {
58
+ throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
59
+ }
60
+ try {
61
+ const query = validateToolArguments(request.params.arguments);
62
+ const text = searchGgactionText(query);
63
+ const packet = JSON.parse(text);
64
+ allowedDocs = new Set(docsFallbackResources(packet).map(resource => resource.uri));
65
+ return { content: [{ type: "text", text }] };
66
+ } catch (error) {
67
+ allowedDocs = new Set();
68
+ return toolError(error);
69
+ }
70
+ });
71
+
72
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
73
+ resources: listKnowledgeResources()
74
+ }));
75
+
76
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
77
+ resourceTemplates: listKnowledgeResourceTemplates()
78
+ }));
79
+
80
+ server.setRequestHandler(ReadResourceRequestSchema, async request => {
81
+ try {
82
+ const resource = readKnowledgeResource(request.params.uri, {
83
+ allowedDocs: [...allowedDocs]
84
+ });
85
+ return { contents: [resource] };
86
+ } catch (error) {
87
+ if (error instanceof KnowledgeResourceError) {
88
+ throw new McpError(ErrorCode.InvalidParams, error.message);
89
+ }
90
+ throw error;
91
+ }
92
+ });
93
+
94
+ return server;
95
+ }
96
+
97
+ export async function runGgactionMcpServer() {
98
+ const server = createGgactionMcpServer();
99
+ await server.connect(new StdioServerTransport());
100
+ return server;
101
+ }
@@ -15,6 +15,21 @@ export interface ActionMetadata {
15
15
  scope?: "unit" | "composition" | "any";
16
16
  }
17
17
 
18
+ export interface RegisteredExtensionActions {}
19
+
20
+ export type RegisteredExtensionAction = (
21
+ this: ChartProgram,
22
+ ...args: any[]
23
+ ) => ChartProgram;
24
+
25
+ export interface ExtensionDefinition<
26
+ TActions extends Readonly<Record<string, RegisteredExtensionAction>> =
27
+ Readonly<Record<string, RegisteredExtensionAction>>
28
+ > {
29
+ name: string;
30
+ actions: TActions;
31
+ }
32
+
18
33
  export function action<TOptions extends ActionOptions = ActionOptions>(
19
34
  metadata: ActionMetadata,
20
35
  implementation: (this: ChartProgram, options: TOptions) => ChartProgram
@@ -22,3 +37,7 @@ export function action<TOptions extends ActionOptions = ActionOptions>(
22
37
  this: TProgram,
23
38
  options?: TOptions
24
39
  ) => TProgram;
40
+
41
+ export function registerExtension<
42
+ TActions extends Readonly<Record<string, RegisteredExtensionAction>>
43
+ >(definition: ExtensionDefinition<TActions>): void;
@@ -1,3 +1,5 @@
1
+ import type { RegisteredExtensionActions } from "./extension.js";
2
+
1
3
  export type FieldType = "quantitative" | "temporal" | "ordinal" | "nominal";
2
4
  export type GraphicType =
3
5
  | "canvas"
@@ -2189,6 +2191,8 @@ export interface EditTitleOptions
2189
2191
  subtitle?: string | false;
2190
2192
  }
2191
2193
 
2194
+ export interface ChartProgram extends RegisteredExtensionActions {}
2195
+
2192
2196
  export class ChartProgram {
2193
2197
  constructor(state?: ActionOptions);
2194
2198
  readonly semanticSpec: SemanticSpec;
@@ -1,5 +0,0 @@
1
- import { createCoordinate } from "./actions.js";
2
-
3
- export function registerCoordinateActions(ProgramClass) {
4
- ProgramClass.prototype.createCoordinate = createCoordinate;
5
- }
@@ -1 +0,0 @@
1
- export { validateSemanticValue } from "./semanticValidation/index.js";