@theagilemonkeys/facility 0.3.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/LICENSE +201 -0
- package/README.md +68 -0
- package/bin/facility.mjs +10 -0
- package/modules/README.md +35 -0
- package/modules/ai-queryability/agents/queryability-reviewer.md +35 -0
- package/modules/ai-queryability/module.json +9 -0
- package/modules/ai-queryability/standard-section.md +22 -0
- package/modules/analytics/agents/analytics-reviewer.md +32 -0
- package/modules/analytics/commands/add-telemetry.md +23 -0
- package/modules/analytics/module.json +10 -0
- package/modules/analytics/standard-section.md +23 -0
- package/modules/database/agents/data-security-reviewer.md +38 -0
- package/modules/database/commands/new-migration.md +24 -0
- package/modules/database/guards/migration-versions.mjs +41 -0
- package/modules/database/guards/migrations-immutable.mjs +57 -0
- package/modules/database/hooks/protect-migrations.fragment.mjs +10 -0
- package/modules/database/module.json +25 -0
- package/modules/database/standard-section.md +20 -0
- package/modules/design-system/agents/design-reviewer.md +37 -0
- package/modules/design-system/module.json +9 -0
- package/modules/design-system/standard-section.md +15 -0
- package/package.json +42 -0
- package/src/add.mjs +77 -0
- package/src/cli.mjs +352 -0
- package/src/detect.mjs +127 -0
- package/src/doctor.mjs +582 -0
- package/src/init.mjs +572 -0
- package/src/instance.mjs +114 -0
- package/src/platform-admin.mjs +1542 -0
- package/src/platform-config.mjs +39 -0
- package/src/platform.mjs +1759 -0
- package/src/prompts.mjs +64 -0
- package/src/render.mjs +66 -0
- package/src/ui.mjs +30 -0
- package/templates/claude/agents/security-reviewer.md +41 -0
- package/templates/claude/agents/standards-reviewer.md +31 -0
- package/templates/claude/commands/open-pr.md +21 -0
- package/templates/claude/commands/verify.md +16 -0
- package/templates/claude/hooks/protect-branch.mjs +58 -0
- package/templates/claude/hooks/protect-files.mjs +35 -0
- package/templates/claude/settings.json +71 -0
- package/templates/claude/skills/maintainable-software/SKILL.md +67 -0
- package/templates/claude/skills/reviewing-to-standard/SKILL.md +49 -0
- package/templates/claude/skills/working-to-standard/SKILL.md +45 -0
- package/templates/delivery/verify.mjs +157 -0
- package/templates/doctor/resolve.mjs +144 -0
- package/templates/guards/README.md +30 -0
- package/templates/guards/_kit.mjs +81 -0
- package/templates/guards/actions-pinned.mjs +38 -0
- package/templates/guards/run.mjs +111 -0
- package/templates/guards/watchtower-locked.mjs +66 -0
- package/templates/prompts/address-review.md +14 -0
- package/templates/prompts/architect.md +62 -0
- package/templates/prompts/builder.md +71 -0
- package/templates/prompts/doctor.md +64 -0
- package/templates/prompts/review.md +14 -0
- package/templates/prompts/sweep.md +75 -0
- package/templates/receipts/collect.mjs +289 -0
- package/templates/review/finalize.mjs +38 -0
- package/templates/scripts/move-board-status.sh +155 -0
- package/templates/security/sync-findings.mjs +226 -0
- package/templates/standard/STANDARD.md +141 -0
- package/templates/standard/agents-block.md +25 -0
- package/templates/watchtower/budgets.json +12 -0
- package/templates/watchtower/canary.mjs +216 -0
- package/templates/watchtower/health.mjs +148 -0
- package/templates/watchtower/outcomes.mjs +188 -0
- package/templates/workflows/facility-address-review.yml +153 -0
- package/templates/workflows/facility-canary.yml +61 -0
- package/templates/workflows/facility-codex.yml +326 -0
- package/templates/workflows/facility-crew.yml +350 -0
- package/templates/workflows/facility-doctor.yml +155 -0
- package/templates/workflows/facility-review.yml +134 -0
- package/templates/workflows/facility-security-sweep.yml +204 -0
- package/templates/workflows/facility-watchtower.yml +87 -0
package/src/detect.mjs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Stack detection: read the target repo and propose sensible defaults so
|
|
2
|
+
// `init` asks six short questions instead of twenty.
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
function readJson(path) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function git(cwd, args) {
|
|
16
|
+
try {
|
|
17
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function detect(dir) {
|
|
24
|
+
const pkg = readJson(join(dir, "package.json"));
|
|
25
|
+
const scripts = pkg?.scripts ?? {};
|
|
26
|
+
|
|
27
|
+
const packageManager = existsSync(join(dir, "pnpm-lock.yaml"))
|
|
28
|
+
? "pnpm"
|
|
29
|
+
: existsSync(join(dir, "yarn.lock"))
|
|
30
|
+
? "yarn"
|
|
31
|
+
: existsSync(join(dir, "package-lock.json"))
|
|
32
|
+
? "npm"
|
|
33
|
+
: pkg
|
|
34
|
+
? "npm"
|
|
35
|
+
: "none";
|
|
36
|
+
|
|
37
|
+
const runner = packageManager === "none" ? null : packageManager === "npm" ? "npm run" : `${packageManager} run`;
|
|
38
|
+
const checks = [];
|
|
39
|
+
if (runner) {
|
|
40
|
+
for (const name of ["typecheck", "lint", "test", "build"]) {
|
|
41
|
+
if (scripts[name]) checks.push(`${runner} ${name}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const provision = runner && scripts["setup"] ? `${runner} setup` : "";
|
|
46
|
+
|
|
47
|
+
const isGitRepo = git(dir, ["rev-parse", "--is-inside-work-tree"]) === "true";
|
|
48
|
+
let defaultBranch = "main";
|
|
49
|
+
const originHead = git(dir, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
50
|
+
if (originHead) defaultBranch = originHead.replace("refs/remotes/origin/", "");
|
|
51
|
+
else {
|
|
52
|
+
const current = git(dir, ["branch", "--show-current"]);
|
|
53
|
+
if (current) defaultBranch = current;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let org = "";
|
|
57
|
+
const remote = git(dir, ["remote", "get-url", "origin"]);
|
|
58
|
+
const remoteMatch = remote.match(/[/:]([^/:]+)\/([^/]+?)(\.git)?$/);
|
|
59
|
+
if (remoteMatch) org = remoteMatch[1];
|
|
60
|
+
|
|
61
|
+
const migrationDirs = ["migrations", "supabase/migrations", "db/migrations", "prisma/migrations"].filter((d) =>
|
|
62
|
+
existsSync(join(dir, d))
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Existing check workflows (by their `name:`), so the doctor knows what to
|
|
66
|
+
// watch. Facility's own workflows are excluded — the watchtower covers them.
|
|
67
|
+
const workflowNames = [];
|
|
68
|
+
const deploymentProviders = new Set();
|
|
69
|
+
try {
|
|
70
|
+
for (const file of readdirSync(join(dir, ".github/workflows"))) {
|
|
71
|
+
if (!/\.ya?ml$/.test(file) || file.startsWith("facility-")) continue;
|
|
72
|
+
const workflow = readFileSync(join(dir, ".github/workflows", file), "utf8");
|
|
73
|
+
const nameMatch = workflow.match(/^name:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
74
|
+
if (nameMatch) workflowNames.push(nameMatch[1].trim());
|
|
75
|
+
detectDeploymentProvider(workflow, deploymentProviders);
|
|
76
|
+
}
|
|
77
|
+
} catch {}
|
|
78
|
+
if (existsSync(join(dir, "vercel.json"))) deploymentProviders.add("vercel");
|
|
79
|
+
if (existsSync(join(dir, "netlify.toml"))) deploymentProviders.add("netlify");
|
|
80
|
+
if (existsSync(join(dir, "fly.toml"))) deploymentProviders.add("fly.io");
|
|
81
|
+
if (existsSync(join(dir, "render.yaml"))) deploymentProviders.add("render");
|
|
82
|
+
if (existsSync(join(dir, "Dockerfile"))) deploymentProviders.add("container-image");
|
|
83
|
+
const board = detectBoard(dir, org);
|
|
84
|
+
const facility = readJson(join(dir, ".facility.json"));
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
isGitRepo,
|
|
88
|
+
defaultBranch,
|
|
89
|
+
packageManager,
|
|
90
|
+
checks,
|
|
91
|
+
provision,
|
|
92
|
+
org,
|
|
93
|
+
workflowNames,
|
|
94
|
+
deploymentProviders: [...deploymentProviders].sort(),
|
|
95
|
+
board,
|
|
96
|
+
previewConfigured: facility?.preview?.enabled === true,
|
|
97
|
+
suggestedModules: migrationDirs.length ? ["database"] : [],
|
|
98
|
+
existing: {
|
|
99
|
+
agentsMd: existsSync(join(dir, "AGENTS.md")),
|
|
100
|
+
claudeMd: existsSync(join(dir, "CLAUDE.md")),
|
|
101
|
+
claudeSettings: existsSync(join(dir, ".claude/settings.json")),
|
|
102
|
+
standard: existsSync(join(dir, "STANDARD.md")),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function detectDeploymentProvider(content, providers) {
|
|
108
|
+
const lower = content.toLowerCase();
|
|
109
|
+
if (lower.includes("vercel")) providers.add("vercel");
|
|
110
|
+
if (lower.includes("netlify")) providers.add("netlify");
|
|
111
|
+
if (lower.includes("flyctl") || lower.includes("fly.io")) providers.add("fly.io");
|
|
112
|
+
if (lower.includes("cloudflare") || lower.includes("wrangler")) providers.add("cloudflare");
|
|
113
|
+
if (lower.includes("amazon-ecr") || lower.includes("amazon-ecs")) providers.add("aws");
|
|
114
|
+
if (lower.includes("render.com")) providers.add("render");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function detectBoard(dir, fallbackOrg) {
|
|
118
|
+
for (const path of ["AGENTS.md", "CLAUDE.md", "README.md"]) {
|
|
119
|
+
try {
|
|
120
|
+
const match = readFileSync(join(dir, path), "utf8").match(
|
|
121
|
+
/github\.com\/orgs\/([^/\s]+)\/projects\/(\d+)/i,
|
|
122
|
+
);
|
|
123
|
+
if (match?.[2]) return { org: match[1] || fallbackOrg, project: Number(match[2]) };
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|