mainflow 0.41.0 → 0.43.0-alpha.1
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 +755 -155
- package/dist/{dist-zSabglPq.js → dist-D5rZ0MQH.js} +71 -23
- package/dist/facts.d.ts +61 -12
- package/dist/facts.js +56 -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-DlR1P_Pt.js → operation-BhBbGauo.js} +610 -343
- package/dist/{ui-9uuI4Jif.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-CMeKJsNd.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,31 @@ 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 unregister': {
|
|
40
|
+
readonly usage: 'hub unregister <name>';
|
|
41
|
+
readonly summary: 'Remove a hub from this machine\'s registry; its files stay';
|
|
18
42
|
readonly args: 1;
|
|
19
43
|
readonly needsHub: false;
|
|
20
44
|
};
|
|
@@ -26,13 +50,13 @@ export declare const COMMAND_SPECS: {
|
|
|
26
50
|
};
|
|
27
51
|
readonly check: {
|
|
28
52
|
readonly usage: 'check';
|
|
29
|
-
readonly summary: 'Validate every file
|
|
53
|
+
readonly summary: 'Validate every file in the hub\'s data directory';
|
|
30
54
|
readonly args: 0;
|
|
31
55
|
readonly needsHub: true;
|
|
32
56
|
};
|
|
33
57
|
readonly migrate: {
|
|
34
58
|
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';
|
|
59
|
+
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
60
|
readonly args: 0;
|
|
37
61
|
readonly needsHub: true;
|
|
38
62
|
};
|
|
@@ -44,7 +68,7 @@ export declare const COMMAND_SPECS: {
|
|
|
44
68
|
};
|
|
45
69
|
readonly 'repo add': {
|
|
46
70
|
readonly usage: 'repo add <name> <path>';
|
|
47
|
-
readonly summary: 'Register a repository that tasks can point at (path relative to the hub root)';
|
|
71
|
+
readonly summary: 'Register a repository that tasks can point at as a primary repository of the hub (path relative to the hub root)';
|
|
48
72
|
readonly args: 2;
|
|
49
73
|
readonly needsHub: true;
|
|
50
74
|
};
|
|
@@ -366,25 +390,25 @@ export declare const COMMAND_SPECS: {
|
|
|
366
390
|
};
|
|
367
391
|
readonly retro: {
|
|
368
392
|
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
|
|
393
|
+
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
394
|
readonly args: 1;
|
|
371
395
|
readonly needsHub: true;
|
|
372
396
|
};
|
|
373
397
|
readonly sync: {
|
|
374
398
|
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;
|
|
399
|
+
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
400
|
readonly args: 0;
|
|
377
401
|
readonly needsHub: true;
|
|
378
402
|
};
|
|
379
403
|
readonly 'sync flush': {
|
|
380
404
|
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';
|
|
405
|
+
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
406
|
readonly args: 0;
|
|
383
407
|
readonly needsHub: true;
|
|
384
408
|
};
|
|
385
409
|
readonly 'sync fetch': {
|
|
386
410
|
readonly usage: 'sync fetch [<path|m|m/t>] [--hook]';
|
|
387
|
-
readonly summary: 'Download files only on the sync server:
|
|
411
|
+
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
412
|
readonly args: 0;
|
|
389
413
|
readonly optionalArgs: 1;
|
|
390
414
|
readonly needsHub: true;
|
|
@@ -395,6 +419,20 @@ export declare const COMMAND_SPECS: {
|
|
|
395
419
|
readonly args: 0;
|
|
396
420
|
readonly needsHub: true;
|
|
397
421
|
};
|
|
422
|
+
readonly 'sync log': {
|
|
423
|
+
readonly usage: 'sync log [<path|m|m/t>] [--limit <n>] [--json]';
|
|
424
|
+
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';
|
|
425
|
+
readonly args: 0;
|
|
426
|
+
readonly optionalArgs: 1;
|
|
427
|
+
readonly needsHub: true;
|
|
428
|
+
};
|
|
429
|
+
readonly 'sync restore': {
|
|
430
|
+
readonly usage: 'sync restore [<path|m|m/t>] (--seq <n> | --at <time>) [--dry-run] [--json]';
|
|
431
|
+
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';
|
|
432
|
+
readonly args: 0;
|
|
433
|
+
readonly optionalArgs: 1;
|
|
434
|
+
readonly needsHub: true;
|
|
435
|
+
};
|
|
398
436
|
};
|
|
399
437
|
export type CommandName = keyof typeof COMMAND_SPECS;
|
|
400
438
|
export declare const OPTIONS: {
|
|
@@ -522,6 +560,15 @@ export declare const OPTIONS: {
|
|
|
522
560
|
readonly month: {
|
|
523
561
|
readonly type: 'string';
|
|
524
562
|
};
|
|
563
|
+
readonly limit: {
|
|
564
|
+
readonly type: 'string';
|
|
565
|
+
};
|
|
566
|
+
readonly seq: {
|
|
567
|
+
readonly type: 'string';
|
|
568
|
+
};
|
|
569
|
+
readonly at: {
|
|
570
|
+
readonly type: 'string';
|
|
571
|
+
};
|
|
525
572
|
readonly all: {
|
|
526
573
|
readonly type: 'boolean';
|
|
527
574
|
readonly default: false;
|
|
@@ -626,8 +673,7 @@ export declare const RETRO_FORMATS: {
|
|
|
626
673
|
};
|
|
627
674
|
};
|
|
628
675
|
/**
|
|
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.
|
|
676
|
+
* 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
677
|
*/
|
|
632
678
|
export declare const UNTIL: readonly {
|
|
633
679
|
kind: Until['kind'];
|
|
@@ -658,7 +704,10 @@ export declare const GRANTS: {
|
|
|
658
704
|
readonly release: '單獨執行 `gh release create <tag> -R <owner/repo>`(可加 --title、--notes、--generate-notes、--draft、--prerelease、--verify-tag)';
|
|
659
705
|
};
|
|
660
706
|
};
|
|
661
|
-
/**
|
|
707
|
+
/**
|
|
708
|
+
* Where each kind of entry lives in the hub's data directory, written as messages name a file of the hub: `.mainflow/`
|
|
709
|
+
* stands for the data directory, $MAINFLOW_HOME/<hub> (~/.mainflow/<hub>) on a machine that registered the hub.
|
|
710
|
+
*/
|
|
662
711
|
export declare const LAYOUT: {
|
|
663
712
|
readonly config: '.mainflow/config.yaml';
|
|
664
713
|
readonly mainline: '.mainflow/mainlines/m{NNN}-{slug}/mainline.md';
|
package/dist/facts.js
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
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 unregister": {
|
|
30
|
+
usage: "hub unregister <name>",
|
|
31
|
+
summary: "Remove a hub from this machine's registry; its files stay",
|
|
8
32
|
args: 1,
|
|
9
33
|
needsHub: false
|
|
10
34
|
},
|
|
@@ -16,13 +40,13 @@ const COMMAND_SPECS = {
|
|
|
16
40
|
},
|
|
17
41
|
check: {
|
|
18
42
|
usage: "check",
|
|
19
|
-
summary: "Validate every file
|
|
43
|
+
summary: "Validate every file in the hub's data directory",
|
|
20
44
|
args: 0,
|
|
21
45
|
needsHub: true
|
|
22
46
|
},
|
|
23
47
|
migrate: {
|
|
24
48
|
usage: "migrate [--dry-run]",
|
|
25
|
-
summary: "Convert the hub to the data format this CLI reads; --dry-run lists the changes without writing",
|
|
49
|
+
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
50
|
args: 0,
|
|
27
51
|
needsHub: true
|
|
28
52
|
},
|
|
@@ -34,7 +58,7 @@ const COMMAND_SPECS = {
|
|
|
34
58
|
},
|
|
35
59
|
"repo add": {
|
|
36
60
|
usage: "repo add <name> <path>",
|
|
37
|
-
summary: "Register a repository that tasks can point at (path relative to the hub root)",
|
|
61
|
+
summary: "Register a repository that tasks can point at as a primary repository of the hub (path relative to the hub root)",
|
|
38
62
|
args: 2,
|
|
39
63
|
needsHub: true
|
|
40
64
|
},
|
|
@@ -356,25 +380,25 @@ const COMMAND_SPECS = {
|
|
|
356
380
|
},
|
|
357
381
|
retro: {
|
|
358
382
|
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
|
|
383
|
+
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
384
|
args: 1,
|
|
361
385
|
needsHub: true
|
|
362
386
|
},
|
|
363
387
|
sync: {
|
|
364
388
|
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;
|
|
389
|
+
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
390
|
args: 0,
|
|
367
391
|
needsHub: true
|
|
368
392
|
},
|
|
369
393
|
"sync flush": {
|
|
370
394
|
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",
|
|
395
|
+
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
396
|
args: 0,
|
|
373
397
|
needsHub: true
|
|
374
398
|
},
|
|
375
399
|
"sync fetch": {
|
|
376
400
|
usage: "sync fetch [<path|m|m/t>] [--hook]",
|
|
377
|
-
summary: "Download files only on the sync server:
|
|
401
|
+
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
402
|
args: 0,
|
|
379
403
|
optionalArgs: 1,
|
|
380
404
|
needsHub: true
|
|
@@ -384,6 +408,20 @@ const COMMAND_SPECS = {
|
|
|
384
408
|
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
409
|
args: 0,
|
|
386
410
|
needsHub: true
|
|
411
|
+
},
|
|
412
|
+
"sync log": {
|
|
413
|
+
usage: "sync log [<path|m|m/t>] [--limit <n>] [--json]",
|
|
414
|
+
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",
|
|
415
|
+
args: 0,
|
|
416
|
+
optionalArgs: 1,
|
|
417
|
+
needsHub: true
|
|
418
|
+
},
|
|
419
|
+
"sync restore": {
|
|
420
|
+
usage: "sync restore [<path|m|m/t>] (--seq <n> | --at <time>) [--dry-run] [--json]",
|
|
421
|
+
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",
|
|
422
|
+
args: 0,
|
|
423
|
+
optionalArgs: 1,
|
|
424
|
+
needsHub: true
|
|
387
425
|
}
|
|
388
426
|
};
|
|
389
427
|
const OPTIONS = {
|
|
@@ -431,6 +469,9 @@ const OPTIONS = {
|
|
|
431
469
|
result: { type: "string" },
|
|
432
470
|
ref: { type: "string" },
|
|
433
471
|
month: { type: "string" },
|
|
472
|
+
limit: { type: "string" },
|
|
473
|
+
seq: { type: "string" },
|
|
474
|
+
at: { type: "string" },
|
|
434
475
|
all: {
|
|
435
476
|
type: "boolean",
|
|
436
477
|
default: false
|
|
@@ -547,8 +588,7 @@ const RETRO_FORMATS = {
|
|
|
547
588
|
}
|
|
548
589
|
};
|
|
549
590
|
/**
|
|
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.
|
|
591
|
+
* 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
592
|
*/
|
|
553
593
|
const UNTIL = UNTIL_FORMS;
|
|
554
594
|
/** Relation types a document's links take; readers derive the reverse direction. */
|
|
@@ -567,7 +607,10 @@ const GRANTS = {
|
|
|
567
607
|
enforcedByHook: ["publish", "release"],
|
|
568
608
|
forms: GRANT_FORMS
|
|
569
609
|
};
|
|
570
|
-
/**
|
|
610
|
+
/**
|
|
611
|
+
* Where each kind of entry lives in the hub's data directory, written as messages name a file of the hub: `.mainflow/`
|
|
612
|
+
* stands for the data directory, $MAINFLOW_HOME/<hub> (~/.mainflow/<hub>) on a machine that registered the hub.
|
|
613
|
+
*/
|
|
571
614
|
const LAYOUT = {
|
|
572
615
|
config: ".mainflow/config.yaml",
|
|
573
616
|
mainline: ".mainflow/mainlines/m{NNN}-{slug}/mainline.md",
|
|
@@ -672,7 +715,7 @@ const FIELDS = {
|
|
|
672
715
|
field("order", false, "在主脈絡內的順序,小的在前"),
|
|
673
716
|
field("dependsOn", false, "開始前必須先完成的任務"),
|
|
674
717
|
field("repo", false, "主要動到的倉庫,名稱需在 config.yaml 登記"),
|
|
675
|
-
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo
|
|
718
|
+
field("touches", false, "預計會改到的路徑或 glob 清單,相對於 repo 的根目錄;只供開場簡報列出兩條進行中主脈絡的未結束任務在同一個 repo 重疊的路徑,check 不警告,重疊由提交時兩邊的會話協調,不必記 depends-on;有 touches 卻沒填 repo 時路徑不會被比對,check 也列警告"),
|
|
676
719
|
field("startedAt", false, "第一次轉為 doing 時由 CLI 寫入"),
|
|
677
720
|
field("checklist", true, "checklist 項目清單,每項的欄位見 checklistItem"),
|
|
678
721
|
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";
|