Skip to content

feat(cli): 2.J — create / import / export YAML-lifecycle commands#58

Merged
cemililik merged 8 commits into
mainfrom
development
Jun 28, 2026
Merged

feat(cli): 2.J — create / import / export YAML-lifecycle commands#58
cemililik merged 8 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

What

Phase-2 workstream 2.J — the three surface-agnostic YAML-lifecycle commands that round out the in-phase CLI authoring lane. All read/write git-native YAML under .relavium/ only — never the keychain or run state.

Command Behavior
relavium create [--force] Interactive @clack/prompts wizard scaffolding an agent (.agent.yaml) or a minimal single-agent workflow (input → agent → output, .relavium.yaml). Answers are validated against the @relavium/shared schema before write — a bad model/provider/name is the same clean exit-2 CliError a run raises. TTY-only (fails loud under --json/non-TTY); cancel writes nothing, exits 0.
relavium import <path> [--force] Copy an external workflow/agent into the project, validating schema + slug uniqueness (collision → exit 2 unless --force).
relavium export <id> [--out <path>] [--force] Portable copy, re-serialized from the validated AST (canonical, comment-free); <id> resolved across both catalogs (unknown/ambiguous → exit 2); --json{id,kind,path} (cwd-relative).

Adds serializeAgent to @relavium/core (the .agent.yaml counterpart of serializeWorkflow).

Acceptance met: create produces a schema-valid file that subsequently runs; import rejects malformed/slug-colliding with exit 2; export carries no secret material and re-imports cleanly (asserted as a true round-trip).

Share-safety model (corrected)

A provider API key is never in the file by construction (no schema field holds a key value — keys live in the OS keychain). MCP-server secrets are referenced via {{secrets.*}} placeholders by convention — the env map is z.record(string,string), so export/import re-serialize faithfully (they do not scrub); the comment-drop is additional hardening. The reference docs (commands.md, keychain-and-secrets.md, workflow-yaml-spec.md) were corrected to state this accurately rather than overclaim a schema-level guarantee.

Review

Three independent relavium-reviewer passes (0 blockers) + an adversarial verification workflow (4 lenses + completeness critic) over the fix diff. The critic caught that the secret-doc correction was only partly applied — the same overclaim survived in 3 more homes — all folded into the review-fix commit. Path-display hygiene (no absolute fs path in any error/--json), seam/engine-purity/secret-safety, and would-fail-on-revert tests all confirmed.

Toolchain

pnpm turbo run lint typecheck test build green (716 CLI tests); Leakwatch 0 findings; no new runtime dependency.

Note

Also carries 28883ad — the 2.R roadmap ✅-flip (a post-merge doc commit from PR #56/#57 that hadn't yet been PR'd to main). The 2.J roadmap flip is deferred to a separate post-merge commit per project convention.

Refs: ADR-0006, ADR-0026, ADR-0033, ADR-0047

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added live relavium create, relavium import, and relavium export commands for YAML-based workflows/agents.
    • Interactive scaffolding for creating new agents or minimal single-agent workflows.
    • Export/import and re-serialization with --force, --out, and --json output.
    • Added share-safe, deterministic agent YAML serialization.
  • Bug Fixes

    • Improved non-interactive safeguards (including when stdin isn’t a TTY).
    • Stricter ID/slug collision handling and safer file-target overwrite behavior.
    • Preserves {{secrets.*}} placeholders while dropping authored comments.
  • Documentation

    • Updated CLI reference, secret-handling guidance, and roadmap/status for the inbound MCP client.

cemililik and others added 7 commits June 28, 2026 01:12
…urfaces (PR #56/#57 merged)

PR #57 merged 2026-06-27 (the inbound MCP client lane, on the PR #56 @relavium/mcp
foundation), so flip 2.R to ✅ Done — the deferred post-merge update per the
done-after-merge rule. Updates the four status homes:

- CLAUDE.md: the Phase-2 status paragraph — 2.R Done (PR #56 + #57, behind
  ADR-0034/0052/0053); next pickup is 2.J.
- docs/roadmap/current.md: the 2.R lane recorded as landed (foundation PR #56,
  host wiring/transports/secrets/e2e PR #57) with the residual-hardening pointer;
  next pickup flipped to 2.J.
- docs/roadmap/phases/phase-2-cli.md: the header Status line, the 2.R workstream
  section header + a shipped summary, the "MCP client live" go/no-go row, the
  Remaining-build-order status + table, and the backbone bullet — 2.R Done, only
  2.J remains in-phase. (The DAG/wave/dependency-matrix planning sections are
  structural and left as-is.)
- README.md: the inbound MCP client added to the landed additive lanes.

No code change. 2.R was off the M3 critical path and the Phase-3 go/no-go, so this
adds capability without changing the gate posture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ializeWorkflow (2.J authoring)

The CLI authoring lifecycle (2.J `create`/`import`/`export`) needs to emit a validated
agent document back to deterministic, round-trippable YAML — the agent-file sibling of
the existing `serializeWorkflow` (which only covers workflows). `serializeAgent` mirrors
it (sorted map keys, preserved array order, byte-stable parse→serialize→parse). An agent
document carries no secret VALUES by construction (keys live in the OS keychain; MCP `env`
references a secret only by `{{secrets.*}}` placeholder), so the emitted file is share-safe;
re-serializing from the validated AST additionally drops any authored comments. Exported
from the package index alongside `serializeWorkflow`.

Refs: ADR-0026, ADR-0006, ADR-0029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e, share-safe)

The first of the 2.J authoring lifecycle commands, on a shared `apps/cli/src/authoring/`
module that the import/create commands will reuse:

- `authoring.ts` — surface-agnostic helpers over git-native YAML under a project
  `.relavium/`, reusing the strict core parsers: `detectAndParse` (kind-sniff by
  extension hint, else workflow-then-agent, with a typed exit-2 CliError on a doc that
  is neither), `resolveById` (find by in-file id across both catalogs; unknown →
  exit 2, ambiguous → exit 2), `slugExists`, `catalogPath`/`authoredFileName`,
  `readAuthoredFile` (the catalog scan's regular-file + size guards), `writeAuthoredFile`
  (atomic `wx`/`w`, EEXIST/EISDIR → exit 2), and `serializeAuthored`.
- `commands/export.ts` — `relavium export <id>` re-serializes the resolved doc from the
  VALIDATED AST (`serializeWorkflow`/`serializeAgent`), so the copy drops authored
  comments and keeps only `{{secrets.*}}` placeholders — never a resolved secret value
  (keys live in the OS keychain). Default target `<id>.<suffix>` in cwd; `--out`/`--force`.
  Pure YAML I/O — no keychain/db.
- specs.ts: replace the `export <id>` stub with the real `registerExport`.

Tests cover the workflow + agent happy paths (comment-drop + placeholder-keep), `--out`
+ `--json`, and the exit-2 faults (unknown id, ambiguous id, existing-target-without-force).

Refs: ADR-0026, ADR-0006, ADR-0029

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he project, validated + deduplicated

`relavium import <path>` parse-validates an external YAML (a malformed file is a clean
exit-2 CliError), rejects an in-file id that already names a same-kind catalog entry
(exit 2 unless --force), and writes the RE-SERIALIZED (canonical, comment-free,
{{secrets.*}}-placeholder-preserving) doc to `.relavium/<workflows|agents>/<id>.<suffix>`.
The kind is taken from the file extension when explicit (`.agent.yaml`/`.relavium.yaml`),
else content-sniffed (workflow then agent). Surface-agnostic — pure YAML I/O over the
shared authoring module; no keychain/db. specs.ts: the `import <path>` stub → registerImport.

Tests cover the agent + bare-.yaml-sniffed-workflow happy paths (comment-drop), the
malformed-file / missing-file / slug-collision exit-2 faults, and --force overwrite.

Refs: ADR-0026

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nimal workflow

Add `relavium create`, the third 2.J YAML-lifecycle command: an interactive
`@clack/prompts` wizard that scaffolds either a new agent (`.agent.yaml`) or a
minimal single-agent workflow (`input → agent → output`, `.relavium.yaml`) as
plain, git-ready YAML under `.relavium/<agents|workflows>/<id>.<suffix>`.

The wizard answers are built into a typed definition and **validated against the
`@relavium/shared` schema before any write** (via `buildAuthored` re-parsing the
serialized AST), so a bad model/provider/name is the same clean exit-2 `CliError`
a `run` would raise. A name colliding with an existing project entry is exit 2
unless `--force`. Surface-agnostic: pure YAML I/O — no keychain, no run state.

- `authoring.ts`: `CreateSpec`, the `CreatePrompter` seam, `toSlug`, `buildAuthored`.
- `create-prompter.ts`: the clack-backed prompter behind an injectable `ClackCreateDeps`
  slice (the only clack import on the authoring path), so the gather/cancel/branch
  flow is unit-tested without a TTY.
- `create.ts`: the pure command core — headless guard (no wizard under --json / a
  non-TTY), cancel → clean exit 0, collision guard, write + confirm.
- `specs.ts`: wire `registerCreate`; drop `create` from the stub list.
- `run.test.ts`: repoint the stub-command regressions at `init` (still a stub),
  since `create` is now real.

Refs: ADR-0026, ADR-0047

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e shipped commands

The authoring trio (`create` / `import` / `export`) is now live, so flip the
canonical CLI commands home and the secret-handling guarantee to match the
shipped behavior:

- commands.md: move the three out of the "lands at 2.J" status into the live
  set; document the `create` wizard (kind → name → provider → model → prompt →
  tools, schema-validated before write, TTY-only, `--force` on collision), and
  the `import`/`export` canonical re-serialize (kind detection, slug dedup,
  cross-catalog id resolution, `--out`/`--json`, why the result is share-safe).
- keychain-and-secrets.md: restate the YAML "never holds a secret" guarantee as
  the by-construction `{{secrets.*}}`-references-only rule + the comment-dropping
  canonical re-serialize, and cross-link the commands home.

Refs: ADR-0026, ADR-0047

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, coverage

Address the multi-reviewer pass on the 2.J authoring commands plus an adversarial
verification round that caught the same overclaim class in three more places.

CLI:
- authoring.ts `writeAuthoredFile` now takes a `displayPath` (mirroring
  `readAuthoredFile`) — its EEXIST/EISDIR `CliError` messages name a cwd-relative
  path, never the absolute fs path. All three callers (create/import/export) pass
  `relative(cwd, target)`.
- export.ts emits a cwd-relative path in BOTH human stdout and the `--json`
  `{id,kind,path}` record (was absolute) — same shape `import --json` already used.
- create-prompter.ts: export `required` for unit test; document the deliberate
  `validate` return-type narrowing (clack's `Validate` also allows `Error`).
- specs.ts: header comment no longer lists create/import/export as stubs (they are
  live); only `init` + `budget` remain in STUB_COMMANDS.

Docs — correct a secret-handling overclaim (the MCP `env` map is
`z.record(z.string(), z.string())`, so it accepts literals; the schema does NOT
forbid them). Reword to the accurate framing in every affected home — provider keys
absent *by construction* (no schema field holds a key), MCP `{{secrets.*}}` *by
convention*, export/import re-serialize *faithfully* (no scrub):
- commands.md (the export table cell + the "why it is safe to share" paragraph),
- keychain-and-secrets.md ("what never holds a secret" row),
- workflow-yaml-spec.md (the validation-rules bullet),
- export.ts / authoring.ts module docstrings.

Tests (+6): the bare-`.yaml` agent-sniff and dual-parse-failure branches of
`detectAndParse`; the `required` validator (empty/whitespace/undefined/valid); a
true export→import round-trip into a fresh project; a relative-path assertion on
export `--json`; cwd-absent assertions on the EEXIST + EISDIR write-error arms.

Toolchain: lint/typecheck/test (716) + build green; Leakwatch 0 findings.

Refs: ADR-0006, ADR-0026, ADR-0033, ADR-0047

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai 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.

Sorry @cemililik, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e1de9b6-dc98-4f2d-8e8b-18272c57dd38

📥 Commits

Reviewing files that changed from the base of the PR and between 7f38cdc and 09fcd45.

📒 Files selected for processing (10)
  • apps/cli/src/authoring/authoring.ts
  • apps/cli/src/authoring/create-prompter.ts
  • apps/cli/src/commands/create.test.ts
  • apps/cli/src/commands/create.ts
  • apps/cli/src/commands/export.test.ts
  • apps/cli/src/commands/import.test.ts
  • apps/cli/src/commands/import.ts
  • apps/cli/src/process/io.ts
  • apps/cli/src/test-support.ts
  • docs/reference/cli/commands.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/cli/src/commands/create.ts
  • apps/cli/src/commands/import.ts
  • apps/cli/src/authoring/authoring.ts
  • apps/cli/src/commands/import.test.ts
  • apps/cli/src/commands/export.test.ts
  • docs/reference/cli/commands.md

📝 Walkthrough

Walkthrough

Adds core agent YAML serialization, authoring helpers, interactive create/import/export commands, live CLI registration, and updated docs/status text for the new YAML authoring flow.

Changes

YAML Authoring Lifecycle Commands

Layer / File(s) Summary
Agent YAML serializer
packages/core/src/export/serializer.ts, packages/core/src/index.ts, packages/core/src/export/serializer.test.ts
Adds serializeAgent, re-exports it, and tests deterministic agent YAML round-tripping and {{secrets.*}} placeholder preservation.
Authoring utilities
apps/cli/src/authoring/authoring.ts
Introduces authoring types, path helpers, YAML detection/parsing, scaffold building, slug helpers, file read/write guards, id resolution, and slug availability checks.
Interactive create wizard
apps/cli/src/authoring/create-prompter.ts, apps/cli/src/authoring/create-prompter.test.ts
Implements the injectable Clack prompt adapter, validator, and step-by-step wizard with cancel handling and tools parsing, plus tests.
Create command and tests
apps/cli/src/commands/create.ts, apps/cli/src/commands/create.test.ts, apps/cli/src/process/io.ts, apps/cli/src/test-support.ts
Adds create command guards, cancellation handling, file writes, TTY detection, and tests for scaffolding, collisions, and non-interactive failures.
Import and export commands
apps/cli/src/commands/import.ts, apps/cli/src/commands/import.test.ts, apps/cli/src/commands/export.ts, apps/cli/src/commands/export.test.ts
Implements canonical import/export flows with cross-catalog resolution, overwrite behavior, JSON/human output, and tests for sniffing, collisions, errors, and round-trips.
Command registration
apps/cli/src/commands/specs.ts, apps/cli/src/run.test.ts
Registers the new commands as live CLI entries, removes them from stub commands, and updates stub-command tests to use init.
Docs and roadmap status
docs/reference/cli/commands.md, docs/reference/contracts/workflow-yaml-spec.md, docs/reference/desktop/keychain-and-secrets.md, docs/roadmap/current.md, docs/roadmap/phases/phase-2-cli.md, CLAUDE.md, README.md
Updates command docs, secret guidance, and roadmap/status text for the inbound MCP client and next pickup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HodeTech/Relavium#40: Same CLI command registration area, where apps/cli/src/commands/specs.ts was previously wired from stub commands toward real implementations.
  • HodeTech/Relavium#43: Related to the CliIo stdin/stdout TTY contract changes used here for interactive command guards.
  • HodeTech/Relavium#55: Shares the apps/cli/src/commands/specs.ts routing layer that this PR extends with additional live commands.

Poem

🐇 I hopped through YAML, neat and bright,
With slugs and prompts all tucked just right.
import and export made their way,
create woke up to start the day.
{{secrets.*}} stayed snug and true,
While rabbit feet stamped docs anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.97% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the 2.J YAML lifecycle commands for create, import, and export in the CLI.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements the YAML-authoring lifecycle (workstream 2.J) by introducing the create, import, and export CLI commands. It adds a shared authoring core for reading, writing, and validating agent and workflow YAML files, along with an interactive @clack/prompts wizard for scaffolding new definitions. The import and export commands allow safe sharing of configurations by re-serializing from the validated AST, which strips comments and preserves secret placeholders. Tests, command registrations, and documentation have been updated accordingly. There are no review comments to address, and I have no additional feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🧹 Nitpick comments (1)
apps/cli/src/commands/export.test.ts (1)

81-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid the unsafe as cast on parsed JSON.

Line 87 bypasses the repo’s strict TS rule in a test that can stay fully typed without it. Parse to unknown and assert the expected shape with matchers (or a tiny type guard) before reading path.

As per coding guidelines, **/*.{ts,tsx}: TypeScript-first, strict. No any, no unsafe as.

Proposed fix
-    const record = JSON.parse(out().trim()) as { id: string; kind: string; path: string };
-    expect(record).toMatchObject({ id: 'reviewer', kind: 'agent' });
-    expect(record.path).toBe(join('shared', 'copy.agent.yaml')); // cwd-relative, never the absolute path
+    const record: unknown = JSON.parse(out().trim());
+    expect(record).toEqual(
+      expect.objectContaining({
+        id: 'reviewer',
+        kind: 'agent',
+        path: join('shared', 'copy.agent.yaml'),
+      }),
+    ); // cwd-relative, never the absolute path
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/cli/src/commands/export.test.ts` around lines 81 - 90, The test in
exportCommand should not use an unsafe TypeScript cast when parsing JSON output.
Update the assertion near exportCommand in export.test.ts to parse the result as
unknown, then validate the expected object shape with matcher checks or a small
type guard before accessing path, keeping the test fully type-safe without any
`as` cast.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/cli/src/authoring/create-prompter.ts`:
- Around line 15-19: The select result in ClackCreateDeps and createPrompter is
widened to string | symbol, which drops the option-value union and allows
invalid fallthrough. Update ClackCreateDeps.select to preserve the inferred
option value type from `@clack/prompts`, then adjust createPrompter’s selection
handling to use that narrowed union directly and treat any impossible value as
an error instead of defaulting to 'agent' or null.

In `@apps/cli/src/commands/create.ts`:
- Around line 60-64: The create command’s duplicate check only uses parsed.kind,
which allows the same bare slug to be created in both catalogs and makes shared
exports ambiguous. Update create.ts to enforce uniqueness across both catalogs
by checking the slug against all relevant kinds via the shared helper used by
slugExists or an equivalent centralized lookup, and apply the same cross-catalog
collision rule in importCommand so both paths reject conflicting IDs
consistently.
- Around line 43-47: The create command only checks stdout for interactivity, so
the wizard can still start when stdin is not a TTY and hang in non-interactive
runs. Update the guard in create.ts before createClackPrompter() to also verify
stdin is TTY, preferably through CliIo alongside stdoutIsTty, and keep the
existing invalid_invocation error path for any non-interactive input/output
combination.

In `@CLAUDE.md`:
- Around line 70-72: The rendered guide contains a stray diff marker before the
ADR-0053 reference, so update the relevant paragraph in CLAUDE.md to remove the
leading plus sign and keep the ADR links as plain prose. Locate the line that
mentions ADR-0034, ADR-0052, and ADR-0053, and ensure the text reads cleanly
without any leftover patch notation.

In `@docs/reference/cli/commands.md`:
- Around line 147-149: Update the `create` CLI docs so the validation sentence
reflects the selected kind’s schema rather than only `@relavium/shared`; this
affects the interactive scaffolder description where `kind` can be either an
agent or a minimal single-agent workflow. Mention that the typed definition is
validated against the appropriate schema for `.agent.yaml` or `.relavium.yaml`
before write, and keep the existing references to the `create` flow, `kind`, and
the `.relavium/agents` / `.relavium/workflows` outputs.

---

Nitpick comments:
In `@apps/cli/src/commands/export.test.ts`:
- Around line 81-90: The test in exportCommand should not use an unsafe
TypeScript cast when parsing JSON output. Update the assertion near
exportCommand in export.test.ts to parse the result as unknown, then validate
the expected object shape with matcher checks or a small type guard before
accessing path, keeping the test fully type-safe without any `as` cast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14bd4455-645c-47e6-a9ec-bea74e40115f

📥 Commits

Reviewing files that changed from the base of the PR and between 2188653 and 7f38cdc.

📒 Files selected for processing (21)
  • CLAUDE.md
  • README.md
  • apps/cli/src/authoring/authoring.ts
  • apps/cli/src/authoring/create-prompter.test.ts
  • apps/cli/src/authoring/create-prompter.ts
  • apps/cli/src/commands/create.test.ts
  • apps/cli/src/commands/create.ts
  • apps/cli/src/commands/export.test.ts
  • apps/cli/src/commands/export.ts
  • apps/cli/src/commands/import.test.ts
  • apps/cli/src/commands/import.ts
  • apps/cli/src/commands/specs.ts
  • apps/cli/src/run.test.ts
  • docs/reference/cli/commands.md
  • docs/reference/contracts/workflow-yaml-spec.md
  • docs/reference/desktop/keychain-and-secrets.md
  • docs/roadmap/current.md
  • docs/roadmap/phases/phase-2-cli.md
  • packages/core/src/export/serializer.test.ts
  • packages/core/src/export/serializer.ts
  • packages/core/src/index.ts

Comment on lines +247 to +255
export function writeAuthoredFile(
path: string,
displayPath: string,
content: string,
force: boolean,
): void {
try {
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, content, { encoding: 'utf8', flag: force ? 'w' : 'wx' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file map =="
git ls-files apps/cli/src/authoring | sed 's#^`#-` #'

echo
echo "== outline =="
ast-grep outline apps/cli/src/authoring/authoring.ts --view expanded || true

echo
echo "== relevant slice =="
sed -n '200,320p' apps/cli/src/authoring/authoring.ts

echo
echo "== usages of writeAuthoredFile =="
rg -n "writeAuthoredFile\(" apps/cli/src -A 3 -B 3

echo
echo "== symlink-related helpers in authoring area =="
rg -n "symlink|lstat|statSync|openSync|O_NOFOLLOW|nofollow|force" apps/cli/src/authoring apps/cli/src -A 2 -B 2

Repository: HodeTech/Relavium

Length of output: 50373


Reject symlinked write targets. writeFileSync(..., { flag: 'w' }) follows an existing symlink, so a .relavium/... link can redirect create/import/export --force outside the catalog. Open with O_NOFOLLOW (or equivalent) and treat symlink targets as exit 2.

Source: Learnings

Comment thread apps/cli/src/authoring/create-prompter.ts
Comment thread apps/cli/src/commands/create.ts Outdated
Comment thread apps/cli/src/commands/create.ts Outdated
Comment thread CLAUDE.md
Comment on lines +70 to +72
[ADR-0034](docs/decisions/0034-mcp-client-sdk-dependency.md) + [ADR-0052](docs/decisions/0052-inbound-mcp-client-package-lifecycle-registration.md)
+ [ADR-0053](docs/decisions/0053-mcp-network-transport-egress-security.md). 2.R was off the M3 critical path and
the Phase-3 go/no-go, so it adds capability without gating. The next pickup is **2.J** (the last in-phase additive

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stray + before the ADR-0053 link.

This looks like an unstripped diff marker and will render awkwardly in the rendered guide.

♻️ Proposed fix
- [ADR-0052](docs/decisions/0052-inbound-mcp-client-package-lifecycle-registration.md) + [ADR-0053](docs/decisions/0053-mcp-network-transport-egress-security.md). 2.R was off the M3 critical path and
+ [ADR-0052](docs/decisions/0052-inbound-mcp-client-package-lifecycle-registration.md) and [ADR-0053](docs/decisions/0053-mcp-network-transport-egress-security.md). 2.R was off the M3 critical path and
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[ADR-0034](docs/decisions/0034-mcp-client-sdk-dependency.md) + [ADR-0052](docs/decisions/0052-inbound-mcp-client-package-lifecycle-registration.md)
+ [ADR-0053](docs/decisions/0053-mcp-network-transport-egress-security.md). 2.R was off the M3 critical path and
the Phase-3 go/no-go, so it adds capability without gating. The next pickup is **2.J** (the last in-phase additive
[ADR-0034](docs/decisions/0034-mcp-client-sdk-dependency.md) + [ADR-0052](docs/decisions/0052-inbound-mcp-client-package-lifecycle-registration.md) and [ADR-0053](docs/decisions/0053-mcp-network-transport-egress-security.md). 2.R was off the M3 critical path and
the Phase-3 go/no-go, so it adds capability without gating. The next pickup is **2.J** (the last in-phase additive
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 70 - 72, The rendered guide contains a stray diff
marker before the ADR-0053 reference, so update the relevant paragraph in
CLAUDE.md to remove the leading plus sign and keep the ADR links as plain prose.
Locate the line that mentions ADR-0034, ADR-0052, and ADR-0053, and ensure the
text reads cleanly without any leftover patch notation.

Comment thread docs/reference/cli/commands.md Outdated
…-TTY guard, select narrowing

Address the PR #58 review round:

- authoring.ts: add `assertSlugAvailable`, a shared id-uniqueness guard for both
  `create` and `import`. An id is a PROJECT-GLOBAL handle (`resolveById` / `export
  <id>` resolve a bare id across BOTH catalogs), so a slug must be unique across
  agents AND workflows — a same-kind clash needs `--force`, a cross-kind clash is
  always rejected (a forced overwrite of one kind would leave the other file and
  make `export <id>` permanently ambiguous). Replaces the per-kind `slugExists`
  check in create.ts + import.ts, closing the create-both-then-export-ambiguous gap.

- io.ts: add `stdinIsTty` to the `CliIo` seam (wired in `processIo` + `captureIo`);
  `create` now guards on BOTH stdout and stdin being a TTY — a non-TTY stdin makes
  `@clack/prompts` raw-mode setup throw, so it fails loud (exit 2) rather than crash.

- create-prompter.ts: derive the wizard `kind` by matching each option explicitly
  (symmetric with the provider narrowing) instead of a silent default-to-`agent`.

- export.test.ts: parse the `--json` record as `unknown` + `toMatchObject` (drop the
  unsafe `as` cast). create.test.ts: `toHaveLength(1)` for the inline-agent assertion.

- commands.md: document the cross-catalog uniqueness rule, the dual-TTY requirement,
  and the kind-appropriate schema (agent vs workflow) the wizard validates against.

Tests (+3): cross-kind collision rejection in create + import (both `--force` modes),
and the non-TTY-stdin guard. Skipped the CLAUDE.md "stray +" finding — it is the
ADR-joining separator ("ADR-0034 + ADR-0052 + ADR-0053"), not a diff marker.

Toolchain: lint/typecheck/test (719) + build green; Leakwatch 0 findings.

Refs: ADR-0026, ADR-0047

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik
cemililik merged commit cd189f2 into main Jun 28, 2026
9 checks passed
@sonarqubecloud

Copy link
Copy Markdown

cemililik added a commit that referenced this pull request Jun 28, 2026
PR #58 merged 2.J (the create/import/export authoring lifecycle) — the last
in-phase workstream — so the in-phase CLI is feature-complete.

- phase-2-cli.md: flip 2.J ✅ Done (PR #58) with an as-built reconcile note
  (agent-or-minimal-workflow create, re-serialize-from-AST share-safety, project-
  global id uniqueness); mark the phase Status ✅ Complete and reconcile every
  status surface (banner, milestones table, remaining-build-order, pickup queue,
  risks) to "every Phase-2 workstream merged".
- current.md: add the 2.J landed entry, flip the active-phase line to
  feature-complete (v0.1.1), refresh the date. (Forward-looking roadmap reshape is
  left to the concurrent Phase-2.5/2.6 work.)
- README.md: add the authoring lifecycle to the Status section and flip "underway"
  → feature-complete; add a concise agent-first **Getting started** (provider key →
  `relavium chat` → `/export` → `relavium run`) with an honest note that the public
  npm publish is the pending final maintainer step of v0.1.1.
- Release v0.1.1: bump apps/cli/package.json 0.1.0 → 0.1.1, add CHANGELOG.md (0.1.1
  = the three additive lanes 2.S/2.M–2.Q/2.R + 2.J authoring; 0.1.0 = the spine),
  and ship the changelog in the published tarball (files allowlist).

Release flow per release-a-surface.md: tag `v0.1.1` → the `Release CLI` workflow
(pack → cross-OS smoke → publish), maintainer-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant