@goodtek/vibeops 3.2.0 → 3.3.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to VibeOps are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 3.3.0 - 2026-10-18
8
+
9
+ ### Added
10
+
11
+ - **`task add --repair-baseline`**: open the TASK that repairs a broken baseline even in `enforce` mode (failures shown as warnings for that command only); the TASK file records it (`## Baseline` → Repair) and its `task ship` requires every baseline check to pass, whatever the mode (D-015, TASK-052).
12
+
7
13
  ## 3.2.0 - 2026-10-18
8
14
 
9
15
  ### Fixed
package/README.md CHANGED
@@ -220,6 +220,7 @@ If a configured remote cannot be fetched, `task add` stops without creating a TA
220
220
  - `crew`: persona crew to require; omitted → the crew checks are skipped.
221
221
  - `vibeops status` shows the same checks (`Baseline …`), the crew's pinned catalog commit, and remote branches other than integration / production / `task/*` / `sync/*` (`status --json` → `baseline`).
222
222
  - The block is validated strictly (unknown keys / values are errors and stop the command).
223
+ - **Repairing in `enforce` mode:** `vibeops task add --repair-baseline` opens the TASK anyway (failures shown as warnings for that command only) and writes a `## Baseline` section ("Repair …") into the TASK file; `task ship` of that TASK goes through only when every baseline check passes, whatever `baseline.mode` is.
223
224
 
224
225
  ## Merge gate
225
226
 
package/dist/cli.js CHANGED
@@ -67,9 +67,11 @@ task
67
67
  .option("--dry-run", "Plan only")
68
68
  .option("--non-interactive", "CI: minimal scaffold (optional --idea)")
69
69
  .option("--idea <text>", "Short description (CI)")
70
+ .option("--repair-baseline", "Open a TASK that repairs the baseline even in enforce mode (its ship requires every check to pass)")
70
71
  .option("--cwd <path>", "Target directory")
71
72
  .action(async (opts) => {
72
73
  await taskAddCommand({
74
+ repairBaseline: Boolean(opts.repairBaseline),
73
75
  dryRun: Boolean(opts.dryRun),
74
76
  nonInteractive: Boolean(opts.nonInteractive),
75
77
  idea: opts.idea,
@@ -3,7 +3,7 @@ import { pathExists, writeText } from "../lib/filesystem.js";
3
3
  import { GitConfigError, requireGitConfig } from "../lib/git-config.js";
4
4
  import { ensureIntegrationSynced, printIntegrationSyncDiagnosis, } from "../lib/git-integration-sync.js";
5
5
  import { askInput } from "../lib/inquirer-helpers.js";
6
- import { preflightBaseline } from "../lib/project-baseline.js";
6
+ import { BASELINE_REPAIR_SECTION, BASELINE_REPAIR_TEXT, preflightBaseline, } from "../lib/project-baseline.js";
7
7
  import { bold, cyan, dim, log, yellow } from "../lib/logger.js";
8
8
  import { projectPaths } from "../lib/paths.js";
9
9
  import { slugify } from "../lib/slug.js";
@@ -13,7 +13,7 @@ import { findBlockingTask, relPath } from "../lib/task-context.js";
13
13
  import { allocateTaskId, TaskIdAllocationError } from "../lib/task-id-allocation.js";
14
14
  import { readTaskLockConfig, TaskLockConfigError, TaskLockHeldError, withTaskLock, } from "../lib/task-lock.js";
15
15
  import { isIncompleteTaskStart, startTaskBranch } from "../lib/task-start.js";
16
- import { branchNameForTaskFile, damagedGitContext, loadActionableTasks, readGitContext, } from "../lib/task.js";
16
+ import { branchNameForTaskFile, damagedGitContext, withSectionAfterStatus, loadActionableTasks, readGitContext, } from "../lib/task.js";
17
17
  async function loadGitConfigOrNull(root, dryRun) {
18
18
  try {
19
19
  return await requireGitConfig(root);
@@ -81,7 +81,8 @@ export async function taskAddCommand(opts = {}) {
81
81
  const gitCfg = await loadGitConfigOrNull(root, dryRun);
82
82
  if (!dryRun && gitCfg === null)
83
83
  return;
84
- if (gitCfg !== null && !(await preflightBaseline(root, gitCfg, "task add"))) {
84
+ const repair = opts.repairBaseline === true;
85
+ if (gitCfg !== null && !(await preflightBaseline(root, gitCfg, "task add", { repair }))) {
85
86
  process.exitCode = 1;
86
87
  return;
87
88
  }
@@ -248,9 +249,12 @@ export async function taskAddCommand(opts = {}) {
248
249
  }
249
250
  }
250
251
  const taskId = formatTaskId(taskNumber);
251
- const markdown = taskId === provisionalId
252
+ const drafted = taskId === provisionalId
252
253
  ? draftMarkdown
253
254
  : draftMarkdown.replace(new RegExp(`\\b${provisionalId}\\b`, "g"), taskId);
255
+ const markdown = repair
256
+ ? withSectionAfterStatus(drafted, BASELINE_REPAIR_SECTION, BASELINE_REPAIR_TEXT)
257
+ : drafted;
254
258
  const { filePath } = uniqueTaskPath(paths.docsTasks, taskId, slugify(slug || title), tasks.map((t) => t.filePath));
255
259
  const relFile = relPath(root, filePath);
256
260
  const integration = gitCfg?.integrationBranch ?? "integration";
@@ -1,7 +1,7 @@
1
1
  import { resolve } from "node:path";
2
2
  import { readText } from "../lib/filesystem.js";
3
3
  import { requireGitConfig } from "../lib/git-config.js";
4
- import { preflightBaseline } from "../lib/project-baseline.js";
4
+ import { BASELINE_REPAIR_SECTION, preflightBaseline } from "../lib/project-baseline.js";
5
5
  import { gitCommitsAhead, gitHeadCommit, gitPush, gitRemoteBranchExists, readGitInfo, } from "../lib/git.js";
6
6
  import { askYesNo, isInteractiveSession } from "../lib/inquirer-helpers.js";
7
7
  import { bold, cyan, dim, green, log, yellow } from "../lib/logger.js";
@@ -15,7 +15,7 @@ import { runPostMergeNewCycle } from "../lib/task-new-cycle.js";
15
15
  import { applyTaskShipMemory, fallbackResultSections, llmCompleteTaskShip, writeTaskResultSections, } from "../lib/task-ship-llm.js";
16
16
  import { detectShipPrState, prRefLabel, resolveShipAction, } from "../lib/task-ship-state.js";
17
17
  import { finishTaskWithPullRequest } from "../lib/task-push-pr.js";
18
- import { hasNonEmptySection, parseTaskFilename, gitContextProblem, readGitContext, readTaskFile, updateInlineStatus, } from "../lib/task.js";
18
+ import { hasNonEmptySection, parseTaskFilename, gitContextProblem, readGitContext, readSection, readTaskFile, updateInlineStatus, } from "../lib/task.js";
19
19
  import { assertTaskIdFreeOnRemote } from "../lib/task-id-allocation.js";
20
20
  function describePushFailure(message) {
21
21
  const lower = message.toLowerCase();
@@ -332,7 +332,9 @@ export async function taskShipCommand(taskRef, options = {}) {
332
332
  }
333
333
  log.blank();
334
334
  const baselineGit = await requireGitConfig(cwd).catch(() => null);
335
- if (baselineGit !== null && !(await preflightBaseline(cwd, baselineGit, "task ship"))) {
335
+ const repairTask = readSection(body, BASELINE_REPAIR_SECTION).startsWith("Repair");
336
+ if (baselineGit !== null &&
337
+ !(await preflightBaseline(cwd, baselineGit, "task ship", { requireOk: repairTask }))) {
336
338
  process.exitCode = 1;
337
339
  return;
338
340
  }
@@ -109,38 +109,56 @@ export async function runBaselineChecks(cwd, git, cfg) {
109
109
  }
110
110
  return checks;
111
111
  }
112
+ /** Section `task add --repair-baseline` writes into the TASK file (read back by `task ship`). */
113
+ export const BASELINE_REPAIR_SECTION = "Baseline";
114
+ export const BASELINE_REPAIR_TEXT = "Repair — opened with `vibeops task add --repair-baseline`. `task ship` requires every baseline check to pass (whatever `baseline.mode` is).";
112
115
  /**
113
116
  * Run the configured checks and print failures. Returns false only in `enforce` mode with a
114
- * failing check (the caller stops); `warn` prints and continues; `off` skips.
117
+ * failing check (the caller stops); `warn` prints and continues; `off` skips. See
118
+ * {@link PreflightOptions} for the baseline-repair flow.
115
119
  */
116
- export async function preflightBaseline(cwd, git, command) {
120
+ export async function preflightBaseline(cwd, git, command, opts = {}) {
117
121
  let cfg;
118
122
  try {
119
123
  cfg = await readBaselineConfig(cwd);
120
124
  }
121
125
  catch (e) {
122
- log.error(e instanceof Error ? e.message : String(e));
123
- return false;
126
+ const msg = e instanceof Error ? e.message : String(e);
127
+ if (opts.repair !== true) {
128
+ log.error(msg);
129
+ return false;
130
+ }
131
+ log.warn(`${msg} — continuing with defaults (baseline repair).`);
132
+ cfg = BASELINE_DEFAULTS;
124
133
  }
125
- if (cfg.mode === "off")
134
+ if (cfg.mode === "off" && opts.repair !== true && opts.requireOk !== true)
126
135
  return true;
127
136
  const failed = (await runBaselineChecks(cwd, git, cfg)).filter((c) => !c.ok);
128
- if (failed.length === 0)
137
+ if (failed.length === 0) {
138
+ if (opts.repair === true)
139
+ log.info(dim("baseline: every check passes already (--repair-baseline not needed)."));
129
140
  return true;
130
- const enforce = cfg.mode === "enforce";
141
+ }
142
+ const stop = opts.requireOk === true || (cfg.mode === "enforce" && opts.repair !== true);
131
143
  for (const c of failed) {
132
144
  const line = `baseline ${c.id}: ${c.detail}`;
133
- if (enforce)
145
+ if (stop)
134
146
  log.error(line);
135
147
  else
136
148
  log.warn(line);
137
149
  if (c.fix)
138
150
  log.info(dim(` fix: ${c.fix}`));
139
151
  }
140
- if (enforce) {
141
- log.info(dim(`${command} stopped (.vibeops.json baseline.mode: enforce).`));
152
+ if (opts.requireOk === true) {
153
+ log.info(dim(`${command} stopped: a baseline-repair TASK ships only when every check passes.`));
154
+ return false;
155
+ }
156
+ if (stop) {
157
+ log.info(dim(`${command} stopped (.vibeops.json baseline.mode: enforce). To repair: vibeops task add --repair-baseline`));
142
158
  return false;
143
159
  }
160
+ if (opts.repair === true)
161
+ log.info(dim("baseline repair: failures reported as warnings for this TASK."));
144
162
  return true;
145
163
  }
146
164
  /** Read-only summary for `vibeops status` (never throws). */
package/dist/lib/task.js CHANGED
@@ -335,6 +335,12 @@ function insertSectionAfter(body, afterTitle, newTitle, newContent) {
335
335
  const after = lines.slice(block.end);
336
336
  return [...before, ...newSection, ...after].join("\n");
337
337
  }
338
+ /** Add (or replace) a `## <title>` section right after `## Status`. */
339
+ export function withSectionAfterStatus(body, title, content) {
340
+ if (locateSection(body, title) !== null)
341
+ return replaceSectionContent(body, title, content);
342
+ return insertSectionAfter(body, "Status", title, content);
343
+ }
338
344
  export async function updateInlineStatus(filePath, next) {
339
345
  const raw = await readText(filePath);
340
346
  const display = statusDisplay(next);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodtek/vibeops",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Minimal CLI rails for Cursor-based TASK-driven vibe coding.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.15.9",