mainflow 0.21.0 → 0.22.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/dist/cli.js +16 -8
- package/dist/{dist-BrotcvKi.js → dist-DCYzrl7J.js} +367 -3
- package/dist/facts.d.ts +23 -27
- package/dist/facts.js +22 -3
- package/dist/grant-commands.d.ts +73 -0
- package/dist/grant-commands.js +245 -0
- package/dist/hooks.d.ts +23 -2
- package/dist/hooks.js +166 -12
- package/dist/{host-BsLqiI0_.js → host-0_zkFbzM.js} +172 -13
- package/dist/host.js +1 -1
- package/dist/index-CV30LkNZ.d.ts +27 -0
- package/dist/{launch-CBXoFsFC.js → launch-B8-NVG2g.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/{ui-ngIAww3a.js → ui-hvirfamo.js} +2 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { $ as
|
|
3
|
-
import { F as parseRef, I as refExample } from "./dist-
|
|
2
|
+
import { $ as reopenIssue, A as addAction, C as setCriterionText, D as updateTask, E as supersedeDecision, F as setSessionArchived, J as appendOps, L as setSessionStatus, M as handoffSession, O as sessionBriefing, P as readSession, Q as planIssue, S as setCriterion, V as updateAction, X as createFinding, Y as closeIssue, Z as createIssue, _ as createTask, b as setArchived, et as setFindingArchived, f as acceptDecision, ft as WriteError, g as createMainline, gt as findHubRoot, h as createDecision, it as loadHub, l as migrateHub, lt as MAINLINE_FILE, m as addRepo, nt as setIssueArchived, p as addHub, rt as HubError, tt as setFindingStatus, ut as TASK_FILE, v as dropChecklistItem, w as setMainlineStatus, x as setChecklistItem, y as initHub, z as startSession } from "./host-0_zkFbzM.js";
|
|
3
|
+
import { F as parseRef, I as refExample } from "./dist-DCYzrl7J.js";
|
|
4
4
|
import { t as listProgress } from "./progress-1BfPg2zN.js";
|
|
5
5
|
import { COMMAND_SPECS, OPTIONS } from "./facts.js";
|
|
6
|
-
import { runHook } from "./hooks.js";
|
|
7
|
-
import { l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-
|
|
6
|
+
import { hookFallback, parsePayload, runHook } from "./hooks.js";
|
|
7
|
+
import { l as CLI_VERSION, r as launchUi, s as startUiInBackground } from "./launch-B8-NVG2g.js";
|
|
8
8
|
import { readFileSync, realpathSync } from "node:fs";
|
|
9
9
|
import { join, relative } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -85,16 +85,24 @@ const RUNS = {
|
|
|
85
85
|
}).status})`);
|
|
86
86
|
},
|
|
87
87
|
hook: ({ args }) => {
|
|
88
|
-
let
|
|
88
|
+
let input = "";
|
|
89
89
|
try {
|
|
90
|
-
|
|
91
|
-
} catch {
|
|
92
|
-
|
|
90
|
+
input = readFileSync(0, "utf8");
|
|
91
|
+
} catch {}
|
|
92
|
+
const payload = parsePayload(input);
|
|
93
|
+
if (!payload) {
|
|
94
|
+
console.error("mainflow hook: stdin is not a JSON object; nothing to do");
|
|
95
|
+
const fallback = hookFallback(args[0], args[1], {
|
|
96
|
+
payload: {},
|
|
97
|
+
cli: CLI
|
|
98
|
+
}, { kind: "unreadable-input" });
|
|
99
|
+
if (fallback) console.log(fallback);
|
|
93
100
|
return 0;
|
|
94
101
|
}
|
|
95
102
|
const { stdout, error } = runHook(args[0], args[1], {
|
|
96
103
|
payload,
|
|
97
104
|
envFile: process.env.CLAUDE_ENV_FILE,
|
|
105
|
+
projectDir: process.env.CLAUDE_PROJECT_DIR,
|
|
98
106
|
cli: CLI,
|
|
99
107
|
startUi: startUiInBackground
|
|
100
108
|
});
|
|
@@ -1,4 +1,349 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
//#region ../schema/dist/grants.js
|
|
3
|
+
/** What a mainline can authorize ahead of time. */
|
|
4
|
+
const GRANT_ACTIONS = [
|
|
5
|
+
"commit",
|
|
6
|
+
"push",
|
|
7
|
+
"merge-pr",
|
|
8
|
+
"publish",
|
|
9
|
+
"release",
|
|
10
|
+
"install-prompts",
|
|
11
|
+
"sync-hubs"
|
|
12
|
+
];
|
|
13
|
+
/** How each action's target is written, for error messages and the framework facts. */
|
|
14
|
+
const GRANT_TARGET_FORMS = {
|
|
15
|
+
commit: {
|
|
16
|
+
form: "{repo}[:{branch}]",
|
|
17
|
+
example: "mainflow:main"
|
|
18
|
+
},
|
|
19
|
+
push: {
|
|
20
|
+
form: "{repo}[:{branch}]",
|
|
21
|
+
example: "mainflow:main"
|
|
22
|
+
},
|
|
23
|
+
"merge-pr": {
|
|
24
|
+
form: "{repo}[:{branch}]",
|
|
25
|
+
example: "mainflow:main"
|
|
26
|
+
},
|
|
27
|
+
publish: {
|
|
28
|
+
form: "{package}@{semver range}",
|
|
29
|
+
example: "mainflow@0.21.x"
|
|
30
|
+
},
|
|
31
|
+
release: {
|
|
32
|
+
form: "{repo}@{tag pattern}",
|
|
33
|
+
example: "mainflow@v0.21.*"
|
|
34
|
+
},
|
|
35
|
+
"install-prompts": {
|
|
36
|
+
form: "{unit}",
|
|
37
|
+
example: "user"
|
|
38
|
+
},
|
|
39
|
+
"sync-hubs": {
|
|
40
|
+
form: "{hub} or *",
|
|
41
|
+
example: "carlos-space"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
45
|
+
/** A git branch name, `*` standing for any run of characters. */
|
|
46
|
+
const BRANCH = /^(?!\/)(?!.*\/\/)(?!.*\.\.)[A-Za-z0-9._/*-]+(?<!\/)$/;
|
|
47
|
+
/** npm package names: lowercase, optionally scoped. */
|
|
48
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
49
|
+
/** A tag pattern: no whitespace, `*` and `?` as in a shell glob. */
|
|
50
|
+
const TAG_PATTERN = /^[^\s~^:\\[]+$/;
|
|
51
|
+
const fail = (action, why) => ({
|
|
52
|
+
ok: false,
|
|
53
|
+
message: `${why}; a ${action} target is written ${GRANT_TARGET_FORMS[action].form}, e.g. ${GRANT_TARGET_FORMS[action].example}`
|
|
54
|
+
});
|
|
55
|
+
/** Split a target by its action's form, or say what is wrong with it. */
|
|
56
|
+
function parseGrantTarget(action, target) {
|
|
57
|
+
switch (action) {
|
|
58
|
+
case "commit":
|
|
59
|
+
case "push":
|
|
60
|
+
case "merge-pr": {
|
|
61
|
+
const colon = target.indexOf(":");
|
|
62
|
+
const repo = colon < 0 ? target : target.slice(0, colon);
|
|
63
|
+
const branch = colon < 0 ? void 0 : target.slice(colon + 1);
|
|
64
|
+
if (!repo) return fail(action, "the repo name is missing");
|
|
65
|
+
if (branch !== void 0 && !BRANCH.test(branch)) return fail(action, `"${branch}" is not a branch name`);
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
target: {
|
|
69
|
+
action,
|
|
70
|
+
repo,
|
|
71
|
+
...branch !== void 0 ? { branch } : {}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
case "publish": {
|
|
76
|
+
const at = target.lastIndexOf("@");
|
|
77
|
+
if (at <= 0) return fail(action, "the version range is missing");
|
|
78
|
+
const name = target.slice(0, at);
|
|
79
|
+
const range = target.slice(at + 1);
|
|
80
|
+
if (!range.trim()) return fail(action, "the version range is missing");
|
|
81
|
+
if (!PACKAGE_NAME.test(name)) return fail(action, `"${name}" is not an npm package name`);
|
|
82
|
+
if (!parseRange(range)) return fail(action, `"${range}" is not a semver range this CLI reads (exact, x-range, ^, ~, comparators, ||)`);
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
target: {
|
|
86
|
+
action,
|
|
87
|
+
name,
|
|
88
|
+
range
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
case "release": {
|
|
93
|
+
const at = target.indexOf("@");
|
|
94
|
+
if (at <= 0) return fail(action, "the tag pattern is missing");
|
|
95
|
+
const repo = target.slice(0, at);
|
|
96
|
+
const tag = target.slice(at + 1);
|
|
97
|
+
if (!TAG_PATTERN.test(tag)) return fail(action, `"${tag}" is not a tag pattern`);
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
target: {
|
|
101
|
+
action,
|
|
102
|
+
repo,
|
|
103
|
+
tag
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
case "install-prompts": return KEBAB.test(target) ? {
|
|
108
|
+
ok: true,
|
|
109
|
+
target: {
|
|
110
|
+
action,
|
|
111
|
+
unit: target
|
|
112
|
+
}
|
|
113
|
+
} : fail(action, `"${target}" is not a unit name`);
|
|
114
|
+
case "sync-hubs": return target === "*" || KEBAB.test(target) ? {
|
|
115
|
+
ok: true,
|
|
116
|
+
target: {
|
|
117
|
+
action,
|
|
118
|
+
hub: target
|
|
119
|
+
}
|
|
120
|
+
} : fail(action, `"${target}" is not a hub name`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** The repository a target names, for the actions that name one. */
|
|
124
|
+
const grantRepo = (target) => "repo" in target ? target.repo : void 0;
|
|
125
|
+
/** Whether a grant's target covers the request. Only the actions a hook can resolve have requests. */
|
|
126
|
+
function grantCovers(target, request) {
|
|
127
|
+
if (target.action !== request.action) return false;
|
|
128
|
+
if (target.action === "publish" && request.action === "publish") return target.name === request.name && satisfies(request.version, target.range);
|
|
129
|
+
if (target.action === "release" && request.action === "release") return request.repos.includes(target.repo) && globMatch(target.tag, request.tag);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The one way each hook-enforced action may be written for a grant to allow it; the hook asks for any other
|
|
134
|
+
* writing, since what a free-form shell call publishes cannot be predicted reliably.
|
|
135
|
+
*/
|
|
136
|
+
const GRANT_FORMS = {
|
|
137
|
+
publish: "在套件目錄單獨執行 `pnpm publish --ignore-scripts` 或 `npm publish --ignore-scripts`(可加 --access public|restricted、--tag <名稱>、--dry-run、--provenance,pnpm 另可加 --no-git-checks)",
|
|
138
|
+
release: "單獨執行 `gh release create <tag> -R <owner/repo>`(可加 --title、--notes、--generate-notes、--draft、--prerelease、--verify-tag)"
|
|
139
|
+
};
|
|
140
|
+
/** The fixed form a grant allows, filled in with its target; undefined for actions the hook does not allow. */
|
|
141
|
+
function grantForm(target) {
|
|
142
|
+
if (target.action === "publish") return `在 ${target.name} 的套件目錄單獨執行 \`pnpm publish --ignore-scripts\` 或 \`npm publish --ignore-scripts\``;
|
|
143
|
+
if (target.action === "release") return `單獨執行 \`gh release create <符合 ${target.tag} 的 tag> -R <${target.repo} 的 GitHub owner/repo>\``;
|
|
144
|
+
}
|
|
145
|
+
/** How much of a request a reason or an ops entry names. */
|
|
146
|
+
const DESCRIBED = 200;
|
|
147
|
+
/** One line naming a request, for reasons and the ops log. */
|
|
148
|
+
function describeRequest(request) {
|
|
149
|
+
const text = request.action === "publish" ? `publish ${request.name}@${request.version}` : `release ${request.repos.join("/") || "(unknown repo)"}@${request.tag}`;
|
|
150
|
+
return text.length > DESCRIBED ? `${text.slice(0, DESCRIBED)}…` : text;
|
|
151
|
+
}
|
|
152
|
+
/** Whether `text` matches a pattern where `*` is any run of characters and `?` one character, neither crossing `/`. */
|
|
153
|
+
function globMatch(pattern, text) {
|
|
154
|
+
const source = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
155
|
+
return new RegExp(`^${source}$`).test(text);
|
|
156
|
+
}
|
|
157
|
+
const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
158
|
+
const PARTIAL = /^v?(\d+|[xX*])(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
159
|
+
const preParts = (pre) => pre ? pre.split(".").map((p) => /^\d+$/.test(p) ? Number(p) : p) : [];
|
|
160
|
+
function parseVersion(text) {
|
|
161
|
+
const m = VERSION.exec(text.trim());
|
|
162
|
+
return m ? {
|
|
163
|
+
major: Number(m[1]),
|
|
164
|
+
minor: Number(m[2]),
|
|
165
|
+
patch: Number(m[3]),
|
|
166
|
+
pre: preParts(m[4])
|
|
167
|
+
} : null;
|
|
168
|
+
}
|
|
169
|
+
function compareVersions(a, b) {
|
|
170
|
+
for (const key of [
|
|
171
|
+
"major",
|
|
172
|
+
"minor",
|
|
173
|
+
"patch"
|
|
174
|
+
]) if (a[key] !== b[key]) return a[key] - b[key];
|
|
175
|
+
if (a.pre.length === 0 || b.pre.length === 0) return (a.pre.length === 0 ? 1 : 0) - (b.pre.length === 0 ? 1 : 0);
|
|
176
|
+
for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
|
|
177
|
+
const x = a.pre[i];
|
|
178
|
+
const y = b.pre[i];
|
|
179
|
+
if (x === void 0) return -1;
|
|
180
|
+
if (y === void 0) return 1;
|
|
181
|
+
if (x === y) continue;
|
|
182
|
+
if (typeof x === "number" && typeof y === "number") return x - y;
|
|
183
|
+
if (typeof x === "number") return -1;
|
|
184
|
+
if (typeof y === "number") return 1;
|
|
185
|
+
return x < y ? -1 : 1;
|
|
186
|
+
}
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
const v = (major, minor, patch, pre = []) => ({
|
|
190
|
+
major,
|
|
191
|
+
minor,
|
|
192
|
+
patch,
|
|
193
|
+
pre
|
|
194
|
+
});
|
|
195
|
+
/** The lowest version above every release of the given line, e.g. 2.0.0-0 above 1.x. */
|
|
196
|
+
const floor = (major, minor, patch) => v(major, minor, patch, [0]);
|
|
197
|
+
function parsePartial(text) {
|
|
198
|
+
if (text === "" || text === "*" || text === "x" || text === "X") return { pre: [] };
|
|
199
|
+
const m = PARTIAL.exec(text);
|
|
200
|
+
if (!m) return null;
|
|
201
|
+
const num = (s) => s === void 0 || /^[xX*]$/.test(s) ? void 0 : Number(s);
|
|
202
|
+
const major = num(m[1]);
|
|
203
|
+
const minor = major === void 0 ? void 0 : num(m[2]);
|
|
204
|
+
const patch = minor === void 0 ? void 0 : num(m[3]);
|
|
205
|
+
if (m[4] && patch === void 0) return null;
|
|
206
|
+
return {
|
|
207
|
+
major,
|
|
208
|
+
minor,
|
|
209
|
+
patch,
|
|
210
|
+
pre: preParts(m[4])
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** The comparators one range item stands for, or null when it cannot be read. */
|
|
214
|
+
function comparators(item) {
|
|
215
|
+
const m = /^(\^|~>?|<=|>=|<|>|=)?\s*(.*)$/.exec(item);
|
|
216
|
+
const op = m[1] ?? "";
|
|
217
|
+
const p = parsePartial(m[2]);
|
|
218
|
+
if (!p) return null;
|
|
219
|
+
const { major, minor, patch, pre } = p;
|
|
220
|
+
const low = v(major ?? 0, minor ?? 0, patch ?? 0, pre);
|
|
221
|
+
switch (op) {
|
|
222
|
+
case "":
|
|
223
|
+
case "=":
|
|
224
|
+
if (major === void 0) return [];
|
|
225
|
+
if (minor === void 0) return [{
|
|
226
|
+
op: ">=",
|
|
227
|
+
version: low
|
|
228
|
+
}, {
|
|
229
|
+
op: "<",
|
|
230
|
+
version: floor(major + 1, 0, 0)
|
|
231
|
+
}];
|
|
232
|
+
if (patch === void 0) return [{
|
|
233
|
+
op: ">=",
|
|
234
|
+
version: low
|
|
235
|
+
}, {
|
|
236
|
+
op: "<",
|
|
237
|
+
version: floor(major, minor + 1, 0)
|
|
238
|
+
}];
|
|
239
|
+
return [{
|
|
240
|
+
op: "=",
|
|
241
|
+
version: low
|
|
242
|
+
}];
|
|
243
|
+
case "^": {
|
|
244
|
+
if (major === void 0) return [];
|
|
245
|
+
const high = major > 0 || minor === void 0 ? floor(major + 1, 0, 0) : minor > 0 || patch === void 0 ? floor(0, minor + 1, 0) : floor(0, 0, patch + 1);
|
|
246
|
+
return [{
|
|
247
|
+
op: ">=",
|
|
248
|
+
version: low
|
|
249
|
+
}, {
|
|
250
|
+
op: "<",
|
|
251
|
+
version: high
|
|
252
|
+
}];
|
|
253
|
+
}
|
|
254
|
+
case "~":
|
|
255
|
+
case "~>": {
|
|
256
|
+
if (major === void 0) return [];
|
|
257
|
+
const high = minor === void 0 ? floor(major + 1, 0, 0) : floor(major, minor + 1, 0);
|
|
258
|
+
return [{
|
|
259
|
+
op: ">=",
|
|
260
|
+
version: low
|
|
261
|
+
}, {
|
|
262
|
+
op: "<",
|
|
263
|
+
version: high
|
|
264
|
+
}];
|
|
265
|
+
}
|
|
266
|
+
case ">=": return major === void 0 ? [] : [{
|
|
267
|
+
op: ">=",
|
|
268
|
+
version: low
|
|
269
|
+
}];
|
|
270
|
+
case "<": return major === void 0 ? [{
|
|
271
|
+
op: "<",
|
|
272
|
+
version: floor(0, 0, 0)
|
|
273
|
+
}] : [{
|
|
274
|
+
op: "<",
|
|
275
|
+
version: patch === void 0 ? floor(major, minor ?? 0, 0) : low
|
|
276
|
+
}];
|
|
277
|
+
case ">":
|
|
278
|
+
if (major === void 0) return [{
|
|
279
|
+
op: "<",
|
|
280
|
+
version: floor(0, 0, 0)
|
|
281
|
+
}];
|
|
282
|
+
if (minor === void 0) return [{
|
|
283
|
+
op: ">=",
|
|
284
|
+
version: v(major + 1, 0, 0)
|
|
285
|
+
}];
|
|
286
|
+
if (patch === void 0) return [{
|
|
287
|
+
op: ">=",
|
|
288
|
+
version: v(major, minor + 1, 0)
|
|
289
|
+
}];
|
|
290
|
+
return [{
|
|
291
|
+
op: ">",
|
|
292
|
+
version: low
|
|
293
|
+
}];
|
|
294
|
+
case "<=":
|
|
295
|
+
if (major === void 0) return [];
|
|
296
|
+
if (minor === void 0) return [{
|
|
297
|
+
op: "<",
|
|
298
|
+
version: floor(major + 1, 0, 0)
|
|
299
|
+
}];
|
|
300
|
+
if (patch === void 0) return [{
|
|
301
|
+
op: "<",
|
|
302
|
+
version: floor(major, minor + 1, 0)
|
|
303
|
+
}];
|
|
304
|
+
return [{
|
|
305
|
+
op: "<=",
|
|
306
|
+
version: low
|
|
307
|
+
}];
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
/** A range as alternatives of comparator sets, or null when any part of it cannot be read. */
|
|
312
|
+
function parseRange(range) {
|
|
313
|
+
const alternatives = range.trim().split(/\s*\|\|\s*/);
|
|
314
|
+
const sets = [];
|
|
315
|
+
for (const alt of alternatives) {
|
|
316
|
+
if (alt.trim() === "") return null;
|
|
317
|
+
if (/\s-\s/.test(alt)) return null;
|
|
318
|
+
const items = alt.trim().replace(/(\^|~>?|<=|>=|<|>|=)\s+/g, "$1").split(/\s+/).filter(Boolean);
|
|
319
|
+
const set = [];
|
|
320
|
+
for (const item of items) {
|
|
321
|
+
const parsed = comparators(item);
|
|
322
|
+
if (!parsed) return null;
|
|
323
|
+
set.push(...parsed);
|
|
324
|
+
}
|
|
325
|
+
sets.push(set);
|
|
326
|
+
}
|
|
327
|
+
return sets;
|
|
328
|
+
}
|
|
329
|
+
const holds = (c, version) => {
|
|
330
|
+
const d = compareVersions(version, c.version);
|
|
331
|
+
return c.op === "=" ? d === 0 : c.op === "<" ? d < 0 : c.op === "<=" ? d <= 0 : c.op === ">" ? d > 0 : d >= 0;
|
|
332
|
+
};
|
|
333
|
+
/** The upper bounds this file adds for x-ranges, caret and tilde; they name no prerelease of their own. */
|
|
334
|
+
const isFloor = (c) => c.op === "<" && c.version.pre.length === 1 && c.version.pre[0] === 0;
|
|
335
|
+
/** Whether `version` satisfies `range`; false when either cannot be read. */
|
|
336
|
+
function satisfies(version, range) {
|
|
337
|
+
const parsed = parseVersion(version);
|
|
338
|
+
const sets = parseRange(range);
|
|
339
|
+
if (!parsed || !sets) return false;
|
|
340
|
+
return sets.some((set) => {
|
|
341
|
+
if (!set.every((c) => holds(c, parsed))) return false;
|
|
342
|
+
if (parsed.pre.length === 0) return true;
|
|
343
|
+
return set.some((c) => c.version.pre.length > 0 && !isFloor(c) && c.version.major === parsed.major && c.version.minor === parsed.minor && c.version.patch === parsed.patch);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
2
347
|
//#region ../schema/dist/index.js
|
|
3
348
|
const FORMAT_VERSION = 2;
|
|
4
349
|
const ID_PATTERNS = {
|
|
@@ -9,7 +354,8 @@ const ID_PATTERNS = {
|
|
|
9
354
|
decision: /^d\d{3}$/,
|
|
10
355
|
action: /^a\d{3}$/,
|
|
11
356
|
issue: /^i\d{3}$/,
|
|
12
|
-
finding: /^f\d{3}
|
|
357
|
+
finding: /^f\d{3}$/,
|
|
358
|
+
grant: /^g\d{3}$/
|
|
13
359
|
};
|
|
14
360
|
const CHECKLIST_REF = /^m\d{3}\/t\d{3}#c\d{3}$/;
|
|
15
361
|
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -357,6 +703,23 @@ const costSchema = z.object({
|
|
|
357
703
|
actual: z.number().nonnegative().optional(),
|
|
358
704
|
note: text.optional()
|
|
359
705
|
});
|
|
706
|
+
/**
|
|
707
|
+
* One thing the mainline authorizes ahead of time: an action plus the target it covers. It applies only while
|
|
708
|
+
* a session's action in progress serves this mainline and the mainline is still open.
|
|
709
|
+
*/
|
|
710
|
+
const grantSchema = z.object({
|
|
711
|
+
id: id("grant"),
|
|
712
|
+
action: z.enum(GRANT_ACTIONS),
|
|
713
|
+
target: text,
|
|
714
|
+
note: text.optional()
|
|
715
|
+
}).superRefine((g, ctx) => {
|
|
716
|
+
const parsed = parseGrantTarget(g.action, g.target);
|
|
717
|
+
if (!parsed.ok) ctx.addIssue({
|
|
718
|
+
code: "custom",
|
|
719
|
+
path: ["target"],
|
|
720
|
+
message: parsed.message
|
|
721
|
+
});
|
|
722
|
+
});
|
|
360
723
|
const mainlineSchema = z.object({
|
|
361
724
|
id: id("mainline"),
|
|
362
725
|
title: text,
|
|
@@ -371,7 +734,8 @@ const mainlineSchema = z.object({
|
|
|
371
734
|
amount: z.number().positive(),
|
|
372
735
|
unit: text
|
|
373
736
|
}).optional(),
|
|
374
|
-
costs: z.array(costSchema).optional()
|
|
737
|
+
costs: z.array(costSchema).optional(),
|
|
738
|
+
grants: z.array(grantSchema).optional()
|
|
375
739
|
});
|
|
376
740
|
const decisionSchema = z.object({
|
|
377
741
|
id: id("decision"),
|
|
@@ -537,4 +901,4 @@ function parseOpsLine(line) {
|
|
|
537
901
|
return parsed.success ? parsed.data : null;
|
|
538
902
|
}
|
|
539
903
|
//#endregion
|
|
540
|
-
export { formatOpsEntry as A, waitingForRef as B, SURVEY_SECTION as C, configSchema as D, WORK_STATUS as E, parseRef as F, refExample as I, refMainline as L, mainlineSchema as M, opsEntrySchema as N, decisionSchema as O, parseOpsLine as P, sessionSchema as R, SURVEY_LINES as S, TASK_SECTIONS as T, PRIORITIES as _, DECISION_STATUS as a, SESSION_STATUS as b, FINDING_STATUS as c, ISSUE_SECTIONS as d, ISSUE_STATUS as f, MERGE_STATES as g, MAINLINE_STATUS as h, DECISION_SECTIONS as i, issueSchema as j, findingSchema as k, FORMAT_VERSION as l, MAINLINE_SECTIONS as m, CLOSED_STATUS as n, DEFAULT_CATEGORIES as o, LINK_RELS as p, DECIDERS as r, FINDING_SECTIONS as s, CHECKLIST_REF as t, ID_PATTERNS as u, REF_EXAMPLES as v, TASK_REQUIRED_SECTIONS as w, SLUG as x, RESERVED_HUB_NAMES as y, taskSchema as z };
|
|
904
|
+
export { formatOpsEntry as A, waitingForRef as B, SURVEY_SECTION as C, configSchema as D, WORK_STATUS as E, parseRef as F, grantCovers as G, GRANT_FORMS as H, refExample as I, parseGrantTarget as J, grantForm as K, refMainline as L, mainlineSchema as M, opsEntrySchema as N, decisionSchema as O, parseOpsLine as P, sessionSchema as R, SURVEY_LINES as S, TASK_SECTIONS as T, GRANT_TARGET_FORMS as U, GRANT_ACTIONS as V, describeRequest as W, PRIORITIES as _, DECISION_STATUS as a, SESSION_STATUS as b, FINDING_STATUS as c, ISSUE_SECTIONS as d, ISSUE_STATUS as f, MERGE_STATES as g, MAINLINE_STATUS as h, DECISION_SECTIONS as i, issueSchema as j, findingSchema as k, FORMAT_VERSION as l, MAINLINE_SECTIONS as m, CLOSED_STATUS as n, DEFAULT_CATEGORIES as o, LINK_RELS as p, grantRepo as q, DECIDERS as r, FINDING_SECTIONS as s, CHECKLIST_REF as t, ID_PATTERNS as u, REF_EXAMPLES as v, TASK_REQUIRED_SECTIONS as w, SLUG as x, RESERVED_HUB_NAMES as y, taskSchema as z };
|
package/dist/facts.d.ts
CHANGED
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region ../schema/dist/index.d.ts
|
|
3
|
-
export declare const FORMAT_VERSION = 2;
|
|
4
|
-
export declare const ID_PATTERNS: {
|
|
5
|
-
readonly mainline: RegExp;
|
|
6
|
-
readonly task: RegExp;
|
|
7
|
-
readonly checklistItem: RegExp;
|
|
8
|
-
readonly criterion: RegExp;
|
|
9
|
-
readonly decision: RegExp;
|
|
10
|
-
readonly action: RegExp;
|
|
11
|
-
readonly issue: RegExp;
|
|
12
|
-
readonly finding: RegExp;
|
|
13
|
-
};
|
|
14
|
-
export declare const CHECKLIST_REF: RegExp;
|
|
15
|
-
export declare const SLUG: RegExp;
|
|
16
|
-
/** One source the survey checks: the line's label, what it holds, and how it may be left unchecked. */
|
|
17
|
-
interface SurveyLine {
|
|
18
|
-
label: string;
|
|
19
|
-
holds: string;
|
|
20
|
-
/** The word that marks the source as not checked, followed by the reason, e.g. 不適用:{理由}. Absent when the source is always checked. */
|
|
21
|
-
skip?: string;
|
|
22
|
-
/** Text a checked line must contain, e.g. the environment a spike ran in. */
|
|
23
|
-
requires?: string;
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
1
|
+
import { a as SurveyLine, i as SLUG, n as FORMAT_VERSION, r as ID_PATTERNS, t as CHECKLIST_REF } from "./index-CV30LkNZ.js";
|
|
26
2
|
//#region src/facts.d.ts
|
|
27
3
|
export interface CommandSpec {
|
|
28
4
|
usage: string;
|
|
@@ -500,6 +476,24 @@ export declare const REFERENCES: readonly [{
|
|
|
500
476
|
export declare const LINKS: {
|
|
501
477
|
readonly rels: readonly ["depends-on", "relates", "split-from"];
|
|
502
478
|
};
|
|
479
|
+
/**
|
|
480
|
+
* What a mainline's grants authorize: each action with the form of its target. A grant applies only while the
|
|
481
|
+
* session's action in progress serves the mainline and the mainline is active or planning. The PreToolUse hook
|
|
482
|
+
* allows `publish` and `release` only when the command is written in the fixed form in `forms`; the other
|
|
483
|
+
* actions are for the executor to read.
|
|
484
|
+
*/
|
|
485
|
+
export declare const GRANTS: {
|
|
486
|
+
readonly actions: readonly ["commit", "push", "merge-pr", "publish", "release", "install-prompts", "sync-hubs"];
|
|
487
|
+
readonly targets: Record<"commit" | "install-prompts" | "merge-pr" | "publish" | "push" | "release" | "sync-hubs", {
|
|
488
|
+
form: string;
|
|
489
|
+
example: string;
|
|
490
|
+
}>;
|
|
491
|
+
readonly enforcedByHook: readonly ['publish', 'release'];
|
|
492
|
+
readonly forms: {
|
|
493
|
+
readonly publish: '在套件目錄單獨執行 `pnpm publish --ignore-scripts` 或 `npm publish --ignore-scripts`(可加 --access public|restricted、--tag <名稱>、--dry-run、--provenance,pnpm 另可加 --no-git-checks)';
|
|
494
|
+
readonly release: '單獨執行 `gh release create <tag> -R <owner/repo>`(可加 --title、--notes、--generate-notes、--draft、--prerelease、--verify-tag)';
|
|
495
|
+
};
|
|
496
|
+
};
|
|
503
497
|
/** Where each kind of entry lives under the hub root. */
|
|
504
498
|
export declare const LAYOUT: {
|
|
505
499
|
readonly config: '.mainflow/config.yaml';
|
|
@@ -551,13 +545,15 @@ export interface FieldSpec {
|
|
|
551
545
|
}
|
|
552
546
|
/** Every front matter field of each document and list entry; facts.test keeps it equal to the schema. */
|
|
553
547
|
export declare const FIELDS: {
|
|
554
|
-
readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
548
|
+
readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
555
549
|
readonly task: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
556
550
|
readonly decision: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
557
551
|
readonly issue: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
558
552
|
readonly finding: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec];
|
|
559
553
|
readonly criterion: FieldSpec[];
|
|
560
554
|
readonly checklistItem: readonly [...FieldSpec[], FieldSpec];
|
|
555
|
+
readonly grant: readonly [FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
561
556
|
readonly link: readonly [FieldSpec, FieldSpec, FieldSpec];
|
|
562
557
|
};
|
|
563
|
-
//#endregion
|
|
558
|
+
//#endregion
|
|
559
|
+
export { CHECKLIST_REF, FORMAT_VERSION, ID_PATTERNS, SLUG };
|
package/dist/facts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as SURVEY_SECTION, E as WORK_STATUS, S as SURVEY_LINES, T as TASK_SECTIONS, _ as PRIORITIES, a as DECISION_STATUS, b as SESSION_STATUS, c as FINDING_STATUS, d as ISSUE_SECTIONS, f as ISSUE_STATUS, g as MERGE_STATES, h as MAINLINE_STATUS, i as DECISION_SECTIONS, l as FORMAT_VERSION, m as MAINLINE_SECTIONS, n as CLOSED_STATUS, o as DEFAULT_CATEGORIES, p as LINK_RELS, r as DECIDERS, s as FINDING_SECTIONS, t as CHECKLIST_REF, u as ID_PATTERNS, w as TASK_REQUIRED_SECTIONS, x as SLUG } from "./dist-
|
|
1
|
+
import { C as SURVEY_SECTION, E as WORK_STATUS, H as GRANT_FORMS, S as SURVEY_LINES, T as TASK_SECTIONS, U as GRANT_TARGET_FORMS, V as GRANT_ACTIONS, _ as PRIORITIES, a as DECISION_STATUS, b as SESSION_STATUS, c as FINDING_STATUS, d as ISSUE_SECTIONS, f as ISSUE_STATUS, g as MERGE_STATES, h as MAINLINE_STATUS, i as DECISION_SECTIONS, l as FORMAT_VERSION, m as MAINLINE_SECTIONS, n as CLOSED_STATUS, o as DEFAULT_CATEGORIES, p as LINK_RELS, r as DECIDERS, s as FINDING_SECTIONS, t as CHECKLIST_REF, u as ID_PATTERNS, w as TASK_REQUIRED_SECTIONS, x as SLUG } from "./dist-DCYzrl7J.js";
|
|
2
2
|
//#region src/facts.ts
|
|
3
3
|
const COMMAND_SPECS = {
|
|
4
4
|
init: {
|
|
@@ -408,6 +408,18 @@ const REFERENCES = [
|
|
|
408
408
|
];
|
|
409
409
|
/** Relation types a document's links take; readers derive the reverse direction. */
|
|
410
410
|
const LINKS = { rels: LINK_RELS };
|
|
411
|
+
/**
|
|
412
|
+
* What a mainline's grants authorize: each action with the form of its target. A grant applies only while the
|
|
413
|
+
* session's action in progress serves the mainline and the mainline is active or planning. The PreToolUse hook
|
|
414
|
+
* allows `publish` and `release` only when the command is written in the fixed form in `forms`; the other
|
|
415
|
+
* actions are for the executor to read.
|
|
416
|
+
*/
|
|
417
|
+
const GRANTS = {
|
|
418
|
+
actions: GRANT_ACTIONS,
|
|
419
|
+
targets: GRANT_TARGET_FORMS,
|
|
420
|
+
enforcedByHook: ["publish", "release"],
|
|
421
|
+
forms: GRANT_FORMS
|
|
422
|
+
};
|
|
411
423
|
/** Where each kind of entry lives under the hub root. */
|
|
412
424
|
const LAYOUT = {
|
|
413
425
|
config: ".mainflow/config.yaml",
|
|
@@ -500,7 +512,8 @@ const FIELDS = {
|
|
|
500
512
|
field("archivedAt", false, "封存的日期,和狀態分開"),
|
|
501
513
|
field("criteria", true, "解決條件清單,每條的欄位見 criterion"),
|
|
502
514
|
field("budget", false, "預算的金額與單位"),
|
|
503
|
-
field("costs", false, "花費清單,每筆有日期、項目、單位,可附預估、實際、備註")
|
|
515
|
+
field("costs", false, "花費清單,每筆有日期、項目、單位,可附預估、實際、備註"),
|
|
516
|
+
field("grants", false, "預先授權清單,每條的欄位見 grant;只在會話進行中的行動服務這條主脈絡、且主脈絡是 active 或 planning 時生效")
|
|
504
517
|
],
|
|
505
518
|
task: [
|
|
506
519
|
...shared(...EVERY),
|
|
@@ -540,6 +553,12 @@ const FIELDS = {
|
|
|
540
553
|
],
|
|
541
554
|
criterion: LIST_ITEM,
|
|
542
555
|
checklistItem: [...LIST_ITEM, field("serves", false, "這個項目推進的解決條件,r{NNN} 清單")],
|
|
556
|
+
grant: [
|
|
557
|
+
field("id", true, "清單內不重複的編號 g{NNN}"),
|
|
558
|
+
field("action", true, "授權的動作:commit、push、merge-pr、publish、release、install-prompts、sync-hubs"),
|
|
559
|
+
field("target", true, "授權的目標,格式依動作而定,見 GRANTS.targets;倉庫名稱需在 config.yaml 登記"),
|
|
560
|
+
field("note", false, "一句話說明")
|
|
561
|
+
],
|
|
543
562
|
link: [
|
|
544
563
|
field("rel", true, "關係類型:depends-on、relates、split-from,反向由讀者推出"),
|
|
545
564
|
field("to", true, "對方的參照,不能在同一條主脈絡內;其他 hub 的加 {hub}: 前綴"),
|
|
@@ -547,4 +566,4 @@ const FIELDS = {
|
|
|
547
566
|
]
|
|
548
567
|
};
|
|
549
568
|
//#endregion
|
|
550
|
-
export { CHECKLIST_REF, CLASSIFICATION, CLOSED_STATUSES, COMMAND_SPECS, FIELDS, FORMAT_VERSION, ID_PATTERNS, LAYOUT, LINKS, OPTIONS, REFERENCES, SECTIONS, SLUG, STATUSES, SURVEY };
|
|
569
|
+
export { CHECKLIST_REF, CLASSIFICATION, CLOSED_STATUSES, COMMAND_SPECS, FIELDS, FORMAT_VERSION, GRANTS, ID_PATTERNS, LAYOUT, LINKS, OPTIONS, REFERENCES, SECTIONS, SLUG, STATUSES, SURVEY };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import "./index-CV30LkNZ.js";
|
|
2
|
+
//#region ../schema/dist/grants.d.ts
|
|
3
|
+
/** An operation about to run, resolved to what a grant is written against. */
|
|
4
|
+
type GrantRequest = {
|
|
5
|
+
action: 'publish';
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
} |
|
|
9
|
+
/** `repos` holds every config.yaml name whose checkout points at the repository, usually one */
|
|
10
|
+
{
|
|
11
|
+
action: 'release';
|
|
12
|
+
repos: string[];
|
|
13
|
+
tag: string;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/grant-commands.d.ts
|
|
17
|
+
export declare const quote: (text: string) => string;
|
|
18
|
+
/** A call in one of the fixed forms, not yet checked against the package or the repository. */
|
|
19
|
+
export type Fixed = {
|
|
20
|
+
action: 'publish';
|
|
21
|
+
tool: 'npm' | 'pnpm';
|
|
22
|
+
text: string;
|
|
23
|
+
} | {
|
|
24
|
+
action: 'release';
|
|
25
|
+
tag: string;
|
|
26
|
+
repo: string;
|
|
27
|
+
text: string;
|
|
28
|
+
};
|
|
29
|
+
/** none: nothing grantable, stay silent. refused: not a fixed form, so ask. fixed: one of the two forms. */
|
|
30
|
+
export type Scan = {
|
|
31
|
+
kind: 'none';
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'refused';
|
|
34
|
+
reason: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: 'fixed';
|
|
37
|
+
call: Fixed;
|
|
38
|
+
};
|
|
39
|
+
/** A git tag a release may create: no path separators, no `..`. */
|
|
40
|
+
export declare const TAG: RegExp;
|
|
41
|
+
/** Whether a Bash call is one this hook decides and, if so, whether it is in a fixed form. */
|
|
42
|
+
export declare function scanCommand(command: string): Scan;
|
|
43
|
+
export interface ResolveContext {
|
|
44
|
+
/** Directory the call runs in, already checked by scopedCwd */
|
|
45
|
+
cwd: string;
|
|
46
|
+
/** Hub root; config.yaml repo paths are relative to it */
|
|
47
|
+
root: string;
|
|
48
|
+
/** config.yaml `repos`: name to path */
|
|
49
|
+
repos: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
export type Resolved = {
|
|
52
|
+
ok: true;
|
|
53
|
+
request: GrantRequest;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The payload's cwd after realpath, when it exists and lies in the hub or in a repository config.yaml lists; a
|
|
60
|
+
* listed repository counts as part of the hub even when it sits outside the hub directory, as ../mainflow does.
|
|
61
|
+
*/
|
|
62
|
+
export declare function scopedCwd(cwd: unknown, root: string, repos: Record<string, string>): {
|
|
63
|
+
ok: true;
|
|
64
|
+
dir: string;
|
|
65
|
+
} | {
|
|
66
|
+
ok: false;
|
|
67
|
+
reason: string;
|
|
68
|
+
};
|
|
69
|
+
/** The origin remote of a checkout as github.com/owner/repo in lowercase, or null when it is not on github.com. */
|
|
70
|
+
export declare function githubOrigin(dir: string): string | null;
|
|
71
|
+
/** What a fixed-form call would publish or release; refused when the package or repository does not qualify. */
|
|
72
|
+
export declare function resolveFixed(call: Fixed, ctx: ResolveContext): Resolved;
|
|
73
|
+
//#endregion
|