mainflow 0.42.0 → 0.43.0-alpha.2
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 +767 -155
- package/dist/{dist-zSabglPq.js → dist-D5rZ0MQH.js} +71 -23
- package/dist/facts.d.ts +67 -12
- package/dist/facts.js +62 -13
- package/dist/grant-commands.d.ts +1 -1
- package/dist/grant-commands.js +1 -1
- package/dist/hooks.d.ts +5 -540
- package/dist/hooks.js +126 -92
- package/dist/{host-BAMZmRy8.js → host-BJvqZy-i.js} +1374 -489
- package/dist/host.d.ts +3 -2
- package/dist/host.js +1 -1
- package/dist/{index-Dk4fCI7q.d.ts → index-BqPQp6TF.d.ts} +2 -8
- package/dist/index-DRpamPge.d.ts +550 -0
- package/dist/launch.d.ts +6 -5
- package/dist/launch.js +323 -1
- package/dist/native/mainflow-swap-darwin-arm64 +0 -0
- package/dist/native/mainflow-swap-darwin-x64 +0 -0
- package/dist/{operation-2wx2ex6x.js → operation-BhBbGauo.js} +395 -337
- package/dist/{ui-D1dg19Jw.js → ui-xxzmhAqF.js} +107 -98
- package/dist/version-Dvv4jwF1.js +6 -0
- package/dist/web/assets/{index-DvYj_6IC.js → index-B4w2iRCQ.js} +2 -2
- package/dist/web/index.html +1 -1
- package/package.json +2 -2
- package/dist/launch-DosYhbwc.js +0 -327
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import validRange from "semver/ranges/valid.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
//#region ../schema/dist/grants.js
|
|
3
4
|
/** What a mainline can authorize ahead of time. */
|
|
@@ -43,7 +44,7 @@ const GRANT_TARGET_FORMS = {
|
|
|
43
44
|
};
|
|
44
45
|
const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
45
46
|
/** A git branch name, `*` standing for any run of characters. */
|
|
46
|
-
const BRANCH
|
|
47
|
+
const BRANCH = /^(?!\/)(?!.*\/\/)(?!.*\.\.)[A-Za-z0-9._/*-]+(?<!\/)$/;
|
|
47
48
|
/** npm package names: lowercase, optionally scoped. */
|
|
48
49
|
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
49
50
|
/** A tag pattern: no whitespace, `*` and `?` as in a shell glob. */
|
|
@@ -62,7 +63,7 @@ function parseGrantTarget(action, target) {
|
|
|
62
63
|
const repo = colon < 0 ? target : target.slice(0, colon);
|
|
63
64
|
const branch = colon < 0 ? void 0 : target.slice(colon + 1);
|
|
64
65
|
if (!repo) return fail(action, "the repo name is missing");
|
|
65
|
-
if (branch !== void 0 && !BRANCH
|
|
66
|
+
if (branch !== void 0 && !BRANCH.test(branch)) return fail(action, `"${branch}" is not a branch name`);
|
|
66
67
|
return {
|
|
67
68
|
ok: true,
|
|
68
69
|
target: {
|
|
@@ -666,17 +667,68 @@ const SURVEY_LINES = [
|
|
|
666
667
|
requires: "環境:"
|
|
667
668
|
}
|
|
668
669
|
];
|
|
670
|
+
/**
|
|
671
|
+
* How a version is compared with config.yaml's `mainflow`: a prerelease satisfies a range by its place in semver order,
|
|
672
|
+
* so 0.44.0-beta.1 satisfies ">=0.43.0" and 0.43.0-beta.1 does not.
|
|
673
|
+
*/
|
|
674
|
+
const MAINFLOW_RANGE = { includePrerelease: true };
|
|
675
|
+
/** Whether `range` is a range config.yaml's `mainflow` may hold: one the semver package reads, and not blank, which it would read as any version. */
|
|
676
|
+
const isMainflowRange = (range) => range.trim() !== "" && validRange(range, MAINFLOW_RANGE) !== null;
|
|
677
|
+
/** A repository under `repos`: a path relative to the hub root, which is a primary repository, or the path with its role. */
|
|
678
|
+
const repoEntrySchema = z.union([z.string(), z.object({
|
|
679
|
+
path: z.string(),
|
|
680
|
+
role: z.enum(["primary", "reference"])
|
|
681
|
+
})]);
|
|
682
|
+
/** The path of a listed repository, relative to the hub root. */
|
|
683
|
+
const repoPath = (entry) => typeof entry === "string" ? entry : entry.path;
|
|
684
|
+
/** The role of a listed repository; the plain path form is a primary repository. */
|
|
685
|
+
const repoRole = (entry) => typeof entry === "string" ? "primary" : entry.role;
|
|
686
|
+
/** Every repository config.yaml lists, by name, with its path relative to the hub root and its role. */
|
|
687
|
+
const listedRepos = (repos) => Object.entries(repos).map(([name, entry]) => ({
|
|
688
|
+
name,
|
|
689
|
+
path: repoPath(entry),
|
|
690
|
+
role: repoRole(entry)
|
|
691
|
+
}));
|
|
692
|
+
/** Every listed repository's path relative to the hub root, by name, whatever its role. */
|
|
693
|
+
const repoPaths = (repos) => Object.fromEntries(Object.entries(repos).map(([name, entry]) => [name, repoPath(entry)]));
|
|
669
694
|
const configSchema = z.object({
|
|
670
695
|
/** Also the first label of the hub's host name, {hub}.mainflow.localhost, so kebab-case */
|
|
671
696
|
hub: z.string().regex(HUB_NAME, "hub name must be kebab-case, e.g. carlos-space"),
|
|
672
697
|
formatVersion: z.number().int().positive(),
|
|
673
|
-
|
|
698
|
+
/** Repositories tasks and grants may name: name to a path relative to the hub root, or to { path, role } */
|
|
699
|
+
repos: z.record(z.string(), repoEntrySchema),
|
|
674
700
|
/** Other hubs this hub's references point into: hub name to its root, relative to this hub's root */
|
|
675
701
|
hubs: z.record(z.string().regex(HUB_NAME, "a hub name must be kebab-case").refine((n) => !RESERVED_HUB_NAMES.includes(n), `${RESERVED_HUB_NAMES.join(" and ")} are reserved`), z.string()).optional(),
|
|
676
702
|
/** Categories this hub uses on top of DEFAULT_CATEGORIES */
|
|
677
703
|
categories: z.array(z.string().regex(SLUG, "a category must be kebab-case")).optional(),
|
|
678
704
|
/** The port `mainflow ui` tries first for this hub; when it is taken, the next free one is used and the host name follows */
|
|
679
|
-
ui: z.object({ port: z.number().int().min(1024).max(65535) }).optional()
|
|
705
|
+
ui: z.object({ port: z.number().int().min(1024).max(65535) }).optional(),
|
|
706
|
+
/**
|
|
707
|
+
* The mainflow versions that may write to this hub through a sync server, as a semver range like ">=0.43.0", compared as
|
|
708
|
+
* MAINFLOW_RANGE says: the server refuses every write from a CLI outside it, and from one that does not say its version
|
|
709
|
+
*/
|
|
710
|
+
mainflow: z.string().refine(isMainflowRange, "mainflow is a semver range of the mainflow versions that may write to this hub, like \">=0.43.0\"").optional()
|
|
711
|
+
});
|
|
712
|
+
/**
|
|
713
|
+
* One hub as this machine registered it, every path absolute and resolved by the file system (realpath), so a path
|
|
714
|
+
* is compared as the file system spells it. Derived from the hub's config.yaml by `mainflow hub register`.
|
|
715
|
+
*/
|
|
716
|
+
const registeredHubSchema = z.object({
|
|
717
|
+
/** The hub repository: the directory config.yaml's repository paths are relative to */
|
|
718
|
+
root: z.string(),
|
|
719
|
+
/** The directory holding config.yaml and the hub's documents */
|
|
720
|
+
data: z.string(),
|
|
721
|
+
/** Content hash of config.yaml when this entry was derived from it, so a changed config.yaml is noticed */
|
|
722
|
+
config: z.string(),
|
|
723
|
+
/** The repositories this hub is the primary hub of, by the name config.yaml gives them; the hub repository is one of them whether listed or not */
|
|
724
|
+
repos: z.record(z.string(), z.string()),
|
|
725
|
+
/** The repositories this hub lists as references, by name */
|
|
726
|
+
references: z.record(z.string(), z.string())
|
|
727
|
+
});
|
|
728
|
+
/** The user-level hub registry, $MAINFLOW_HOME/hubs.json: which hub a directory on this machine belongs to. */
|
|
729
|
+
const hubRegistrySchema = z.object({
|
|
730
|
+
version: z.literal(1),
|
|
731
|
+
hubs: z.record(z.string().regex(HUB_NAME), registeredHubSchema)
|
|
680
732
|
});
|
|
681
733
|
const refSchema = z.object({
|
|
682
734
|
title: text,
|
|
@@ -889,11 +941,6 @@ function parseActionRef(input) {
|
|
|
889
941
|
const waitingForAction = (waitingFor) => waitingFor && parseActionRef(waitingFor) ? waitingFor : void 0;
|
|
890
942
|
/** The condition forms, one example each; facts and error messages read them from here. */
|
|
891
943
|
const UNTIL_FORMS = [
|
|
892
|
-
{
|
|
893
|
-
kind: "commit",
|
|
894
|
-
format: "commit:{branch}:{text}",
|
|
895
|
-
example: "commit:main:feat(core): 行動阻塞"
|
|
896
|
-
},
|
|
897
944
|
{
|
|
898
945
|
kind: "action-done",
|
|
899
946
|
format: "action-done:{sessionId}#a{NNN}",
|
|
@@ -911,8 +958,14 @@ const UNTIL_FORMS = [
|
|
|
911
958
|
}
|
|
912
959
|
];
|
|
913
960
|
const UNTIL_EXAMPLES = UNTIL_FORMS.map((f) => f.format).join(", ");
|
|
914
|
-
/**
|
|
915
|
-
|
|
961
|
+
/** What to tell the writer of an `input` that was a wait condition once and is removed now, or null when it is not one. */
|
|
962
|
+
function removedUntil(input) {
|
|
963
|
+
return input.startsWith("commit:") ? `the commit: condition was removed; use one of ${UNTIL_EXAMPLES}, for example file:{relative path} for a file the commit produces` : null;
|
|
964
|
+
}
|
|
965
|
+
/** Why `input` is not a wait condition, for the messages that reject it; a form that was removed says so and names what to use. */
|
|
966
|
+
function untilProblem(input) {
|
|
967
|
+
return removedUntil(input) ?? `expected a condition like ${UNTIL_EXAMPLES}`;
|
|
968
|
+
}
|
|
916
969
|
/** Parse a wait condition, or return null when it matches none of the forms. */
|
|
917
970
|
function parseUntil(input) {
|
|
918
971
|
const colon = input.indexOf(":");
|
|
@@ -920,16 +973,6 @@ function parseUntil(input) {
|
|
|
920
973
|
const kind = input.slice(0, colon);
|
|
921
974
|
const argument = input.slice(colon + 1);
|
|
922
975
|
switch (kind) {
|
|
923
|
-
case "commit": {
|
|
924
|
-
const split = argument.indexOf(":");
|
|
925
|
-
const branch = argument.slice(0, split);
|
|
926
|
-
const text = argument.slice(split + 1);
|
|
927
|
-
return split > 0 && BRANCH.test(branch) && !branch.includes("..") && text.length > 0 ? {
|
|
928
|
-
kind,
|
|
929
|
-
branch,
|
|
930
|
-
text
|
|
931
|
-
} : null;
|
|
932
|
-
}
|
|
933
976
|
case "action-done": {
|
|
934
977
|
const ref = parseActionRef(argument);
|
|
935
978
|
return ref ? {
|
|
@@ -956,7 +999,12 @@ function parseUntil(input) {
|
|
|
956
999
|
const actionBlockSchema = z.object({
|
|
957
1000
|
reason: text,
|
|
958
1001
|
waitingFor: text.optional(),
|
|
959
|
-
until: z.string().
|
|
1002
|
+
until: z.string().superRefine((s, ctx) => {
|
|
1003
|
+
if (parseUntil(s) === null) ctx.addIssue({
|
|
1004
|
+
code: "custom",
|
|
1005
|
+
message: untilProblem(s)
|
|
1006
|
+
});
|
|
1007
|
+
}).optional()
|
|
960
1008
|
});
|
|
961
1009
|
const actionSchema = z.object({
|
|
962
1010
|
id: id("action"),
|
|
@@ -1110,4 +1158,4 @@ function parseOpsLine(line) {
|
|
|
1110
1158
|
return parsed.success ? parsed.data : null;
|
|
1111
1159
|
}
|
|
1112
1160
|
//#endregion
|
|
1113
|
-
export {
|
|
1161
|
+
export { removedUntil as $, SURVEY_LINES as A, groupSchema as B, PRIORITIES as C, SESSION_ID as D, SERVES_REF as E, WORK_STATUS as F, mainlineSchema as G, isMainflowRange as H, configSchema as I, parseOpsLine as J, opsEntrySchema as K, decisionSchema as L, TASK_REQUIRED_SECTIONS as M, TASK_SECTIONS as N, SESSION_STATUS as O, UNTIL_FORMS as P, refMainline as Q, findingSchema as R, MERGE_STATES as S, RESERVED_HUB_NAMES as T, issueSchema as U, hubRegistrySchema as V, listedRepos as W, parseUntil as X, parseRef as Y, refExample as Z, ISSUE_STATUS as _, DECISION_SECTIONS as a, waitingForAction as at, MAINLINE_SECTIONS as b, FINDING_SECTIONS as c, stampMs as ct, GROUP_SECTIONS as d, GRANT_TARGET_FORMS as dt, repoPath as et, GROUP_STATUS as f, describeRequest as ft, ISSUE_SECTIONS as g, parseGrantTarget as gt, ID_PATTERNS as h, grantRepo as ht, DECISION_BASES as i, untilProblem as it, SURVEY_SECTION as j, SLUG as k, FINDING_STATUS as l, GRANT_ACTIONS as lt, HUB_NAME as m, grantForm as mt, CLOSED_STATUS as n, sessionSchema as nt, DECISION_STATUS as o, waitingForRef as ot, GROUP_SUMMARY_SECTION as p, grantCovers as pt, parseActionRef as q, DECIDERS as r, taskSchema as rt, DEFAULT_CATEGORIES as s, OFFSET as st, CHECKLIST_REF as t, repoPaths as tt, FORMAT_VERSION as u, GRANT_FORMS as ut, LINK_RELS as v, REF_EXAMPLES as w, MAINLINE_STATUS as x, MAINFLOW_RANGE as y, formatOpsEntry as z };
|
package/dist/facts.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as SurveyLine, l as ID_PATTERNS, m as SLUG, o as FORMAT_VERSION, p as SERVES_REF, r as CHECKLIST_REF, v as Until } from "./index-
|
|
1
|
+
import { g as SurveyLine, l as ID_PATTERNS, m as SLUG, o as FORMAT_VERSION, p as SERVES_REF, r as CHECKLIST_REF, v as Until } from "./index-BqPQp6TF.js";
|
|
2
2
|
//#region src/facts.d.ts
|
|
3
3
|
export interface CommandSpec {
|
|
4
4
|
usage: string;
|
|
@@ -14,7 +14,37 @@ export interface CommandSpec {
|
|
|
14
14
|
export declare const COMMAND_SPECS: {
|
|
15
15
|
readonly init: {
|
|
16
16
|
readonly usage: 'init <hub-name>';
|
|
17
|
-
readonly summary: 'Create
|
|
17
|
+
readonly summary: 'Create the data directory of a new hub in $MAINFLOW_HOME/<hub-name> (~/.mainflow/<hub-name> by default) and register the hub on this machine with the current directory as its repository; nothing is written into the repository';
|
|
18
|
+
readonly args: 1;
|
|
19
|
+
readonly needsHub: false;
|
|
20
|
+
};
|
|
21
|
+
readonly 'hub register': {
|
|
22
|
+
readonly usage: 'hub register <name> <path>';
|
|
23
|
+
readonly summary: 'Register on this machine the hub whose repository is <path>, so mainflow run in its primary repositories and their worktrees works on it; run it again after config.yaml changes to update it at once';
|
|
24
|
+
readonly args: 2;
|
|
25
|
+
readonly needsHub: false;
|
|
26
|
+
};
|
|
27
|
+
readonly 'hub migrate-data': {
|
|
28
|
+
readonly usage: 'hub migrate-data <path> [--dry-run]';
|
|
29
|
+
readonly summary: 'Move the data of the hub whose repository is <path> from <path>/.mainflow/ to $MAINFLOW_HOME/<name>, and register the hub with its data there. The data is copied and compared before the registry switches to it; the original is then renamed to <path>/.mainflow.migrated, not deleted. A .git file of a git worktree is not copied and git is not run: the command prints the git worktree remove to run. Refused while mainflow sync or the hub\'s interface runs, or when $MAINFLOW_HOME/<name> is taken; --dry-run says what it would do';
|
|
30
|
+
readonly args: 1;
|
|
31
|
+
readonly needsHub: false;
|
|
32
|
+
};
|
|
33
|
+
readonly 'hub list': {
|
|
34
|
+
readonly usage: 'hub list';
|
|
35
|
+
readonly summary: 'List the hubs registered on this machine with their primary and referenced repositories';
|
|
36
|
+
readonly args: 0;
|
|
37
|
+
readonly needsHub: false;
|
|
38
|
+
};
|
|
39
|
+
readonly 'hub which': {
|
|
40
|
+
readonly usage: 'hub which [--json]';
|
|
41
|
+
readonly summary: 'Print the name, repository root and data directory of the hub the current directory belongs to, as hub list finds it. --json prints one object {"name","root","data"}. Outside any registered hub it prints nothing to stdout, one line to stderr, and exits 1. Writes nothing';
|
|
42
|
+
readonly args: 0;
|
|
43
|
+
readonly needsHub: false;
|
|
44
|
+
};
|
|
45
|
+
readonly 'hub unregister': {
|
|
46
|
+
readonly usage: 'hub unregister <name>';
|
|
47
|
+
readonly summary: 'Remove a hub from this machine\'s registry; its files stay';
|
|
18
48
|
readonly args: 1;
|
|
19
49
|
readonly needsHub: false;
|
|
20
50
|
};
|
|
@@ -26,13 +56,13 @@ export declare const COMMAND_SPECS: {
|
|
|
26
56
|
};
|
|
27
57
|
readonly check: {
|
|
28
58
|
readonly usage: 'check';
|
|
29
|
-
readonly summary: 'Validate every file
|
|
59
|
+
readonly summary: 'Validate every file in the hub\'s data directory';
|
|
30
60
|
readonly args: 0;
|
|
31
61
|
readonly needsHub: true;
|
|
32
62
|
};
|
|
33
63
|
readonly migrate: {
|
|
34
64
|
readonly usage: 'migrate [--dry-run]';
|
|
35
|
-
readonly summary: 'Convert the hub to the data format this CLI reads; --dry-run lists the changes without writing';
|
|
65
|
+
readonly summary: 'Convert the hub to the data format this CLI reads, on the sync server when it keeps the hub; --dry-run lists the changes without writing';
|
|
36
66
|
readonly args: 0;
|
|
37
67
|
readonly needsHub: true;
|
|
38
68
|
};
|
|
@@ -44,7 +74,7 @@ export declare const COMMAND_SPECS: {
|
|
|
44
74
|
};
|
|
45
75
|
readonly 'repo add': {
|
|
46
76
|
readonly usage: 'repo add <name> <path>';
|
|
47
|
-
readonly summary: 'Register a repository that tasks can point at (path relative to the hub root)';
|
|
77
|
+
readonly summary: 'Register a repository that tasks can point at as a primary repository of the hub (path relative to the hub root)';
|
|
48
78
|
readonly args: 2;
|
|
49
79
|
readonly needsHub: true;
|
|
50
80
|
};
|
|
@@ -366,25 +396,25 @@ export declare const COMMAND_SPECS: {
|
|
|
366
396
|
};
|
|
367
397
|
readonly retro: {
|
|
368
398
|
readonly usage: 'retro <m> [--json]';
|
|
369
|
-
readonly summary: 'Read the Claude Code transcripts of the sessions that served a mainline, and their subagents\', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With
|
|
399
|
+
readonly summary: 'Read the Claude Code transcripts of the sessions that served a mainline, and their subagents\', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With the measurement upload\'s settings (the receiver\'s origin from TRANSCRIPT_UPLOAD_URL, in the environment or the env of the Claude Code user settings; the Cloudflare Access service token from TELEMETRY_ACCESS_CLIENT_ID and TELEMETRY_ACCESS_CLIENT_SECRET, else the macOS keychain items telemetry-access-client-id and telemetry-access-client-secret) found it also reads the measurement receiver\'s export: billed cost per request, and the run time of every hook, the silent ones too; sessions whose transcript is not on this machine are downloaded from the receiver into .mainflow/local/transcripts/ first';
|
|
370
400
|
readonly args: 1;
|
|
371
401
|
readonly needsHub: true;
|
|
372
402
|
};
|
|
373
403
|
readonly sync: {
|
|
374
404
|
readonly usage: 'sync';
|
|
375
|
-
readonly summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server is https://mainflow-sync.carlos209310.space unless MAINFLOW_SYNC_URL, or url in .mainflow/local/sync.json, names another; its Cloudflare Access service token comes from MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or clientId and clientSecret in the same file. On connecting it downloads only the files that differ from the versions it last synced;
|
|
405
|
+
readonly summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server is https://mainflow-sync.carlos209310.space unless MAINFLOW_SYNC_URL, or url in .mainflow/local/sync.json, names another; its Cloudflare Access service token comes from MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or clientId and clientSecret in the same file. On connecting it downloads only the files that differ from the versions it last synced; unless MAINFLOW_SYNC_ON_DEMAND=0, or onDemand: false in sync.json, it leaves the artifacts of done and dropped mainlines on the server until they are used (sync fetch). In a new environment whose data directory has no config.yaml yet (after mainflow hub register), the hub's name comes from the registry and config.yaml and the other files are downloaded. While it runs, or with a service token set, every mainflow write is applied here and put in the outbox, which it sends in order; a writing command waits up to 15 s for the server's answer`;
|
|
376
406
|
readonly args: 0;
|
|
377
407
|
readonly needsHub: true;
|
|
378
408
|
};
|
|
379
409
|
readonly 'sync flush': {
|
|
380
410
|
readonly usage: 'sync flush [--hook]';
|
|
381
|
-
readonly summary: 'Send every change to .mainflow/ now and print what did not reach the hub as written, such as a refused edit and where it was saved; --hook reads a PostToolUse payload from stdin and answers as the hook';
|
|
411
|
+
readonly summary: 'Send every change to .mainflow/ now and print what did not reach the hub as written, such as a refused edit and where it was saved; --hook reads a PostToolUse payload from stdin and answers as the hook, for the hub whose data directory holds the file the tool wrote, else the hub of the payload\'s cwd, and nothing outside a registered hub';
|
|
382
412
|
readonly args: 0;
|
|
383
413
|
readonly needsHub: true;
|
|
384
414
|
};
|
|
385
415
|
readonly 'sync fetch': {
|
|
386
416
|
readonly usage: 'sync fetch [<path|m|m/t>] [--hook]';
|
|
387
|
-
readonly summary: 'Download files only on the sync server:
|
|
417
|
+
readonly summary: 'Download files only on the sync server: unless MAINFLOW_SYNC_ON_DEMAND=0, or onDemand: false in .mainflow/local/sync.json, the artifacts of done and dropped mainlines stay there until used. Give a file or directory (from here or from .mainflow/), or a mainline or task ID for all of its artifacts; mainflow sync must be running. Grep and Glob do not see a file that is not downloaded, so fetch the mainline before searching it. --hook reads a PreToolUse payload from stdin and acts only when its file_path is in a registered hub\'s data directory, from whatever directory it runs: a Read or Write of such a file downloads it first (Claude Code 2.1.285 answers an Edit of a missing file before any hook runs, so Read it or fetch it before editing); a Write, a failed download, or a mainflow sync that does not run or does not answer within 30 s answers deny with the reason';
|
|
388
418
|
readonly args: 0;
|
|
389
419
|
readonly optionalArgs: 1;
|
|
390
420
|
readonly needsHub: true;
|
|
@@ -395,6 +425,20 @@ export declare const COMMAND_SPECS: {
|
|
|
395
425
|
readonly args: 0;
|
|
396
426
|
readonly needsHub: true;
|
|
397
427
|
};
|
|
428
|
+
readonly 'sync log': {
|
|
429
|
+
readonly usage: 'sync log [<path|m|m/t>] [--limit <n>] [--json]';
|
|
430
|
+
readonly summary: 'List the changes the sync server logged, newest first (50 unless --limit says otherwise): of a file or directory (from here or from .mainflow/), of a mainline or task by its ID, or of the whole hub. Each line is the seq, when the server made the change, who sent it (the first 8 characters of the Cloudflare Access Client ID of its environment, (here) for this one), the request, what it did, the path, and what the path held before and after (a hash\'s first 7 digits, dir, or -). The server keeps a change a year, 1 GiB of texts and 2 GiB of binary files a hub; it lists from what seq the hub can be put back';
|
|
431
|
+
readonly args: 0;
|
|
432
|
+
readonly optionalArgs: 1;
|
|
433
|
+
readonly needsHub: true;
|
|
434
|
+
};
|
|
435
|
+
readonly 'sync restore': {
|
|
436
|
+
readonly usage: 'sync restore [<path|m|m/t>] (--seq <n> | --at <time>) [--dry-run] [--json]';
|
|
437
|
+
readonly summary: 'Put the whole hub, or a file, directory, mainline or task, back to what it held after a seq of sync log (--seq), or at a time (--at, local time unless it names its offset, like 2026-10-19T15:30), as a new change on the sync server, which every sync client takes in; nothing of the log is undone, so it prints the command that undoes it. --dry-run lists what it would change. A path whose content the server no longer keeps, or a directory it could not remove, is left as it is and the command fails, after the rest is put back';
|
|
438
|
+
readonly args: 0;
|
|
439
|
+
readonly optionalArgs: 1;
|
|
440
|
+
readonly needsHub: true;
|
|
441
|
+
};
|
|
398
442
|
};
|
|
399
443
|
export type CommandName = keyof typeof COMMAND_SPECS;
|
|
400
444
|
export declare const OPTIONS: {
|
|
@@ -522,6 +566,15 @@ export declare const OPTIONS: {
|
|
|
522
566
|
readonly month: {
|
|
523
567
|
readonly type: 'string';
|
|
524
568
|
};
|
|
569
|
+
readonly limit: {
|
|
570
|
+
readonly type: 'string';
|
|
571
|
+
};
|
|
572
|
+
readonly seq: {
|
|
573
|
+
readonly type: 'string';
|
|
574
|
+
};
|
|
575
|
+
readonly at: {
|
|
576
|
+
readonly type: 'string';
|
|
577
|
+
};
|
|
525
578
|
readonly all: {
|
|
526
579
|
readonly type: 'boolean';
|
|
527
580
|
readonly default: false;
|
|
@@ -626,8 +679,7 @@ export declare const RETRO_FORMATS: {
|
|
|
626
679
|
};
|
|
627
680
|
};
|
|
628
681
|
/**
|
|
629
|
-
* Conditions a blocked action's `until` takes and `wait` checks, one example each: a
|
|
630
|
-
* message contains the text (git log runs at the hub root), an action that is done, a path relative to the hub root that exists (`..` reaches sibling repositories), a mainline of this hub or a registered hub that is done.
|
|
682
|
+
* Conditions a blocked action's `until` takes and `wait` checks, one example each: an action that is done, a path relative to the hub root that exists (`..` reaches sibling repositories), a mainline of this hub or a registered hub that is done.
|
|
631
683
|
*/
|
|
632
684
|
export declare const UNTIL: readonly {
|
|
633
685
|
kind: Until['kind'];
|
|
@@ -658,7 +710,10 @@ export declare const GRANTS: {
|
|
|
658
710
|
readonly release: '單獨執行 `gh release create <tag> -R <owner/repo>`(可加 --title、--notes、--generate-notes、--draft、--prerelease、--verify-tag)';
|
|
659
711
|
};
|
|
660
712
|
};
|
|
661
|
-
/**
|
|
713
|
+
/**
|
|
714
|
+
* Where each kind of entry lives in the hub's data directory, written as messages name a file of the hub: `.mainflow/`
|
|
715
|
+
* stands for the data directory, $MAINFLOW_HOME/<hub> (~/.mainflow/<hub>) on a machine that registered the hub.
|
|
716
|
+
*/
|
|
662
717
|
export declare const LAYOUT: {
|
|
663
718
|
readonly config: '.mainflow/config.yaml';
|
|
664
719
|
readonly mainline: '.mainflow/mainlines/m{NNN}-{slug}/mainline.md';
|
package/dist/facts.js
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as SURVEY_LINES, C as PRIORITIES, E as SERVES_REF, F as WORK_STATUS, M as TASK_REQUIRED_SECTIONS, N as TASK_SECTIONS, O as SESSION_STATUS, P as UNTIL_FORMS, S as MERGE_STATES, _ as ISSUE_STATUS, a as DECISION_SECTIONS, b as MAINLINE_SECTIONS, c as FINDING_SECTIONS, d as GROUP_SECTIONS, dt as GRANT_TARGET_FORMS, f as GROUP_STATUS, g as ISSUE_SECTIONS, h as ID_PATTERNS, i as DECISION_BASES, j as SURVEY_SECTION, k as SLUG, l as FINDING_STATUS, lt as GRANT_ACTIONS, n as CLOSED_STATUS, o as DECISION_STATUS, r as DECIDERS, s as DEFAULT_CATEGORIES, t as CHECKLIST_REF, u as FORMAT_VERSION, ut as GRANT_FORMS, v as LINK_RELS, x as MAINLINE_STATUS } from "./dist-D5rZ0MQH.js";
|
|
2
2
|
import { a as LABEL_ROLES, i as FINDING_SEVERITIES, n as FINDING_CLASSES, r as FINDING_FIELDS, t as FINDINGS_FENCE } from "./retro-formats-ECy0gGOL.js";
|
|
3
3
|
//#region src/facts.ts
|
|
4
4
|
const COMMAND_SPECS = {
|
|
5
5
|
init: {
|
|
6
6
|
usage: "init <hub-name>",
|
|
7
|
-
summary: "Create
|
|
7
|
+
summary: "Create the data directory of a new hub in $MAINFLOW_HOME/<hub-name> (~/.mainflow/<hub-name> by default) and register the hub on this machine with the current directory as its repository; nothing is written into the repository",
|
|
8
|
+
args: 1,
|
|
9
|
+
needsHub: false
|
|
10
|
+
},
|
|
11
|
+
"hub register": {
|
|
12
|
+
usage: "hub register <name> <path>",
|
|
13
|
+
summary: "Register on this machine the hub whose repository is <path>, so mainflow run in its primary repositories and their worktrees works on it; run it again after config.yaml changes to update it at once",
|
|
14
|
+
args: 2,
|
|
15
|
+
needsHub: false
|
|
16
|
+
},
|
|
17
|
+
"hub migrate-data": {
|
|
18
|
+
usage: "hub migrate-data <path> [--dry-run]",
|
|
19
|
+
summary: "Move the data of the hub whose repository is <path> from <path>/.mainflow/ to $MAINFLOW_HOME/<name>, and register the hub with its data there. The data is copied and compared before the registry switches to it; the original is then renamed to <path>/.mainflow.migrated, not deleted. A .git file of a git worktree is not copied and git is not run: the command prints the git worktree remove to run. Refused while mainflow sync or the hub's interface runs, or when $MAINFLOW_HOME/<name> is taken; --dry-run says what it would do",
|
|
20
|
+
args: 1,
|
|
21
|
+
needsHub: false
|
|
22
|
+
},
|
|
23
|
+
"hub list": {
|
|
24
|
+
usage: "hub list",
|
|
25
|
+
summary: "List the hubs registered on this machine with their primary and referenced repositories",
|
|
26
|
+
args: 0,
|
|
27
|
+
needsHub: false
|
|
28
|
+
},
|
|
29
|
+
"hub which": {
|
|
30
|
+
usage: "hub which [--json]",
|
|
31
|
+
summary: "Print the name, repository root and data directory of the hub the current directory belongs to, as hub list finds it. --json prints one object {\"name\",\"root\",\"data\"}. Outside any registered hub it prints nothing to stdout, one line to stderr, and exits 1. Writes nothing",
|
|
32
|
+
args: 0,
|
|
33
|
+
needsHub: false
|
|
34
|
+
},
|
|
35
|
+
"hub unregister": {
|
|
36
|
+
usage: "hub unregister <name>",
|
|
37
|
+
summary: "Remove a hub from this machine's registry; its files stay",
|
|
8
38
|
args: 1,
|
|
9
39
|
needsHub: false
|
|
10
40
|
},
|
|
@@ -16,13 +46,13 @@ const COMMAND_SPECS = {
|
|
|
16
46
|
},
|
|
17
47
|
check: {
|
|
18
48
|
usage: "check",
|
|
19
|
-
summary: "Validate every file
|
|
49
|
+
summary: "Validate every file in the hub's data directory",
|
|
20
50
|
args: 0,
|
|
21
51
|
needsHub: true
|
|
22
52
|
},
|
|
23
53
|
migrate: {
|
|
24
54
|
usage: "migrate [--dry-run]",
|
|
25
|
-
summary: "Convert the hub to the data format this CLI reads; --dry-run lists the changes without writing",
|
|
55
|
+
summary: "Convert the hub to the data format this CLI reads, on the sync server when it keeps the hub; --dry-run lists the changes without writing",
|
|
26
56
|
args: 0,
|
|
27
57
|
needsHub: true
|
|
28
58
|
},
|
|
@@ -34,7 +64,7 @@ const COMMAND_SPECS = {
|
|
|
34
64
|
},
|
|
35
65
|
"repo add": {
|
|
36
66
|
usage: "repo add <name> <path>",
|
|
37
|
-
summary: "Register a repository that tasks can point at (path relative to the hub root)",
|
|
67
|
+
summary: "Register a repository that tasks can point at as a primary repository of the hub (path relative to the hub root)",
|
|
38
68
|
args: 2,
|
|
39
69
|
needsHub: true
|
|
40
70
|
},
|
|
@@ -356,25 +386,25 @@ const COMMAND_SPECS = {
|
|
|
356
386
|
},
|
|
357
387
|
retro: {
|
|
358
388
|
usage: "retro <m> [--json]",
|
|
359
|
-
summary: "Read the Claude Code transcripts of the sessions that served a mainline, and their subagents', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With
|
|
389
|
+
summary: "Read the Claude Code transcripts of the sessions that served a mainline, and their subagents', into estimated list-price cost (per model, opening context, cache hit rate, hook injections) and candidates for where work went wrong (tool errors, rerun Bash commands, hook injections followed by no tool call, blocks, pauses over 5 minutes, failed completion checks); read-only. With the measurement upload's settings (the receiver's origin from TRANSCRIPT_UPLOAD_URL, in the environment or the env of the Claude Code user settings; the Cloudflare Access service token from TELEMETRY_ACCESS_CLIENT_ID and TELEMETRY_ACCESS_CLIENT_SECRET, else the macOS keychain items telemetry-access-client-id and telemetry-access-client-secret) found it also reads the measurement receiver's export: billed cost per request, and the run time of every hook, the silent ones too; sessions whose transcript is not on this machine are downloaded from the receiver into .mainflow/local/transcripts/ first",
|
|
360
390
|
args: 1,
|
|
361
391
|
needsHub: true
|
|
362
392
|
},
|
|
363
393
|
sync: {
|
|
364
394
|
usage: "sync",
|
|
365
|
-
summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server is https://mainflow-sync.carlos209310.space unless MAINFLOW_SYNC_URL, or url in .mainflow/local/sync.json, names another; its Cloudflare Access service token comes from MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or clientId and clientSecret in the same file. On connecting it downloads only the files that differ from the versions it last synced;
|
|
395
|
+
summary: `Keep .mainflow/ in step with the hub on the sync server until stopped: pull its changes, send every local change. The server is https://mainflow-sync.carlos209310.space unless MAINFLOW_SYNC_URL, or url in .mainflow/local/sync.json, names another; its Cloudflare Access service token comes from MAINFLOW_SYNC_CLIENT_ID and MAINFLOW_SYNC_CLIENT_SECRET, or clientId and clientSecret in the same file. On connecting it downloads only the files that differ from the versions it last synced; unless MAINFLOW_SYNC_ON_DEMAND=0, or onDemand: false in sync.json, it leaves the artifacts of done and dropped mainlines on the server until they are used (sync fetch). In a new environment whose data directory has no config.yaml yet (after mainflow hub register), the hub's name comes from the registry and config.yaml and the other files are downloaded. While it runs, or with a service token set, every mainflow write is applied here and put in the outbox, which it sends in order; a writing command waits up to 15 s for the server's answer`,
|
|
366
396
|
args: 0,
|
|
367
397
|
needsHub: true
|
|
368
398
|
},
|
|
369
399
|
"sync flush": {
|
|
370
400
|
usage: "sync flush [--hook]",
|
|
371
|
-
summary: "Send every change to .mainflow/ now and print what did not reach the hub as written, such as a refused edit and where it was saved; --hook reads a PostToolUse payload from stdin and answers as the hook",
|
|
401
|
+
summary: "Send every change to .mainflow/ now and print what did not reach the hub as written, such as a refused edit and where it was saved; --hook reads a PostToolUse payload from stdin and answers as the hook, for the hub whose data directory holds the file the tool wrote, else the hub of the payload's cwd, and nothing outside a registered hub",
|
|
372
402
|
args: 0,
|
|
373
403
|
needsHub: true
|
|
374
404
|
},
|
|
375
405
|
"sync fetch": {
|
|
376
406
|
usage: "sync fetch [<path|m|m/t>] [--hook]",
|
|
377
|
-
summary: "Download files only on the sync server:
|
|
407
|
+
summary: "Download files only on the sync server: unless MAINFLOW_SYNC_ON_DEMAND=0, or onDemand: false in .mainflow/local/sync.json, the artifacts of done and dropped mainlines stay there until used. Give a file or directory (from here or from .mainflow/), or a mainline or task ID for all of its artifacts; mainflow sync must be running. Grep and Glob do not see a file that is not downloaded, so fetch the mainline before searching it. --hook reads a PreToolUse payload from stdin and acts only when its file_path is in a registered hub's data directory, from whatever directory it runs: a Read or Write of such a file downloads it first (Claude Code 2.1.285 answers an Edit of a missing file before any hook runs, so Read it or fetch it before editing); a Write, a failed download, or a mainflow sync that does not run or does not answer within 30 s answers deny with the reason",
|
|
378
408
|
args: 0,
|
|
379
409
|
optionalArgs: 1,
|
|
380
410
|
needsHub: true
|
|
@@ -384,6 +414,20 @@ const COMMAND_SPECS = {
|
|
|
384
414
|
summary: "Say whether mainflow sync runs for this hub, whether it is online and caught up with the server, and how many writes wait to be sent",
|
|
385
415
|
args: 0,
|
|
386
416
|
needsHub: true
|
|
417
|
+
},
|
|
418
|
+
"sync log": {
|
|
419
|
+
usage: "sync log [<path|m|m/t>] [--limit <n>] [--json]",
|
|
420
|
+
summary: "List the changes the sync server logged, newest first (50 unless --limit says otherwise): of a file or directory (from here or from .mainflow/), of a mainline or task by its ID, or of the whole hub. Each line is the seq, when the server made the change, who sent it (the first 8 characters of the Cloudflare Access Client ID of its environment, (here) for this one), the request, what it did, the path, and what the path held before and after (a hash's first 7 digits, dir, or -). The server keeps a change a year, 1 GiB of texts and 2 GiB of binary files a hub; it lists from what seq the hub can be put back",
|
|
421
|
+
args: 0,
|
|
422
|
+
optionalArgs: 1,
|
|
423
|
+
needsHub: true
|
|
424
|
+
},
|
|
425
|
+
"sync restore": {
|
|
426
|
+
usage: "sync restore [<path|m|m/t>] (--seq <n> | --at <time>) [--dry-run] [--json]",
|
|
427
|
+
summary: "Put the whole hub, or a file, directory, mainline or task, back to what it held after a seq of sync log (--seq), or at a time (--at, local time unless it names its offset, like 2026-10-19T15:30), as a new change on the sync server, which every sync client takes in; nothing of the log is undone, so it prints the command that undoes it. --dry-run lists what it would change. A path whose content the server no longer keeps, or a directory it could not remove, is left as it is and the command fails, after the rest is put back",
|
|
428
|
+
args: 0,
|
|
429
|
+
optionalArgs: 1,
|
|
430
|
+
needsHub: true
|
|
387
431
|
}
|
|
388
432
|
};
|
|
389
433
|
const OPTIONS = {
|
|
@@ -431,6 +475,9 @@ const OPTIONS = {
|
|
|
431
475
|
result: { type: "string" },
|
|
432
476
|
ref: { type: "string" },
|
|
433
477
|
month: { type: "string" },
|
|
478
|
+
limit: { type: "string" },
|
|
479
|
+
seq: { type: "string" },
|
|
480
|
+
at: { type: "string" },
|
|
434
481
|
all: {
|
|
435
482
|
type: "boolean",
|
|
436
483
|
default: false
|
|
@@ -547,8 +594,7 @@ const RETRO_FORMATS = {
|
|
|
547
594
|
}
|
|
548
595
|
};
|
|
549
596
|
/**
|
|
550
|
-
* Conditions a blocked action's `until` takes and `wait` checks, one example each: a
|
|
551
|
-
* message contains the text (git log runs at the hub root), an action that is done, a path relative to the hub root that exists (`..` reaches sibling repositories), a mainline of this hub or a registered hub that is done.
|
|
597
|
+
* Conditions a blocked action's `until` takes and `wait` checks, one example each: an action that is done, a path relative to the hub root that exists (`..` reaches sibling repositories), a mainline of this hub or a registered hub that is done.
|
|
552
598
|
*/
|
|
553
599
|
const UNTIL = UNTIL_FORMS;
|
|
554
600
|
/** Relation types a document's links take; readers derive the reverse direction. */
|
|
@@ -567,7 +613,10 @@ const GRANTS = {
|
|
|
567
613
|
enforcedByHook: ["publish", "release"],
|
|
568
614
|
forms: GRANT_FORMS
|
|
569
615
|
};
|
|
570
|
-
/**
|
|
616
|
+
/**
|
|
617
|
+
* Where each kind of entry lives in the hub's data directory, written as messages name a file of the hub: `.mainflow/`
|
|
618
|
+
* stands for the data directory, $MAINFLOW_HOME/<hub> (~/.mainflow/<hub>) on a machine that registered the hub.
|
|
619
|
+
*/
|
|
571
620
|
const LAYOUT = {
|
|
572
621
|
config: ".mainflow/config.yaml",
|
|
573
622
|
mainline: ".mainflow/mainlines/m{NNN}-{slug}/mainline.md",
|
|
@@ -672,7 +721,7 @@ const FIELDS = {
|
|
|
672
721
|
field("order", false, "在主脈絡內的順序,小的在前"),
|
|
673
722
|
field("dependsOn", false, "開始前必須先完成的任務"),
|
|
674
723
|
field("repo", false, "主要動到的倉庫,名稱需在 config.yaml 登記"),
|
|
675
|
-
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo
|
|
724
|
+
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo 的根目錄;只供開場簡報列出兩條進行中主脈絡的未結束任務在同一個 repo 重疊的路徑,check 不警告,重疊由提交時兩邊的會話協調,不必記 depends-on;有 touches 卻沒填 repo 時路徑不會被比對,check 也列警告"),
|
|
676
725
|
field("startedAt", false, "第一次轉為 doing 時由 CLI 寫入"),
|
|
677
726
|
field("checklist", true, "checklist 項目清單,每項的欄位見 checklistItem"),
|
|
678
727
|
field("blockedBy", false, "卡住時的原因、等待對象與繼續的訊號,blocked 時必填;等待對象整串是參照時(例如 m031/t002)會檢查它存在"),
|
package/dist/grant-commands.d.ts
CHANGED
package/dist/grant-commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ut as GRANT_FORMS } from "./dist-D5rZ0MQH.js";
|
|
2
2
|
import { closeSync, constants, fstatSync, openSync, readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|