sequant 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "sequant",
10
10
  "description": "AI coding agent orchestrator for Claude Code — resolve GitHub issues end-to-end with isolated git worktrees, quality gates, and an MCP server. Includes 17 skills, workflow MCP tools, and pre/post-tool hooks.",
11
- "version": "2.6.2",
11
+ "version": "2.7.0",
12
12
  "author": {
13
13
  "name": "sequant-io",
14
14
  "email": "hello@sequant.io"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "AI coding agent orchestrator for Claude Code — resolve GitHub issues end-to-end with isolated git worktrees and quality gates, through spec → exec → qa phases.",
4
- "version": "2.6.2",
4
+ "version": "2.7.0",
5
5
  "author": {
6
6
  "name": "sequant-io",
7
7
  "email": "hello@sequant.io"
package/README.md CHANGED
@@ -16,6 +16,10 @@ AI coding agents write code well, but leave you to run the workflow around it
16
16
 
17
17
  See the [CHANGELOG](CHANGELOG.md) for release notes, or the [migration guide](CHANGELOG.md#migration-from-v1x) if upgrading from v1.x.
18
18
 
19
+ ### What's new in 2.7
20
+
21
+ - **Trustworthy `--dry-run` previews for `sync` and `update`** — `sequant sync --dry-run` (`-d`) previews the exact set the apply would write (`new` + `modified` + `local-override`) and mutates nothing. Both `sync --dry-run` and `update --dry-run` now exit non-zero when work is pending, so a CI/automation job can gate on the exit code instead of parsing stdout. (`update` is the interactive command; `sync` is the documented non-interactive/CI surface.)
22
+
19
23
  ### What's new in 2.6
20
24
 
21
25
  - **Boxed Ink TUI is the default for `sequant run`** — on a TTY, `run` now renders the boxed dashboard by default (matching `sequant ready`). Opt out with `--no-tui` (line renderer) or `-s`/`--quiet` (heartbeat-only); non-TTY output auto-degrades.
package/dist/bin/cli.js CHANGED
@@ -135,6 +135,7 @@ program
135
135
  .description("Sync skills and templates from the Sequant package (non-interactive)")
136
136
  .option("-f, --force", "Sync even if versions match")
137
137
  .option("-q, --quiet", "Suppress output")
138
+ .option("-d, --dry-run", "Show what sync would write without making changes (exits non-zero if work is pending)")
138
139
  .action(syncCommand);
139
140
  program
140
141
  .command("doctor")
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "AI coding agent orchestrator for Claude Code — resolve GitHub issues end-to-end with isolated git worktrees and quality gates, through spec → exec → qa phases.",
4
- "version": "2.6.2",
4
+ "version": "2.7.0",
5
5
  "author": {
6
6
  "name": "sequant-io",
7
7
  "email": "hello@sequant.io"
@@ -7,6 +7,7 @@
7
7
  interface SyncOptions {
8
8
  force?: boolean;
9
9
  quiet?: boolean;
10
+ dryRun?: boolean;
10
11
  }
11
12
  /**
12
13
  * Get the version of skills currently installed
@@ -181,7 +181,7 @@ async function updateSkillsVersion() {
181
181
  await writeFile(SKILLS_VERSION_PATH, getPackageVersion());
182
182
  }
183
183
  export async function syncCommand(options = {}) {
184
- const { force = false, quiet = false } = options;
184
+ const { force = false, quiet = false, dryRun = false } = options;
185
185
  if (!quiet) {
186
186
  console.log(chalk.blue("\nSyncing templates...\n"));
187
187
  console.log(chalk.yellow("Note: For seamless auto-updates, install sequant as a Claude Code plugin:\n" +
@@ -231,6 +231,61 @@ export async function syncCommand(options = {}) {
231
231
  process.exitCode = 1;
232
232
  return;
233
233
  }
234
+ // Preview path: report exactly what the apply would write, then stop without
235
+ // mutating (#722). This branch is only reached when `force` is set or the
236
+ // version marker mismatches — i.e. the path that runs `copyTemplates(force:
237
+ // true)` and rewrites the whole tree. (A matching-version, non-force dry-run
238
+ // already returned at the report-only short-circuit above, which never
239
+ // mutates.) `copyTemplates` does NOT protect in-place customizations the way
240
+ // `update` does — the force copy overwrites them — so the preview counts
241
+ // `local-override` files alongside `new`/`modified`. Reporting only
242
+ // new+modified would under-report the write-set, the exact divergence #722
243
+ // is about.
244
+ if (dryRun) {
245
+ const changes = await computeTemplateChanges(manifest.stack, tokens);
246
+ const newFiles = changes.filter((c) => c.status === "new");
247
+ const modifiedFiles = changes.filter((c) => c.status === "modified");
248
+ const localOverrides = changes.filter((c) => c.status === "local-override");
249
+ const toWrite = [...newFiles, ...modifiedFiles, ...localOverrides];
250
+ if (!quiet) {
251
+ console.log(chalk.bold("Summary (dry-run):"));
252
+ console.log(chalk.green(` New files: ${newFiles.length}`));
253
+ console.log(chalk.yellow(` Modified: ${modifiedFiles.length}`));
254
+ console.log(chalk.blue(` Local overrides (overwritten by sync): ${localOverrides.length}`));
255
+ if (modifiedFiles.length > 0) {
256
+ console.log(chalk.bold("\nModified files:"));
257
+ for (const file of modifiedFiles) {
258
+ console.log(chalk.yellow(` ${file.path}`));
259
+ }
260
+ }
261
+ if (newFiles.length > 0) {
262
+ console.log(chalk.bold("\nNew files:"));
263
+ for (const file of newFiles) {
264
+ console.log(chalk.green(` ${file.path}`));
265
+ }
266
+ }
267
+ if (localOverrides.length > 0) {
268
+ console.log(chalk.bold("\nLocal overrides (will be overwritten by sync):"));
269
+ for (const file of localOverrides) {
270
+ console.log(chalk.blue(` ${file.path}`));
271
+ }
272
+ }
273
+ if (toWrite.length === 0) {
274
+ console.log(chalk.green("\n✔ Skills are already up to date!"));
275
+ }
276
+ else {
277
+ console.log(chalk.gray("\n(dry-run mode - no changes made)"));
278
+ }
279
+ }
280
+ // Non-zero exit when work is pending so the documented preview surface can
281
+ // gate CI/automation (the #709 intent): a dry-run reporting nothing must
282
+ // mean nothing to do. The matching-version short-circuit signals drift the
283
+ // same way.
284
+ if (toWrite.length > 0) {
285
+ process.exitCode = 1;
286
+ }
287
+ return;
288
+ }
234
289
  // Copy templates with force to overwrite existing files
235
290
  const copyOptions = {
236
291
  force: true, // Always overwrite when syncing
@@ -167,6 +167,13 @@ export async function updateCommand(options) {
167
167
  }
168
168
  if (options.dryRun) {
169
169
  console.log(chalk.gray("\n(dry-run mode - no changes made)"));
170
+ // Non-zero exit when work is pending so a CI/automation job can gate on the
171
+ // preview, matching `sync --dry-run` (#724 / #709 intent): a dry-run that
172
+ // reports nothing must mean nothing to do. The no-op case short-circuits at
173
+ // the "Everything is up to date!" return above, so it correctly stays 0.
174
+ if (applySet.length > 0) {
175
+ process.exitCode = 1;
176
+ }
170
177
  return;
171
178
  }
172
179
  // Confirm update. --yes and --force both auto-confirm; otherwise we need a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "2.6.2",
3
+ "version": "2.7.0",
4
4
  "description": "AI coding agent orchestrator — resolve GitHub issues end-to-end with isolated git worktrees, quality gates, and an MCP server. Works with Claude Code or Aider.",
5
5
  "type": "module",
6
6
  "bin": {