mainflow 0.21.0 → 0.23.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 +21 -9
- package/dist/{dist-BrotcvKi.js → dist-Dy45myuN.js} +370 -3
- package/dist/facts.d.ts +30 -30
- package/dist/facts.js +28 -6
- 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-DkaCYnnc.js} +179 -16
- package/dist/host.js +1 -1
- package/dist/index-CV30LkNZ.d.ts +27 -0
- package/dist/{launch-CBXoFsFC.js → launch-DOX1TTGx.js} +2 -2
- package/dist/launch.js +1 -1
- package/dist/{ui-ngIAww3a.js → ui-Do7fLlUC.js} +2 -2
- package/dist/web/assets/{index-CrBRJSja.js → index-BvKRkaWK.js} +27 -27
- package/dist/web/index.html +1 -1
- 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 {
|
|
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-DkaCYnnc.js";
|
|
3
|
+
import { I as parseRef, L as refExample } from "./dist-Dy45myuN.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-DOX1TTGx.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
|
});
|
|
@@ -125,6 +133,7 @@ const RUNS = {
|
|
|
125
133
|
title: required(opts, "title", "--title \"以文字檔為準\""),
|
|
126
134
|
status: opts.status,
|
|
127
135
|
by: opts.by,
|
|
136
|
+
basis: opts.basis,
|
|
128
137
|
affects: opts.affects ? list(opts.affects) : void 0
|
|
129
138
|
});
|
|
130
139
|
return done(`created ${ref} ${relative(root, file)}`);
|
|
@@ -204,7 +213,10 @@ const RUNS = {
|
|
|
204
213
|
setChecklistItem(root, args[0], false);
|
|
205
214
|
return done(`unchecked ${args[0]}`);
|
|
206
215
|
},
|
|
207
|
-
"decision accept": ({ root, args, opts }) => done(`accepted ${args[0]} (${acceptDecision(root, args[0],
|
|
216
|
+
"decision accept": ({ root, args, opts }) => done(`accepted ${args[0]} (${acceptDecision(root, args[0], {
|
|
217
|
+
by: opts.by,
|
|
218
|
+
basis: opts.basis
|
|
219
|
+
}).decidedAt})`),
|
|
208
220
|
"item drop": ({ root, args, opts }) => {
|
|
209
221
|
dropChecklistItem(root, args[0], {
|
|
210
222
|
supersededBy: opts.by,
|
|
@@ -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]+)*$/;
|
|
@@ -162,6 +508,8 @@ const DECIDERS = [
|
|
|
162
508
|
"ai",
|
|
163
509
|
"relay"
|
|
164
510
|
];
|
|
511
|
+
/** How the decider settled it: by naming an option, or by going with the recommendation without naming one */
|
|
512
|
+
const DECISION_BASES = ["explicit", "recommended"];
|
|
165
513
|
const MERGE_STATES = [
|
|
166
514
|
"open",
|
|
167
515
|
"merged",
|
|
@@ -357,6 +705,23 @@ const costSchema = z.object({
|
|
|
357
705
|
actual: z.number().nonnegative().optional(),
|
|
358
706
|
note: text.optional()
|
|
359
707
|
});
|
|
708
|
+
/**
|
|
709
|
+
* One thing the mainline authorizes ahead of time: an action plus the target it covers. It applies only while
|
|
710
|
+
* a session's action in progress serves this mainline and the mainline is still open.
|
|
711
|
+
*/
|
|
712
|
+
const grantSchema = z.object({
|
|
713
|
+
id: id("grant"),
|
|
714
|
+
action: z.enum(GRANT_ACTIONS),
|
|
715
|
+
target: text,
|
|
716
|
+
note: text.optional()
|
|
717
|
+
}).superRefine((g, ctx) => {
|
|
718
|
+
const parsed = parseGrantTarget(g.action, g.target);
|
|
719
|
+
if (!parsed.ok) ctx.addIssue({
|
|
720
|
+
code: "custom",
|
|
721
|
+
path: ["target"],
|
|
722
|
+
message: parsed.message
|
|
723
|
+
});
|
|
724
|
+
});
|
|
360
725
|
const mainlineSchema = z.object({
|
|
361
726
|
id: id("mainline"),
|
|
362
727
|
title: text,
|
|
@@ -371,7 +736,8 @@ const mainlineSchema = z.object({
|
|
|
371
736
|
amount: z.number().positive(),
|
|
372
737
|
unit: text
|
|
373
738
|
}).optional(),
|
|
374
|
-
costs: z.array(costSchema).optional()
|
|
739
|
+
costs: z.array(costSchema).optional(),
|
|
740
|
+
grants: z.array(grantSchema).optional()
|
|
375
741
|
});
|
|
376
742
|
const decisionSchema = z.object({
|
|
377
743
|
id: id("decision"),
|
|
@@ -382,6 +748,7 @@ const decisionSchema = z.object({
|
|
|
382
748
|
decidedAt: dateOrTime.optional(),
|
|
383
749
|
supersededBy: id("decision").optional(),
|
|
384
750
|
by: z.enum(DECIDERS).optional(),
|
|
751
|
+
basis: z.enum(DECISION_BASES).optional(),
|
|
385
752
|
/** Tasks and items the decision shapes; while it is proposed, the checklist items among them wait for it */
|
|
386
753
|
affects: z.array(anyRef).optional(),
|
|
387
754
|
/** Whether the choice can be undone cheaply later */
|
|
@@ -537,4 +904,4 @@ function parseOpsLine(line) {
|
|
|
537
904
|
return parsed.success ? parsed.data : null;
|
|
538
905
|
}
|
|
539
906
|
//#endregion
|
|
540
|
-
export {
|
|
907
|
+
export { findingSchema as A, taskSchema as B, SURVEY_LINES as C, WORK_STATUS as D, TASK_SECTIONS as E, parseOpsLine as F, describeRequest as G, GRANT_ACTIONS as H, parseRef as I, grantRepo as J, grantCovers as K, refExample as L, issueSchema as M, mainlineSchema as N, configSchema as O, opsEntrySchema as P, refMainline as R, SLUG as S, TASK_REQUIRED_SECTIONS as T, GRANT_FORMS as U, waitingForRef as V, GRANT_TARGET_FORMS as W, parseGrantTarget as Y, MERGE_STATES as _, DECISION_SECTIONS as a, RESERVED_HUB_NAMES as b, FINDING_SECTIONS as c, ID_PATTERNS as d, ISSUE_SECTIONS as f, MAINLINE_STATUS as g, MAINLINE_SECTIONS as h, DECISION_BASES as i, formatOpsEntry as j, decisionSchema as k, FINDING_STATUS as l, LINK_RELS as m, CLOSED_STATUS as n, DECISION_STATUS as o, ISSUE_STATUS as p, grantForm as q, DECIDERS as r, DEFAULT_CATEGORIES as s, CHECKLIST_REF as t, FORMAT_VERSION as u, PRIORITIES as v, SURVEY_SECTION as w, SESSION_STATUS as x, REF_EXAMPLES as y, sessionSchema 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;
|
|
@@ -151,7 +127,7 @@ export declare const COMMAND_SPECS: {
|
|
|
151
127
|
readonly needsHub: true;
|
|
152
128
|
};
|
|
153
129
|
readonly 'new decision': {
|
|
154
|
-
readonly usage: 'new decision <m> <slug> --title <title> [--status proposed] [--by user|ai|relay] [--affects <m/t or m/t#c>,...]';
|
|
130
|
+
readonly usage: 'new decision <m> <slug> --title <title> [--status proposed] [--by user|ai|relay] [--basis explicit|recommended] [--affects <m/t or m/t#c>,...]';
|
|
155
131
|
readonly summary: 'Create a decision file; proposed while the question is still open';
|
|
156
132
|
readonly args: 2;
|
|
157
133
|
readonly needsHub: true;
|
|
@@ -229,7 +205,7 @@ export declare const COMMAND_SPECS: {
|
|
|
229
205
|
readonly needsHub: true;
|
|
230
206
|
};
|
|
231
207
|
readonly 'decision accept': {
|
|
232
|
-
readonly usage: 'decision accept <m/d> [--by user|ai|relay]';
|
|
208
|
+
readonly usage: 'decision accept <m/d> [--by user|ai|relay] [--basis explicit|recommended]';
|
|
233
209
|
readonly summary: 'Settle a proposed decision; the items it affects stop waiting for it';
|
|
234
210
|
readonly args: 1;
|
|
235
211
|
readonly needsHub: true;
|
|
@@ -348,6 +324,9 @@ export declare const OPTIONS: {
|
|
|
348
324
|
readonly by: {
|
|
349
325
|
readonly type: 'string';
|
|
350
326
|
};
|
|
327
|
+
readonly basis: {
|
|
328
|
+
readonly type: 'string';
|
|
329
|
+
};
|
|
351
330
|
readonly port: {
|
|
352
331
|
readonly type: 'string';
|
|
353
332
|
};
|
|
@@ -457,6 +436,7 @@ export declare const STATUSES: {
|
|
|
457
436
|
readonly session: readonly ["active", "paused", "ended", "handed-off"];
|
|
458
437
|
readonly merge: readonly ["open", "merged", "abandoned"];
|
|
459
438
|
readonly decider: readonly ["user", "ai", "relay"];
|
|
439
|
+
readonly basis: readonly ["explicit", "recommended"];
|
|
460
440
|
};
|
|
461
441
|
/** Reference formats, one example each, in the order a reader meets them. */
|
|
462
442
|
export declare const REFERENCES: readonly [{
|
|
@@ -500,6 +480,24 @@ export declare const REFERENCES: readonly [{
|
|
|
500
480
|
export declare const LINKS: {
|
|
501
481
|
readonly rels: readonly ["depends-on", "relates", "split-from"];
|
|
502
482
|
};
|
|
483
|
+
/**
|
|
484
|
+
* What a mainline's grants authorize: each action with the form of its target. A grant applies only while the
|
|
485
|
+
* session's action in progress serves the mainline and the mainline is active or planning. The PreToolUse hook
|
|
486
|
+
* allows `publish` and `release` only when the command is written in the fixed form in `forms`; the other
|
|
487
|
+
* actions are for the executor to read.
|
|
488
|
+
*/
|
|
489
|
+
export declare const GRANTS: {
|
|
490
|
+
readonly actions: readonly ["commit", "push", "merge-pr", "publish", "release", "install-prompts", "sync-hubs"];
|
|
491
|
+
readonly targets: Record<"commit" | "install-prompts" | "merge-pr" | "publish" | "push" | "release" | "sync-hubs", {
|
|
492
|
+
form: string;
|
|
493
|
+
example: string;
|
|
494
|
+
}>;
|
|
495
|
+
readonly enforcedByHook: readonly ['publish', 'release'];
|
|
496
|
+
readonly forms: {
|
|
497
|
+
readonly publish: '在套件目錄單獨執行 `pnpm publish --ignore-scripts` 或 `npm publish --ignore-scripts`(可加 --access public|restricted、--tag <名稱>、--dry-run、--provenance,pnpm 另可加 --no-git-checks)';
|
|
498
|
+
readonly release: '單獨執行 `gh release create <tag> -R <owner/repo>`(可加 --title、--notes、--generate-notes、--draft、--prerelease、--verify-tag)';
|
|
499
|
+
};
|
|
500
|
+
};
|
|
503
501
|
/** Where each kind of entry lives under the hub root. */
|
|
504
502
|
export declare const LAYOUT: {
|
|
505
503
|
readonly config: '.mainflow/config.yaml';
|
|
@@ -551,13 +549,15 @@ export interface FieldSpec {
|
|
|
551
549
|
}
|
|
552
550
|
/** Every front matter field of each document and list entry; facts.test keeps it equal to the schema. */
|
|
553
551
|
export declare const FIELDS: {
|
|
554
|
-
readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
552
|
+
readonly mainline: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
555
553
|
readonly task: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
556
|
-
readonly decision: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
554
|
+
readonly decision: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
557
555
|
readonly issue: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
558
556
|
readonly finding: readonly [...FieldSpec[], FieldSpec, FieldSpec, FieldSpec];
|
|
559
557
|
readonly criterion: FieldSpec[];
|
|
560
558
|
readonly checklistItem: readonly [...FieldSpec[], FieldSpec];
|
|
559
|
+
readonly grant: readonly [FieldSpec, FieldSpec, FieldSpec, FieldSpec];
|
|
561
560
|
readonly link: readonly [FieldSpec, FieldSpec, FieldSpec];
|
|
562
561
|
};
|
|
563
|
-
//#endregion
|
|
562
|
+
//#endregion
|
|
563
|
+
export { CHECKLIST_REF, FORMAT_VERSION, ID_PATTERNS, SLUG };
|