@brainervirus/workit-core 0.6.0 → 0.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.
- package/package.json +10 -14
- package/scripts/doctor-check.ts +20 -0
- package/scripts/install-cursor-plugin.sh +51 -28
- package/scripts/install-opencode-plugin.sh +25 -22
- package/scripts/rewrite-workspace-deps.ts +15 -9
- package/scripts/sync-runtime.sh +71 -19
- package/scripts/vendor-assets.ts +37 -0
- package/skills/wk-implement/SKILL.md +2 -2
- package/skills/wk-pr/SKILL.md +1 -1
- package/src/core/boundary.ts +27 -0
- package/src/core/branch-policy.ts +63 -0
- package/src/core/branch.ts +166 -57
- package/src/core/changelog.ts +17 -14
- package/src/core/config-guard.ts +9 -2
- package/src/core/config.ts +227 -32
- package/src/core/detector.ts +22 -11
- package/src/core/docs-layout.ts +251 -0
- package/src/core/docs-migration.ts +639 -0
- package/src/core/docs-repo.ts +58 -21
- package/src/core/docs-validate.ts +181 -43
- package/src/core/doctor.ts +801 -0
- package/src/core/flow-state.ts +1579 -137
- package/src/core/git.ts +22 -5
- package/src/core/gitignore.ts +11 -2
- package/src/core/handoff-context.ts +18 -5
- package/src/{tools/handoff.ts → core/handoff-tools.ts} +16 -63
- package/src/core/hygiene.ts +53 -17
- package/src/core/init.ts +128 -31
- package/src/core/logger.ts +321 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/parse-sections.ts +2 -2
- package/src/core/plan-tasks.ts +13 -3
- package/src/core/ports/init-toolkit-status.ts +1 -1
- package/src/core/ports/vcs-verify-token.ts +1 -1
- package/src/core/ports/youtrack-api.ts +4 -2
- package/src/core/ports/youtrack-config.ts +1 -3
- package/src/core/ports/youtrack-verify-token.ts +1 -1
- package/src/core/pr-create.ts +156 -29
- package/src/core/present.ts +11 -2
- package/src/core/registration.ts +215 -0
- package/src/core/reminder.ts +1 -2
- package/src/core/repo-context.ts +447 -0
- package/src/core/repo-tool.ts +4 -1
- package/src/core/repo-tools.ts +23 -0
- package/src/core/rules.ts +10 -7
- package/src/core/safe-write.ts +22 -0
- package/src/core/scripts.ts +3 -39
- package/src/core/sdd.ts +56 -31
- package/src/core/setup-state.ts +54 -0
- package/src/core/setup.ts +1216 -0
- package/src/core/skill-manifests.ts +95 -0
- package/src/core/support-matrix.ts +12 -0
- package/src/core/sync-runtime.ts +348 -0
- package/src/core/templates.ts +16 -6
- package/src/core/vcs-config.ts +191 -62
- package/src/core/verify-parse.ts +4 -2
- package/src/core/verify-project.ts +181 -0
- package/src/core/workspaces.ts +136 -17
- package/src/core/youtrack-tools.ts +228 -0
- package/src/core/youtrack.ts +320 -87
- package/src/core.ts +18 -3
- package/templates/execution-contract.md +9 -7
- package/templates/superpowers-doc-contract.md +5 -4
- package/scripts/_shared/common.sh +0 -158
- package/scripts/changelog-context.sh +0 -42
- package/scripts/docs-refresh-context.sh +0 -40
- package/scripts/init/apply.sh +0 -5
- package/scripts/init/status.sh +0 -5
- package/scripts/init/toolkit-status.sh +0 -5
- package/scripts/pr-create.sh +0 -5
- package/scripts/pr-ready-context.sh +0 -88
- package/scripts/present/ascii-wireframe.sh +0 -5
- package/scripts/present/flow-diagram.sh +0 -5
- package/scripts/release-notes-context.sh +0 -40
- package/scripts/vcs/config.sh +0 -5
- package/scripts/vcs/merged-style.sh +0 -5
- package/scripts/vcs/token-create-urls.sh +0 -5
- package/scripts/vcs/verify-token.sh +0 -5
- package/scripts/verify-project.sh +0 -140
- package/scripts/youtrack/api.sh +0 -5
- package/scripts/youtrack/config.sh +0 -5
- package/scripts/youtrack/greeting.sh +0 -5
- package/scripts/youtrack/parse-duration.sh +0 -5
- package/scripts/youtrack/token-create-url.sh +0 -5
- package/scripts/youtrack/verify-token.sh +0 -5
- package/scripts/youtrack/work-date-ms.sh +0 -5
- package/src/tools/docs-repo.ts +0 -42
- package/src/tools/flow.ts +0 -88
- package/src/tools/index.ts +0 -22
- package/src/tools/present.ts +0 -45
- package/src/tools/repo.ts +0 -357
- package/src/tools/rules.ts +0 -30
- package/src/tools/sdd.ts +0 -189
- package/src/tools/templates.ts +0 -27
- package/src/tools/youtrack.ts +0 -360
package/src/core/branch.ts
CHANGED
|
@@ -3,22 +3,46 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { gitContext } from "./git";
|
|
5
5
|
import { readConfig, resolveBranchPolicy } from "./config";
|
|
6
|
+
import { resolveWorkspace } from "./workspaces";
|
|
7
|
+
import { vcsConfig } from "./vcs-config";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
9
|
+
/** CA-09: the one policy resolver every consumer calls. */
|
|
10
|
+
export const resolveBranchPolicyFor = (workspaceRoot: string) =>
|
|
11
|
+
resolveBranchPolicy(readConfig(), resolveWorkspace(workspaceRoot));
|
|
12
|
+
|
|
13
|
+
const policy = (root: string) => resolveBranchPolicyFor(root);
|
|
14
|
+
const allowedBranch = (root: string, name: string) =>
|
|
15
|
+
policy(root).allowed.some((r) => r.test(name));
|
|
16
|
+
const isProtected = (root: string, name: string) => policy(root).protected.has(name.toLowerCase());
|
|
17
|
+
// RL-01: malformed vcs.json blocks branch resolution with an exact-path error.
|
|
18
|
+
const baseBranch = (cwd: string): { base: string } | { error: string } => {
|
|
19
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
20
|
+
if (resolved.ok === false) return { error: String(resolved.error) };
|
|
21
|
+
return { base: String(resolved.defaultTargetBranch ?? "develop") };
|
|
22
|
+
};
|
|
10
23
|
const DECLARE_RE = /^\s*\*+Branch:\*+\s*`?([^`\s|]+)`?\s*$/gim;
|
|
11
24
|
const USE_CURRENT_RE = /^\s*\*+Branch:\*+\s*use-current\s*$/im;
|
|
12
25
|
const readSafe = (p: string): string | null => {
|
|
13
|
-
try {
|
|
26
|
+
try {
|
|
27
|
+
return readFileSync(p, "utf8");
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
14
31
|
};
|
|
15
32
|
|
|
16
|
-
const normalizeBranch = (name: string): string | null => {
|
|
33
|
+
const normalizeBranch = (root: string, name: string): string | null => {
|
|
17
34
|
const n = name.trim().replace(/`/g, "").replace(/\.+$/, "");
|
|
18
|
-
if (isProtected(n)) return null;
|
|
19
|
-
if (!allowedBranch(n)) return null;
|
|
20
|
-
const parts = n
|
|
21
|
-
|
|
35
|
+
if (isProtected(root, n)) return null;
|
|
36
|
+
if (!allowedBranch(root, n)) return null;
|
|
37
|
+
const parts = n
|
|
38
|
+
.toLowerCase()
|
|
39
|
+
.split("/")
|
|
40
|
+
.map((p) =>
|
|
41
|
+
p
|
|
42
|
+
.replace(/[^\w.-]+/g, "-")
|
|
43
|
+
.replace(/^-+|-+$/g, "")
|
|
44
|
+
.replace(/-{2,}/g, "-"),
|
|
45
|
+
);
|
|
22
46
|
if (parts.some((p) => !p)) return null;
|
|
23
47
|
return parts.join("/");
|
|
24
48
|
};
|
|
@@ -29,14 +53,21 @@ const deriveSlug = (planPath: string): string => {
|
|
|
29
53
|
return dirName === "." || dirName === "/" || dirName === "" ? "" : dirName;
|
|
30
54
|
};
|
|
31
55
|
|
|
32
|
-
const deriveKind = (
|
|
56
|
+
const deriveKind = (
|
|
57
|
+
planPath: string,
|
|
58
|
+
fallback: "feature" | "bugfix" = "feature",
|
|
59
|
+
): "feature" | "bugfix" => {
|
|
33
60
|
const slug = deriveSlug(planPath);
|
|
34
61
|
const text = readSafe(planPath) ?? "";
|
|
35
62
|
let kind = fallback;
|
|
36
63
|
if (/\bbugfix\b/i.test(slug) || /^fix-/i.test(slug)) {
|
|
37
64
|
kind = "bugfix";
|
|
38
65
|
} else {
|
|
39
|
-
const goal =
|
|
66
|
+
const goal =
|
|
67
|
+
text
|
|
68
|
+
.split("\n")
|
|
69
|
+
.find((line) => line.startsWith("**Goal:**"))
|
|
70
|
+
?.toLowerCase() ?? "";
|
|
40
71
|
if (/\b(bugfix|bug fix)\b/.test(goal) && !/\b(feat|feature|upgrade|add)\b/.test(goal)) {
|
|
41
72
|
kind = "bugfix";
|
|
42
73
|
}
|
|
@@ -49,7 +80,11 @@ export const resolveBranch = ({
|
|
|
49
80
|
spec_path,
|
|
50
81
|
plan_path,
|
|
51
82
|
workspace_root,
|
|
52
|
-
}: {
|
|
83
|
+
}: {
|
|
84
|
+
spec_path: string;
|
|
85
|
+
plan_path: string;
|
|
86
|
+
workspace_root: string;
|
|
87
|
+
}) => {
|
|
53
88
|
const cwd = path.resolve(workspace_root);
|
|
54
89
|
const abs = (p: string) => (path.isAbsolute(p) ? p : path.join(cwd, p));
|
|
55
90
|
const spec = abs(spec_path);
|
|
@@ -69,27 +104,30 @@ export const resolveBranch = ({
|
|
|
69
104
|
const text = readSafe(file);
|
|
70
105
|
if (!text) continue;
|
|
71
106
|
if (USE_CURRENT_RE.test(text)) {
|
|
72
|
-
if (!current || !allowedBranch(current) || isProtected(current)) {
|
|
107
|
+
if (!current || !allowedBranch(cwd, current) || isProtected(cwd, current)) {
|
|
73
108
|
return { error: `use-current but HEAD ${current} is not an allowed branch` };
|
|
74
109
|
}
|
|
75
110
|
return finish(current, "use-current");
|
|
76
111
|
}
|
|
77
112
|
}
|
|
78
113
|
|
|
79
|
-
if (current && allowedBranch(current) && !isProtected(
|
|
114
|
+
if (current && allowedBranch(cwd, current) && !isProtected(cwd, current))
|
|
115
|
+
return finish(current, "keep-current");
|
|
80
116
|
|
|
81
117
|
let declaredButInvalid: string | null = null;
|
|
82
118
|
for (const file of [spec, plan]) {
|
|
83
119
|
const text = readSafe(file);
|
|
84
120
|
if (!text) continue;
|
|
85
121
|
for (const match of text.matchAll(DECLARE_RE)) {
|
|
86
|
-
const normalized = normalizeBranch(match[1]);
|
|
122
|
+
const normalized = normalizeBranch(cwd, match[1]);
|
|
87
123
|
if (normalized) return finish(normalized, file === spec ? "spec" : "plan");
|
|
88
124
|
declaredButInvalid ??= match[1];
|
|
89
125
|
}
|
|
90
126
|
}
|
|
91
127
|
if (declaredButInvalid) {
|
|
92
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
error: `declared branch ${JSON.stringify(declaredButInvalid)} is not allowed by the branch policy`,
|
|
130
|
+
};
|
|
93
131
|
}
|
|
94
132
|
|
|
95
133
|
const slug = deriveSlug(plan);
|
|
@@ -103,55 +141,103 @@ export const docsBranch = ({
|
|
|
103
141
|
plan_path,
|
|
104
142
|
kind,
|
|
105
143
|
workspace_root,
|
|
106
|
-
}: {
|
|
144
|
+
}: {
|
|
145
|
+
plan_path?: string;
|
|
146
|
+
kind?: string;
|
|
147
|
+
workspace_root: string;
|
|
148
|
+
}) => {
|
|
107
149
|
const cwd = path.resolve(workspace_root);
|
|
108
150
|
const git = gitContext(cwd);
|
|
109
151
|
const current = git.branch;
|
|
110
152
|
const kindArg = (kind ?? "feature").toLowerCase();
|
|
153
|
+
const baseResolved = baseBranch(cwd);
|
|
154
|
+
if ("error" in baseResolved) return { error: baseResolved.error };
|
|
155
|
+
const base = baseResolved.base;
|
|
111
156
|
|
|
112
|
-
if (current
|
|
113
|
-
return { branch: current, action: "keep", current_branch: current, base: "develop", dirty: Boolean(git.status_short.trim()) };
|
|
114
|
-
}
|
|
115
|
-
if (current === "main" || current === "master" || current === "develop") {
|
|
157
|
+
if (current === base || current === "main" || current === "master" || current === "develop") {
|
|
116
158
|
let slug = "";
|
|
117
159
|
if (plan_path) {
|
|
118
160
|
const plan = path.isAbsolute(plan_path) ? plan_path : path.join(cwd, plan_path);
|
|
119
161
|
slug = deriveSlug(plan);
|
|
120
162
|
}
|
|
121
163
|
if (!slug) {
|
|
122
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
error: "plan_path required to derive branch slug when not on feature/* or bugfix/*",
|
|
166
|
+
};
|
|
123
167
|
}
|
|
124
168
|
const branchKind = kindArg === "bugfix" ? "bugfix" : "feature";
|
|
125
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
branch: `${branchKind}/${slug}`,
|
|
171
|
+
action: base === "develop" ? "create_from_develop" : "create_from_base",
|
|
172
|
+
current_branch: current,
|
|
173
|
+
base,
|
|
174
|
+
dirty: Boolean(git.status_short.trim()),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (current && allowedBranch(cwd, current) && !isProtected(cwd, current)) {
|
|
178
|
+
return {
|
|
179
|
+
branch: current,
|
|
180
|
+
action: "keep",
|
|
181
|
+
current_branch: current,
|
|
182
|
+
base,
|
|
183
|
+
dirty: Boolean(git.status_short.trim()),
|
|
184
|
+
};
|
|
126
185
|
}
|
|
127
186
|
return { error: `cannot resolve docs branch from HEAD ${JSON.stringify(current)}` };
|
|
128
187
|
};
|
|
129
188
|
|
|
130
189
|
// Port of scripts/lib/ensure-develop-base.sh
|
|
131
|
-
export const
|
|
190
|
+
export const ensureBaseBranch = (cwd: string, base: string): { ok: boolean; error?: string } => {
|
|
132
191
|
const git = gitContext(cwd);
|
|
133
|
-
if (!git.branch || git.branch === "unknown")
|
|
134
|
-
|
|
192
|
+
if (!git.branch || git.branch === "unknown")
|
|
193
|
+
return { ok: false, error: "not in a git repository" };
|
|
194
|
+
const run = (args: string[]) =>
|
|
195
|
+
execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
135
196
|
try {
|
|
136
197
|
try {
|
|
137
|
-
run(["fetch", "origin",
|
|
198
|
+
run(["fetch", "origin", base, "--prune"]);
|
|
138
199
|
} catch {
|
|
139
200
|
run(["fetch", "origin", "--prune"]);
|
|
140
201
|
}
|
|
141
|
-
let
|
|
142
|
-
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
202
|
+
let hasOriginBase = true;
|
|
203
|
+
try {
|
|
204
|
+
execFileSync("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${base}`], {
|
|
205
|
+
cwd,
|
|
206
|
+
stdio: "pipe",
|
|
207
|
+
});
|
|
208
|
+
} catch {
|
|
209
|
+
hasOriginBase = false;
|
|
210
|
+
}
|
|
211
|
+
if (!hasOriginBase)
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
error: `origin/${base} missing — push ${base} before creating feature/* or bugfix/* branches`,
|
|
215
|
+
};
|
|
216
|
+
let hasLocalBase = true;
|
|
217
|
+
try {
|
|
218
|
+
execFileSync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${base}`], {
|
|
219
|
+
cwd,
|
|
220
|
+
stdio: "pipe",
|
|
221
|
+
});
|
|
222
|
+
} catch {
|
|
223
|
+
hasLocalBase = false;
|
|
224
|
+
}
|
|
225
|
+
if (hasLocalBase) {
|
|
226
|
+
run(["checkout", base]);
|
|
227
|
+
try {
|
|
228
|
+
run(["merge", "--ff-only", `origin/${base}`]);
|
|
229
|
+
} catch {
|
|
230
|
+
/* non-fast-forward: keep local */
|
|
231
|
+
}
|
|
149
232
|
} else {
|
|
150
|
-
run(["checkout", "-b",
|
|
233
|
+
run(["checkout", "-b", base, "--track", `origin/${base}`]);
|
|
151
234
|
}
|
|
152
235
|
return { ok: true };
|
|
153
236
|
} catch (error) {
|
|
154
|
-
return {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
error: error instanceof Error ? error.message : "ensure-base-branch failed",
|
|
240
|
+
};
|
|
155
241
|
}
|
|
156
242
|
};
|
|
157
243
|
|
|
@@ -162,17 +248,29 @@ export const branchSetup = ({
|
|
|
162
248
|
target_branch,
|
|
163
249
|
stash,
|
|
164
250
|
workspace_root,
|
|
165
|
-
}: {
|
|
251
|
+
}: {
|
|
252
|
+
action?: string;
|
|
253
|
+
sdd_dir?: string;
|
|
254
|
+
target_branch?: string;
|
|
255
|
+
stash?: string;
|
|
256
|
+
workspace_root: string;
|
|
257
|
+
}) => {
|
|
166
258
|
const cwd = path.resolve(workspace_root);
|
|
167
259
|
const exec = (args: string[]): string =>
|
|
168
260
|
execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
169
261
|
const current = gitContext(cwd).branch;
|
|
170
262
|
if (!current || current === "unknown") return { error: "not in a git repository" };
|
|
171
263
|
const sdd = sdd_dir ?? "docs";
|
|
172
|
-
const manifestPath = path.isAbsolute(sdd)
|
|
264
|
+
const manifestPath = path.isAbsolute(sdd)
|
|
265
|
+
? path.join(sdd, "manifest.json")
|
|
266
|
+
: path.join(cwd, sdd, "manifest.json");
|
|
173
267
|
mkdirSync(path.dirname(manifestPath), { recursive: true, mode: 0o755 });
|
|
174
268
|
const readManifest = (): Record<string, unknown> => {
|
|
175
|
-
try {
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
271
|
+
} catch {
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
176
274
|
};
|
|
177
275
|
const writeManifest = (data: Record<string, unknown>) =>
|
|
178
276
|
writeFileSync(manifestPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
@@ -181,7 +279,9 @@ export const branchSetup = ({
|
|
|
181
279
|
const manifest = readManifest();
|
|
182
280
|
const ref = manifest.stash_ref;
|
|
183
281
|
if (!ref) return { error: "no stash_ref in manifest" };
|
|
184
|
-
try {
|
|
282
|
+
try {
|
|
283
|
+
exec(["stash", "pop", String(ref)]);
|
|
284
|
+
} catch (error) {
|
|
185
285
|
return { error: error instanceof Error ? error.message : "stash pop failed" };
|
|
186
286
|
}
|
|
187
287
|
delete manifest.stash_ref;
|
|
@@ -192,15 +292,19 @@ export const branchSetup = ({
|
|
|
192
292
|
|
|
193
293
|
const target = target_branch ?? "";
|
|
194
294
|
if (!target) return { error: "target branch required" };
|
|
195
|
-
if (isProtected(target)) return { error: `protected branch ${target}` };
|
|
196
|
-
if (!allowedBranch(target))
|
|
295
|
+
if (isProtected(cwd, target)) return { error: `protected branch ${target}` };
|
|
296
|
+
if (!allowedBranch(cwd, target))
|
|
297
|
+
return { error: `target branch ${target} is not allowed by the branch policy` };
|
|
197
298
|
|
|
198
299
|
let stash_ref: string | undefined;
|
|
199
300
|
if (current !== target) {
|
|
200
301
|
const dirty = Boolean(gitContext(cwd).status_short.trim());
|
|
201
302
|
if (dirty) {
|
|
202
303
|
if (stash !== "yes") {
|
|
203
|
-
return {
|
|
304
|
+
return {
|
|
305
|
+
error:
|
|
306
|
+
"dirty working tree — ask with native question, then call workflow_branch_setup with stash=yes",
|
|
307
|
+
};
|
|
204
308
|
}
|
|
205
309
|
try {
|
|
206
310
|
exec(["stash", "push", "-u", "-m", `workit: pre-checkout ${target}`, "--", ":!docs/*/sdd"]);
|
|
@@ -214,22 +318,20 @@ export const branchSetup = ({
|
|
|
214
318
|
} catch (error) {
|
|
215
319
|
const message = error instanceof Error ? error.message : "checkout failed";
|
|
216
320
|
if (/worktree/i.test(message)) {
|
|
217
|
-
return {
|
|
321
|
+
return {
|
|
322
|
+
error: `branch ${target} is locked by an existing git worktree — remove it first (we do not use worktrees)`,
|
|
323
|
+
};
|
|
218
324
|
}
|
|
219
325
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!baseResult.ok) return { error: baseResult.error };
|
|
225
|
-
} else {
|
|
226
|
-
// Already on develop or another feature branch: still require origin/develop to exist.
|
|
227
|
-
const baseResult = ensureDevelopBase(cwd);
|
|
228
|
-
if (!baseResult.ok) return { error: baseResult.error };
|
|
229
|
-
}
|
|
326
|
+
const baseResolved = baseBranch(cwd);
|
|
327
|
+
if ("error" in baseResolved) return { error: baseResolved.error };
|
|
328
|
+
const baseResult = ensureBaseBranch(cwd, baseResolved.base);
|
|
329
|
+
if (!baseResult.ok) return { error: baseResult.error };
|
|
230
330
|
exec(["checkout", "-b", target]);
|
|
231
331
|
} catch (createError) {
|
|
232
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
error: createError instanceof Error ? createError.message : "branch create failed",
|
|
334
|
+
};
|
|
233
335
|
}
|
|
234
336
|
}
|
|
235
337
|
}
|
|
@@ -242,5 +344,12 @@ export const branchSetup = ({
|
|
|
242
344
|
manifest.stash_created_at = new Date().toISOString();
|
|
243
345
|
}
|
|
244
346
|
writeManifest(manifest);
|
|
245
|
-
return {
|
|
347
|
+
return {
|
|
348
|
+
action: "setup",
|
|
349
|
+
ok: true,
|
|
350
|
+
branch: target,
|
|
351
|
+
previous_branch: current,
|
|
352
|
+
stash_ref: stash_ref ?? null,
|
|
353
|
+
manifest: manifestPath,
|
|
354
|
+
};
|
|
246
355
|
};
|
package/src/core/changelog.ts
CHANGED
|
@@ -2,14 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveWorkspaceRoot } from "./scripts";
|
|
4
4
|
|
|
5
|
-
const CATEGORIES = [
|
|
6
|
-
"Added",
|
|
7
|
-
"Changed",
|
|
8
|
-
"Deprecated",
|
|
9
|
-
"Removed",
|
|
10
|
-
"Fixed",
|
|
11
|
-
"Security",
|
|
12
|
-
];
|
|
5
|
+
const CATEGORIES = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"];
|
|
13
6
|
|
|
14
7
|
// Port of scripts/changelog/apply-unreleased.py — merge Keep a Changelog
|
|
15
8
|
// entries into ## [Unreleased] without duplicating ### headings.
|
|
@@ -73,10 +66,21 @@ function splitUnreleased(text: string): [string, string, string] {
|
|
|
73
66
|
function canonicalCategory(heading: string): string | null {
|
|
74
67
|
const match = CAT_RE.exec(heading.replace(/\r?\n$/, ""));
|
|
75
68
|
if (!match) return null;
|
|
76
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
CATEGORIES.find(
|
|
71
|
+
(c) =>
|
|
72
|
+
c.toLowerCase() ===
|
|
73
|
+
match[0]
|
|
74
|
+
.replace(/^###\s+/, "")
|
|
75
|
+
.trim()
|
|
76
|
+
.toLowerCase(),
|
|
77
|
+
) ?? null
|
|
78
|
+
);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
function splitSections(
|
|
81
|
+
function splitSections(
|
|
82
|
+
body: string,
|
|
83
|
+
): [string[], Array<{ heading: string; category: string | null; body: string[] }>] {
|
|
80
84
|
const preamble: string[] = [];
|
|
81
85
|
const sections: Array<{ heading: string; category: string | null; body: string[] }> = [];
|
|
82
86
|
let current: { heading: string; category: string | null; body: string[] } | null = null;
|
|
@@ -207,7 +211,8 @@ function applyChangelog(
|
|
|
207
211
|
break;
|
|
208
212
|
}
|
|
209
213
|
}
|
|
210
|
-
text =
|
|
214
|
+
text =
|
|
215
|
+
lines.slice(0, insertAt).join("") + "## [Unreleased]\n\n" + lines.slice(insertAt).join("");
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
const [before, body, after] = splitUnreleased(text);
|
|
@@ -290,9 +295,7 @@ export function changelogApply({
|
|
|
290
295
|
|
|
291
296
|
export function changelogUnreleasedStats(workspace_root: string, changelogPath = "CHANGELOG.md") {
|
|
292
297
|
const cwd = resolveWorkspaceRoot(workspace_root);
|
|
293
|
-
const abs = path.isAbsolute(changelogPath)
|
|
294
|
-
? changelogPath
|
|
295
|
-
: path.join(cwd, changelogPath);
|
|
298
|
+
const abs = path.isAbsolute(changelogPath) ? changelogPath : path.join(cwd, changelogPath);
|
|
296
299
|
if (!fs.existsSync(abs)) return { exists: false };
|
|
297
300
|
const text = fs.readFileSync(abs, "utf8");
|
|
298
301
|
const m = text.match(/##\s+\[Unreleased\]([\s\S]*?)(?=\n##\s+\[|$)/i);
|
package/src/core/config-guard.ts
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { initStatus } from "./init";
|
|
2
2
|
|
|
3
|
-
export const ALL_ITEM_IDS = [
|
|
3
|
+
export const ALL_ITEM_IDS = [
|
|
4
|
+
"youtrack_json",
|
|
5
|
+
"youtrack_token",
|
|
6
|
+
"vcs_json",
|
|
7
|
+
"gitlab_token",
|
|
8
|
+
"github_token",
|
|
9
|
+
];
|
|
4
10
|
export const CONFIG_GAP_MARKER = "workflow config missing";
|
|
5
11
|
|
|
6
12
|
export function describeConfigGaps(scope?: string[]): { missing: string[]; ok: boolean } {
|
|
7
13
|
const all = scope ?? ALL_ITEM_IDS;
|
|
8
14
|
try {
|
|
9
15
|
const status: unknown = initStatus();
|
|
10
|
-
if (!status || typeof status !== "object" || (status as Record<string, unknown>).error)
|
|
16
|
+
if (!status || typeof status !== "object" || (status as Record<string, unknown>).error)
|
|
17
|
+
return { missing: all, ok: false };
|
|
11
18
|
const items = (status as Record<string, unknown>).items;
|
|
12
19
|
if (!Array.isArray(items) || items.length === 0) return { missing: all, ok: false };
|
|
13
20
|
const known = all;
|