@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 +6 -0
- package/README.md +1 -0
- package/dist/cli.js +2 -0
- package/dist/commands/task-add.js +8 -4
- package/dist/commands/task-ship.js +5 -3
- package/dist/lib/project-baseline.js +28 -10
- package/dist/lib/task.js +6 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
141
|
-
log.info(dim(`${command} stopped
|
|
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);
|