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,260 @@
|
|
|
1
|
+
import { f as projectName, u as isInsideProject, x as CONFIG_DIR } from "./utils-CpkOMuQN.mjs";
|
|
2
|
+
import { r as lockSchema, s as safeParseJson } from "./schemas-Dsbtf6P2.mjs";
|
|
3
|
+
import { t as audit } from "./audit-oQQfgtxr.mjs";
|
|
4
|
+
import { c as writeCheckResult, n as emitToSocket } from "./watch-emitter-uTmZ3oQd.mjs";
|
|
5
|
+
import { n as discoverSources, t as discover } from "./discover-d8ENQC1K.mjs";
|
|
6
|
+
import { a as syncPackageJson, c as syncUi, i as syncFumadocsGithubUrl, n as syncConfigs, o as syncSubPackages, r as syncFumadocsBuild, s as syncTsconfig, t as syncClaudeMd } from "./sync-DN1rgN3P.mjs";
|
|
7
|
+
import { r as createEvent } from "./watch-types-BzSNCGb2.mjs";
|
|
8
|
+
import { $ } from "bun";
|
|
9
|
+
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
//#region src/log.ts
|
|
13
|
+
const logDir = join(homedir(), CONFIG_DIR, "logs");
|
|
14
|
+
const leadingSepRe = /^--/u;
|
|
15
|
+
const logPath = (path) => join(logDir, `${path.replaceAll("/", "--").replace(leadingSepRe, "")}.json`);
|
|
16
|
+
const updateLog = (entry) => {
|
|
17
|
+
mkdirSync(logDir, { recursive: true });
|
|
18
|
+
writeFileSync(logPath(entry.path), JSON.stringify(entry));
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/fix.ts
|
|
22
|
+
const violationRe = /(?<count>\d+)\s*(?:error|violation|problem|issue)/iu;
|
|
23
|
+
const maintain = async (projectPath) => {
|
|
24
|
+
const issues = [];
|
|
25
|
+
if (!existsSync(join(projectPath, "up.sh"))) {
|
|
26
|
+
issues.push({
|
|
27
|
+
detail: "missing, cannot maintain",
|
|
28
|
+
type: "up.sh"
|
|
29
|
+
});
|
|
30
|
+
return issues;
|
|
31
|
+
}
|
|
32
|
+
const result = await $`sh up.sh`.cwd(projectPath).quiet().nothrow();
|
|
33
|
+
const { exitCode } = result;
|
|
34
|
+
const stderr = [result.stdout.toString(), result.stderr.toString()].join("\n").trim();
|
|
35
|
+
if (exitCode === 0) {
|
|
36
|
+
const snapshotDir = join(homedir(), CONFIG_DIR, "snapshots", projectName(projectPath));
|
|
37
|
+
const lockfile = join(projectPath, "bun.lock");
|
|
38
|
+
if (existsSync(lockfile)) {
|
|
39
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
40
|
+
copyFileSync(lockfile, join(snapshotDir, "bun.lock"));
|
|
41
|
+
}
|
|
42
|
+
writeCheckResult({
|
|
43
|
+
pass: true,
|
|
44
|
+
projectPath,
|
|
45
|
+
violations: 0
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
const errorLine = stderr.split("\n").findLast(Boolean) ?? "unknown error";
|
|
49
|
+
issues.push({
|
|
50
|
+
detail: `failed: ${errorLine}`,
|
|
51
|
+
type: "up.sh"
|
|
52
|
+
});
|
|
53
|
+
const violationMatch = violationRe.exec(stderr);
|
|
54
|
+
writeCheckResult({
|
|
55
|
+
pass: false,
|
|
56
|
+
projectPath,
|
|
57
|
+
summary: errorLine,
|
|
58
|
+
violations: violationMatch?.groups?.count ? Number.parseInt(violationMatch.groups.count, 10) : 1
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
updateLog({
|
|
62
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
error: exitCode === 0 ? void 0 : stderr.slice(0, 500),
|
|
64
|
+
pass: exitCode === 0,
|
|
65
|
+
path: projectPath,
|
|
66
|
+
project: projectName(projectPath)
|
|
67
|
+
});
|
|
68
|
+
return issues;
|
|
69
|
+
};
|
|
70
|
+
const fix = async (all = false, excludes = []) => {
|
|
71
|
+
const lockFile = join(homedir(), CONFIG_DIR, "fix.lock");
|
|
72
|
+
mkdirSync(join(homedir(), CONFIG_DIR), { recursive: true });
|
|
73
|
+
const lockData = JSON.stringify({
|
|
74
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
pid: process.pid
|
|
76
|
+
});
|
|
77
|
+
const tryAcquireLock = () => {
|
|
78
|
+
try {
|
|
79
|
+
const fd = openSync(lockFile, "wx");
|
|
80
|
+
writeFileSync(fd, lockData);
|
|
81
|
+
closeSync(fd);
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
if (!tryAcquireLock()) {
|
|
88
|
+
try {
|
|
89
|
+
const lock = safeParseJson(lockSchema, readFileSync(lockFile, "utf8"));
|
|
90
|
+
if (!lock) return;
|
|
91
|
+
const age = Date.now() - new Date(lock.at).getTime();
|
|
92
|
+
let alive = false;
|
|
93
|
+
try {
|
|
94
|
+
process.kill(lock.pid, 0);
|
|
95
|
+
alive = true;
|
|
96
|
+
} catch {}
|
|
97
|
+
if (alive && age < 6e5) {
|
|
98
|
+
console.log("another fix is already running");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
rmSync(lockFile, { force: true });
|
|
103
|
+
if (!tryAcquireLock()) {
|
|
104
|
+
console.log("another fix is already running");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const resolveTargets = async () => {
|
|
110
|
+
if (all) return discover(void 0, excludes);
|
|
111
|
+
const projectPath = await isInsideProject();
|
|
112
|
+
if (projectPath) {
|
|
113
|
+
const { self, cnsync } = await discoverSources();
|
|
114
|
+
return {
|
|
115
|
+
cnsync,
|
|
116
|
+
consumers: [{
|
|
117
|
+
isCnsync: false,
|
|
118
|
+
isSelf: false,
|
|
119
|
+
name: projectName(projectPath),
|
|
120
|
+
path: projectPath
|
|
121
|
+
}],
|
|
122
|
+
self
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return discover(void 0, excludes);
|
|
126
|
+
};
|
|
127
|
+
const { cnsync, consumers, self } = await resolveTargets();
|
|
128
|
+
console.log(`found ${consumers.length} projects`);
|
|
129
|
+
console.log();
|
|
130
|
+
const allRepos = [
|
|
131
|
+
self,
|
|
132
|
+
cnsync,
|
|
133
|
+
...consumers
|
|
134
|
+
];
|
|
135
|
+
const blocked = [];
|
|
136
|
+
const pullable = [];
|
|
137
|
+
const checkResults = await Promise.all(allRepos.map(async (repo) => {
|
|
138
|
+
const name = projectName(repo.path);
|
|
139
|
+
if ((await $`git status --porcelain`.cwd(repo.path).quiet().nothrow()).stdout.toString().trim()) return {
|
|
140
|
+
name,
|
|
141
|
+
reason: "uncommitted changes"
|
|
142
|
+
};
|
|
143
|
+
await $`git fetch`.cwd(repo.path).quiet().nothrow();
|
|
144
|
+
const behind = await $`git rev-list --count HEAD..@{u}`.cwd(repo.path).quiet().nothrow();
|
|
145
|
+
const ahead = await $`git rev-list --count @{u}..HEAD`.cwd(repo.path).quiet().nothrow();
|
|
146
|
+
const b = Number.parseInt(behind.stdout.toString().trim(), 10);
|
|
147
|
+
const a = Number.parseInt(ahead.stdout.toString().trim(), 10);
|
|
148
|
+
if (b > 0 && a > 0) return {
|
|
149
|
+
name,
|
|
150
|
+
reason: `diverged (${b} behind, ${a} ahead)`
|
|
151
|
+
};
|
|
152
|
+
if (a > 0) return {
|
|
153
|
+
name,
|
|
154
|
+
reason: `${a} commits ahead, push first`
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
behind: b,
|
|
158
|
+
name,
|
|
159
|
+
path: repo.path
|
|
160
|
+
};
|
|
161
|
+
}));
|
|
162
|
+
for (const r of checkResults) if ("reason" in r) blocked.push(`${r.name}: ${r.reason}`);
|
|
163
|
+
else if (r.behind > 0) pullable.push({
|
|
164
|
+
name: r.name,
|
|
165
|
+
path: r.path
|
|
166
|
+
});
|
|
167
|
+
if (blocked.length > 0) {
|
|
168
|
+
console.log("fix requires clean git state:");
|
|
169
|
+
for (const msg of blocked) console.log(` ${msg}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
await Promise.all(pullable.map(async (repo) => {
|
|
173
|
+
await $`git pull`.cwd(repo.path).quiet().nothrow();
|
|
174
|
+
console.log(`${repo.name}: pulled`);
|
|
175
|
+
}));
|
|
176
|
+
const selfSubPkgIssues = await syncSubPackages(self.path, self.path);
|
|
177
|
+
if (selfSubPkgIssues.length > 0) for (const i of selfSubPkgIssues) console.log(` ${i.type} ${i.detail}`);
|
|
178
|
+
const allTargets = [cnsync, ...consumers];
|
|
179
|
+
const tasks = allTargets.map(async (project) => {
|
|
180
|
+
const name = projectName(project.path);
|
|
181
|
+
const issues = [];
|
|
182
|
+
emitToSocket(createEvent({
|
|
183
|
+
project: name,
|
|
184
|
+
status: "start",
|
|
185
|
+
step: "sync"
|
|
186
|
+
}));
|
|
187
|
+
const [configIssues, claudeIssues, pkgIssues, tsconfigIssues, fumadocsBuildIssues, fumadocsGithubIssues] = await Promise.all([
|
|
188
|
+
syncConfigs(self.path, project.path),
|
|
189
|
+
syncClaudeMd(self.path, project.path),
|
|
190
|
+
syncPackageJson(project.path, self.path),
|
|
191
|
+
syncTsconfig(project.path),
|
|
192
|
+
syncFumadocsBuild(project.path),
|
|
193
|
+
syncFumadocsGithubUrl(project.path)
|
|
194
|
+
]);
|
|
195
|
+
const subPkgIssues = await syncSubPackages(self.path, project.path);
|
|
196
|
+
issues.push(...configIssues, ...claudeIssues, ...pkgIssues, ...tsconfigIssues, ...fumadocsBuildIssues, ...fumadocsGithubIssues, ...subPkgIssues);
|
|
197
|
+
if (existsSync(join(project.path, "readonly/ui"))) issues.push(...syncUi(cnsync.path, project.path));
|
|
198
|
+
const syncCount = issues.filter((i) => i.type === "synced").length;
|
|
199
|
+
emitToSocket(createEvent({
|
|
200
|
+
detail: syncCount > 0 ? `${syncCount} synced` : void 0,
|
|
201
|
+
project: name,
|
|
202
|
+
status: "ok",
|
|
203
|
+
step: "sync"
|
|
204
|
+
}));
|
|
205
|
+
emitToSocket(createEvent({
|
|
206
|
+
project: name,
|
|
207
|
+
status: "start",
|
|
208
|
+
step: "audit"
|
|
209
|
+
}));
|
|
210
|
+
const auditIssues = await audit(project.path);
|
|
211
|
+
issues.push(...auditIssues);
|
|
212
|
+
emitToSocket(createEvent({
|
|
213
|
+
detail: auditIssues.length > 0 ? `${auditIssues.length} issues` : void 0,
|
|
214
|
+
project: name,
|
|
215
|
+
status: auditIssues.length > 0 ? "fail" : "ok",
|
|
216
|
+
step: "audit"
|
|
217
|
+
}));
|
|
218
|
+
emitToSocket(createEvent({
|
|
219
|
+
project: name,
|
|
220
|
+
status: "start",
|
|
221
|
+
step: "maintain"
|
|
222
|
+
}));
|
|
223
|
+
const maintainIssues = await maintain(project.path);
|
|
224
|
+
issues.push(...maintainIssues);
|
|
225
|
+
const maintainDetail = maintainIssues[0]?.detail;
|
|
226
|
+
emitToSocket(createEvent({
|
|
227
|
+
detail: maintainDetail,
|
|
228
|
+
project: name,
|
|
229
|
+
status: maintainIssues.length > 0 ? "fail" : "ok",
|
|
230
|
+
step: "maintain"
|
|
231
|
+
}));
|
|
232
|
+
const changed = (await $`git status --porcelain`.cwd(project.path).quiet().nothrow()).stdout.toString().trim();
|
|
233
|
+
const fileCount = changed ? changed.split("\n").length : 0;
|
|
234
|
+
emitToSocket(createEvent({
|
|
235
|
+
detail: fileCount > 0 ? `${fileCount} files modified` : "clean",
|
|
236
|
+
project: name,
|
|
237
|
+
status: "ok",
|
|
238
|
+
step: "done"
|
|
239
|
+
}));
|
|
240
|
+
if (issues.length > 0) {
|
|
241
|
+
const lines = [project.path, ...issues.map((i) => ` ${i.type} ${i.detail}`)];
|
|
242
|
+
console.log(lines.join("\n"));
|
|
243
|
+
console.log();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
await Promise.all(tasks);
|
|
247
|
+
console.log("--- changes ---");
|
|
248
|
+
const summaries = await Promise.all(allTargets.map(async (project) => {
|
|
249
|
+
const changed = (await $`git status --porcelain`.cwd(project.path).quiet().nothrow()).stdout.toString().trim();
|
|
250
|
+
const count = changed ? changed.split("\n").length : 0;
|
|
251
|
+
return count > 0 ? `${projectName(project.path)}: ${count} files modified` : `${projectName(project.path)}: clean`;
|
|
252
|
+
}));
|
|
253
|
+
for (const s of summaries) console.log(s);
|
|
254
|
+
if (process.platform === "darwin") await $`open swiftbar://refreshplugin?name=pm4ai`.quiet().nothrow();
|
|
255
|
+
} finally {
|
|
256
|
+
rmSync(lockFile, { force: true });
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
//#endregion
|
|
260
|
+
export { maintain as n, fix as t };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region src/guide.d.ts
|
|
2
|
+
declare const guide = "pm4ai \u2014 agent-first anti-slop project management for TypeScript monorepos\ndiscovers projects by scanning for lintmax in package.json deps\ndiscovers itself and cnsync the same way (auto-clones if not found)\ncontext-aware: inside a project, operates on that project only\ncommands:\n pm4ai this guide\n pm4ai status check current project (or all if outside a project)\n pm4ai fix sync + maintain current project (requires clean git)\n pm4ai init <n> scaffold a new pm4ai-ready project\n pm4ai watch live terminal dashboard (connects to running fix/status)\n pm4ai ignores rank lint suppressions by frequency across projects\n pm4ai dashboard local web dashboard at http://localhost:4200\n pm4ai setup install swiftbar menubar plugin + launchd daily auto-run\nflags:\n --all force global scan across all projects\n --swiftbar output in SwiftBar menubar format\n --json (watch) output raw newline-delimited JSON events\n --verbose print debug info to stderr\nfix behavior:\n blocks if git is dirty or ahead (unpushed)\n pulls clean repos before syncing\n syncs: .github/workflows/ci.yml, clean.sh, up.sh, bunfig.toml, .gitignore, CLAUDE.md, readonly/ui\n maintains: runs sh up.sh (clean + install + build + fix + check)\n shows file change summary after completion\nstatus output (only issues shown, healthy projects omitted):\n /path/to/project\n git 3 uncommitted changes\n file clean.sh out of sync\n missing turbo.json\n drift clean should start with \"sh clean.sh\"\n dep react should be \"latest\" or \"^major\"\n duplicate ai already provided by workspace dep\n forbidden npm found, use bun only\n ci failed 2026-04-02\n deploy vercel deployment failed\nchecks:\n git status (auto-pulls clean repos)\n config drift (synced files match source)\n missing infra (turbo.json, tsconfig.json, ci.yml)\n root package.json (private, packageManager, hooks, sherif, prepare, clean)\n tsconfig extends lintmax/tsconfig, no include\n vercel.json installCommand is bun i\n vercel deployment status\n layout conventions (suppressHydrationWarning, antialiased, tracking, min-h-screen, font-sans, metadata, fonts.ts, providers.tsx, global.css, arrow function export, no RootLayout, no Provider inline)\n page conventions (arrow function export)\n next.config (reactStrictMode, no redundant postcss in apps)\n app tsconfig (extends lintmax, no include)\n banned packages (817 entries), bun globals, @a/ui deep imports\n deps on latest or ^major, no duplicates across workspaces\n no npm/yarn/pnpm in scripts or lockfiles\n turbo scripts have --output-logs=errors-only\n published packages have type:module, exports, files, license, repository\n workspace devDependencies hoisted to root\n no nested .gitignore, no postcss.config.mjs, no @ts-nocheck\n no bun.lock tracked in git, no redundant clean scripts\n ci status via github api";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { guide as t };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//#region src/guide.ts
|
|
2
|
+
const guide = `pm4ai — agent-first anti-slop project management for TypeScript monorepos
|
|
3
|
+
discovers projects by scanning for lintmax in package.json deps
|
|
4
|
+
discovers itself and cnsync the same way (auto-clones if not found)
|
|
5
|
+
context-aware: inside a project, operates on that project only
|
|
6
|
+
commands:
|
|
7
|
+
pm4ai this guide
|
|
8
|
+
pm4ai status check current project (or all if outside a project)
|
|
9
|
+
pm4ai fix sync + maintain current project (requires clean git)
|
|
10
|
+
pm4ai init <n> scaffold a new pm4ai-ready project
|
|
11
|
+
pm4ai watch live terminal dashboard (connects to running fix/status)
|
|
12
|
+
pm4ai ignores rank lint suppressions by frequency across projects
|
|
13
|
+
pm4ai dashboard local web dashboard at http://localhost:4200
|
|
14
|
+
pm4ai setup install swiftbar menubar plugin + launchd daily auto-run
|
|
15
|
+
flags:
|
|
16
|
+
--all force global scan across all projects
|
|
17
|
+
--swiftbar output in SwiftBar menubar format
|
|
18
|
+
--json (watch) output raw newline-delimited JSON events
|
|
19
|
+
--verbose print debug info to stderr
|
|
20
|
+
fix behavior:
|
|
21
|
+
blocks if git is dirty or ahead (unpushed)
|
|
22
|
+
pulls clean repos before syncing
|
|
23
|
+
syncs: .github/workflows/ci.yml, clean.sh, up.sh, bunfig.toml, .gitignore, CLAUDE.md, readonly/ui
|
|
24
|
+
maintains: runs sh up.sh (clean + install + build + fix + check)
|
|
25
|
+
shows file change summary after completion
|
|
26
|
+
status output (only issues shown, healthy projects omitted):
|
|
27
|
+
/path/to/project
|
|
28
|
+
git 3 uncommitted changes
|
|
29
|
+
file clean.sh out of sync
|
|
30
|
+
missing turbo.json
|
|
31
|
+
drift clean should start with "sh clean.sh"
|
|
32
|
+
dep react should be "latest" or "^major"
|
|
33
|
+
duplicate ai already provided by workspace dep
|
|
34
|
+
forbidden npm found, use bun only
|
|
35
|
+
ci failed 2026-04-02
|
|
36
|
+
deploy vercel deployment failed
|
|
37
|
+
checks:
|
|
38
|
+
git status (auto-pulls clean repos)
|
|
39
|
+
config drift (synced files match source)
|
|
40
|
+
missing infra (turbo.json, tsconfig.json, ci.yml)
|
|
41
|
+
root package.json (private, packageManager, hooks, sherif, prepare, clean)
|
|
42
|
+
tsconfig extends lintmax/tsconfig, no include
|
|
43
|
+
vercel.json installCommand is bun i
|
|
44
|
+
vercel deployment status
|
|
45
|
+
layout conventions (suppressHydrationWarning, antialiased, tracking, min-h-screen, font-sans, metadata, fonts.ts, providers.tsx, global.css, arrow function export, no RootLayout, no Provider inline)
|
|
46
|
+
page conventions (arrow function export)
|
|
47
|
+
next.config (reactStrictMode, no redundant postcss in apps)
|
|
48
|
+
app tsconfig (extends lintmax, no include)
|
|
49
|
+
banned packages (817 entries), bun globals, @a/ui deep imports
|
|
50
|
+
deps on latest or ^major, no duplicates across workspaces
|
|
51
|
+
no npm/yarn/pnpm in scripts or lockfiles
|
|
52
|
+
turbo scripts have --output-logs=errors-only
|
|
53
|
+
published packages have type:module, exports, files, license, repository
|
|
54
|
+
workspace devDependencies hoisted to root
|
|
55
|
+
no nested .gitignore, no postcss.config.mjs, no @ts-nocheck
|
|
56
|
+
no bun.lock tracked in git, no redundant clean scripts
|
|
57
|
+
ci status via github api`;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { guide as t };
|
package/dist/guide.d.mts
ADDED
package/dist/guide.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { f as projectName, u as isInsideProject } from "./utils-CpkOMuQN.mjs";
|
|
2
|
+
import { t as discover } from "./discover-d8ENQC1K.mjs";
|
|
3
|
+
import { $ } from "bun";
|
|
4
|
+
//#region src/ignores.ts
|
|
5
|
+
/** biome-ignore-all lint/performance/noAwaitInLoops: sequential project scan */
|
|
6
|
+
const ignores = async (all = false) => {
|
|
7
|
+
let projects;
|
|
8
|
+
if (all) {
|
|
9
|
+
const { consumers, self, cnsync } = await discover();
|
|
10
|
+
projects = [
|
|
11
|
+
self,
|
|
12
|
+
cnsync,
|
|
13
|
+
...consumers
|
|
14
|
+
];
|
|
15
|
+
} else {
|
|
16
|
+
const projectPath = await isInsideProject();
|
|
17
|
+
if (projectPath) projects = [{
|
|
18
|
+
name: projectName(projectPath),
|
|
19
|
+
path: projectPath
|
|
20
|
+
}];
|
|
21
|
+
else {
|
|
22
|
+
const { consumers, self, cnsync } = await discover();
|
|
23
|
+
projects = [
|
|
24
|
+
self,
|
|
25
|
+
cnsync,
|
|
26
|
+
...consumers
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const project of projects) {
|
|
31
|
+
console.log(`${project.name}:`);
|
|
32
|
+
await $`bunx lintmax@latest ignores`.cwd(project.path).nothrow();
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ignores };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { t as guide } from "./guide-BS7-RqpH.mjs";
|
|
2
|
+
import { A as createEvent, C as timeAgo, D as WatchEvent, E as WATCH_STEPS, O as WatchStatus, S as tickProjects, T as WATCH_STATUSES, _ 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, k as WatchStep, 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, w as CreateEventArgs, x as sparkline, y as smoothBar } from "./watch-state-DIMHiLr5.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
interface Issue {
|
|
6
|
+
detail: string;
|
|
7
|
+
type: IssueType;
|
|
8
|
+
}
|
|
9
|
+
type IssueType = 'bun' | 'check' | 'ci' | 'dep' | 'deploy' | 'drift' | 'duplicate' | 'error' | 'file' | 'forbidden' | 'git' | 'info' | 'lintmax' | 'missing' | 'synced' | 'unused' | 'up.sh';
|
|
10
|
+
interface PackageJson {
|
|
11
|
+
bin?: Record<string, string> | string;
|
|
12
|
+
dependencies?: Record<string, string>;
|
|
13
|
+
devDependencies?: Record<string, string>;
|
|
14
|
+
exports?: Record<string, Record<string, string> | string>;
|
|
15
|
+
files?: string[];
|
|
16
|
+
license?: string;
|
|
17
|
+
main?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
packageManager?: string;
|
|
20
|
+
peerDependencies?: Record<string, string>;
|
|
21
|
+
private?: boolean;
|
|
22
|
+
repository?: {
|
|
23
|
+
directory?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
url?: string;
|
|
26
|
+
};
|
|
27
|
+
scripts?: Record<string, string>;
|
|
28
|
+
'simple-git-hooks'?: Record<string, string>;
|
|
29
|
+
trustedDependencies?: string[];
|
|
30
|
+
type?: string;
|
|
31
|
+
workspaces?: string[];
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/audit.d.ts
|
|
35
|
+
declare const audit: (projectPath: string) => Promise<Issue[]>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/check-cache.d.ts
|
|
38
|
+
interface CheckResult {
|
|
39
|
+
at: string;
|
|
40
|
+
commit: string;
|
|
41
|
+
pass: boolean;
|
|
42
|
+
summary?: string;
|
|
43
|
+
violations: number;
|
|
44
|
+
}
|
|
45
|
+
declare const readCheckResult: (projectPath: string) => CheckResult | undefined;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/constants.d.ts
|
|
48
|
+
declare const TSDOWN_BASE: {
|
|
49
|
+
clean: boolean;
|
|
50
|
+
deps: {
|
|
51
|
+
neverBundle: string[];
|
|
52
|
+
};
|
|
53
|
+
dts: boolean;
|
|
54
|
+
format: "esm";
|
|
55
|
+
outDir: string;
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/discover.d.ts
|
|
59
|
+
interface Project {
|
|
60
|
+
isCnsync: boolean;
|
|
61
|
+
isSelf: boolean;
|
|
62
|
+
name: string;
|
|
63
|
+
path: string;
|
|
64
|
+
}
|
|
65
|
+
declare const discover: (searchRoot?: string, excludes?: readonly string[]) => Promise<{
|
|
66
|
+
cnsync: Project;
|
|
67
|
+
consumers: Project[];
|
|
68
|
+
self: Project;
|
|
69
|
+
}>;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/fix.d.ts
|
|
72
|
+
declare const fix: (all?: boolean, excludes?: readonly string[]) => Promise<void>;
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/infer.d.ts
|
|
75
|
+
declare const inferRules: (projectPath: string, rulesDir?: string) => Promise<string[]>;
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/status.d.ts
|
|
78
|
+
declare const status: (swiftbar?: boolean, all?: boolean, excludes?: readonly string[]) => Promise<void>;
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/watch-emitter.d.ts
|
|
81
|
+
declare const SOCKET_PATH: string;
|
|
82
|
+
//#endregion
|
|
83
|
+
export { type CheckResult, type CreateEventArgs, DISPLAY_STEPS, type DerivedStats, IDLE_FALLBACK, type Issue, type IssueType, type PackageJson, type ProjectInfo, type ProjectState, RESET_DELAY, type RunAction, type RunState, SOCKET_PATH, STATUS_ORDER, STEP_COUNT, STEP_LABELS, WATCH_STATUSES, WATCH_STEPS, type WatchEvent, type WatchStatus, type WatchStep, audit, createEvent, createInitState, deriveStats, discover, fix, formatTime, guide, inferRules, nextProjectState, progressDots, readCheckResult, runReducer, smoothBar, sortByStatus, sparkline, status, tickProjects, timeAgo, TSDOWN_BASE as tsdownBase };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { L as TSDOWN_BASE } from "./utils-CpkOMuQN.mjs";
|
|
2
|
+
import { t as audit } from "./audit-oQQfgtxr.mjs";
|
|
3
|
+
import { o as readCheckResult, t as SOCKET_PATH } from "./watch-emitter-uTmZ3oQd.mjs";
|
|
4
|
+
import { t as discover } from "./discover-d8ENQC1K.mjs";
|
|
5
|
+
import { t as fix } from "./fix-BcMN_cuG.mjs";
|
|
6
|
+
import { l as inferRules } from "./sync-DN1rgN3P.mjs";
|
|
7
|
+
import { n as WATCH_STEPS, r as createEvent, t as WATCH_STATUSES } from "./watch-types-BzSNCGb2.mjs";
|
|
8
|
+
import { t as guide } from "./guide-CifUmtQN.mjs";
|
|
9
|
+
import { t as status } from "./status-CzCNkG58.mjs";
|
|
10
|
+
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, s as STEP_LABELS, t as DISPLAY_STEPS, u as formatTime, v as timeAgo } from "./watch-state-wF-NfC-k.mjs";
|
|
11
|
+
export { DISPLAY_STEPS, IDLE_FALLBACK, RESET_DELAY, SOCKET_PATH, STATUS_ORDER, STEP_COUNT, STEP_LABELS, WATCH_STATUSES, WATCH_STEPS, audit, createEvent, createInitState, deriveStats, discover, fix, formatTime, guide, inferRules, nextProjectState, progressDots, readCheckResult, runReducer, smoothBar, sortByStatus, sparkline, status, tickProjects, timeAgo, TSDOWN_BASE as tsdownBase };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { a as getBunVersion } from "./utils-CpkOMuQN.mjs";
|
|
2
|
+
import { t as discover } from "./discover-d8ENQC1K.mjs";
|
|
3
|
+
import { t as syncClaudeMd } from "./sync-DN1rgN3P.mjs";
|
|
4
|
+
import { $, file, write } from "bun";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
//#region src/init.ts
|
|
9
|
+
const SKIP = new Set([
|
|
10
|
+
".git",
|
|
11
|
+
".next",
|
|
12
|
+
".turbo",
|
|
13
|
+
".vercel",
|
|
14
|
+
"bun.lock",
|
|
15
|
+
"CLAUDE.md",
|
|
16
|
+
"dist",
|
|
17
|
+
"LEARNING.md",
|
|
18
|
+
"node_modules",
|
|
19
|
+
"PLAN.md",
|
|
20
|
+
"PROGRESS.md",
|
|
21
|
+
"prompts",
|
|
22
|
+
"README.md",
|
|
23
|
+
"RULES.md",
|
|
24
|
+
"vercel.json"
|
|
25
|
+
]);
|
|
26
|
+
const REMOVE_PATHS = [
|
|
27
|
+
"apps/web/src/lib/router.ts",
|
|
28
|
+
"apps/web/src/lib/socket.ts",
|
|
29
|
+
"apps/web/src/lib/auth.ts",
|
|
30
|
+
"apps/web/src/lib/client.ts",
|
|
31
|
+
"apps/web/src/app/api",
|
|
32
|
+
"apps/web/src/app/auth",
|
|
33
|
+
"apps/web/src/tests",
|
|
34
|
+
"apps/docs/src/app/llms-full.txt",
|
|
35
|
+
"apps/docs/src/app/llms.txt",
|
|
36
|
+
"apps/docs/src/app/api"
|
|
37
|
+
];
|
|
38
|
+
const copyTree = (src, dst, skipExtra) => {
|
|
39
|
+
for (const entry of readdirSync(src)) if (!(SKIP.has(entry) || skipExtra?.has(entry))) {
|
|
40
|
+
const s = join(src, entry);
|
|
41
|
+
const d = join(dst, entry);
|
|
42
|
+
if (statSync(s).isDirectory()) {
|
|
43
|
+
mkdirSync(d, { recursive: true });
|
|
44
|
+
copyTree(s, d);
|
|
45
|
+
} else cpSync(s, d);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const copyTemplateDir = async (tplDir, dstDir, projectName) => {
|
|
49
|
+
await Promise.all(readdirSync(tplDir).map(async (entry) => {
|
|
50
|
+
const s = join(tplDir, entry);
|
|
51
|
+
const d = join(dstDir, entry);
|
|
52
|
+
if (statSync(s).isDirectory()) {
|
|
53
|
+
mkdirSync(d, { recursive: true });
|
|
54
|
+
await copyTemplateDir(s, d, projectName);
|
|
55
|
+
} else {
|
|
56
|
+
const content = await file(s).text();
|
|
57
|
+
mkdirSync(dirname(d), { recursive: true });
|
|
58
|
+
await write(d, content.replaceAll("__NAME__", projectName));
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
};
|
|
62
|
+
const patchFile = async (path, replacements) => {
|
|
63
|
+
if (!existsSync(path)) return;
|
|
64
|
+
let content = await file(path).text();
|
|
65
|
+
for (const [from, to] of replacements) content = content.replaceAll(from, to);
|
|
66
|
+
await write(path, content);
|
|
67
|
+
};
|
|
68
|
+
const templateDir = join(dirname(new URL(import.meta.url).pathname), "templates");
|
|
69
|
+
const init = async (name) => {
|
|
70
|
+
const projectName = basename(resolve(process.cwd(), name));
|
|
71
|
+
const dir = resolve(process.cwd(), name);
|
|
72
|
+
if (existsSync(dir)) {
|
|
73
|
+
console.log(`${dir} already exists`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const { self } = await discover();
|
|
77
|
+
const src = self.path;
|
|
78
|
+
console.log(`scaffolding ${projectName} from ${src}...`);
|
|
79
|
+
mkdirSync(dir, { recursive: true });
|
|
80
|
+
copyTree(src, dir, new Set(["packages"]));
|
|
81
|
+
for (const p of REMOVE_PATHS) rmSync(join(dir, p), {
|
|
82
|
+
force: true,
|
|
83
|
+
recursive: true
|
|
84
|
+
});
|
|
85
|
+
rmSync(join(dir, "packages"), {
|
|
86
|
+
force: true,
|
|
87
|
+
recursive: true
|
|
88
|
+
});
|
|
89
|
+
rmSync(join(dir, "apps", "docs", "content", "rules"), {
|
|
90
|
+
force: true,
|
|
91
|
+
recursive: true
|
|
92
|
+
});
|
|
93
|
+
mkdirSync(join(dir, "apps", "docs", "content", "docs"), { recursive: true });
|
|
94
|
+
await Promise.all([
|
|
95
|
+
copyTemplateDir(join(templateDir, "cli"), join(dir, "packages", "cli"), projectName),
|
|
96
|
+
copyTemplateDir(join(templateDir, "lib"), join(dir, "packages", "lib"), projectName),
|
|
97
|
+
copyTemplateDir(join(templateDir, "web"), join(dir, "apps", "web"), projectName),
|
|
98
|
+
copyTemplateDir(join(templateDir, "docs"), join(dir, "apps", "docs"), projectName)
|
|
99
|
+
]);
|
|
100
|
+
const bunVersion = await getBunVersion();
|
|
101
|
+
const rootPkgText = (await file(join(templateDir, "root-package.txt")).text()).replace("__PACKAGE_MANAGER__", `bun@${bunVersion}`).replace("__NAME__", `${projectName}-workspace`);
|
|
102
|
+
await Promise.all([
|
|
103
|
+
write(join(dir, "package.json"), rootPkgText),
|
|
104
|
+
patchFile(join(dir, "apps", "docs", "src", "lib", "shared.ts"), [["pm4ai", projectName]]),
|
|
105
|
+
patchFile(join(dir, "apps", "docs", "src", "lib", "layout.shared.tsx"), [["pm4ai", projectName]]),
|
|
106
|
+
patchFile(join(dir, "apps", "web", "src", "app", "layout.tsx"), [["pm4ai dashboard", projectName]])
|
|
107
|
+
]);
|
|
108
|
+
await syncClaudeMd(src, dir);
|
|
109
|
+
const bookInstall = join(homedir(), "book", "install.sh");
|
|
110
|
+
if (existsSync(bookInstall)) await $`sh ${bookInstall} ${dir}`.quiet().nothrow();
|
|
111
|
+
await $`git init`.cwd(dir).quiet();
|
|
112
|
+
await $`git add -A`.cwd(dir).quiet();
|
|
113
|
+
await $`git -c user.name=pm4ai -c user.email=pm4ai commit -m "init: scaffold from pm4ai"`.cwd(dir).quiet().nothrow();
|
|
114
|
+
console.log(`\ncreated ${projectName}/`);
|
|
115
|
+
console.log(`\n cd ${projectName}`);
|
|
116
|
+
console.log(" bun i");
|
|
117
|
+
console.log(" bun dev");
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
export { init };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { t as discover } from "./discover-d8ENQC1K.mjs";
|
|
2
|
+
import { $ } from "bun";
|
|
3
|
+
//#region src/list.ts
|
|
4
|
+
const AHEAD_RE = /\+(?<n>\d+)/u;
|
|
5
|
+
const BEHIND_RE = /-(?<n>\d+)/u;
|
|
6
|
+
const gitStatus = async (path) => {
|
|
7
|
+
const out = (await $`git -C ${path} status -sb --porcelain=v2 --branch`.quiet().nothrow()).stdout.toString();
|
|
8
|
+
const aheadMatch = AHEAD_RE.exec(out);
|
|
9
|
+
const behindMatch = BEHIND_RE.exec(out);
|
|
10
|
+
const dirty = out.split("\n").some((l) => l.startsWith("1 ") || l.startsWith("2 ") || l.startsWith("?"));
|
|
11
|
+
return {
|
|
12
|
+
ahead: aheadMatch ? Number(aheadMatch.groups?.n) : 0,
|
|
13
|
+
behind: behindMatch ? Number(behindMatch.groups?.n) : 0,
|
|
14
|
+
dirty
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
const list = async (excludes = [], searchRoot, write = (row) => console.log(row)) => {
|
|
18
|
+
const { consumers } = await discover(searchRoot, excludes);
|
|
19
|
+
const rows = await Promise.all(consumers.map(async (c) => {
|
|
20
|
+
const g = await gitStatus(c.path);
|
|
21
|
+
const flags = [
|
|
22
|
+
g.dirty ? "dirty" : "clean",
|
|
23
|
+
g.behind ? `behind:${g.behind}` : "",
|
|
24
|
+
g.ahead ? `ahead:${g.ahead}` : ""
|
|
25
|
+
].filter(Boolean).join(",");
|
|
26
|
+
return `${c.path}\t${flags}`;
|
|
27
|
+
}));
|
|
28
|
+
for (const r of rows.toSorted()) write(r);
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { list };
|