pm4ai 0.0.73
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.
Potentially problematic release.
This version of pm4ai might be problematic. Click here for more details.
- package/README.md +34 -0
- package/dist/audit-oQQfgtxr.mjs +287 -0
- package/dist/cleanup-M-ALxTqh.mjs +35 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +77 -0
- package/dist/dashboard-DVRNZGun.mjs +39 -0
- package/dist/discover-d8ENQC1K.mjs +172 -0
- package/dist/fix-BcMN_cuG.mjs +260 -0
- package/dist/fix-DvtItv_V.mjs +2 -0
- package/dist/guide-BS7-RqpH.d.mts +4 -0
- package/dist/guide-CifUmtQN.mjs +59 -0
- package/dist/guide.d.mts +2 -0
- package/dist/guide.mjs +2 -0
- package/dist/ignores-BBl55eUM.mjs +37 -0
- package/dist/index.d.mts +83 -0
- package/dist/index.mjs +11 -0
- package/dist/init-C-073mRX.mjs +120 -0
- package/dist/list-QdJPgkEO.mjs +31 -0
- package/dist/package-NpIViQjo.mjs +4 -0
- package/dist/schemas-Dsbtf6P2.mjs +51 -0
- package/dist/schemas.d.mts +48 -0
- package/dist/schemas.mjs +2 -0
- package/dist/setup-BPuE4oWT.mjs +164 -0
- package/dist/status-ByiuW1iF.mjs +2 -0
- package/dist/status-CzCNkG58.mjs +1775 -0
- package/dist/sync-DN1rgN3P.mjs +732 -0
- package/dist/templates/cli/package.json +30 -0
- package/dist/templates/cli/src/cli.ts +16 -0
- package/dist/templates/cli/src/index.ts +2 -0
- package/dist/templates/cli/src/tui.tsx +57 -0
- package/dist/templates/cli/tsdown.config.ts +9 -0
- package/dist/templates/docs/content/docs/index.mdx +6 -0
- package/dist/templates/docs/package.json +20 -0
- package/dist/templates/docs/source.config.ts +16 -0
- package/dist/templates/docs/src/app/(home)/page.tsx +11 -0
- package/dist/templates/lib/package.json +22 -0
- package/dist/templates/lib/src/index.ts +2 -0
- package/dist/templates/lib/tsdown.config.ts +9 -0
- package/dist/templates/root-package.txt +38 -0
- package/dist/templates/web/package.json +17 -0
- package/dist/templates/web/src/app/page.tsx +6 -0
- package/dist/templates/web/src/app/providers.tsx +15 -0
- package/dist/utils-CpkOMuQN.mjs +221 -0
- package/dist/watch-D4OSFClu.mjs +566 -0
- package/dist/watch-emitter-uTmZ3oQd.mjs +177 -0
- package/dist/watch-state-DIMHiLr5.d.mts +118 -0
- package/dist/watch-state-wF-NfC-k.mjs +274 -0
- package/dist/watch-state.d.mts +2 -0
- package/dist/watch-state.mjs +2 -0
- package/dist/watch-types-BzSNCGb2.mjs +22 -0
- package/package.json +65 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { x as CONFIG_DIR } from "./utils-CpkOMuQN.mjs";
|
|
2
|
+
import { c as watchEventSchema, r as lockSchema, s as safeParseJson, t as checkResultSchema } from "./schemas-Dsbtf6P2.mjs";
|
|
3
|
+
import { spawn } from "bun";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { createConnection, createServer } from "node:net";
|
|
9
|
+
//#region src/check-cache.ts
|
|
10
|
+
/** biome-ignore-all lint/suspicious/noEmptyBlockStatements: intentional catch-swallow */
|
|
11
|
+
const checksDir = () => join(homedir(), CONFIG_DIR, "checks");
|
|
12
|
+
const leadingSepRe = /^--/u;
|
|
13
|
+
const safeFileName = (projectPath) => projectPath.replaceAll("/", "--").replace(leadingSepRe, "");
|
|
14
|
+
const cachePath = (projectPath) => join(checksDir(), `${safeFileName(projectPath)}.json`);
|
|
15
|
+
const lockPath = (projectPath) => join(checksDir(), `${safeFileName(projectPath)}.lock`);
|
|
16
|
+
const readCheckResult = (projectPath) => {
|
|
17
|
+
const p = cachePath(projectPath);
|
|
18
|
+
if (!existsSync(p)) return;
|
|
19
|
+
try {
|
|
20
|
+
return safeParseJson(checkResultSchema, readFileSync(p, "utf8"));
|
|
21
|
+
} catch {}
|
|
22
|
+
};
|
|
23
|
+
const getHeadCommit = (projectPath) => {
|
|
24
|
+
try {
|
|
25
|
+
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
26
|
+
cwd: projectPath,
|
|
27
|
+
stdio: "pipe"
|
|
28
|
+
}).toString().trim();
|
|
29
|
+
} catch {}
|
|
30
|
+
return "";
|
|
31
|
+
};
|
|
32
|
+
const isCheckRunning = (projectPath) => {
|
|
33
|
+
const lp = lockPath(projectPath);
|
|
34
|
+
if (!existsSync(lp)) return false;
|
|
35
|
+
try {
|
|
36
|
+
const lock = safeParseJson(lockSchema, readFileSync(lp, "utf8"));
|
|
37
|
+
if (!lock) {
|
|
38
|
+
rmSync(lp);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (Date.now() - new Date(lock.at).getTime() > 6e5) {
|
|
42
|
+
rmSync(lp);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
process.kill(lock.pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
rmSync(lp);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
rmSync(lp);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const writeCheckResult = (opts) => {
|
|
58
|
+
mkdirSync(checksDir(), { recursive: true });
|
|
59
|
+
const result = {
|
|
60
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
61
|
+
commit: getHeadCommit(opts.projectPath),
|
|
62
|
+
pass: opts.pass,
|
|
63
|
+
summary: opts.summary,
|
|
64
|
+
violations: opts.violations
|
|
65
|
+
};
|
|
66
|
+
writeFileSync(cachePath(opts.projectPath), JSON.stringify(result));
|
|
67
|
+
};
|
|
68
|
+
const spawnBackgroundCheck = (projectPath) => {
|
|
69
|
+
if (isCheckRunning(projectPath)) return;
|
|
70
|
+
mkdirSync(checksDir(), { recursive: true });
|
|
71
|
+
spawn([
|
|
72
|
+
"bun",
|
|
73
|
+
join(import.meta.dir, "check-worker.js"),
|
|
74
|
+
projectPath
|
|
75
|
+
], {
|
|
76
|
+
stderr: "ignore",
|
|
77
|
+
stdin: "ignore",
|
|
78
|
+
stdout: "ignore"
|
|
79
|
+
}).unref();
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/watch-emitter.ts
|
|
83
|
+
const SOCKET_DIR = join(homedir(), CONFIG_DIR);
|
|
84
|
+
const SOCKET_PATH = join(SOCKET_DIR, "watch.sock");
|
|
85
|
+
const clients = /* @__PURE__ */ new Set();
|
|
86
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
87
|
+
let server;
|
|
88
|
+
const removeSocket = () => {
|
|
89
|
+
try {
|
|
90
|
+
unlinkSync(SOCKET_PATH);
|
|
91
|
+
} catch {}
|
|
92
|
+
};
|
|
93
|
+
const broadcast = (line, exclude) => {
|
|
94
|
+
const msg = `${line}\n`;
|
|
95
|
+
for (const client of clients) if (client !== exclude) try {
|
|
96
|
+
client.write(msg);
|
|
97
|
+
} catch {}
|
|
98
|
+
};
|
|
99
|
+
const startEmitter = async () => {
|
|
100
|
+
if (server) return;
|
|
101
|
+
mkdirSync(SOCKET_DIR, { recursive: true });
|
|
102
|
+
if (existsSync(SOCKET_PATH)) removeSocket();
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
const s = createServer((socket) => {
|
|
105
|
+
clients.add(socket);
|
|
106
|
+
socket.on("close", () => clients.delete(socket));
|
|
107
|
+
socket.on("error", () => clients.delete(socket));
|
|
108
|
+
let buffer = "";
|
|
109
|
+
socket.on("data", (chunk) => {
|
|
110
|
+
buffer += chunk.toString();
|
|
111
|
+
const lines = buffer.split("\n");
|
|
112
|
+
buffer = lines.pop() ?? "";
|
|
113
|
+
for (const line of lines.filter(Boolean)) {
|
|
114
|
+
broadcast(line, socket);
|
|
115
|
+
const event = safeParseJson(watchEventSchema, line);
|
|
116
|
+
if (event) for (const fn of listeners) fn(event);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
s.on("error", reject);
|
|
121
|
+
s.listen(SOCKET_PATH, () => {
|
|
122
|
+
server = s;
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const stopEmitter = async () => {
|
|
128
|
+
if (!server) return;
|
|
129
|
+
const s = server;
|
|
130
|
+
server = void 0;
|
|
131
|
+
for (const c of clients) c.destroy();
|
|
132
|
+
clients.clear();
|
|
133
|
+
await new Promise((resolve) => {
|
|
134
|
+
s.close(() => {
|
|
135
|
+
removeSocket();
|
|
136
|
+
resolve();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
const emitToSocket = (event) => {
|
|
141
|
+
if (!existsSync(SOCKET_PATH)) return;
|
|
142
|
+
try {
|
|
143
|
+
const sock = createConnection(SOCKET_PATH);
|
|
144
|
+
sock.on("error", () => {});
|
|
145
|
+
sock.on("connect", () => {
|
|
146
|
+
sock.write(`${JSON.stringify(event)}\n`);
|
|
147
|
+
sock.end();
|
|
148
|
+
});
|
|
149
|
+
} catch {}
|
|
150
|
+
};
|
|
151
|
+
const cleanupOnExit = () => {
|
|
152
|
+
stopEmitter().catch(() => {});
|
|
153
|
+
};
|
|
154
|
+
const installCleanup = () => {
|
|
155
|
+
process.on("exit", () => {
|
|
156
|
+
if (server) {
|
|
157
|
+
for (const c of clients) c.destroy();
|
|
158
|
+
clients.clear();
|
|
159
|
+
server.close();
|
|
160
|
+
removeSocket();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
process.on("SIGINT", () => {
|
|
164
|
+
cleanupOnExit();
|
|
165
|
+
process.exit(130);
|
|
166
|
+
});
|
|
167
|
+
process.on("SIGTERM", () => {
|
|
168
|
+
cleanupOnExit();
|
|
169
|
+
process.exit(143);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const onEvent = (fn) => {
|
|
173
|
+
listeners.add(fn);
|
|
174
|
+
return () => listeners.delete(fn);
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
export { startEmitter as a, writeCheckResult as c, onEvent as i, emitToSocket as n, readCheckResult as o, installCleanup as r, spawnBackgroundCheck as s, SOCKET_PATH as t };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/watch-types.d.ts
|
|
2
|
+
interface WatchEvent {
|
|
3
|
+
at: string;
|
|
4
|
+
detail?: string;
|
|
5
|
+
project: string;
|
|
6
|
+
status: WatchStatus;
|
|
7
|
+
step: WatchStep;
|
|
8
|
+
}
|
|
9
|
+
type WatchStatus = 'fail' | 'ok' | 'start';
|
|
10
|
+
type WatchStep = 'audit' | 'check' | 'done' | 'maintain' | 'sync';
|
|
11
|
+
declare const WATCH_STEPS: readonly ["audit", "check", "done", "maintain", "sync"];
|
|
12
|
+
declare const WATCH_STATUSES: readonly ["fail", "ok", "start"];
|
|
13
|
+
interface CreateEventArgs {
|
|
14
|
+
detail?: string;
|
|
15
|
+
project: string;
|
|
16
|
+
status: WatchStatus;
|
|
17
|
+
step: WatchStep;
|
|
18
|
+
}
|
|
19
|
+
declare const createEvent: ({
|
|
20
|
+
detail,
|
|
21
|
+
project,
|
|
22
|
+
status,
|
|
23
|
+
step
|
|
24
|
+
}: CreateEventArgs) => WatchEvent;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/watch-state.d.ts
|
|
27
|
+
interface DerivedStats {
|
|
28
|
+
completedStepCount: number;
|
|
29
|
+
done: number;
|
|
30
|
+
eta?: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
running: number;
|
|
33
|
+
slowestElapsed: number;
|
|
34
|
+
slowestName: string;
|
|
35
|
+
}
|
|
36
|
+
interface ProjectInfo {
|
|
37
|
+
name: string;
|
|
38
|
+
path: string;
|
|
39
|
+
}
|
|
40
|
+
interface ProjectState {
|
|
41
|
+
cachedPass?: boolean;
|
|
42
|
+
completedSteps: Set<string>;
|
|
43
|
+
detail?: string;
|
|
44
|
+
elapsed: number;
|
|
45
|
+
startedAt?: number;
|
|
46
|
+
status: 'done' | 'failed' | 'idle' | 'running';
|
|
47
|
+
step?: string;
|
|
48
|
+
}
|
|
49
|
+
type RunAction = {
|
|
50
|
+
event: WatchEvent;
|
|
51
|
+
type: 'event';
|
|
52
|
+
} | {
|
|
53
|
+
focused: string;
|
|
54
|
+
type: 'focus';
|
|
55
|
+
} | {
|
|
56
|
+
mkIdle: (p: ProjectInfo) => ProjectState;
|
|
57
|
+
projects: ProjectInfo[];
|
|
58
|
+
type: 'reset';
|
|
59
|
+
} | {
|
|
60
|
+
type: 'bell-acked';
|
|
61
|
+
} | {
|
|
62
|
+
type: 'tick';
|
|
63
|
+
};
|
|
64
|
+
interface RunState {
|
|
65
|
+
bellPending: boolean;
|
|
66
|
+
elapsed: number;
|
|
67
|
+
focused: string;
|
|
68
|
+
history: number[];
|
|
69
|
+
lastDone: number;
|
|
70
|
+
lastElapsed: number;
|
|
71
|
+
lastFailed: number;
|
|
72
|
+
lastTime: string;
|
|
73
|
+
phase: 'done' | 'idle' | 'running';
|
|
74
|
+
projects: Record<string, ProjectState>;
|
|
75
|
+
runCount: number;
|
|
76
|
+
sortSnapshot: string[];
|
|
77
|
+
startTime?: number;
|
|
78
|
+
}
|
|
79
|
+
declare const RESET_DELAY = 5000;
|
|
80
|
+
declare const DISPLAY_STEPS: readonly ["sync", "audit", "maintain", "check"];
|
|
81
|
+
declare const STEP_COUNT: 4;
|
|
82
|
+
declare const STEP_LABELS: {
|
|
83
|
+
readonly audit: "🔍 auditing";
|
|
84
|
+
readonly check: "🧪 checking";
|
|
85
|
+
readonly maintain: "🔧 maintaining";
|
|
86
|
+
readonly sync: "📦 syncing";
|
|
87
|
+
};
|
|
88
|
+
declare const STATUS_ORDER: {
|
|
89
|
+
readonly done: 1;
|
|
90
|
+
readonly failed: 2;
|
|
91
|
+
readonly idle: 3;
|
|
92
|
+
readonly running: 0;
|
|
93
|
+
};
|
|
94
|
+
declare const MAX_HISTORY = 8;
|
|
95
|
+
declare const IDLE_FALLBACK: ProjectState;
|
|
96
|
+
declare const smoothBar: (fraction: number, width: number) => string;
|
|
97
|
+
declare const sparkline: (values: number[]) => string;
|
|
98
|
+
declare const progressDots: (completed: Set<string>, current?: string) => string;
|
|
99
|
+
declare const formatTime: (seconds: number) => string;
|
|
100
|
+
declare const timeAgo: (iso: string) => string;
|
|
101
|
+
declare const nextProjectState: (prev: ProjectState, event: WatchEvent) => ProjectState;
|
|
102
|
+
declare const sortByStatus: (names: string[], projects: Record<string, ProjectState>) => string[];
|
|
103
|
+
declare const tickProjects: (projects: Record<string, ProjectState>) => Record<string, ProjectState>;
|
|
104
|
+
declare const runReducer: (state: RunState, action: RunAction) => RunState;
|
|
105
|
+
declare const deriveStats: ({
|
|
106
|
+
elapsed,
|
|
107
|
+
history,
|
|
108
|
+
lastElapsed,
|
|
109
|
+
projects
|
|
110
|
+
}: {
|
|
111
|
+
elapsed: number;
|
|
112
|
+
history: number[];
|
|
113
|
+
lastElapsed: number;
|
|
114
|
+
projects: Record<string, ProjectState>;
|
|
115
|
+
}) => DerivedStats;
|
|
116
|
+
declare const createInitState: (projects: ProjectInfo[], mkIdle: (p: ProjectInfo) => ProjectState) => RunState;
|
|
117
|
+
//#endregion
|
|
118
|
+
export { createEvent as A, timeAgo as C, WatchEvent as D, WATCH_STEPS as E, WatchStatus as O, tickProjects as S, WATCH_STATUSES as T, progressDots as _, ProjectInfo as a, sortByStatus as b, RunAction as c, STEP_COUNT as d, STEP_LABELS as f, nextProjectState as g, formatTime as h, MAX_HISTORY as i, WatchStep as k, RunState as l, deriveStats as m, DerivedStats as n, ProjectState as o, createInitState as p, IDLE_FALLBACK as r, RESET_DELAY as s, DISPLAY_STEPS as t, STATUS_ORDER as u, runReducer as v, CreateEventArgs as w, sparkline as x, smoothBar as y };
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
//#region src/watch-state.ts
|
|
2
|
+
const RESET_DELAY = 5e3;
|
|
3
|
+
const DISPLAY_STEPS = [
|
|
4
|
+
"sync",
|
|
5
|
+
"audit",
|
|
6
|
+
"maintain",
|
|
7
|
+
"check"
|
|
8
|
+
];
|
|
9
|
+
const STEP_COUNT = DISPLAY_STEPS.length;
|
|
10
|
+
const STEP_LABELS = {
|
|
11
|
+
audit: "🔍 auditing",
|
|
12
|
+
check: "🧪 checking",
|
|
13
|
+
maintain: "🔧 maintaining",
|
|
14
|
+
sync: "📦 syncing"
|
|
15
|
+
};
|
|
16
|
+
const STATUS_ORDER = {
|
|
17
|
+
done: 1,
|
|
18
|
+
failed: 2,
|
|
19
|
+
idle: 3,
|
|
20
|
+
running: 0
|
|
21
|
+
};
|
|
22
|
+
const BAR_CHARS = " ▏▎▍▌▋▊▉█";
|
|
23
|
+
const BAR_FULL = BAR_CHARS.at(-1) ?? "█";
|
|
24
|
+
const SPARK_CHARS = "▁▂▃▄▅▆▇█";
|
|
25
|
+
const SPARK_ZERO = SPARK_CHARS[0] ?? "▁";
|
|
26
|
+
const MAX_HISTORY = 8;
|
|
27
|
+
const IDLE_FALLBACK = {
|
|
28
|
+
completedSteps: /* @__PURE__ */ new Set(),
|
|
29
|
+
elapsed: 0,
|
|
30
|
+
status: "idle"
|
|
31
|
+
};
|
|
32
|
+
const smoothBar = (fraction, width) => {
|
|
33
|
+
const total = Math.min(1, Math.max(0, fraction)) * width;
|
|
34
|
+
const full = Math.floor(total);
|
|
35
|
+
const partial = total - full;
|
|
36
|
+
const partialIdx = Math.round(partial * 8);
|
|
37
|
+
const partialChar = partialIdx > 0 && full < width ? BAR_CHARS[partialIdx] ?? "" : "";
|
|
38
|
+
const filled = full + (partialChar ? 1 : 0);
|
|
39
|
+
const empty = Math.max(0, width - filled);
|
|
40
|
+
return `${BAR_FULL.repeat(full)}${partialChar}${"░".repeat(empty)}`;
|
|
41
|
+
};
|
|
42
|
+
const sparkline = (values) => {
|
|
43
|
+
if (values.length === 0) return "";
|
|
44
|
+
let max = 0;
|
|
45
|
+
for (const v of values) if (v > max) max = v;
|
|
46
|
+
if (max === 0) return SPARK_ZERO.repeat(values.length);
|
|
47
|
+
return values.map((v) => SPARK_CHARS[Math.round(v / max * 7)] ?? SPARK_ZERO).join("");
|
|
48
|
+
};
|
|
49
|
+
const progressDots = (completed, current) => {
|
|
50
|
+
const parts = [];
|
|
51
|
+
for (const s of DISPLAY_STEPS) if (completed.has(s)) parts.push("●");
|
|
52
|
+
else if (s === current) parts.push("◌");
|
|
53
|
+
else parts.push("·");
|
|
54
|
+
return parts.join("");
|
|
55
|
+
};
|
|
56
|
+
const formatTime = (seconds) => {
|
|
57
|
+
if (seconds <= 0) return "<1s";
|
|
58
|
+
if (seconds < 60) return `${seconds}s`;
|
|
59
|
+
if (seconds < 3600) {
|
|
60
|
+
const m = Math.floor(seconds / 60);
|
|
61
|
+
const s = seconds % 60;
|
|
62
|
+
return `${m}m${s > 0 ? `${s}s` : ""}`;
|
|
63
|
+
}
|
|
64
|
+
const h = Math.floor(seconds / 3600);
|
|
65
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
66
|
+
return `${h}h${m > 0 ? `${m}m` : ""}`;
|
|
67
|
+
};
|
|
68
|
+
const timeAgo = (iso) => {
|
|
69
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
70
|
+
const mins = Math.floor(ms / 6e4);
|
|
71
|
+
if (mins < 1) return "just now";
|
|
72
|
+
if (mins < 60) return `${mins}m ago`;
|
|
73
|
+
const hours = Math.floor(mins / 60);
|
|
74
|
+
if (hours < 24) return `${hours}h ago`;
|
|
75
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
76
|
+
};
|
|
77
|
+
const nextProjectState = (prev, event) => {
|
|
78
|
+
if (event.step === "done") {
|
|
79
|
+
const projectElapsed = prev.startedAt ? Math.floor((Date.now() - prev.startedAt) / 1e3) : 0;
|
|
80
|
+
return event.status === "fail" ? {
|
|
81
|
+
completedSteps: new Set(DISPLAY_STEPS),
|
|
82
|
+
detail: event.detail,
|
|
83
|
+
elapsed: projectElapsed,
|
|
84
|
+
status: "failed"
|
|
85
|
+
} : {
|
|
86
|
+
completedSteps: new Set(DISPLAY_STEPS),
|
|
87
|
+
detail: event.detail ?? "clean",
|
|
88
|
+
elapsed: projectElapsed,
|
|
89
|
+
status: "done"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (event.status === "start") {
|
|
93
|
+
const freshStart = prev.status === "done" || prev.status === "failed" || prev.status === "idle";
|
|
94
|
+
return {
|
|
95
|
+
completedSteps: freshStart ? /* @__PURE__ */ new Set() : prev.completedSteps,
|
|
96
|
+
elapsed: 0,
|
|
97
|
+
startedAt: freshStart ? Date.now() : prev.startedAt ?? Date.now(),
|
|
98
|
+
status: "running",
|
|
99
|
+
step: event.step
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (event.status === "fail") return {
|
|
103
|
+
completedSteps: prev.completedSteps,
|
|
104
|
+
detail: event.detail,
|
|
105
|
+
elapsed: prev.elapsed,
|
|
106
|
+
startedAt: prev.startedAt,
|
|
107
|
+
status: "running",
|
|
108
|
+
step: event.step
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
completedSteps: prev.completedSteps.has(event.step) ? prev.completedSteps : new Set([...prev.completedSteps, event.step]),
|
|
112
|
+
detail: event.detail,
|
|
113
|
+
elapsed: prev.elapsed,
|
|
114
|
+
startedAt: prev.startedAt,
|
|
115
|
+
status: "running",
|
|
116
|
+
step: event.step
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const sortByStatus = (names, projects) => [...names].toSorted((a, b) => {
|
|
120
|
+
const sa = projects[a]?.status ?? "idle";
|
|
121
|
+
const sb = projects[b]?.status ?? "idle";
|
|
122
|
+
return (STATUS_ORDER[sa] ?? 9) - (STATUS_ORDER[sb] ?? 9);
|
|
123
|
+
});
|
|
124
|
+
const tickProjects = (projects) => {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
let changed = false;
|
|
127
|
+
const next = {};
|
|
128
|
+
for (const [k, v] of Object.entries(projects)) {
|
|
129
|
+
const startedAt = v.status === "running" ? v.startedAt : void 0;
|
|
130
|
+
const elapsed = startedAt === void 0 ? v.elapsed : Math.floor((now - startedAt) / 1e3);
|
|
131
|
+
if (startedAt !== void 0 && elapsed !== v.elapsed) {
|
|
132
|
+
next[k] = {
|
|
133
|
+
...v,
|
|
134
|
+
elapsed
|
|
135
|
+
};
|
|
136
|
+
changed = true;
|
|
137
|
+
} else next[k] = v;
|
|
138
|
+
}
|
|
139
|
+
return changed ? next : projects;
|
|
140
|
+
};
|
|
141
|
+
const runReducer = (state, action) => {
|
|
142
|
+
if (action.type === "tick") {
|
|
143
|
+
if (!state.startTime) return state;
|
|
144
|
+
const projects = tickProjects(state.projects);
|
|
145
|
+
return {
|
|
146
|
+
...state,
|
|
147
|
+
elapsed: Math.floor((Date.now() - state.startTime) / 1e3),
|
|
148
|
+
projects
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (action.type === "bell-acked") return state.bellPending ? {
|
|
152
|
+
...state,
|
|
153
|
+
bellPending: false
|
|
154
|
+
} : state;
|
|
155
|
+
if (action.type === "focus") return state.focused === action.focused ? state : {
|
|
156
|
+
...state,
|
|
157
|
+
focused: action.focused
|
|
158
|
+
};
|
|
159
|
+
if (action.type === "reset") {
|
|
160
|
+
const newProjects = Object.fromEntries(action.projects.map((p) => [p.name, action.mkIdle(p)]));
|
|
161
|
+
let doneCount = 0;
|
|
162
|
+
let failedCount = 0;
|
|
163
|
+
for (const s of Object.values(state.projects)) {
|
|
164
|
+
if (s.status === "done") doneCount += 1;
|
|
165
|
+
if (s.status === "failed") failedCount += 1;
|
|
166
|
+
}
|
|
167
|
+
const snapshot = sortByStatus(Object.keys(newProjects), newProjects);
|
|
168
|
+
return {
|
|
169
|
+
...state,
|
|
170
|
+
bellPending: false,
|
|
171
|
+
elapsed: 0,
|
|
172
|
+
focused: snapshot[0] ?? state.focused,
|
|
173
|
+
history: state.elapsed > 0 ? [...state.history, state.elapsed].slice(-8) : state.history,
|
|
174
|
+
lastDone: doneCount,
|
|
175
|
+
lastElapsed: state.elapsed,
|
|
176
|
+
lastFailed: failedCount,
|
|
177
|
+
lastTime: (/* @__PURE__ */ new Date()).toLocaleTimeString(),
|
|
178
|
+
phase: "idle",
|
|
179
|
+
projects: newProjects,
|
|
180
|
+
runCount: state.runCount + 1,
|
|
181
|
+
sortSnapshot: snapshot,
|
|
182
|
+
startTime: void 0
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const { event } = action;
|
|
186
|
+
const nextProj = nextProjectState(state.projects[event.project] ?? IDLE_FALLBACK, event);
|
|
187
|
+
const next = {
|
|
188
|
+
...state.projects,
|
|
189
|
+
[event.project]: nextProj
|
|
190
|
+
};
|
|
191
|
+
const startTime = event.status === "start" ? state.startTime ?? Date.now() : state.startTime;
|
|
192
|
+
let finished = 0;
|
|
193
|
+
let total = 0;
|
|
194
|
+
let hasRunning = false;
|
|
195
|
+
for (const s of Object.values(next)) {
|
|
196
|
+
total += 1;
|
|
197
|
+
if (s.status === "done" || s.status === "failed") finished += 1;
|
|
198
|
+
if (s.status === "running") hasRunning = true;
|
|
199
|
+
}
|
|
200
|
+
const wasRunning = state.phase === "running";
|
|
201
|
+
const phase = finished === total && total > 0 ? "done" : hasRunning ? "running" : state.phase;
|
|
202
|
+
const shouldResort = !wasRunning && phase === "running" || phase === "done";
|
|
203
|
+
const sortSnapshot = shouldResort ? sortByStatus(Object.keys(next), next) : state.sortSnapshot;
|
|
204
|
+
const bellPending = wasRunning && phase === "done" ? true : phase === "running" ? false : state.bellPending;
|
|
205
|
+
const focused = shouldResort && !sortSnapshot.includes(state.focused) ? sortSnapshot[0] ?? state.focused : state.focused;
|
|
206
|
+
return {
|
|
207
|
+
...state,
|
|
208
|
+
bellPending,
|
|
209
|
+
focused,
|
|
210
|
+
phase,
|
|
211
|
+
projects: next,
|
|
212
|
+
sortSnapshot,
|
|
213
|
+
startTime
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
const deriveStats = ({ elapsed, history, lastElapsed, projects }) => {
|
|
217
|
+
let runningCount = 0;
|
|
218
|
+
let doneCount = 0;
|
|
219
|
+
let failedCount = 0;
|
|
220
|
+
let steps = 0;
|
|
221
|
+
let maxE = 0;
|
|
222
|
+
let maxN = "";
|
|
223
|
+
for (const [n, s] of Object.entries(projects)) {
|
|
224
|
+
if (s.status === "running") runningCount += 1;
|
|
225
|
+
if (s.status === "done") doneCount += 1;
|
|
226
|
+
if (s.status === "failed") {
|
|
227
|
+
failedCount += 1;
|
|
228
|
+
steps += STEP_COUNT;
|
|
229
|
+
}
|
|
230
|
+
steps += s.completedSteps.size;
|
|
231
|
+
if ((s.status === "done" || s.status === "failed") && s.elapsed > maxE) {
|
|
232
|
+
maxE = s.elapsed;
|
|
233
|
+
maxN = n;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
let eta;
|
|
237
|
+
if (runningCount > 0) {
|
|
238
|
+
if (history.length > 0) {
|
|
239
|
+
let sum = 0;
|
|
240
|
+
for (const h of history) sum += h;
|
|
241
|
+
eta = Math.max(0, Math.round(sum / history.length - elapsed));
|
|
242
|
+
} else if (lastElapsed > 0) eta = Math.max(0, lastElapsed - elapsed);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
completedStepCount: steps,
|
|
246
|
+
done: doneCount,
|
|
247
|
+
eta,
|
|
248
|
+
failed: failedCount,
|
|
249
|
+
running: runningCount,
|
|
250
|
+
slowestElapsed: maxE,
|
|
251
|
+
slowestName: maxN
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
const createInitState = (projects, mkIdle) => {
|
|
255
|
+
const initial = Object.fromEntries(projects.map((p) => [p.name, mkIdle(p)]));
|
|
256
|
+
const snapshot = sortByStatus(Object.keys(initial), initial);
|
|
257
|
+
return {
|
|
258
|
+
bellPending: false,
|
|
259
|
+
elapsed: 0,
|
|
260
|
+
focused: snapshot[0] ?? "",
|
|
261
|
+
history: [],
|
|
262
|
+
lastDone: 0,
|
|
263
|
+
lastElapsed: 0,
|
|
264
|
+
lastFailed: 0,
|
|
265
|
+
lastTime: "",
|
|
266
|
+
phase: "idle",
|
|
267
|
+
projects: initial,
|
|
268
|
+
runCount: 0,
|
|
269
|
+
sortSnapshot: snapshot,
|
|
270
|
+
startTime: void 0
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
//#endregion
|
|
274
|
+
export { tickProjects as _, STATUS_ORDER as a, createInitState as c, nextProjectState as d, progressDots as f, sparkline as g, sortByStatus as h, RESET_DELAY as i, deriveStats as l, smoothBar as m, IDLE_FALLBACK as n, STEP_COUNT as o, runReducer as p, MAX_HISTORY as r, STEP_LABELS as s, DISPLAY_STEPS as t, formatTime as u, timeAgo as v };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { C as timeAgo, S as tickProjects, _ as progressDots, a as ProjectInfo, b as sortByStatus, c as RunAction, d as STEP_COUNT, f as STEP_LABELS, g as nextProjectState, h as formatTime, i as MAX_HISTORY, l as RunState, m as deriveStats, n as DerivedStats, o as ProjectState, p as createInitState, r as IDLE_FALLBACK, s as RESET_DELAY, t as DISPLAY_STEPS, u as STATUS_ORDER, v as runReducer, x as sparkline, y as smoothBar } from "./watch-state-DIMHiLr5.mjs";
|
|
2
|
+
export { DISPLAY_STEPS, type DerivedStats, IDLE_FALLBACK, MAX_HISTORY, type ProjectInfo, type ProjectState, RESET_DELAY, type RunAction, type RunState, STATUS_ORDER, STEP_COUNT, STEP_LABELS, createInitState, deriveStats, formatTime, nextProjectState, progressDots, runReducer, smoothBar, sortByStatus, sparkline, tickProjects, timeAgo };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as tickProjects, a as STATUS_ORDER, c as createInitState, d as nextProjectState, f as progressDots, g as sparkline, h as sortByStatus, i as RESET_DELAY, l as deriveStats, m as smoothBar, n as IDLE_FALLBACK, o as STEP_COUNT, p as runReducer, r as MAX_HISTORY, s as STEP_LABELS, t as DISPLAY_STEPS, u as formatTime, v as timeAgo } from "./watch-state-wF-NfC-k.mjs";
|
|
2
|
+
export { DISPLAY_STEPS, IDLE_FALLBACK, MAX_HISTORY, RESET_DELAY, STATUS_ORDER, STEP_COUNT, STEP_LABELS, createInitState, deriveStats, formatTime, nextProjectState, progressDots, runReducer, smoothBar, sortByStatus, sparkline, tickProjects, timeAgo };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/watch-types.ts
|
|
2
|
+
const WATCH_STEPS = [
|
|
3
|
+
"audit",
|
|
4
|
+
"check",
|
|
5
|
+
"done",
|
|
6
|
+
"maintain",
|
|
7
|
+
"sync"
|
|
8
|
+
];
|
|
9
|
+
const WATCH_STATUSES = [
|
|
10
|
+
"fail",
|
|
11
|
+
"ok",
|
|
12
|
+
"start"
|
|
13
|
+
];
|
|
14
|
+
const createEvent = ({ detail, project, status, step }) => ({
|
|
15
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16
|
+
...detail ? { detail } : {},
|
|
17
|
+
project,
|
|
18
|
+
status,
|
|
19
|
+
step
|
|
20
|
+
});
|
|
21
|
+
//#endregion
|
|
22
|
+
export { WATCH_STEPS as n, createEvent as r, WATCH_STATUSES as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pm4ai",
|
|
3
|
+
"version": "0.0.73",
|
|
4
|
+
"description": "Agent-first anti-slop project management for TypeScript monorepos",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"lintmax",
|
|
8
|
+
"monorepo",
|
|
9
|
+
"project-manager",
|
|
10
|
+
"sync",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/1qh/pm4ai"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "1qh",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.mts",
|
|
23
|
+
"import": "./dist/index.mjs"
|
|
24
|
+
},
|
|
25
|
+
"./guide": {
|
|
26
|
+
"types": "./dist/guide.d.mts",
|
|
27
|
+
"import": "./dist/guide.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./schemas": {
|
|
30
|
+
"types": "./dist/schemas.d.mts",
|
|
31
|
+
"import": "./dist/schemas.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./watch-state": {
|
|
34
|
+
"types": "./dist/watch-state.d.mts",
|
|
35
|
+
"import": "./dist/watch-state.mjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"pm4ai": "dist/cli.mjs"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsdown",
|
|
46
|
+
"prepublishOnly": "bun run build",
|
|
47
|
+
"postpublish": "bun ../../tools/prune-versions.ts",
|
|
48
|
+
"test": "bun test"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"ink": "latest",
|
|
52
|
+
"ink-spinner": "latest",
|
|
53
|
+
"react": "latest",
|
|
54
|
+
"zod": "latest"
|
|
55
|
+
},
|
|
56
|
+
"requiredDevDeps": [
|
|
57
|
+
"lintmax",
|
|
58
|
+
"sherif",
|
|
59
|
+
"simple-git-hooks",
|
|
60
|
+
"tsdown",
|
|
61
|
+
"turbo",
|
|
62
|
+
"typescript",
|
|
63
|
+
"unrun"
|
|
64
|
+
]
|
|
65
|
+
}
|