Skip to content

feat(sync): compile and publish repository sources - #994

Merged
ctawiah merged 1 commit into
mainfrom
ctawiah/sync-source-publication-3bbf
Nov 9, 2026
Merged

ctawiah merged 1 commit into
mainfrom
ctawiah/sync-source-publication-3bbf

Conversation

@ctawiah

@ctawiah ctawiah commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Context

Descriptor-backed sources need compilation into projections and a publication path that cannot lose or corrupt an edit.

What changes

  • Compile descriptors into variation, tool, and skill projections with their static repository dependencies.
  • Publish pulls as one unit. One byte snapshot per file, one edit pass per file, a reparse of every target, and one atomic replacement.
  • Check every other descriptor that reads an edited file, so that a pull cannot change the projection of a neighbor.
  • Keep the top-level tools and skills lists of a descriptor in step with a pulled variation, and keep its comments.

Design notes

  • Two pulls that edit one shared imported file land as one edit pass. A regression test covers the lost update that two passes caused.
  • A read-only mapped field blocks the whole resource.
  • A generated, vendored, or minified file is read as a dependency but never written. A mapped value in it is read-only, and a descriptor that names such a file as its source blocks the resource.

Review focus

  • Can stale bytes reach a write?
  • Does a failed file leave every other file unchanged?

Verification

  • make build
  • go test ./...
  • golangci-lint run ./...

Related changes

  1. refactor(sync): remove Phase 1 source references #1005: Phase 1 reference removal
  2. feat(sync): add projection and problem core #987: projection and problem core
  3. feat(sync): add the adapter core and the Markdown adapter #988: adapter core and Markdown
  4. feat(sync): add the structured document adapters #989: structured adapters
  5. feat(sync): add the Python adapter #991: Python adapter
  6. feat(sync): add the TypeScript and JavaScript adapters #992: TypeScript and JavaScript adapters
  7. feat(sync): add source descriptor storage #993: descriptor storage
  8. This PR: compile and publish
  9. feat(sync): detach descriptor-backed sources #1003: descriptor detach
  10. feat(sync): prepare repository source links #997: link preparation
  11. feat(sync): integrate repository sources with planning and link #995: planning and link
  12. feat(sync): add the link commands #998: link commands

🤖 Generated with Claude Code


Note

Overview
Adds descriptor-backed source sync: CompileWorkspace / CompileAttachments now compile .ref.yaml definitions (variations, tools, skills) through registered format adapters, resolve static dependencies, and reject conflicting managed-file vs descriptor definitions, unsafe paths, and symlink-only managed entries.

Introduces ReplaceSources / CheckSources — a single publication unit with per-file snapshots, grouped text edits, post-edit verification, descriptor mapping guards, and atomic replaceFiles; edits refuse stale fingerprints, overlapping changes, read-only/generated targets, and changes that would alter non-target descriptors sharing a file.

Descriptor relationship pulls update top-level tools / skills in place (preserving comments when possible) or re-render when YAML layout blocks in-place edits; missing local attachment defs can be materialized during pull when content is supplied.

SourceFiles now tracks referenced sources and resolved symlink targets for watch/planning; ProjectionIncludes compares projections that omit field states.

Reviewed by Cursor Bugbot for commit b262b74. Bugbot is set up for automated code reviews on this repo. Configure here.

@ctawiah
ctawiah added this pull request to stack #1000 October 30, 2026 20:15
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from eaf8d23 to 9c1ef3d Compare October 31, 2026 00:06
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch 2 times, most recently from 124b85b to 5b0eb9e Compare November 2, 2026 02:50
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from 5b0eb9e to a260389 Compare November 3, 2026 00:29
@ctawiah
ctawiah removed this pull request from stack #1000 November 3, 2026 00:32
@ctawiah
ctawiah added this pull request to stack #1004 November 3, 2026 00:34
@ctawiah
ctawiah removed this pull request from stack #1004 November 3, 2026 00:53
@ctawiah
ctawiah added this pull request to stack #1006 November 3, 2026 00:53
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from a260389 to 4952ca9 Compare November 4, 2026 19:46
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch 2 times, most recently from e893968 to 6509da2 Compare November 4, 2026 22:26
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from 6509da2 to 619db60 Compare November 4, 2026 22:52
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from 50306f7 to ee3a323 Compare November 6, 2026 18:30
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from ee3a323 to a1acb87 Compare November 6, 2026 19:08

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved
@ctawiah
ctawiah force-pushed the ctawiah/sync-source-publication-3bbf branch from a1acb87 to ec06ea8 Compare November 6, 2026 19:51

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved
Comment thread internal/sync/local/source_publish.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/sources.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_reference.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_reference.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved
Comment thread internal/sync/local/source_publish.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/compile.go Resolved
Comment thread internal/sync/local/source_reference.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/compile.go Resolved
Comment thread internal/sync/local/attachment.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/compile.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved
Comment thread internal/sync/local/compile.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/attachment_test.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/sync/local/source_publish.go Resolved

