Skip to content

Commit 4ad5e16

Browse files
UnbreakableMJclaude
andcommitted
feat(construct-cli): Phase 4 — skill ship (edit -> commit -> push -> sync)
Adds `construct skill ship`: detects local skill edits in the construct clone, ENFORCES the .zip/.skill bundling discipline (refuses a skill-dir change whose bundles weren't rebuilt, exit 5 with the exact rebuild command — it does not rebuild them), stages shipped paths explicitly by name (never git add -A), makes a signed UTC commit via the repo's gitway config, pushes origin main, then runs skill sync. All git ops shell out to system git. Validates the origin is the Construct remote. flake.nix adds git to nativeCheckInputs for the ship tests. 20 tests, clippy -D warnings, reuse lint, nix build all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e816685 commit 4ad5e16

7 files changed

Lines changed: 798 additions & 25 deletions

File tree

construct-cli/src/cli.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ pub(crate) enum SkillCommand {
190190
after_help = "Examples:\n construct skill sync\n construct skill sync --json\n construct skill sync --flake-dir /etc/nixos --dry-run"
191191
)]
192192
Sync(SyncArgs),
193+
194+
/// Ship local skill edits: commit (signed) + push, then sync.
195+
#[command(
196+
after_help = "Examples:\n construct skill ship --dry-run\n construct skill ship --skills spacecraft-rust-guidelines\n construct skill ship --message \"docs: clarify X\" --json"
197+
)]
198+
Ship(ShipArgs),
193199
}
194200

195201
/// Arguments for `construct skill add` / `construct skill update`.
@@ -284,6 +290,31 @@ pub(crate) struct SyncArgs {
284290
pub(crate) flake_dir: Option<PathBuf>,
285291
}
286292

293+
/// Arguments for `construct skill ship`.
294+
#[derive(Debug, Args)]
295+
pub(crate) struct ShipArgs {
296+
/// The Construct clone to ship from (default: the local catalogue clone).
297+
#[arg(long, value_name = "DIR")]
298+
pub(crate) repo: Option<PathBuf>,
299+
300+
/// Restrict to these skills (comma-separated); defaults to all changed.
301+
#[arg(
302+
short = 's',
303+
long = "skills",
304+
value_delimiter = ',',
305+
value_name = "NAME[,NAME...]"
306+
)]
307+
pub(crate) skills: Vec<String>,
308+
309+
/// Commit message subject (default: derived from shipped skills).
310+
#[arg(short = 'm', long = "message", value_name = "MSG")]
311+
pub(crate) message: Option<String>,
312+
313+
/// Commit and push but skip the final `skill sync` step.
314+
#[arg(long)]
315+
pub(crate) no_sync: bool,
316+
}
317+
287318
/// Render a clap parse outcome (help, version, or error) and return the exit
288319
/// code to use. Help and version are successes; everything else is a usage
289320
/// error reported structurally in machine mode (Standard §1 item 8).

construct-cli/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
pub(crate) mod agent;
1212
pub(crate) mod describe;
1313
pub(crate) mod schema;
14+
pub(crate) mod ship;
1415
pub(crate) mod skill;
1516
pub(crate) mod sync;
1617

@@ -33,6 +34,7 @@ pub(crate) fn dispatch(cli: &Cli, ctx: &Context) -> Result<Option<CommandOutput>
3334
SkillCommand::Remove(args) => skill::remove(ctx, args).map(Some),
3435
SkillCommand::Update(args) => skill::update(ctx, args).map(Some),
3536
SkillCommand::Sync(args) => sync::run(ctx, args).map(Some),
37+
SkillCommand::Ship(args) => ship::run(ctx, args).map(Some),
3638
},
3739
Some(Command::Agent { verb }) => match verb {
3840
AgentCommand::List => Ok(Some(agent::list(ctx))),

0 commit comments

Comments
 (0)