@theagilemonkeys/facility 0.11.4 → 0.12.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/README.md +61 -47
- package/package.json +3 -4
- package/src/cli.mjs +27 -176
- package/src/detect.mjs +24 -94
- package/src/doctor.mjs +54 -559
- package/src/init.mjs +92 -535
- package/templates/agents/address-review.md +53 -0
- package/templates/agents/architect.md +50 -0
- package/templates/agents/builder.md +58 -0
- package/templates/agents/ci-doctor.md +55 -0
- package/templates/agents/pr-reviewer.md +52 -0
- package/templates/agents/security-audit.md +54 -0
- package/modules/README.md +0 -35
- package/modules/ai-queryability/agents/queryability-reviewer.md +0 -35
- package/modules/ai-queryability/module.json +0 -9
- package/modules/ai-queryability/standard-section.md +0 -22
- package/modules/analytics/agents/analytics-reviewer.md +0 -32
- package/modules/analytics/commands/add-telemetry.md +0 -23
- package/modules/analytics/module.json +0 -10
- package/modules/analytics/standard-section.md +0 -23
- package/modules/database/agents/data-security-reviewer.md +0 -38
- package/modules/database/commands/new-migration.md +0 -24
- package/modules/database/guards/migration-versions.mjs +0 -41
- package/modules/database/guards/migrations-immutable.mjs +0 -57
- package/modules/database/hooks/protect-migrations.fragment.mjs +0 -10
- package/modules/database/module.json +0 -25
- package/modules/database/standard-section.md +0 -20
- package/modules/design-system/agents/design-reviewer.md +0 -37
- package/modules/design-system/module.json +0 -9
- package/modules/design-system/standard-section.md +0 -15
- package/src/add.mjs +0 -77
- package/src/platform-admin.mjs +0 -1552
- package/src/platform-config.mjs +0 -39
- package/src/platform.mjs +0 -1759
- package/src/render.mjs +0 -66
- package/templates/claude/settings.json +0 -71
- package/templates/delivery/verify.mjs +0 -157
- package/templates/doctor/resolve.mjs +0 -572
- package/templates/guards/README.md +0 -30
- package/templates/guards/_kit.mjs +0 -81
- package/templates/guards/actions-pinned.mjs +0 -38
- package/templates/guards/run.mjs +0 -111
- package/templates/guards/watchtower-locked.mjs +0 -66
- package/templates/prompts/address-review.md +0 -14
- package/templates/prompts/architect.md +0 -63
- package/templates/prompts/builder.md +0 -79
- package/templates/prompts/doctor.md +0 -69
- package/templates/prompts/review.md +0 -14
- package/templates/prompts/sweep.md +0 -75
- package/templates/receipts/collect.mjs +0 -297
- package/templates/review/finalize.mjs +0 -38
- package/templates/scripts/move-board-status.sh +0 -155
- package/templates/security/sync-findings.mjs +0 -226
- package/templates/standard/STANDARD.md +0 -141
- package/templates/standard/agents-block.md +0 -25
- package/templates/watchtower/budgets.json +0 -12
- package/templates/watchtower/canary.mjs +0 -216
- package/templates/watchtower/health.mjs +0 -148
- package/templates/watchtower/outcomes.mjs +0 -188
- package/templates/workflows/facility-address-review.yml +0 -154
- package/templates/workflows/facility-canary.yml +0 -61
- package/templates/workflows/facility-codex.yml +0 -327
- package/templates/workflows/facility-crew.yml +0 -351
- package/templates/workflows/facility-doctor.yml +0 -174
- package/templates/workflows/facility-review.yml +0 -135
- package/templates/workflows/facility-security-sweep.yml +0 -204
- package/templates/workflows/facility-watchtower.yml +0 -87
package/README.md
CHANGED
|
@@ -1,69 +1,83 @@
|
|
|
1
1
|
# @theagilemonkeys/facility
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
place.
|
|
3
|
+
The Facility CLI installs and validates the small repository contract used by
|
|
4
|
+
Facility 0.12. Day-to-day story work happens through the Facility MCP server or
|
|
5
|
+
web application.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
```bash
|
|
8
|
+
cd your-repository
|
|
9
|
+
npx @theagilemonkeys/facility init
|
|
10
|
+
npx @theagilemonkeys/facility doctor
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Node.js 24 LTS is recommended; Node.js 22 is supported from 22.13.0.
|
|
14
|
+
|
|
15
|
+
## Initialize a repository
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **An installer for a repository.** When a team wants the process running in
|
|
14
|
-
its own CI, invoked from issue comments, `facility init` writes the workflows,
|
|
15
|
-
the standard, the skills and the guards into the repository.
|
|
17
|
+
Interactive init detects the GitHub remote, package manager, setup command,
|
|
18
|
+
development command, and application port. For automation, provide the values
|
|
19
|
+
explicitly:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
|
-
npx @theagilemonkeys/facility --
|
|
22
|
+
npx @theagilemonkeys/facility init --yes \
|
|
23
|
+
--repo=acme/application \
|
|
24
|
+
--provision='pnpm install --frozen-lockfile' \
|
|
25
|
+
--start='docker compose up -d' \
|
|
26
|
+
--preview-readiness-command='curl --fail http://localhost:3000/health' \
|
|
27
|
+
--service-port=3000
|
|
19
28
|
```
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
configuration to read; one dependency.
|
|
30
|
+
`init` writes exactly two kinds of project-owned configuration:
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
- `.facility.yml` describes the repositories, setup command, development
|
|
33
|
+
command, readiness check, and exposed services for a persistent workspace.
|
|
34
|
+
- `.agents/*.md` describes each agent's prompt, engine, model, and manual, MCP,
|
|
35
|
+
UI, scheduled, or GitHub triggers.
|
|
28
36
|
|
|
29
|
-
|
|
37
|
+
Existing files are preserved unless `--force` is explicit. Agent manifests do
|
|
38
|
+
not contain permission profiles: every enabled agent receives the same full
|
|
39
|
+
workspace and GitHub maintainer capability for the connected project.
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
Init creates `architect`, `builder`, `pr-reviewer`, `address-review`,
|
|
42
|
+
`ci-doctor`, and `security-audit`. Model flags customize the initial templates:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
--build-model=<id> --review-model=<id>
|
|
46
|
+
--plan-model=<id> --codex-build-model=<id>
|
|
47
|
+
--codex-plan-model=<id>
|
|
36
48
|
```
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
Use `--dir=<path>` to configure another checkout. Existing targets are
|
|
51
|
+
preserved independently; `--force` overwrites only `.facility.yml` and those
|
|
52
|
+
six agent files. Review them before using force because they are project-owned
|
|
53
|
+
configuration.
|
|
54
|
+
|
|
55
|
+
## Validate locally
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
`doctor` checks the seven-file contract locally. It never connects to a
|
|
58
|
+
Facility instance and supports machine-readable output with `--json`.
|
|
44
59
|
|
|
45
60
|
```bash
|
|
46
|
-
|
|
47
|
-
npx @theagilemonkeys/facility init
|
|
48
|
-
npx @theagilemonkeys/facility doctor --run-guards --github
|
|
61
|
+
npx @theagilemonkeys/facility doctor --json
|
|
49
62
|
```
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
answers. It never overwrites an existing generated file unless you pass
|
|
56
|
-
`--force`, and it appends to `AGENTS.md` and `CLAUDE.md` inside a delimited
|
|
57
|
-
managed block rather than replacing them.
|
|
64
|
+
Doctor provides fast local feedback; the server's strict manifest parsers and
|
|
65
|
+
a real disposable workspace remain authoritative for runtime acceptance.
|
|
66
|
+
|
|
67
|
+
## Bootstrap an instance
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
Instance operators can create the first organization, owner, and GitHub App
|
|
70
|
+
installation with `facility instance bootstrap`. See the
|
|
71
|
+
[Facility documentation](https://github.com/theam/facility/tree/main/apps/docs/docs)
|
|
72
|
+
for deployment and MCP setup.
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
Bootstrap requires `DATABASE_URL` and explicit organization, owner GitHub
|
|
75
|
+
identity, account, and installation values. It is safe to repeat only with the
|
|
76
|
+
exact same binding and refuses a different binding when instance data exists.
|
|
77
|
+
Run `facility instance bootstrap --help` for the full command.
|
|
64
78
|
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
Facility 0.x is early software and does not promise in-place database upgrades
|
|
80
|
+
between minor versions. Facility 0.12 specifically requires a new database
|
|
81
|
+
rather than a 0.11 in-place migration.
|
|
68
82
|
|
|
69
|
-
Apache-2.0
|
|
83
|
+
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theagilemonkeys/facility",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "Create and validate Facility 0.12 repository workspace and agent manifests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
7
7
|
"agents",
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
"files": [
|
|
28
28
|
"bin",
|
|
29
29
|
"src",
|
|
30
|
-
"templates"
|
|
31
|
-
"modules"
|
|
30
|
+
"templates"
|
|
32
31
|
],
|
|
33
32
|
"engines": {
|
|
34
33
|
"node": "^22.13.0 || ^24.0.0"
|
package/src/cli.mjs
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Facility 0.12 CLI entry: repository kickstart, validation, and instance bootstrap.
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { init } from "./init.mjs";
|
|
6
|
-
import { addModule } from "./add.mjs";
|
|
7
6
|
import { doctor } from "./doctor.mjs";
|
|
8
7
|
import { bootstrapInstance } from "./instance.mjs";
|
|
9
|
-
import { runPlatformCommand } from "./platform.mjs";
|
|
10
8
|
import { banner, bold, dim, item } from "./ui.mjs";
|
|
11
9
|
|
|
12
10
|
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -20,7 +18,7 @@ function parseFlags(args) {
|
|
|
20
18
|
if (arg === "--yes" || arg === "-y") flags.yes = true;
|
|
21
19
|
else if (arg === "--json") flags.json = true;
|
|
22
20
|
else if (arg === "--force") flags.force = true;
|
|
23
|
-
else if (
|
|
21
|
+
else if (arg === "--help") flags.help = true;
|
|
24
22
|
else if (arg.startsWith("--") && arg.includes("=")) {
|
|
25
23
|
const [key, ...rest] = arg.slice(2).split("=");
|
|
26
24
|
flags[key] = rest.join("=");
|
|
@@ -38,63 +36,21 @@ function parseFlags(args) {
|
|
|
38
36
|
|
|
39
37
|
function help() {
|
|
40
38
|
banner(version);
|
|
41
|
-
helpGroup("
|
|
42
|
-
["init", "
|
|
43
|
-
["
|
|
44
|
-
["doctor", "verify local and platform readiness"],
|
|
39
|
+
helpGroup("Configure a repository", [
|
|
40
|
+
["init", "write .facility.yml and the .agents catalog"],
|
|
41
|
+
["doctor", "verify the workspace contract"],
|
|
45
42
|
]);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
["
|
|
50
|
-
["agents", "configure agent definitions and schedules"],
|
|
51
|
-
["sessions", "trigger, watch, steer, or cancel governed sessions"],
|
|
52
|
-
["conversations", "continue durable agent conversations"],
|
|
53
|
-
["github", "discover repositories and act on GitHub issues"],
|
|
54
|
-
["inbox", "review and decide proposals"],
|
|
55
|
-
["issues", "acknowledge and resolve platform issues"],
|
|
56
|
-
["kickstart", "preview and open a governed installation PR"],
|
|
57
|
-
["upgrade", "open a governed template upgrade PR"],
|
|
58
|
-
]);
|
|
59
|
-
helpGroup("Knowledge & policy", [
|
|
60
|
-
["kb", "manage the project knowledge base"],
|
|
61
|
-
["tasks", "manage the product-owner task queue"],
|
|
62
|
-
["registry", "version contracts, harnesses, and policies"],
|
|
63
|
-
["proposals", "create, inspect, and execute approval gates"],
|
|
64
|
-
["action-types", "discover proposal payload contracts"],
|
|
65
|
-
["budgets", "enforce org, project, and agent budgets"],
|
|
66
|
-
["health", "inspect project readiness"],
|
|
67
|
-
["outcomes", "inspect pull-request delivery outcomes"],
|
|
68
|
-
["catalog", "discover engines, models, permissions, and triggers"],
|
|
69
|
-
]);
|
|
70
|
-
helpGroup("Platform administration", [
|
|
71
|
-
["instance bootstrap", "bootstrap a dedicated Facility instance"],
|
|
72
|
-
["org", "organization settings"],
|
|
73
|
-
["members", "organization membership"],
|
|
74
|
-
["roles", "permission policy"],
|
|
75
|
-
["keys", "control-plane API credentials"],
|
|
76
|
-
["virtual-keys", "project-scoped model credentials"],
|
|
77
|
-
["providers", "model-provider credentials"],
|
|
78
|
-
["sandboxes", "execution profiles and isolation"],
|
|
79
|
-
["integrations", "inbound and outbound webhooks"],
|
|
80
|
-
["analytics", "run and reliability trends"],
|
|
81
|
-
["spend", "cost attribution"],
|
|
82
|
-
["audit", "tamper-evident change ledger"],
|
|
83
|
-
["llm-requests", "inspect metering and request envelopes"],
|
|
84
|
-
]);
|
|
85
|
-
helpGroup("Account", [
|
|
86
|
-
["login", "verify and save an environment profile"],
|
|
87
|
-
["logout", "remove saved profile credentials"],
|
|
88
|
-
["profiles", "switch between Facility environments"],
|
|
43
|
+
item(dim("Run stories through Facility's MCP server or web UI."));
|
|
44
|
+
console.log("");
|
|
45
|
+
helpGroup("Instance administration", [
|
|
46
|
+
["instance bootstrap", "create the first organization, owner, and GitHub installation"],
|
|
89
47
|
]);
|
|
90
48
|
console.log("");
|
|
91
|
-
item(dim("init flags: --yes --force --dir=<path> --
|
|
92
|
-
item(dim(
|
|
49
|
+
item(dim("init flags: --yes --force --dir=<path> --repo=<owner/name> --provision=<cmd> --start=<cmd>"));
|
|
50
|
+
item(dim(" --service-port=<n> --preview-readiness-command=<cmd>"));
|
|
93
51
|
item(dim(' --build-model=<id> --review-model=<id> --plan-model=<id>'));
|
|
94
|
-
item(dim(' --codex-build-model=<id> --codex-plan-model=<id>
|
|
95
|
-
item(dim(' --preview-image=<image> --preview-command=<cmd> --preview-port=<n> --preview-readiness-path=</health> --preview-ttl-hours=<n>'));
|
|
52
|
+
item(dim(' --codex-build-model=<id> --codex-plan-model=<id>'));
|
|
96
53
|
item(dim("Run facility <command> --help for precise usage."));
|
|
97
|
-
item(dim("Global platform flags: --profile <name> --json --timeout <seconds>"));
|
|
98
54
|
console.log("");
|
|
99
55
|
item(dim("Docs: https://github.com/theam/facility"));
|
|
100
56
|
console.log("");
|
|
@@ -121,7 +77,7 @@ export async function main(argv) {
|
|
|
121
77
|
}
|
|
122
78
|
if (flags.dir) flags.dir = resolve(flags.dir);
|
|
123
79
|
|
|
124
|
-
if (flags.help && ["init", "
|
|
80
|
+
if (flags.help && ["init", "doctor"].includes(command)) {
|
|
125
81
|
localHelp(command);
|
|
126
82
|
return 0;
|
|
127
83
|
}
|
|
@@ -129,56 +85,12 @@ export async function main(argv) {
|
|
|
129
85
|
switch (command) {
|
|
130
86
|
case "init":
|
|
131
87
|
return init(flags, pkgRoot, version);
|
|
132
|
-
case "add": {
|
|
133
|
-
if (!positional[0]) {
|
|
134
|
-
help();
|
|
135
|
-
return 1;
|
|
136
|
-
}
|
|
137
|
-
return addModule(positional[0], { dir: flags.dir || process.cwd(), pkgRoot });
|
|
138
|
-
}
|
|
139
88
|
case "doctor":
|
|
140
89
|
return doctor(flags, version);
|
|
141
90
|
case "instance":
|
|
142
91
|
if (positional[0] === "bootstrap") return bootstrapInstance(flags);
|
|
143
92
|
console.error("Usage: facility instance bootstrap [options]");
|
|
144
93
|
return 1;
|
|
145
|
-
case "login":
|
|
146
|
-
case "logout":
|
|
147
|
-
case "profiles":
|
|
148
|
-
case "status":
|
|
149
|
-
case "projects":
|
|
150
|
-
case "sessions":
|
|
151
|
-
case "runs":
|
|
152
|
-
case "conversations":
|
|
153
|
-
case "github":
|
|
154
|
-
case "outcomes":
|
|
155
|
-
case "catalog":
|
|
156
|
-
case "inbox":
|
|
157
|
-
case "issues":
|
|
158
|
-
case "kickstart":
|
|
159
|
-
case "upgrade":
|
|
160
|
-
case "keys":
|
|
161
|
-
case "llm-requests":
|
|
162
|
-
case "org":
|
|
163
|
-
case "members":
|
|
164
|
-
case "roles":
|
|
165
|
-
case "repos":
|
|
166
|
-
case "agents":
|
|
167
|
-
case "providers":
|
|
168
|
-
case "budgets":
|
|
169
|
-
case "registry":
|
|
170
|
-
case "sandboxes":
|
|
171
|
-
case "tasks":
|
|
172
|
-
case "virtual-keys":
|
|
173
|
-
case "kb":
|
|
174
|
-
case "analytics":
|
|
175
|
-
case "audit":
|
|
176
|
-
case "integrations":
|
|
177
|
-
case "spend":
|
|
178
|
-
case "proposals":
|
|
179
|
-
case "action-types":
|
|
180
|
-
case "health":
|
|
181
|
-
return runPlatformCommand(command, rest);
|
|
182
94
|
case "--version":
|
|
183
95
|
case "-v":
|
|
184
96
|
console.log(version);
|
|
@@ -198,40 +110,20 @@ function validateLocalFlags(command, flags) {
|
|
|
198
110
|
"yes",
|
|
199
111
|
"force",
|
|
200
112
|
"dir",
|
|
201
|
-
"branch",
|
|
202
113
|
"provision",
|
|
203
|
-
"
|
|
204
|
-
"
|
|
114
|
+
"start",
|
|
115
|
+
"repo",
|
|
116
|
+
"service-port",
|
|
117
|
+
"preview-readiness-command",
|
|
205
118
|
"org",
|
|
206
|
-
"modules",
|
|
207
|
-
"auth",
|
|
208
119
|
"build-model",
|
|
209
120
|
"review-model",
|
|
210
121
|
"plan-model",
|
|
211
122
|
"codex-build-model",
|
|
212
123
|
"codex-plan-model",
|
|
213
|
-
"preview-image",
|
|
214
|
-
"preview-command",
|
|
215
|
-
"preview-port",
|
|
216
|
-
"preview-readiness-path",
|
|
217
|
-
"preview-ttl-hours",
|
|
218
|
-
"help",
|
|
219
|
-
]),
|
|
220
|
-
add: new Set(["dir", "help"]),
|
|
221
|
-
doctor: new Set([
|
|
222
|
-
"dir",
|
|
223
|
-
"local",
|
|
224
|
-
"platform",
|
|
225
|
-
"profile",
|
|
226
|
-
"url",
|
|
227
|
-
"key",
|
|
228
|
-
"allow-insecure",
|
|
229
|
-
"timeout",
|
|
230
|
-
"run-guards",
|
|
231
|
-
"github",
|
|
232
|
-
"json",
|
|
233
124
|
"help",
|
|
234
125
|
]),
|
|
126
|
+
doctor: new Set(["dir", "json", "help"]),
|
|
235
127
|
instance: new Set([
|
|
236
128
|
"org-name",
|
|
237
129
|
"org-slug",
|
|
@@ -254,29 +146,23 @@ function validateLocalFlags(command, flags) {
|
|
|
254
146
|
}
|
|
255
147
|
const valueNames =
|
|
256
148
|
command === "doctor"
|
|
257
|
-
? ["dir"
|
|
149
|
+
? ["dir"]
|
|
258
150
|
: command === "instance"
|
|
259
151
|
? [...allowed].filter((name) => !["json", "help"].includes(name))
|
|
260
152
|
: command === "init"
|
|
261
153
|
? [
|
|
262
154
|
"dir",
|
|
263
|
-
"branch",
|
|
264
155
|
"provision",
|
|
265
|
-
"
|
|
266
|
-
"
|
|
156
|
+
"start",
|
|
157
|
+
"repo",
|
|
158
|
+
"service-port",
|
|
159
|
+
"preview-readiness-command",
|
|
267
160
|
"org",
|
|
268
|
-
"modules",
|
|
269
|
-
"auth",
|
|
270
161
|
"build-model",
|
|
271
162
|
"review-model",
|
|
272
163
|
"plan-model",
|
|
273
164
|
"codex-build-model",
|
|
274
165
|
"codex-plan-model",
|
|
275
|
-
"preview-image",
|
|
276
|
-
"preview-command",
|
|
277
|
-
"preview-port",
|
|
278
|
-
"preview-readiness-path",
|
|
279
|
-
"preview-ttl-hours",
|
|
280
166
|
]
|
|
281
167
|
: ["dir"];
|
|
282
168
|
for (const name of valueNames) {
|
|
@@ -284,29 +170,10 @@ function validateLocalFlags(command, flags) {
|
|
|
284
170
|
return `--${name} requires a value`;
|
|
285
171
|
}
|
|
286
172
|
}
|
|
287
|
-
if (
|
|
288
|
-
command === "doctor" &&
|
|
289
|
-
flags.local &&
|
|
290
|
-
(flags.platform || flags.profile || flags.url || flags.key)
|
|
291
|
-
) {
|
|
292
|
-
return "--local cannot be combined with platform target options";
|
|
293
|
-
}
|
|
294
|
-
if (
|
|
295
|
-
command === "doctor" &&
|
|
296
|
-
(flags.platform || flags.profile || flags.url || flags.key) &&
|
|
297
|
-
(flags["run-guards"] || flags.github)
|
|
298
|
-
) {
|
|
299
|
-
return "--run-guards and --github are local-doctor options";
|
|
300
|
-
}
|
|
301
173
|
for (const name of [
|
|
302
174
|
"yes",
|
|
303
175
|
"force",
|
|
304
176
|
"help",
|
|
305
|
-
"local",
|
|
306
|
-
"platform",
|
|
307
|
-
"allow-insecure",
|
|
308
|
-
"run-guards",
|
|
309
|
-
"github",
|
|
310
177
|
"json",
|
|
311
178
|
]) {
|
|
312
179
|
if (name in flags && flags[name] !== true) return `--${name} does not take a value`;
|
|
@@ -315,34 +182,18 @@ function validateLocalFlags(command, flags) {
|
|
|
315
182
|
}
|
|
316
183
|
|
|
317
184
|
function extractInvocation(argv) {
|
|
318
|
-
|
|
319
|
-
const valueFlags = new Set(["--profile", "--timeout", "--dir", "--url", "--key"]);
|
|
320
|
-
let index = 0;
|
|
321
|
-
while (index < argv.length && argv[index]?.startsWith("-") && !["--help", "--version", "-v"].includes(argv[index])) {
|
|
322
|
-
const flag = argv[index];
|
|
323
|
-
leading.push(flag);
|
|
324
|
-
index += 1;
|
|
325
|
-
if (valueFlags.has(flag) && index < argv.length) {
|
|
326
|
-
leading.push(argv[index]);
|
|
327
|
-
index += 1;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return { command: argv[index], rest: [...leading, ...argv.slice(index + 1)] };
|
|
185
|
+
return { command: argv[0], rest: argv.slice(1) };
|
|
331
186
|
}
|
|
332
187
|
|
|
333
188
|
function localHelp(command) {
|
|
334
189
|
const lines = {
|
|
335
190
|
init: [
|
|
336
191
|
"facility init [--dir <path>] [--yes] [--force]",
|
|
337
|
-
"Detect the repository and install
|
|
338
|
-
],
|
|
339
|
-
add: [
|
|
340
|
-
"facility add <module> [--dir <path>]",
|
|
341
|
-
"Add a Facility quality module to an existing installation.",
|
|
192
|
+
"Detect the repository and install .facility.yml plus the shared .agents catalog.",
|
|
342
193
|
],
|
|
343
194
|
doctor: [
|
|
344
|
-
"facility doctor [--dir <path>] [--
|
|
345
|
-
"Verify
|
|
195
|
+
"facility doctor [--dir <path>] [--json]",
|
|
196
|
+
"Verify the repository's .facility.yml and .agents catalog.",
|
|
346
197
|
],
|
|
347
198
|
};
|
|
348
199
|
const [usage, description] = lines[command];
|
package/src/detect.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
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
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
3
|
import { join } from "node:path";
|
|
6
4
|
|
|
7
5
|
function readJson(path) {
|
|
@@ -14,7 +12,11 @@ function readJson(path) {
|
|
|
14
12
|
|
|
15
13
|
function git(cwd, args) {
|
|
16
14
|
try {
|
|
17
|
-
return execFileSync("git", args, {
|
|
15
|
+
return execFileSync("git", args, {
|
|
16
|
+
cwd,
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
19
|
+
}).trim();
|
|
18
20
|
} catch {
|
|
19
21
|
return "";
|
|
20
22
|
}
|
|
@@ -23,105 +25,33 @@ function git(cwd, args) {
|
|
|
23
25
|
export function detect(dir) {
|
|
24
26
|
const pkg = readJson(join(dir, "package.json"));
|
|
25
27
|
const scripts = pkg?.scripts ?? {};
|
|
26
|
-
|
|
27
28
|
const packageManager = existsSync(join(dir, "pnpm-lock.yaml"))
|
|
28
29
|
? "pnpm"
|
|
29
30
|
: existsSync(join(dir, "yarn.lock"))
|
|
30
31
|
? "yarn"
|
|
31
|
-
: existsSync(join(dir, "package-lock.json"))
|
|
32
|
+
: existsSync(join(dir, "package-lock.json")) || pkg
|
|
32
33
|
? "npm"
|
|
33
|
-
:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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 = "";
|
|
34
|
+
: "none";
|
|
35
|
+
const runner =
|
|
36
|
+
packageManager === "none" ? null : packageManager === "npm" ? "npm run" : `${packageManager} run`;
|
|
37
|
+
const provision = runner && scripts.setup ? `${runner} setup` : "";
|
|
38
|
+
const start =
|
|
39
|
+
existsSync(join(dir, "compose.yml")) || existsSync(join(dir, "docker-compose.yml"))
|
|
40
|
+
? "docker compose up -d"
|
|
41
|
+
: runner && scripts.dev
|
|
42
|
+
? `${runner} dev`
|
|
43
|
+
: runner && scripts.start
|
|
44
|
+
? `${runner} start`
|
|
45
|
+
: "";
|
|
57
46
|
const remote = git(dir, ["remote", "get-url", "origin"]);
|
|
58
|
-
const
|
|
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"));
|
|
47
|
+
const match = remote.match(/[/:]([^/:]+)\/([^/]+?)(\.git)?$/);
|
|
85
48
|
|
|
86
49
|
return {
|
|
87
|
-
isGitRepo,
|
|
88
|
-
defaultBranch,
|
|
50
|
+
isGitRepo: git(dir, ["rev-parse", "--is-inside-work-tree"]) === "true",
|
|
89
51
|
packageManager,
|
|
90
|
-
checks,
|
|
91
52
|
provision,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
},
|
|
53
|
+
start,
|
|
54
|
+
org: match?.[1] ?? "",
|
|
55
|
+
repository: match ? `${match[1]}/${match[2]}` : "",
|
|
104
56
|
};
|
|
105
57
|
}
|
|
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
|
-
}
|