@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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +68 -0
  3. package/bin/facility.mjs +10 -0
  4. package/modules/README.md +35 -0
  5. package/modules/ai-queryability/agents/queryability-reviewer.md +35 -0
  6. package/modules/ai-queryability/module.json +9 -0
  7. package/modules/ai-queryability/standard-section.md +22 -0
  8. package/modules/analytics/agents/analytics-reviewer.md +32 -0
  9. package/modules/analytics/commands/add-telemetry.md +23 -0
  10. package/modules/analytics/module.json +10 -0
  11. package/modules/analytics/standard-section.md +23 -0
  12. package/modules/database/agents/data-security-reviewer.md +38 -0
  13. package/modules/database/commands/new-migration.md +24 -0
  14. package/modules/database/guards/migration-versions.mjs +41 -0
  15. package/modules/database/guards/migrations-immutable.mjs +57 -0
  16. package/modules/database/hooks/protect-migrations.fragment.mjs +10 -0
  17. package/modules/database/module.json +25 -0
  18. package/modules/database/standard-section.md +20 -0
  19. package/modules/design-system/agents/design-reviewer.md +37 -0
  20. package/modules/design-system/module.json +9 -0
  21. package/modules/design-system/standard-section.md +15 -0
  22. package/package.json +42 -0
  23. package/src/add.mjs +77 -0
  24. package/src/cli.mjs +352 -0
  25. package/src/detect.mjs +127 -0
  26. package/src/doctor.mjs +582 -0
  27. package/src/init.mjs +572 -0
  28. package/src/instance.mjs +114 -0
  29. package/src/platform-admin.mjs +1542 -0
  30. package/src/platform-config.mjs +39 -0
  31. package/src/platform.mjs +1759 -0
  32. package/src/prompts.mjs +64 -0
  33. package/src/render.mjs +66 -0
  34. package/src/ui.mjs +30 -0
  35. package/templates/claude/agents/security-reviewer.md +41 -0
  36. package/templates/claude/agents/standards-reviewer.md +31 -0
  37. package/templates/claude/commands/open-pr.md +21 -0
  38. package/templates/claude/commands/verify.md +16 -0
  39. package/templates/claude/hooks/protect-branch.mjs +58 -0
  40. package/templates/claude/hooks/protect-files.mjs +35 -0
  41. package/templates/claude/settings.json +71 -0
  42. package/templates/claude/skills/maintainable-software/SKILL.md +67 -0
  43. package/templates/claude/skills/reviewing-to-standard/SKILL.md +49 -0
  44. package/templates/claude/skills/working-to-standard/SKILL.md +45 -0
  45. package/templates/delivery/verify.mjs +157 -0
  46. package/templates/doctor/resolve.mjs +144 -0
  47. package/templates/guards/README.md +30 -0
  48. package/templates/guards/_kit.mjs +81 -0
  49. package/templates/guards/actions-pinned.mjs +38 -0
  50. package/templates/guards/run.mjs +111 -0
  51. package/templates/guards/watchtower-locked.mjs +66 -0
  52. package/templates/prompts/address-review.md +14 -0
  53. package/templates/prompts/architect.md +62 -0
  54. package/templates/prompts/builder.md +71 -0
  55. package/templates/prompts/doctor.md +64 -0
  56. package/templates/prompts/review.md +14 -0
  57. package/templates/prompts/sweep.md +75 -0
  58. package/templates/receipts/collect.mjs +289 -0
  59. package/templates/review/finalize.mjs +38 -0
  60. package/templates/scripts/move-board-status.sh +155 -0
  61. package/templates/security/sync-findings.mjs +226 -0
  62. package/templates/standard/STANDARD.md +141 -0
  63. package/templates/standard/agents-block.md +25 -0
  64. package/templates/watchtower/budgets.json +12 -0
  65. package/templates/watchtower/canary.mjs +216 -0
  66. package/templates/watchtower/health.mjs +148 -0
  67. package/templates/watchtower/outcomes.mjs +188 -0
  68. package/templates/workflows/facility-address-review.yml +153 -0
  69. package/templates/workflows/facility-canary.yml +61 -0
  70. package/templates/workflows/facility-codex.yml +326 -0
  71. package/templates/workflows/facility-crew.yml +350 -0
  72. package/templates/workflows/facility-doctor.yml +155 -0
  73. package/templates/workflows/facility-review.yml +134 -0
  74. package/templates/workflows/facility-security-sweep.yml +204 -0
  75. package/templates/workflows/facility-watchtower.yml +87 -0
