@brainervirus/workit-core 7.0.0 → 7.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-core",
3
- "version": "7.0.0",
3
+ "version": "7.1.0",
4
4
  "private": false,
5
5
  "description": "Workit shared core — task, policy, evidence, review, decision, worker, and writer state for agentic coding workflows",
6
6
  "keywords": [
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { matchCommitFlavor } from "./commit-flavors";
6
6
  import { gitContext } from "./git";
7
7
  import {
8
+ branchGlobPattern,
8
9
  readConfig,
9
10
  resolveBranchPolicy as resolveConfiguredBranchPolicy,
10
11
  resolveCommitPolicy as resolveConfiguredCommitPolicy,
@@ -15,6 +16,47 @@ import {
15
16
  type WorkspaceCommitPolicy,
16
17
  } from "./workspaces";
17
18
  import { vcsCliIdentity, vcsConfig } from "./vcs-config";
19
+ import {
20
+ ReleaseTrackError,
21
+ namingGlob,
22
+ trackBranches,
23
+ workspaceReleaseTracks,
24
+ } from "./release-tracks";
25
+
26
+ /**
27
+ * Release tracks extend the branch policy: every track's long-lived branches
28
+ * are protected (the union across tracks plus the explicit protected list),
29
+ * and the tracks' naming templates are allowed branch patterns. A track read
30
+ * that must fail closed (a `critical` field) throws, so callers block.
31
+ */
32
+ const withReleaseTracks = (
33
+ workspaceRoot: string,
34
+ policy: ReturnType<typeof resolveConfiguredBranchPolicy>,
35
+ ): ReturnType<typeof resolveConfiguredBranchPolicy> => {
36
+ const read = workspaceReleaseTracks(workspaceRoot);
37
+ if (read.error) throw new ReleaseTrackError(read.error);
38
+ if (read.tracks.length === 0 && read.protectedBranches.length === 0) return policy;
39
+ const known = new Set(policy.allowed.map((pattern) => pattern.source));
40
+ const allowed = [...policy.allowed];
41
+ for (const glob of read.tracks.flatMap((track) => Object.values(track.naming).map(namingGlob))) {
42
+ const pattern = branchGlobPattern(glob);
43
+ if (!known.has(pattern.source)) {
44
+ known.add(pattern.source);
45
+ allowed.push(pattern);
46
+ }
47
+ }
48
+ return {
49
+ ...policy,
50
+ allowed,
51
+ protected: new Set([
52
+ ...policy.protected,
53
+ // Fail-safe: every branch any track entry names, including dropped ones.
54
+ ...[...trackBranches(read.tracks), ...read.protectedBranches].map((name) =>
55
+ name.toLowerCase(),
56
+ ),
57
+ ]),
58
+ };
59
+ };
18
60
 
19
61
  function effectiveWorkspacePolicy(
20
62
  workspaceRoot: string,
@@ -53,9 +95,12 @@ function effectiveWorkspacePolicy(
53
95
  const config = readConfig();
54
96
  return kind === "branch"
55
97
  ? {
56
- branchPolicy: resolveConfiguredBranchPolicy(
57
- config,
58
- selected.policy ? { branchPolicy: selected.policy as WorkspaceBranchPolicy } : null,
98
+ branchPolicy: withReleaseTracks(
99
+ workspaceRoot,
100
+ resolveConfiguredBranchPolicy(
101
+ config,
102
+ selected.policy ? { branchPolicy: selected.policy as WorkspaceBranchPolicy } : null,
103
+ ),
59
104
  ),
60
105
  provenance: { branchPolicy: selected.source },
61
106
  }
@@ -262,10 +307,16 @@ export const verifyPushIdentity = (
262
307
  return { ok: true };
263
308
  };
264
309
  // RL-01: malformed vcs.json blocks branch resolution with an exact-path error.
265
- const baseBranch = (cwd: string): { base: string } | { error: string } => {
310
+ // With release tracks, the base is the resolved track's base branch.
311
+ const baseBranch = (cwd: string): { base: string; trackBranches: string[] } | { error: string } => {
266
312
  const resolved = vcsConfig("resolve", cwd);
267
313
  if (resolved.ok === false) return { error: String(resolved.error) };
268
- return { base: String(resolved.defaultTargetBranch ?? "develop") };
314
+ // Creating a branch on an undetermined release line would guess the base.
315
+ if (resolved.releaseTrack?.blocking) return { error: String(resolved.releaseTrack.blocking) };
316
+ return {
317
+ base: String(resolved.baseBranch ?? resolved.defaultTargetBranch ?? "develop"),
318
+ trackBranches: resolved.releaseTrack ? trackBranches(resolved.releaseTrack.tracks) : [],
319
+ };
269
320
  };
270
321
  const DECLARE_RE = /^\s*\*+Branch:\*+\s*`?([^`\s|]+)`?\s*$/gim;
271
322
  const USE_CURRENT_RE = /^\s*\*+Branch:\*+\s*use-current\s*$/im;
@@ -407,7 +458,13 @@ export const docsBranch = ({
407
458
  if ("error" in baseResolved) return { error: baseResolved.error };
408
459
  const base = baseResolved.base;
409
460
 
410
- if (current === base || current === "main" || current === "master" || current === "develop") {
461
+ if (
462
+ current === base ||
463
+ current === "main" ||
464
+ current === "master" ||
465
+ current === "develop" ||
466
+ baseResolved.trackBranches.includes(current)
467
+ ) {
411
468
  let slug = "";
412
469
  if (plan_path) {
413
470
  const plan = path.isAbsolute(plan_path) ? plan_path : path.join(cwd, plan_path);
@@ -287,6 +287,14 @@ export const describeConfigSource = (
287
287
  }
288
288
  };
289
289
 
290
+ /** The global vcs.json path (WORKFLOW_VCS_CONFIG overrides it). */
291
+ export const vcsConfigPath = (): string =>
292
+ process.env.WORKFLOW_VCS_CONFIG ?? path.join(configDir(), "vcs.json");
293
+
294
+ /** An allowed-branch glob (`feature/*`) as a case-insensitive whole-name RegExp. */
295
+ export const branchGlobPattern = (glob: string): RegExp =>
296
+ new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
297
+
290
298
  export const resolveBranchPolicy = (
291
299
  config: ToolkitConfig,
292
300
  workspace?: { branchPolicy?: Record<string, any> } | null,
@@ -315,9 +323,7 @@ export const resolveBranchPolicy = (
315
323
  },
316
324
  config,
317
325
  );
318
- const allowed = merged.allowed.map(
319
- (p) => new RegExp(`^${p.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i"),
320
- );
326
+ const allowed = merged.allowed.map(branchGlobPattern);
321
327
  return {
322
328
  preset,
323
329
  allowed,
@@ -231,9 +231,14 @@ export function prCreate(env: NodeJS.ProcessEnv, cwd: string): Record<string, an
231
231
 
232
232
  // The caller selects the target; GitHub/GitLab enforce their own protections.
233
233
  const targetOverride = env.WF_PR_TARGET;
234
- const resolvedDefault = String(
235
- cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop",
236
- );
234
+ let resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
235
+ if (!targetOverride) {
236
+ // Release tracks resolve in resolve mode: the target is the branch's line.
237
+ const resolved = vcsConfig("resolve", root);
238
+ if (resolved.ok === false) return { error: String(resolved.error) };
239
+ if (resolved.releaseTrack?.blocking) return { error: String(resolved.releaseTrack.blocking) };
240
+ resolvedDefault = String(resolved.defaultTargetBranch ?? resolvedDefault);
241
+ }
237
242
  const target = targetOverride || resolvedDefault;
238
243
  const title = String(env.WF_PR_TITLE ?? "");
239
244
  const br = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {