Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions internal/sync/reference/adapter_conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ func TestRegisteredFormatsReadAndWriteRealisticSources(t *testing.T) {
selectPath: `agents[id="support"]`, fieldMap: map[string]string{"prompt": "instructions"},
want: "Ask for an order ID before discussing an order.", update: "Ask for an order ID and region.",
},
{
name: "TypeScript MCP tool", fixture: "typescript/server.ts", format: "typescript",
id: syncdomain.ResourceID{Kind: syncdomain.KindTool, ProjectKey: "project", LookupKey: "lookup_order"},
selectPath: `tools[id="lookup_order"]`, fieldMap: map[string]string{"description": "description"},
want: "Look up one order.", update: "Look up one order and status.",
},
{
name: "JavaScript MCP tool", fixture: "javascript/server.js", format: "javascript",
id: syncdomain.ResourceID{Kind: syncdomain.KindTool, ProjectKey: "project", LookupKey: "weather"},
selectPath: `tools[id="weather"]`, fieldMap: map[string]string{"description": "description"},
want: "Return the weather for one city.", update: "Return weather and alerts.",
},
{
name: "JSON tool", fixture: "json/mcp.json", format: "json",
id: syncdomain.ResourceID{Kind: syncdomain.KindTool, ProjectKey: "project", LookupKey: "lookup_order"},
Expand Down Expand Up @@ -87,10 +99,6 @@ func TestRegisteredFormatsReadAndWriteRealisticSources(t *testing.T) {
}
}

func sourceBundle(path string, data []byte) SourceBundle {
return SourceBundle{Primary: Document{Path: path, Data: data}}
}

func primaryText(t *testing.T, projection syncdomain.Projection) string {
t.Helper()
switch value := projection.(type) {
Expand Down
23 changes: 23 additions & 0 deletions internal/sync/reference/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,29 @@ func encodePythonString(value, quote string) []byte {
return []byte(quote + body + quote)
}

func decodeTypeScriptString(raw []byte) (string, byte, error) {
if len(raw) < 2 || raw[0] != raw[len(raw)-1] {
return "", 0, fmt.Errorf("invalid TypeScript string")
}
quote := raw[0]
if quote != '\'' && quote != '"' && quote != '`' {
return "", 0, fmt.Errorf("unsupported TypeScript string")
}
body := string(raw[1 : len(raw)-1])
if quote == '`' && strings.Contains(body, "${") {
return "", 0, fmt.Errorf("template interpolation is read-only")
}
value, err := decodeEscapes(body, false)
return value, quote, err
}

func encodeTypeScriptString(value string, quote byte) ([]byte, error) {
if quote == '`' && strings.Contains(value, "${") {
return nil, fmt.Errorf("template interpolation syntax requires patch review")
}
return []byte(string(quote) + encodeEscapes(value, quote, false) + string(quote)), nil
}

func decodeEscapes(text string, python bool) (string, error) {
var result strings.Builder
for offset := 0; offset < len(text); {
Expand Down
102 changes: 102 additions & 0 deletions internal/sync/reference/code_inspector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,105 @@ func TestCompletionMessagesEditContentButRejectStructure(t *testing.T) {
assert.Equal(t, CapabilityReadOnly, result.Fields[syncdomain.FieldMessages].Capability)
assert.Contains(t, result.Fields[syncdomain.FieldMessages].Reason, "add or remove messages by hand")
}

func TestTypeScriptLangChainAndQualifiedConstant(t *testing.T) {
source := fixtureFile(t, "typescript/langchain.ts")
id := syncdomain.VariationID("project", "config", "default")
tests := []struct {
name, selectPath, field, mapPath string
mode syncdomain.VariationMode
}{
{name: "messages", selectPath: `prompts[id="chat"]`, field: "messages", mapPath: "messages", mode: syncdomain.VariationModeCompletion},
{name: "constant messages", selectPath: `prompts[id="sharedChat"]`, field: "messages", mapPath: "messages", mode: syncdomain.VariationModeCompletion},
{name: "template", selectPath: `prompts[id="simple"]`, field: "prompt", mapPath: "template", mode: syncdomain.VariationModeAgent},
{name: "constructor", selectPath: `prompts[id="constructor"]`, field: "messages", mapPath: "messages", mode: syncdomain.VariationModeCompletion},
{name: "function constant", selectPath: `constants[id="build.prompt"]`, field: "prompt", mapPath: "$", mode: syncdomain.VariationModeAgent},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
reference := syncdomain.Reference{
File: "langchain.ts", Format: "typescript", Select: test.selectPath,
Map: map[string]string{test.field: test.mapPath},
}
projection, err := ParseSource(sourceBundle(reference.File, source), id, test.mode, reference)
require.NoError(t, err)
assert.NotNil(t, projection)
})
}
}

func TestMutationAndSpreadStayLocalToTheirFields(t *testing.T) {
pythonSource := []byte(`PROMPT = "Initial"
PROMPT += " changed"
CONFIG = {"prompt": "Initial"}
CONFIG["prompt"] = "changed"
`)
pythonRoot, err := parseSourceDocument(Document{Path: "settings.py", Data: pythonSource}, "python")
require.NoError(t, err)
prompt, err := resolveSourcePath(pythonRoot, `constants[id="PROMPT"]`)
require.NoError(t, err)
assert.Equal(t, CapabilityReadOnly, prompt.capability)
assert.Contains(t, prompt.reason, "mutated")
config, err := resolveSourcePath(pythonRoot, `constants[id="CONFIG"]`)
require.NoError(t, err)
assert.Equal(t, CapabilityReadOnly, config.capability)

typeScriptSource := []byte(`const CONFIG = {
description: "Static description",
...runtimeValues,
model: helper(),
};
CONFIG.description = "changed";`)
id := syncdomain.ResourceID{Kind: syncdomain.KindTool, ProjectKey: "project", LookupKey: "config"}
reference := syncdomain.Reference{
File: "config.ts", Format: "typescript", Select: `constants[id="CONFIG"]`,
Map: map[string]string{"description": "description", "schema": "model"},
}
projection, err := ParseSource(sourceBundle(reference.File, typeScriptSource), id, "", reference)
require.NoError(t, err)
tool := projection.(syncdomain.ToolProjection)
assert.Equal(t, "Static description", *tool.Description)
schemaState, ok := syncdomain.ProjectionFieldState(tool, syncdomain.FieldSchema)
require.True(t, ok)
assert.Equal(t, syncdomain.CapabilityReadOnly, schemaState.Capability)
root, err := parseSourceDocument(Document{Path: "config.ts", Data: typeScriptSource}, "typescript")
require.NoError(t, err)
config, err = resolveSourcePath(root, `constants[id="CONFIG"]`)
require.NoError(t, err)
assert.Equal(t, CapabilityReadOnly, config.capability)
}

func TestTypeScriptAndJavaScriptConstantLiteralKinds(t *testing.T) {
source := []byte(`const CONFIG = {
single: 'one',
double: "two",
template: ` + "`three`" + `,
retries: 3,
enabled: true,
models: ["gpt-5", "claude-sonnet"],
nested: { prompt: "Help" },
};`)
for _, format := range []string{"typescript", "javascript"} {
t.Run(format, func(t *testing.T) {
root, err := parseSourceDocument(Document{Path: "config.js", Data: source}, format)
require.NoError(t, err)
tests := []struct {
path string
want any
}{
{path: `constants[id="CONFIG"].single`, want: "one"},
{path: `constants[id="CONFIG"].double`, want: "two"},
{path: `constants[id="CONFIG"].template`, want: "three"},
{path: `constants[id="CONFIG"].retries`, want: 3.0},
{path: `constants[id="CONFIG"].enabled`, want: true},
{path: `constants[id="CONFIG"].models`, want: []any{"gpt-5", "claude-sonnet"}},
{path: `constants[id="CONFIG"].nested.prompt`, want: "Help"},
}
for _, test := range tests {
node, err := resolveSourcePath(root, test.path)
require.NoError(t, err)
assert.Equal(t, test.want, node.value)
}
})
}
}
Loading
Loading