@@ -0,0 +1,64 @@
1
+ // Interactive prompts on node:readline. Zero dependencies.
2
+ import { createInterface } from "node:readline/promises";
3
+ import { bold, dim } from "./ui.mjs";
4
+
5
+ let rl = null;
6
+
7
+ function iface() {
8
+ if (!rl) rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ return rl;
10
+ }
11
+
12
+ export function closePrompts() {
13
+ if (rl) {
14
+ rl.close();
15
+ rl = null;
16
+ }
17
+ }
18
+
19
+ /** Free-text question with a default. Empty answer returns the default. */
20
+ export async function ask(question, defaultValue = "") {
21
+ const suffix = defaultValue ? ` ${dim(`(${defaultValue})`)}` : "";
22
+ const answer = (await questionOrEof(` ${bold(question)}${suffix} `)).trim();
23
+ return answer || defaultValue;
24
+ }
25
+
26
+ /** Yes/no question. */
27
+ export async function confirm(question, defaultYes = true) {
28
+ const hint = defaultYes ? "Y/n" : "y/N";
29
+ const answer = (await questionOrEof(` ${bold(question)} ${dim(`[${hint}]`)} `))
30
+ .trim()
31
+ .toLowerCase();
32
+ if (!answer) return defaultYes;
33
+ return answer.startsWith("y");
34
+ }
35
+
36
+ function questionOrEof(question) {
37
+ const readline = iface();
38
+ return new Promise((resolve, reject) => {
39
+ let settled = false;
40
+ const onClose = () => {
41
+ if (!settled) {
42
+ settled = true;
43
+ const error = new Error("Input ended before the prompt was answered.");
44
+ error.code = "prompt_eof";
45
+ reject(error);
46
+ }
47
+ };
48
+ readline.once("close", onClose);
49
+ readline.question(question).then(
50
+ (answer) => {
51
+ if (settled) return;
52
+ settled = true;
53
+ readline.removeListener("close", onClose);
54
+ resolve(answer);
55
+ },
56
+ (error) => {
57
+ if (settled) return;
58
+ settled = true;
59
+ readline.removeListener("close", onClose);
60
+ reject(error);
61
+ },
62
+ );
63
+ });
64
+ }
package/src/render.mjs ADDED
@@ -0,0 +1,66 @@
1
+ // Template rendering and managed-content insertion.
2
+ //
3
+ // Placeholders are {{UPPER_SNAKE}} with no spaces, so GitHub Actions
4
+ // expressions (`${{ secrets.X }}` — spaces, lowercase, dots) never match and
5
+ // pass through untouched.
6
+
7
+ /**
8
+ * Substitute {{VARS}} in a template. A placeholder alone on its line is a
9
+ * block variable: an empty value removes the whole line, a multi-line value
10
+ * lands verbatim. Unknown placeholders are left as-is.
11
+ */
12
+ export function render(template, vars) {
13
+ const withBlocks = template.replace(
14
+ /^[ \t]*\{\{([A-Z0-9_]+)\}\}[ \t]*\r?\n/gm,
15
+ (match, name) => {
16
+ const value = vars[name];
17
+ if (value === undefined) return match;
18
+ if (value === "") return "";
19
+ return value.endsWith("\n") ? value : `${value}\n`;
20
+ }
21
+ );
22
+ return withBlocks.replace(/\{\{([A-Z0-9_]+)\}\}/g, (match, name) => vars[name] ?? match);
23
+ }
24
+
25
+ /** True when `content` already contains the facility managed-block marker. */
26
+ export function hasManagedBlock(content) {
27
+ return content.includes("<!-- facility:start");
28
+ }
29
+
30
+ /** Append a managed block to existing file content (with a blank line). */
31
+ export function appendManagedBlock(content, block) {
32
+ const base = content.replace(/\s*$/, "");
33
+ return base ? `${base}\n\n${block}` : block;
34
+ }
35
+
36
+ const MODULES_START = "<!-- facility:modules:start -->";
37
+ const MODULES_END = "<!-- facility:modules:end -->";
38
+
39
+ /** Insert a module's standard section between the modules markers. */
40
+ export function insertModuleSection(standard, section, moduleTitle) {
41
+ if (standard.includes(`(facility module)`) && standard.includes(`### ${moduleTitle} (facility module)`)) {
42
+ return { content: standard, inserted: false };
43
+ }
44
+ const end = standard.indexOf(MODULES_END);
45
+ if (end === -1) {
46
+ // No markers (user removed them): append under a Modules heading instead.
47
+ const block = `\n## Modules\n\n${section.trim()}\n`;
48
+ return { content: `${standard.replace(/\s*$/, "")}\n${block}`, inserted: true };
49
+ }
50
+ const before = standard.slice(0, end).replace(/[ \t]*$/, "");
51
+ const after = standard.slice(end);
52
+ return { content: `${before}${section.trim()}\n\n${after}`, inserted: true };
53
+ }
54
+
55
+ const HOOK_MARKER = "/* facility:module-rules */";
56
+
57
+ /** Splice a module's hook rules at the marker in protect-files.mjs. */
58
+ export function insertHookRules(hookSource, fragment, moduleName) {
59
+ const sentinel = `facility module: ${moduleName}`;
60
+ if (hookSource.includes(sentinel)) return { content: hookSource, inserted: false };
61
+ if (!hookSource.includes(HOOK_MARKER)) return { content: hookSource, inserted: false };
62
+ return {
63
+ content: hookSource.replace(HOOK_MARKER, `${fragment.trim()}\n\n${HOOK_MARKER}`),
64
+ inserted: true,
65
+ };
66
+ }
package/src/ui.mjs ADDED
@@ -0,0 +1,30 @@
1
+ // Terminal output helpers. Zero dependencies; ANSI only when it's a TTY and
2
+ // NO_COLOR is unset. Restrained on purpose — the CLI should read like a
3
+ // well-run factory floor, not a slot machine.
4
+ const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
5
+ const ESC = "\u001b";
6
+
7
+ const wrap = (code) => (text) => (useColor ? `${ESC}[${code}m${text}${ESC}[0m` : String(text));
8
+
9
+ export const bold = wrap("1");
10
+ export const dim = wrap("2");
11
+ export const accent = wrap("38;5;220"); // facility yellow — reserved for agent work
12
+ export const green = wrap("32");
13
+ export const red = wrap("31");
14
+ export const yellow = wrap("33");
15
+
16
+ export function banner(version) {
17
+ console.log("");
18
+ console.log(` ${bold("facility")} ${dim(`v${version}`)} ${dim("— the AI software factory for your repo")}`);
19
+ console.log("");
20
+ }
21
+
22
+ export function heading(text) {
23
+ console.log(`\n${bold(text)}`);
24
+ }
25
+
26
+ export const ok = (text) => console.log(` ${green("✓")} ${text}`);
27
+ export const skip = (text) => console.log(` ${dim("—")} ${dim(text)}`);
28
+ export const warn = (text) => console.log(` ${yellow("!")} ${text}`);
29
+ export const fail = (text) => console.log(` ${red("✗")} ${text}`);
30
+ export const item = (text) => console.log(` ${text}`);
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: security-reviewer
3
+ description: Adversarial reviewer for auth boundaries, secret handling, injection paths, and privilege escalation. Use proactively before merging any change that touches auth, privileged access, external input parsing, CI workflows, or agent-facing surfaces.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You are a security reviewer. You review a diff in a fresh context, so you are
8
+ not biased toward the code under review. Assume authorization gaps **fail
9
+ silently** — verify the negative case, never trust a prose claim that a
10
+ boundary is enforced.
11
+
12
+ ## What to check
13
+ 1. **Secrets**: no credentials, tokens, or signed URLs in code, fixtures,
14
+ logs, or test snapshots. `.env` handling respects the repo's hooks.
15
+ 2. **Input trust**: external input (HTTP, webhooks, issue/PR text consumed by
16
+ agents, file uploads) is validated at the boundary and never interpolated
17
+ into shell, SQL, or query builders.
18
+ 3. **Authorization**: every new read/write path re-checks permission at the
19
+ boundary that owns the data; no privileged client or service account leaks
20
+ into user-facing or agent-facing read paths.
21
+ 4. **CI surface**: workflow changes keep actions pinned to full commit SHAs,
22
+ never echo secrets, never widen `permissions:`, and never expose secrets to
23
+ fork-originated code.
24
+ 5. **Agent surface**: prompts and tool contracts keep untrusted text framed as
25
+ DATA; no new path lets repository content instruct an agent to exfiltrate
26
+ or escalate.
27
+ 6. **Failure modes**: errors don't leak internals; empty results are
28
+ permission-safe (they don't hint that hidden data exists).
29
+
30
+ ## How to verify
31
+ - `node guards/run.mjs` — deterministic repo invariants, including the
32
+ workflow-pinning guard.
33
+ - Targeted greps for the diff's trust boundaries; run the repo's auth/security
34
+ tests when the diff justifies it.
35
+
36
+ ## Output contract
37
+ Return findings ordered by severity (Blocker / High / Medium), each with
38
+ file:line, the exact risk, and the smallest fix. State which checks you ran
39
+ and their result. Report **only** security/privacy/correctness gaps — not
40
+ style. If you find nothing, say "No security gaps found" and list the checks
41
+ that prove it.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: standards-reviewer
3
+ description: Lead quality reviewer that runs this repo's STANDARD.md completion checklist end to end. Use before finalizing or opening a PR for any change with product behavior.
4
+ tools: Read, Grep, Glob, Bash
5
+ ---
6
+
7
+ You are the lead reviewer enforcing `STANDARD.md` as the development standard
8
+ for the whole team, not only as agent guidance. You review the diff in a fresh
9
+ context and lead with correctness, security, maintainability, and
10
+ product-quality before any polish. Be the adversarial second opinion: report
11
+ only real correctness/security/requirements/quality gaps — do not invent style
12
+ nits or impossible-case concerns.
13
+
14
+ ## Method
15
+ 1. Establish scope: read the diff and identify user-facing behavior, affected
16
+ domains, and risk level.
17
+ 2. Walk the **Pull request review standard** and **Completion checklist** from
18
+ `STANDARD.md`, including any module sections between the
19
+ `facility:modules` markers.
20
+ 3. For any domain that carries real risk, recommend (or dispatch, when doing a
21
+ deep review) the matching specialist subagent from `.claude/agents/`.
22
+ 4. Run the lightest useful verification yourself and escalate based on risk.
23
+ `node guards/run.mjs` is always cheap and always relevant.
24
+
25
+ ## Output contract
26
+ Produce a verdict: **Ready** / **Ready with follow-ups** / **Not ready**. Then
27
+ findings grouped by severity (Blocker / High / Medium), each with file:line,
28
+ the exact risk, the missed standard, and the smallest fix. Avoid vague asks
29
+ like "clean this up" or "add tests" without naming the missing case. Finish
30
+ with the exact checks you ran and their results. Keep it concise and
31
+ team-lead-ready.
@@ -0,0 +1,21 @@
1
+ ---
2
+ description: Open a concise, team-lead-ready PR following STANDARD.md
3
+ ---
4
+
5
+ Open a pull request for the current branch following STANDARD.md's PR rules.
6
+
7
+ 1. Preconditions: branch name is semantic (`feature/...`, `fix/...`, ... —
8
+ no agent/tool prefixes); commits follow Conventional Commits; the
9
+ relevant checks from /verify have run in this session — if not, run them
10
+ first.
11
+ 2. Push the branch and create the PR with `gh pr create` targeting
12
+ {{DEFAULT_BRANCH}}, with:
13
+ - A Conventional-Commits title describing the product/domain intent.
14
+ - The body structure from STANDARD.md (Summary / Context / Verification /
15
+ Linked issues) — tight, no implementation diary, no filler.
16
+ - The full issue URL when one exists in the branch name, commits, or
17
+ conversation, as a `Closes #<n>` closing keyword. Never invent issue
18
+ links.
19
+ 3. Mention what was intentionally NOT touched (migrations, analytics,
20
+ security surface) when a reviewer would otherwise expect it.
21
+ 4. Print the PR URL.
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: Run the right checks for the current change and report honestly
3
+ ---
4
+
5
+ Verify the current working-tree change per STANDARD.md's verification ladder.
6
+
7
+ 1. Look at the diff (`git status`, `git diff`) and classify what it touches:
8
+ pure code, data/migrations, security surface, UI, analytics, agent
9
+ surface.
10
+ 2. Run the lightest relevant checks first, escalating by risk:
11
+
12
+ {{CHECKS_LIST}}
13
+
14
+ 3. Report a short table: check → result. Name every relevant check you did
15
+ NOT run and why. Never summarize a failure away — paste the meaningful
16
+ tail of the output and say what you'd try next.
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ // Generated by facility — https://github.com/theam/facility
3
+ //
4
+ // PreToolUse hook (Bash). Blocks a small set of unambiguously dangerous
5
+ // commands before they run (exit 2 = deny, stderr is shown to the agent).
6
+ // Deliberately narrow: it guards destructive/irreversible actions, not normal
7
+ // development.
8
+ import { readFileSync } from "node:fs";
9
+
10
+ const PROTECTED = /^(origin\/)?({{DEFAULT_BRANCH}}|main|master)$/;
11
+
12
+ function readPayload() {
13
+ try {
14
+ return JSON.parse(readFileSync(0, "utf8") || "{}");
15
+ } catch {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ const command = readPayload()?.tool_input?.command ?? "";
21
+ if (!command) process.exit(0);
22
+
23
+ const tokens = command.split(/\s+/);
24
+ const has = (t) => tokens.includes(t);
25
+ const targetsProtected = (t) => PROTECTED.test(t) || /:({{DEFAULT_BRANCH}}|main|master)$/.test(t);
26
+ const targetsProtectedRef = tokens.some(targetsProtected);
27
+ const isForce = has("--force") || has("-f") || has("--force-with-lease");
28
+
29
+ function block(reason) {
30
+ console.error(`⛔ Blocked by .claude/hooks/protect-branch.mjs\n${reason}`);
31
+ process.exit(2);
32
+ }
33
+
34
+ // Recursive force-remove rooted at filesystem root or $HOME.
35
+ if (
36
+ /\brm\b/.test(command) &&
37
+ /-[a-z]*r/i.test(command) &&
38
+ /-[a-z]*f/i.test(command) &&
39
+ /\s(\/|~|\$HOME)(\s|\/|$)/.test(command)
40
+ ) {
41
+ block("Recursive force-remove of a root or home path is not allowed.");
42
+ }
43
+
44
+ if (has("git") && has("push") && targetsProtectedRef && isForce) {
45
+ block("Force-pushing the default branch is not allowed. Use a feature branch + PR.");
46
+ }
47
+
48
+ if (has("git") && has("push") && targetsProtectedRef) {
49
+ block("Direct pushes to the default branch are not allowed — open a PR (see STANDARD.md).");
50
+ }
51
+
52
+ if (/>>?\s*\.?\/?\.env(\.[\w.]+)?\b/.test(command) && !/\.env\.example\b/.test(command)) {
53
+ block(
54
+ "Refusing to redirect output into a real .env file (secrets). Edit .env.example for placeholders.",
55
+ );
56
+ }
57
+
58
+ process.exit(0);
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // Generated by facility — https://github.com/theam/facility
3
+ //
4
+ // PreToolUse hook (Edit|Write|MultiEdit). Protects files that must not be
5
+ // mutated casually (exit 2 = deny, stderr is shown to the agent).
6
+ //
7
+ // Real .env files hold live secrets — placeholders belong in .env.example.
8
+ // Facility modules may extend this file with extra protections (for example,
9
+ // the database module adds migration immutability).
10
+ import { readFileSync } from "node:fs";
11
+
12
+ function readPayload() {
13
+ try {
14
+ return JSON.parse(readFileSync(0, "utf8") || "{}");
15
+ } catch {
16
+ return {};
17
+ }
18
+ }
19
+
20
+ const payload = readPayload();
21
+ const filePath = payload?.tool_input?.file_path ?? "";
22
+ if (!filePath) process.exit(0);
23
+
24
+ function block(reason) {
25
+ console.error(`⛔ Blocked by .claude/hooks/protect-files.mjs\n${reason}`);
26
+ process.exit(2);
27
+ }
28
+
29
+ if (/(^|\/)\.env(\.[\w.]+)?$/.test(filePath) && !/\.env\.example$/.test(filePath)) {
30
+ block("Refusing to write a real .env file (secrets). Use .env.example for placeholders.");
31
+ }
32
+
33
+ /* facility:module-rules */
34
+
35
+ process.exit(0);
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "includeCoAuthoredBy": false,
4
+ "permissions": {
5
+ "defaultMode": "default",
6
+ "allow": [
7
+ {{ALLOW_CHECKS_JSON}}
8
+ "Bash(node guards/run.mjs)",
9
+ "Bash(git status:*)",
10
+ "Bash(git diff:*)",
11
+ "Bash(git log:*)",
12
+ "Bash(git show:*)",
13
+ "Bash(git add:*)",
14
+ "Bash(git restore:*)",
15
+ "Bash(git branch:*)",
16
+ "Bash(git switch:*)",
17
+ "Bash(git checkout:*)",
18
+ "Bash(git commit:*)",
19
+ "Bash(git fetch:*)",
20
+ "Bash(git rev-parse:*)",
21
+ "Bash(gh pr view:*)",
22
+ "Bash(gh pr list:*)",
23
+ "Bash(gh pr diff:*)",
24
+ "Bash(gh pr checks:*)",
25
+ "Bash(gh issue view:*)",
26
+ "Bash(gh issue list:*)",
27
+ "Bash(gh run list:*)",
28
+ "Bash(gh run view:*)"
29
+ ],
30
+ "ask": [
31
+ "Bash(git push:*)",
32
+ "Bash(gh pr create:*)",
33
+ "Bash(gh pr merge:*)",
34
+ "Bash(gh issue create:*)"
35
+ ],
36
+ "deny": [
37
+ "Read(.env)",
38
+ "Read(.env.local)",
39
+ "Read(.env.*.local)",
40
+ "Bash(sudo:*)",
41
+ "Bash(rm -rf /)",
42
+ "Bash(rm -rf ~)",
43
+ "Bash(git push --force:*)",
44
+ "Bash(git push -f:*)"
45
+ ]
46
+ },
47
+ "hooks": {
48
+ "PreToolUse": [
49
+ {
50
+ "matcher": "Bash",
51
+ "hooks": [
52
+ {
53
+ "type": "command",
54
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-branch.mjs\"",
55
+ "timeout": 30
56
+ }
57
+ ]
58
+ },
59
+ {
60
+ "matcher": "Edit|Write|MultiEdit",
61
+ "hooks": [
62
+ {
63
+ "type": "command",
64
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect-files.mjs\"",
65
+ "timeout": 30
66
+ }
67
+ ]
68
+ }
69
+ ]
70
+ }
71
+ }
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: maintainable-software
3
+ description: Engineering judgment for writing code that stays cheap to change — simplicity, naming, boundaries, duplication, tests. Apply to any feature work, refactor, API design, or architecture decision, in any language.
4
+ ---
5
+
6
+ # Maintainable software
7
+
8
+ The cost of software is the cost of changing it later. Every rule here
9
+ optimizes for the next reader and the next change, not for the author.
10
+
11
+ ## Simplicity
12
+
13
+ - Solve the problem in front of you. Speculative generality — the parameter
14
+ nobody passes, the interface with one implementation — is debt with no
15
+ loan.
16
+ - Prefer boring constructs. Cleverness needs a comment; boring code doesn't.
17
+ - One concept per unit: a function that needs "and" in its description is two
18
+ functions.
19
+
20
+ ## Naming
21
+
22
+ - Names come from the domain, not the implementation: `settleInvoice`, not
23
+ `processData2`.
24
+ - A name that needs a comment to be understood is the wrong name. Rename
25
+ first, comment only for what code cannot say (constraints, why-not-the-
26
+ obvious-way).
27
+ - Inconsistent vocabulary is a bug factory: one concept, one word, repo-wide.
28
+
29
+ ## Boundaries and interfaces
30
+
31
+ - Narrow interfaces, deep modules: expose the smallest surface that serves
32
+ the caller; keep the complexity behind it.
33
+ - Dependencies point one way. A module that reaches into its consumers — or
34
+ into globals — can't be tested or replaced.
35
+ - Side effects live at the edges; the core is data in, data out. If a
36
+ function both computes and writes, split it.
37
+
38
+ ## Duplication
39
+
40
+ - Duplicate **shape** is fine; duplicate **concept** is not. Two functions
41
+ that look alike but change for different reasons should stay apart; one
42
+ business rule written twice will diverge and one copy will be wrong.
43
+ - Extract on the second real occurrence of the same concept, with a domain
44
+ name. Extracting on resemblance creates the worst abstraction: the shared
45
+ helper full of flags.
46
+
47
+ ## Errors
48
+
49
+ - Make failure explicit at the boundary: validate input where it enters,
50
+ fail with a message the caller can act on, never swallow.
51
+ - Impossible states should be unrepresentable (types, schemas, constraints)
52
+ rather than checked everywhere.
53
+
54
+ ## Tests
55
+
56
+ - Tests protect behavior, not implementation. A test that breaks on a
57
+ rename is friction; a behavior change that breaks no test is a hole.
58
+ - Test the boundary you'd be afraid to change. If a bug got through, the
59
+ first fix is the test that would have caught it.
60
+
61
+ ## Changes
62
+
63
+ - The best diff is the smallest one that fully solves the problem. Refactors
64
+ travel separately from behavior changes — a reviewer can hold one of them
65
+ in their head, not both.
66
+ - Leave the campsite slightly cleaner, never reorganized: opportunistic
67
+ renames yes, opportunistic redesigns no.
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: reviewing-to-standard
3
+ description: Review code changes against this repo's STANDARD.md. Use when reviewing a PR, a diff, or another agent's work — enforces the review order, the comment quality bar, and severity discipline.
4
+ ---
5
+
6
+ # Reviewing to standard
7
+
8
+ A review exists to change the implementer's next action. Everything else is
9
+ noise.
10
+
11
+ ## Review order (stop-the-line first)
12
+
13
+ 1. **Correctness and fit** — the change does what was asked, no more. Scope
14
+ creep is a finding, not a bonus.
15
+ 2. **Security and privacy** — auth regressions, data exposure, secret
16
+ handling, injection paths, new attack surface. Verify the negative case;
17
+ never accept a prose claim that a boundary holds.
18
+ 3. **Maintainability** — same concept reused not duplicated, clear ownership
19
+ boundaries, domain names, side effects visible, easy for the next caller
20
+ and the next test.
21
+ 4. **Standard compliance** — `STANDARD.md` and its module sections were
22
+ followed. A missed module requirement (seeds, analytics, evidence,
23
+ exposure) is a product-quality gap, not optional polish.
24
+ 5. **Verification evidence** — the right checks ran for the risk taken, and
25
+ skipped checks are named. `node guards/run.mjs` should be green.
26
+
27
+ ## The comment contract
28
+
29
+ Every finding carries four things: `file:line`, the exact risk, the smallest
30
+ practical fix, and the standard it missed. Severity is explicit — Blocker /
31
+ High / Medium — and separated from optional suggestions. Never post:
32
+
33
+ - Style-only nits, unless they hide a real bug or future maintenance cost.
34
+ - Vague asks ("clean this up", "add tests") — name the missing case, command,
35
+ or assertion instead.
36
+ - Requests for broad rewrites when a narrow change closes the risk.
37
+
38
+ ## Leverage
39
+
40
+ For domains with real risk, dispatch the matching reviewer subagent from
41
+ `.claude/agents/` and consolidate its findings rather than re-deriving them.
42
+ If the same problem has now appeared twice in reviews, the review is the
43
+ wrong tool: propose the guard (`guards/`) that makes the third occurrence
44
+ impossible, and say so in the review.
45
+
46
+ ## Verdict
47
+
48
+ End with **Ready / Ready with follow-ups / Not ready**, the checks you ran,
49
+ and nothing else. You never approve or merge — that signature is human.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: working-to-standard
3
+ description: Apply this repo's STANDARD.md while implementing, refactoring, or fixing. Use for any change that affects product behavior — before editing, while verifying, and when reporting done.
4
+ ---
5
+
6
+ # Working to standard
7
+
8
+ `STANDARD.md` is the contract; this skill is how it's applied while the work
9
+ is happening, not checked after the fact.
10
+
11
+ ## Before editing
12
+
13
+ 1. Read `STANDARD.md`, including the module sections between the
14
+ `facility:modules` markers — they carry domain rules (data, analytics,
15
+ design, AI exposure) that change what "done" means.
16
+ 2. Name the user-facing outcome, the affected boundaries, the invariants that
17
+ must hold, and the rollback surface. If you can't, the task isn't clear
18
+ enough to edit yet — ask the one question that unblocks it.
19
+ 3. Find the local pattern first. The repo's existing shape beats the general
20
+ best practice; new abstractions need to remove real complexity.
21
+
22
+ ## While editing
23
+
24
+ - Deliver the WHOLE request as the smallest coherent change — no drive-by
25
+ refactors, no "phase 1" unless phasing was requested.
26
+ - Keep side effects at the edges and visible: network, database, time,
27
+ randomness, file system, model calls.
28
+ - Validate input at trust boundaries with the existing schema/parsing
29
+ patterns; make invalid states hard to represent.
30
+ - When you touch a domain with a reviewer subagent in `.claude/agents/`,
31
+ apply its checklist as you go — cheaper than failing its review later.
32
+
33
+ ## Verifying
34
+
35
+ Run the lightest useful checks first, escalate by risk, per the verification
36
+ ladder in `STANDARD.md`. `node guards/run.mjs` is always cheap and always
37
+ relevant. A check you cannot run is reported by name with the reason — never
38
+ claimed.
39
+
40
+ ## Reporting done
41
+
42
+ One concise summary: what changed and why, the checks you ran with their
43
+ results, and any genuinely out-of-scope follow-ups. Walk the completion
44
+ checklist in `STANDARD.md` before saying done; an unmet item is either fixed
45
+ or explicitly reported, never silent.