Skip to content

feat: pin a project and environment to a directory with vf link (COR-14197) - #35

Open
Bradenream wants to merge 4 commits into
masterfrom
braden/vf-link/COR-14197
Open

Bradenream wants to merge 4 commits into
masterfrom
braden/vf-link/COR-14197

Conversation

@Bradenream

Copy link
Copy Markdown
Contributor

Summary

Every project command needs --project-id and --environment-alias, and nothing could set a default. A coding agent therefore starts every task with discovery (workspace list, then project list). That costs turns, and it opens a path into workspaces the agent has no business reading.

  • vf link <project-id> [--environment-alias main] checks that the project and environment exist, then writes .voiceflow/project.json. The file holds ids, the project name and the alias, nothing secret.
  • Every command run in that directory or below it gets the linked project, environment and workspace unless the flag is passed. The root PersistentPreRunE sets the flags before cobra's required-flag checks run, so all generated commands pick up the link without per-command changes.
  • An explicit flag always wins. A link never fills in what project delete, environment delete or workspace delete would destroy.
  • vf unlink removes the link that applies here, including a damaged one. vf whoami shows the link in effect.
  • vf link prints a snippet for the agent's own instructions file (CLAUDE.md, AGENTS.md).
  • A Creator URL is refused with an explanation. The id in /project/:id/ is a version id, and the public API cannot map a version to its project. The error points to Settings → General → Metadata → Project ID.

Test plan

  • gofmt, go vet ./... and go test ./... pass; go.mod is unchanged.
  • internal/link/link_test.go covers:
    • finding the link while walking up, with the nearest link winning
    • save, read and remove
    • explicit flags winning
    • the delete-target exclusion
    • a damaged link failing only the commands that need it
  • test/link.test.ts has 14 hermetic cases (mock server, isolated HOME). They cover:
    • link and unlink, the Creator-URL refusal, an unknown environment and --dry-run
    • defaults from a subdirectory, explicit flags winning and the delete exclusion
    • whoami, damaged links, and plain TOON keys in agent mode
  • Live, on a throwaway project. In a linked folder with no project flags:
    1. agent patch-instructions added a rule with a code word.
    2. A draft conversation before environment compile still gave the template's answer.
    3. After compiling, the agent replied with the code word.
  • The full vitest suite passes on master plus this branch, apart from the 4 cases that already fail on master (docs-command ×2, flag-errors, flag-raw-text).
  • CI

Part of COR-14197. vf context follows as a stacked PR.

…14197)

Every project command needs --project-id and --environment-alias, and nothing
can set a default, so a coding agent starts every task with discovery
(workspace list, then project list): extra turns, and a path into workspaces
it has no business reading.

vf link <project-id> checks that the project and environment exist, then
writes .voiceflow/project.json. Every command run in that directory, or below
it, gets the linked project, environment and workspace unless the flag is
passed. The root PersistentPreRunE sets the flags before cobra's required-flag
checks run, so all generated commands pick the link up without per-command
changes. An explicit flag always wins, and a link never fills in what
project/environment/workspace delete would destroy.

- vf unlink removes the link that applies here, including a damaged one
- vf whoami shows the link in effect
- vf link prints a snippet for the agent's instructions file
- a Creator URL is refused with an explanation: its id is a version id, and
  the public API cannot map a version to its project
…4197)

In agent mode the output is TOON, whose encoder prints a json tag verbatim, so omitempty became part of the key: "file,omitempty", "projectID,omitempty". Found by the live end-to-end test. The tags are plain now, and a behaviour test checks both commands' TOON output.
Copilot AI balanced review requested due to automatic review settings September 29, 2026 17:58
@linear-code

linear-code Bot commented Sep 29, 2026

Copy link
Copy Markdown

COR-14197

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Usage and dry-run modes can mutate links, while linked defaults can override explicit request-body values.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Adds directory-scoped project and environment linking to reduce repeated CLI flags.

Changes:

  • Adds link storage, discovery, and default flag application.
  • Introduces vf link, vf unlink, and whoami integration.
  • Adds documentation and unit/end-to-end coverage.
File Description
README.md Documents project linking.
docs/​vf.md Lists the new commands.
docs/​vf_link.md Documents vf link.
docs/​vf_unlink.md Documents vf unlink.
internal/​cli/​link.go Implements linking commands and CLI integration.
internal/​cli/​root.go Applies linked defaults globally.
internal/​cli/​whoami.go Displays active link information.
internal/​link/​link.go Implements link persistence and default resolution.
internal/​link/​link_test.go Tests link storage and flag behavior.
internal/​output/​format.go Exposes resolved output formatting.
test/​link.test.ts Adds end-to-end command coverage.
Files not reviewed (2)
  • internal/cli/root.go: Generated file
  • internal/cli/whoami.go: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/link/link.go Outdated
Comment thread internal/cli/link.go
Comment thread internal/cli/link.go
Review found that a linked environment silently replaced one the user wrote
in the request body. Link defaults were applied with Flags().Set, which marks
a flag as set, and BuildRequest lets set flags override --body and stdin. In
a folder linked to "dev", `transcript search --body
'{"environmentAlias":"production"}'` sent "dev". 21 commands carry the
environment alias in their body.

A linked value is now a fallback, never an override: explicit flag, then
--body or stdin, then the link. flagutil.SetLinkDefault puts the value in the
flag without marking it set, and tags it as linked:

- A required path or query parameter still gets it from the flag's value, as
  before.
- A body field is only filled by BuildRequest's new last pass, and only when
  the flags, --body and stdin left it empty.
- The interactive prompt treats a linked value as answered.

Tests pin down the order with a request shaped like transcript search. Its
body and stdin cases fail under the old behaviour. An end-to-end case runs
transcript search in a linked folder.
Review found that both hand-written commands ignored flags every command
inherits:

- `vf link --usage` called the API and could write the link. `vf link --usage`
  with no project id failed argument validation first.
- `vf unlink --usage` removed the link.
- `vf unlink --dry-run` removed the link despite promising no changes.
- The generated usage table had no entry for either command, so EmitSchema
  errored for both.

Both commands now start with the usual UsageRequested / EmitSchema guard, and
vf link's argument check lets --usage through. The new usage.RegisterCommand
adds a hand-written command's schema to the table and to the root schema,
leaving the generated file untouched. vf unlink --dry-run prints the file it
would remove and stops.
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