@nkester-ld nkester-ld left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an actually big and important bug to cover. There was one other bugs I found with failing tests (AI wrote this? No I wrote this) , but it seems like an edge edge case.

When a file under .launchdarkly is a broken symlink, such as a dangling link after a checkout, workspaceFS.Open resolves the link before checking for symlinks and gets "not found". That makes a broken search.json link report "no local definition, run ldcli sync tools add", and a broken search.ref.yaml beside a valid search.json is silently ignored, so the local file gets compiled.

})
require.ErrorContains(t, err, "8 MiB")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love the test coverage case where an authored Python or TypeScript file has an unrelated relative import of a generated module covered. I created this test that seems to fail one is present:

.launchdarkly/project/configs/support/default.ref.yaml: source is generated, vendored, or minified. Link the authored source file.

Suggested change
func TestCompileWorkspaceIgnoresAnUnrelatedGeneratedImport(t *testing.T) {
root := t.TempDir()
writeTestFile(t, root, "app/agent.py", "from agents import Agent\nfrom .schema_pb2 import SCHEMA\n"+
"support = Agent(instructions=\"Help with orders.\")\n")
writeTestFile(t, root, "app/schema_pb2.py", "# Code generated by protoc. DO NOT EDIT.\nSCHEMA = {}\n")
writeTestFile(t, root, ".launchdarkly/project/configs/support/default.ref.yaml",
"formatVersion: 1\nfile: app/agent.py\nformat: python\nselect: 'agents[id=\"support\"]'\n"+
"map:\n prompt: instructions\n")
resources, err := CompileWorkspace(root)
require.NoError(t, err)
require.Len(t, resources, 1)
assert.Equal(t, "Help with orders.", resources[0].Variation.Instructions)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, and thank you for the test. A generated, vendored, or minified file now reads as a read-only dependency instead of failing the resource. readSourceDocument and the publication snapshot mark such a file with Document.ReadOnly, and code.LocateConstants makes every value that an import resolves in it read-only, with the reason <file> is generated, vendored, or minified, so sync does not write it, so a mapped value in it blocks the resource at plan time. The primary file of a descriptor still blocks the resource, and staging still rejects a write to such a file. Your test is in as TestCompileWorkspaceIgnoresAnUnrelatedGeneratedImport. TestCompileSourceMarksAValueInAGeneratedDependencyReadOnly covers a mapped value in a generated module, a link into a vendored tree, and an authored dependency that stays direct, and TestLocateConstantsMakesTheValuesOfAReadOnlyDependencyReadOnly covers the helper.

Compile descriptors into projections with their repository dependencies. Publish pulls as one unit: one byte snapshot per file, one edit pass per file, a reparse of every target, a check of every other binding that reads an edited file, and one atomic replacement.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit b262b74. Configure here.

return stagedFile{
displayPath: file, path: path, original: original, originalExists: true,
replacement: updated, mode: info.Mode().Perm(), sources: names, root: store.repositoryRoot,
}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staging re-resolves snapshot source paths

High Severity

stageSourceDocument calls resolveSourcePath again instead of writing to the path the snapshot already resolved. A link retargeted after that read can make the commit's name check compare the new target to itself. When the new file still has the snapshot bytes, the publish writes the old file's edits onto the new target.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b262b74. Configure here.

if !slices.Contains(snapshot.names[key], file) {
snapshot.names[key] = append(snapshot.names[key], file)
}
return syncadapter.Document{Path: file, Data: document.Data}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snapshot cache drops generated ReadOnly

Medium Severity

A cached sourceSnapshot.document returns only Path and Data, so ReadOnly from the first read is dropped. A later bundle of the same generated file (another descriptor, another name, or a primary after a dependency) treats it as writable, and read() will not reject it as a generated primary.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b262b74. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants