create-religion 0.1.0

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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/bin/religion.js +209 -0
  4. package/dist/lib/dashboard.js +123 -0
  5. package/dist/lib/doctor.js +164 -0
  6. package/dist/lib/install.js +170 -0
  7. package/dist/lib/paths.js +40 -0
  8. package/dist/lib/state.js +117 -0
  9. package/dist/lib/status.js +88 -0
  10. package/package.json +40 -0
  11. package/template/.agents/skills/audit/SKILL.md +169 -0
  12. package/template/.agents/skills/auto/SKILL.md +110 -0
  13. package/template/.agents/skills/browser-tests/SKILL.md +65 -0
  14. package/template/.agents/skills/check/SKILL.md +86 -0
  15. package/template/.agents/skills/ci/SKILL.md +79 -0
  16. package/template/.agents/skills/complete/SKILL.md +184 -0
  17. package/template/.agents/skills/debug/SKILL.md +72 -0
  18. package/template/.agents/skills/discovery/SKILL.md +83 -0
  19. package/template/.agents/skills/distill/SKILL.md +95 -0
  20. package/template/.agents/skills/doctor/SKILL.md +65 -0
  21. package/template/.agents/skills/extend/SKILL.md +102 -0
  22. package/template/.agents/skills/feature/SKILL.md +172 -0
  23. package/template/.agents/skills/feature/reference/spec-template.md +67 -0
  24. package/template/.agents/skills/fix/SKILL.md +76 -0
  25. package/template/.agents/skills/implement/SKILL.md +174 -0
  26. package/template/.agents/skills/overview/SKILL.md +150 -0
  27. package/template/.agents/skills/overview/reference/project-overview-template.md +101 -0
  28. package/template/.agents/skills/prototype/SKILL.md +90 -0
  29. package/template/.agents/skills/release/SKILL.md +88 -0
  30. package/template/.agents/skills/rollback/SKILL.md +114 -0
  31. package/template/.agents/skills/setup/SKILL.md +105 -0
  32. package/template/.agents/skills/status/SKILL.md +63 -0
  33. package/template/.agents/skills/tests/SKILL.md +90 -0
  34. package/template/.agents/skills/try/SKILL.md +59 -0
  35. package/template/.claude/hooks/guard-rendered-output.mjs +49 -0
  36. package/template/.claude/hooks/guard-tier-one.mjs +63 -0
  37. package/template/.claude/hooks/write-handoff.mjs +83 -0
  38. package/template/.claude/skills/audit/SKILL.md +169 -0
  39. package/template/.claude/skills/auto/SKILL.md +110 -0
  40. package/template/.claude/skills/browser-tests/SKILL.md +65 -0
  41. package/template/.claude/skills/check/SKILL.md +86 -0
  42. package/template/.claude/skills/ci/SKILL.md +79 -0
  43. package/template/.claude/skills/complete/SKILL.md +184 -0
  44. package/template/.claude/skills/debug/SKILL.md +72 -0
  45. package/template/.claude/skills/discovery/SKILL.md +83 -0
  46. package/template/.claude/skills/distill/SKILL.md +95 -0
  47. package/template/.claude/skills/doctor/SKILL.md +65 -0
  48. package/template/.claude/skills/extend/SKILL.md +102 -0
  49. package/template/.claude/skills/feature/SKILL.md +172 -0
  50. package/template/.claude/skills/feature/reference/spec-template.md +67 -0
  51. package/template/.claude/skills/fix/SKILL.md +76 -0
  52. package/template/.claude/skills/implement/SKILL.md +174 -0
  53. package/template/.claude/skills/overview/SKILL.md +150 -0
  54. package/template/.claude/skills/overview/reference/project-overview-template.md +101 -0
  55. package/template/.claude/skills/prototype/SKILL.md +90 -0
  56. package/template/.claude/skills/release/SKILL.md +88 -0
  57. package/template/.claude/skills/rollback/SKILL.md +114 -0
  58. package/template/.claude/skills/setup/SKILL.md +105 -0
  59. package/template/.claude/skills/status/SKILL.md +63 -0
  60. package/template/.claude/skills/tests/SKILL.md +90 -0
  61. package/template/.claude/skills/try/SKILL.md +59 -0
  62. package/template/AGENTS.md +206 -0
  63. package/template/CLAUDE.md +175 -0
  64. package/template/religion/.state/README.md +10 -0
  65. package/template/religion/.state/settings-template.json +19 -0
  66. package/template/religion/build-plan.md +43 -0
  67. package/template/religion/config.json +26 -0
  68. package/template/religion/context/ai-interaction.md +84 -0
  69. package/template/religion/context/coding-standards.md +77 -0
  70. package/template/religion/context/current-work.md +8 -0
  71. package/template/religion/context/findings.md +9 -0
  72. package/template/religion/context/handoff.md +8 -0
  73. package/template/religion/context/project-overview.md +8 -0
  74. package/template/religion/history/features/README.md +8 -0
  75. package/template/religion/history/fixes/README.md +6 -0
  76. package/template/religion/history/rollbacks/README.md +7 -0
  77. package/template/religion/learning/journal.md +7 -0
  78. package/template/religion/learning/lessons.md +8 -0
  79. package/template/religion/project-plan.md +49 -0
  80. package/template/religion/reference/README.md +7 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Installing and updating the workflow files.
3
+ *
4
+ * The rule that shapes all of this: files the project owns are seeded once and never
5
+ * touched again, while files the workflow owns are managed and can be updated. Getting
6
+ * that backwards destroys someone's plans on an update, so the two sets are kept explicit
7
+ * rather than inferred from paths at the point of use.
8
+ */
9
+ import crypto from "node:crypto";
10
+ import fs from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { exists, STATE_DIR } from "./paths.js";
13
+ export const ADAPTERS = {
14
+ claude: { label: "Claude Code", trees: [".claude/skills", ".claude/hooks"], entry: ["CLAUDE.md"] },
15
+ codex: { label: "Codex", trees: [".agents/skills"], entry: ["AGENTS.md"] },
16
+ copilot: { label: "GitHub Copilot", trees: [".agents/skills"], entry: ["AGENTS.md"] },
17
+ opencode: { label: "OpenCode", trees: [".agents/skills"], entry: ["AGENTS.md"] }
18
+ };
19
+ const MANIFEST = path.join(STATE_DIR, ".state", "manifest.json");
20
+ export function hash(contents) {
21
+ return crypto.createHash("sha256").update(contents).digest("hex").slice(0, 16);
22
+ }
23
+ /** Managed files are the workflow's own; everything under the state directory is the project's. */
24
+ export function isManaged(relative) {
25
+ return !relative.startsWith(`${STATE_DIR}/`);
26
+ }
27
+ export async function planInstall(templateRoot, target, adapters, previous) {
28
+ const wanted = wantedPaths(adapters);
29
+ const plan = [];
30
+ for (const relative of await walk(templateRoot)) {
31
+ if (!wanted(relative))
32
+ continue;
33
+ const destination = path.join(target, relative);
34
+ const source = await fs.readFile(path.join(templateRoot, relative));
35
+ if (!(await exists(destination))) {
36
+ plan.push({ relative, action: "create" });
37
+ continue;
38
+ }
39
+ // State files hold the project's real work. Once present they are never rewritten.
40
+ if (!isManaged(relative)) {
41
+ plan.push({ relative, action: "seed-skip" });
42
+ continue;
43
+ }
44
+ const current = await fs.readFile(destination);
45
+ if (hash(current) === hash(source)) {
46
+ plan.push({ relative, action: "unchanged" });
47
+ continue;
48
+ }
49
+ // A managed file that differs is only safe to replace when it still matches what was
50
+ // installed. Anything else is a local edit, and overwriting it silently is the failure
51
+ // this manifest exists to prevent.
52
+ const recorded = previous?.managed[relative];
53
+ plan.push({ relative, action: recorded && recorded === hash(current) ? "update" : "conflict" });
54
+ }
55
+ return plan;
56
+ }
57
+ export async function applyInstall(templateRoot, target, plan, options) {
58
+ const written = [];
59
+ const conflicts = [];
60
+ const backups = [];
61
+ for (const entry of plan) {
62
+ if (entry.action === "unchanged" || entry.action === "seed-skip")
63
+ continue;
64
+ if (entry.action === "conflict" && !options.force) {
65
+ conflicts.push(entry.relative);
66
+ continue;
67
+ }
68
+ const destination = path.join(target, entry.relative);
69
+ if (entry.action === "conflict") {
70
+ const backup = path.join(target, STATE_DIR, ".state", "backups", entry.relative);
71
+ await fs.mkdir(path.dirname(backup), { recursive: true });
72
+ await fs.copyFile(destination, backup);
73
+ backups.push(entry.relative);
74
+ }
75
+ await fs.mkdir(path.dirname(destination), { recursive: true });
76
+ await fs.copyFile(path.join(templateRoot, entry.relative), destination);
77
+ written.push(entry.relative);
78
+ }
79
+ return { written, conflicts, backups };
80
+ }
81
+ /**
82
+ * Records what was *installed*, not what is currently on disk.
83
+ *
84
+ * This distinction is the whole mechanism. The manifest answers "is this file still the one
85
+ * we shipped, or did someone edit it", so it must hold the template's hash. Hashing the
86
+ * working copy instead records a local edit as though it were the installed version, and
87
+ * the next update then treats that edit as stale and overwrites it. The protection would
88
+ * work exactly once.
89
+ *
90
+ * A file left alone as a conflict keeps its previous entry, since that is still the last
91
+ * version actually installed there.
92
+ */
93
+ export async function writeManifest(target, version, adapters, templateRoot, previous, skipped) {
94
+ const managed = {};
95
+ const untouched = new Set(skipped);
96
+ for (const relative of await walk(templateRoot, wantedPaths(adapters))) {
97
+ if (!isManaged(relative))
98
+ continue;
99
+ if (untouched.has(relative)) {
100
+ const recorded = previous?.managed[relative];
101
+ if (recorded)
102
+ managed[relative] = recorded;
103
+ continue;
104
+ }
105
+ managed[relative] = hash(await fs.readFile(path.join(templateRoot, relative)));
106
+ }
107
+ const manifest = { schemaVersion: 1, version, adapters: [...adapters], managed };
108
+ const file = path.join(target, MANIFEST);
109
+ await fs.mkdir(path.dirname(file), { recursive: true });
110
+ await fs.writeFile(file, JSON.stringify(manifest, null, 2) + "\n");
111
+ }
112
+ /**
113
+ * Wires the hooks into Claude Code's settings.
114
+ *
115
+ * Only when there are no settings yet. A project's existing settings are its own, and
116
+ * merging into them blind is how a tool destroys configuration it did not write. When they
117
+ * exist, the caller is told to wire it manually and shown where the template is.
118
+ */
119
+ export async function wireHooks(target) {
120
+ const template = path.join(target, STATE_DIR, ".state", "settings-template.json");
121
+ const settings = path.join(target, ".claude", "settings.json");
122
+ if (!(await exists(template)))
123
+ return "no-template";
124
+ if (await exists(settings))
125
+ return "exists";
126
+ await fs.mkdir(path.dirname(settings), { recursive: true });
127
+ await fs.copyFile(template, settings);
128
+ return "written";
129
+ }
130
+ export async function readManifest(target) {
131
+ try {
132
+ return JSON.parse(await fs.readFile(path.join(target, MANIFEST), "utf8"));
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ function wantedPaths(adapters) {
139
+ const prefixes = new Set();
140
+ for (const adapter of adapters) {
141
+ for (const tree of ADAPTERS[adapter].trees)
142
+ prefixes.add(tree);
143
+ for (const entry of ADAPTERS[adapter].entry)
144
+ prefixes.add(entry);
145
+ }
146
+ prefixes.add(STATE_DIR);
147
+ return (relative) => [...prefixes].some((p) => relative === p || relative.startsWith(`${p}/`));
148
+ }
149
+ async function walk(dir, filter, prefix = "") {
150
+ let entries;
151
+ try {
152
+ entries = await fs.readdir(dir, { withFileTypes: true });
153
+ }
154
+ catch {
155
+ return [];
156
+ }
157
+ const files = [];
158
+ for (const entry of entries) {
159
+ if (entry.name === "node_modules" || entry.name === ".git")
160
+ continue;
161
+ const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
162
+ if (entry.isDirectory()) {
163
+ files.push(...(await walk(path.join(dir, entry.name), filter, relative)));
164
+ }
165
+ else if (!filter || filter(relative)) {
166
+ files.push(relative);
167
+ }
168
+ }
169
+ return files.sort();
170
+ }
@@ -0,0 +1,40 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const STATE_DIR = "religion";
4
+ /**
5
+ * Walks up from `start` to the nearest directory holding the state directory.
6
+ *
7
+ * Commands are routinely run from a subdirectory, and a tool that only works from the
8
+ * repository root is a tool people stop using.
9
+ */
10
+ export async function findProjectRoot(start) {
11
+ let current = path.resolve(start);
12
+ for (;;) {
13
+ if (await exists(path.join(current, STATE_DIR, "config.json")))
14
+ return current;
15
+ const parent = path.dirname(current);
16
+ if (parent === current)
17
+ return null;
18
+ current = parent;
19
+ }
20
+ }
21
+ export function statePath(root, ...parts) {
22
+ return path.join(root, STATE_DIR, ...parts);
23
+ }
24
+ export async function exists(target) {
25
+ try {
26
+ await fs.stat(target);
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ export async function readIfPresent(file) {
34
+ try {
35
+ return await fs.readFile(file, "utf8");
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Parsers for the state files.
3
+ *
4
+ * Each file has one parser and one shape. These are deliberately tolerant: a half-written
5
+ * plan or a hand-edited ledger should degrade into partial information, never an exception,
6
+ * because the commands that use this are how someone finds out what is wrong.
7
+ */
8
+ import crypto from "node:crypto";
9
+ import { readIfPresent, statePath } from "./paths.js";
10
+ const CHECKBOX = /^(\s*)- \[( |x|X)\]\s*(.*)$/;
11
+ export function parsePlan(source) {
12
+ if (!source)
13
+ return [];
14
+ // The shipped template explains the format with worked examples, which are checkboxes
15
+ // too. Reading the whole file counts those as real work. When a `## Plan` heading exists,
16
+ // only what follows it is the plan; the guidance above it is documentation.
17
+ const planSection = /^##\s+Plan\s*$/im.exec(source);
18
+ const body = planSection ? source.slice(planSection.index + planSection[0].length) : source;
19
+ const items = [];
20
+ for (const line of body.split(/\r?\n/)) {
21
+ const match = CHECKBOX.exec(line);
22
+ if (!match)
23
+ continue;
24
+ const body = (match[3] ?? "").trim();
25
+ // The template ships two placeholder items so the shape is visible. They are not work.
26
+ if (/^\d+\.\s+\*\*(Feature one|Feature two)\*\*/.test(body))
27
+ continue;
28
+ items.push({
29
+ number: /^([\dA-Za-z]+)\./.exec(body)?.[1] ?? null,
30
+ title: body.replace(/^[\dA-Za-z]+\.\s*/, "").replace(/\*\*/g, ""),
31
+ done: (match[2] ?? " ").toLowerCase() === "x",
32
+ depth: Math.floor((match[1] ?? "").length / 2)
33
+ });
34
+ }
35
+ return items;
36
+ }
37
+ export function parseWork(source) {
38
+ const empty = {
39
+ active: false,
40
+ title: null,
41
+ type: null,
42
+ status: null,
43
+ stepsDone: 0,
44
+ stepsTotal: 0,
45
+ nextStep: null
46
+ };
47
+ if (!source || /_Nothing in progress\./.test(source))
48
+ return empty;
49
+ const steps = [...source.matchAll(/^\s*- \[( |x|X)\]\s*(?:\*\*)?(.+?)(?:\*\*)?(?:\s+-\s|$)/gim)];
50
+ const done = steps.filter((s) => (s[1] ?? " ").toLowerCase() === "x").length;
51
+ return {
52
+ active: true,
53
+ title: /^#\s+(.+)$/m.exec(source)?.[1]?.trim() ?? null,
54
+ type: /^\*\*Type:\*\*\s*(.+)$/m.exec(source)?.[1]?.trim() ?? null,
55
+ status: /^\*\*Status:\*\*\s*(.+)$/m.exec(source)?.[1]?.trim() ?? null,
56
+ stepsDone: done,
57
+ stepsTotal: steps.length,
58
+ nextStep: steps.find((s) => (s[1] ?? " ").toLowerCase() !== "x")?.[2]?.trim() ?? null
59
+ };
60
+ }
61
+ export function parseFindings(source) {
62
+ if (!source)
63
+ return [];
64
+ return [...source.matchAll(/^###\s+(F-\d+)\s+\[(P[0-3])\]\s+(\w+)\s+-\s+(.+)$/gim)].map((m) => ({
65
+ id: m[1],
66
+ severity: m[2],
67
+ status: m[3],
68
+ title: (m[4] ?? "").trim()
69
+ }));
70
+ }
71
+ export function parseOpenQuestions(overview) {
72
+ if (!overview)
73
+ return [];
74
+ const section = /##\s+Open questions\s*\n([\s\S]*?)(?:\n##\s|$)/i.exec(overview);
75
+ if (!section?.[1])
76
+ return [];
77
+ return [...section[1].matchAll(/^-\s+\*\*(.+?)\*\*/gim)].map((m) => (m[1] ?? "").trim());
78
+ }
79
+ /** The stamp the overview carries, and the hash the current plans produce. */
80
+ export function overviewHash(projectPlan, buildPlan) {
81
+ return crypto.createHash("sha256").update(projectPlan).update(buildPlan).digest("hex").slice(0, 16);
82
+ }
83
+ export async function readProjectState(root) {
84
+ const [configRaw, projectPlan, buildPlan, work, findings, overview] = await Promise.all([
85
+ readIfPresent(statePath(root, "config.json")),
86
+ readIfPresent(statePath(root, "project-plan.md")),
87
+ readIfPresent(statePath(root, "build-plan.md")),
88
+ readIfPresent(statePath(root, "context", "current-work.md")),
89
+ readIfPresent(statePath(root, "context", "findings.md")),
90
+ readIfPresent(statePath(root, "context", "project-overview.md"))
91
+ ]);
92
+ let config = null;
93
+ let configValid = true;
94
+ if (configRaw !== null) {
95
+ try {
96
+ config = JSON.parse(configRaw);
97
+ }
98
+ catch {
99
+ configValid = false;
100
+ }
101
+ }
102
+ const stamped = overview ? /source-hash\s+([0-9a-f]{16})/.exec(overview)?.[1] ?? null : null;
103
+ const generated = overview !== null && !/_Not generated yet\./.test(overview);
104
+ return {
105
+ root,
106
+ config,
107
+ configValid,
108
+ plan: parsePlan(buildPlan),
109
+ work: parseWork(work),
110
+ findings: parseFindings(findings),
111
+ overviewPresent: generated,
112
+ overviewFresh: !generated || stamped === null
113
+ ? null
114
+ : stamped === overviewHash(projectPlan ?? "", buildPlan ?? ""),
115
+ openQuestions: parseOpenQuestions(overview)
116
+ };
117
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * What the project's state means, and the one action that follows from it.
3
+ *
4
+ * The next action is the point. A report that lists five things someone could do has not
5
+ * answered the question they asked, which is almost always "what now".
6
+ */
7
+ export function computeStatus(state) {
8
+ const leaves = state.plan.filter((item, index) => state.plan[index + 1] === undefined || state.plan[index + 1].depth <= item.depth);
9
+ const done = leaves.filter((i) => i.done).length;
10
+ const nextItem = leaves.find((i) => !i.done) ?? null;
11
+ const byStatus = {};
12
+ for (const finding of state.findings) {
13
+ byStatus[finding.status] = (byStatus[finding.status] ?? 0) + 1;
14
+ }
15
+ const blocking = state.findings
16
+ .filter((f) => (f.severity === "P0" || f.severity === "P1") && (f.status === "open" || f.status === "fixed"))
17
+ .map((f) => `${f.id} [${f.severity}] ${f.status} - ${f.title}`);
18
+ const warnings = [];
19
+ if (!state.configValid)
20
+ warnings.push("config.json does not parse; workflow commands will stop rather than guess");
21
+ if (state.overviewFresh === false)
22
+ warnings.push("the plans changed after the overview was generated");
23
+ if (state.openQuestions.length > 0) {
24
+ warnings.push(`open question(s) in the overview: ${state.openQuestions.join(", ")}`);
25
+ }
26
+ if (state.work.active && state.work.stepsTotal > 0 && state.work.stepsDone === state.work.stepsTotal &&
27
+ state.work.status !== "verified") {
28
+ warnings.push("every step is ticked but the work is not verified");
29
+ }
30
+ return {
31
+ schemaVersion: 1,
32
+ next: nextAction(state, nextItem?.title ?? null, blocking.length > 0),
33
+ plan: { done, total: leaves.length, nextItem: nextItem?.title ?? null },
34
+ work: state.work,
35
+ findings: { total: state.findings.length, blocking, byStatus },
36
+ overview: { present: state.overviewPresent, fresh: state.overviewFresh },
37
+ warnings
38
+ };
39
+ }
40
+ function nextAction(state, nextItem, blocked) {
41
+ if (!state.configValid)
42
+ return { command: "doctor", because: "configuration cannot be parsed" };
43
+ if (!state.overviewPresent)
44
+ return { command: "overview", because: "no source of truth has been generated" };
45
+ if (state.overviewFresh === false)
46
+ return { command: "overview", because: "the plans have changed since it was generated" };
47
+ if (state.work.active) {
48
+ if (blocked)
49
+ return { command: "audit", because: "a blocking finding must be re-reviewed before completion" };
50
+ if (state.work.stepsDone < state.work.stepsTotal) {
51
+ return { command: "implement", because: `step ${state.work.stepsDone + 1} of ${state.work.stepsTotal} is next` };
52
+ }
53
+ if (state.work.status !== "verified")
54
+ return { command: "check", because: "every step is built but nothing has proved it" };
55
+ return { command: "complete", because: "the work is built and verified" };
56
+ }
57
+ if (nextItem)
58
+ return { command: "feature", because: `next in the plan: ${nextItem}` };
59
+ if (state.plan.length === 0)
60
+ return { command: "overview", because: "the build plan has no items yet" };
61
+ return { command: "release", because: "the build plan is complete" };
62
+ }
63
+ export function renderStatus(status, cmd = "/") {
64
+ const out = [];
65
+ const pct = status.plan.total > 0 ? Math.round((status.plan.done / status.plan.total) * 100) : 0;
66
+ out.push(`Next: ${cmd}${status.next.command} (${status.next.because})`, "");
67
+ out.push(status.work.active
68
+ ? `Active: ${status.work.title ?? "untitled"} - step ${status.work.stepsDone}/${status.work.stepsTotal}` +
69
+ (status.work.status ? ` (${status.work.status})` : "")
70
+ : "Active: nothing in progress");
71
+ out.push(`Plan: ${status.plan.done}/${status.plan.total} complete (${pct}%)`);
72
+ if (status.findings.total === 0) {
73
+ out.push("Findings: none");
74
+ }
75
+ else {
76
+ const summary = Object.entries(status.findings.byStatus).map(([k, v]) => `${v} ${k}`).join(", ");
77
+ out.push(`Findings: ${summary}`);
78
+ for (const item of status.findings.blocking)
79
+ out.push(` BLOCKING ${item}`);
80
+ }
81
+ out.push(`Overview: ${!status.overview.present ? "not generated" : status.overview.fresh === false ? "stale" : "current"}`);
82
+ if (status.warnings.length > 0) {
83
+ out.push("", "Warnings:");
84
+ for (const warning of status.warnings)
85
+ out.push(` - ${warning}`);
86
+ }
87
+ return out.join("\n");
88
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "create-religion",
3
+ "version": "0.1.0",
4
+ "description": "A file-backed, spec-driven AI development workflow. Spec before code, small reviewed diffs, and project state in markdown so a cleared context costs nothing.",
5
+ "license": "MIT",
6
+ "author": "Nate",
7
+ "type": "module",
8
+ "homepage": "https://github.com/foo-stack/religion#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/foo-stack/religion.git",
12
+ "directory": "packages/create-religion"
13
+ },
14
+ "bugs": { "url": "https://github.com/foo-stack/religion/issues" },
15
+ "keywords": [
16
+ "ai",
17
+ "ai-coding",
18
+ "agent",
19
+ "claude",
20
+ "claude-code",
21
+ "codex",
22
+ "copilot",
23
+ "opencode",
24
+ "spec-driven-development",
25
+ "context-engineering",
26
+ "workflow",
27
+ "scaffold"
28
+ ],
29
+ "bin": {
30
+ "create-religion": "dist/bin/religion.js",
31
+ "religion": "dist/bin/religion.js"
32
+ },
33
+ "files": ["dist/", "template/", "README.md", "LICENSE"],
34
+ "engines": { "node": ">=20" },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "prepack": "npm --prefix ../.. run build && npm run build"
38
+ },
39
+ "publishConfig": { "access": "public" }
40
+ }
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: audit
3
+ summary: review the code itself across four lenses, recording findings in the ledger
4
+ description: "Read-only code review across four lenses (quality, security, performance, tests) and four scopes (current work, changed files, a path, or the whole project), fanning the lenses out to subagents that read and return findings while this skill performs the single write. Records each finding in religion/context/findings.md with a durable identifier, a severity from P0 to P3, and a status, where an open or fixed P0 or P1 blocks completion. Use when the user runs $audit, asks for a code, quality, security, performance, or test review, wants dead code or duplication found, or wants a repair re-reviewed before it closes."
5
+ ---
6
+
7
+ # audit - review the code, and record what you find
8
+
9
+ $check proves the work behaves as promised. This skill reviews the code itself.
10
+
11
+ It changes nothing except the findings ledger. It never edits source, installs anything,
12
+ commits, merges, or starts repair work.
13
+
14
+ ## Input
15
+
16
+ Scope and lens are separate, and may appear in either order: `$audit security current`,
17
+ `$audit src/auth tests`.
18
+
19
+ **Scope**, defaulting to `current` when work is active, else `changed` when the tree is
20
+ dirty, else `full`:
21
+
22
+ - `current` - the active spec, everything the work item has changed so far, and the code
23
+ around it that the change affects
24
+ - `changed` - staged, unstaged, and untracked source, plus nearby code
25
+ - `full` - all project-owned source, tests, and configuration, excluding dependencies,
26
+ generated files, build output, caches, vendored code, and minified assets
27
+ - a path - that area, plus the tests and callers needed to understand it
28
+
29
+ **Lens**, defaulting to all four:
30
+
31
+ - `quality` - duplication, dead code, unreachable paths, oversized modules, abstractions
32
+ that do not pay for themselves, inconsistency, drift from the project's standards
33
+ - `security` - missing authorization, client-controlled ownership, injection, unsafe
34
+ parsing, data exposure, secret handling, insecure defaults, trust-boundary mistakes
35
+ - `performance` - repeated queries and network calls, unnecessary rendering, blocking work
36
+ on hot paths, unbounded loops and collections, memory growth, oversized payloads,
37
+ missing pagination, unsafe concurrency
38
+ - `tests` - important logic with no coverage, weak assertions, tests that only mirror the
39
+ implementation, excessive mocking, shared state, order or time dependence, skipped or
40
+ focused tests, swallowed failures
41
+
42
+ State the scope and lens you chose before reviewing.
43
+
44
+ ## Step 1 - gather context
45
+
46
+ Read the project overview, the coding standards, the active spec, the existing findings
47
+ ledger (for identifiers and statuses), the configuration, and the relevant source.
48
+
49
+ For `current`, resolve the comparison base without touching the network: a base named by
50
+ the spec or project instructions, else the recorded default branch, else a local `main`
51
+ then `master`. Find the merge base and inspect the committed range through `HEAD`, then add
52
+ staged, unstaged, and untracked work. If no reliable base exists, say so and review the
53
+ spec plus local changes, without claiming committed work was covered.
54
+
55
+ For `full`, state the excluded paths before reviewing, so generated or third-party code
56
+ does not consume the review.
57
+
58
+ ## Step 2 - run the signals that exist
59
+
60
+ Use only commands the project already has. Do not install tools, and do not fetch.
61
+
62
+ Lint and typecheck when relevant; the test command for the tests lens or to validate a
63
+ suspected risk; the build when the lens needs compilation evidence. A useful command that
64
+ does not exist is a gap worth reporting, not a reason to add one.
65
+
66
+ ## Step 3 - review, one subagent per lens
67
+
68
+ Run each selected lens as its own subagent over the resolved scope. They read code and
69
+ **return** findings; they never write. This skill merges what comes back, assigns
70
+ identifiers, and performs the single write in Step 4. Parallel reviewers cannot race a
71
+ file that only one of them writes.
72
+
73
+ Give each subagent the scope, the file list, the standards, and its own lens only. A lens
74
+ that wanders into another lens produces duplicates that then have to be reconciled.
75
+
76
+ **The ledger never scopes the review.** Review the code fresh, then record what was found.
77
+ Working from the open findings as a checklist and verifying only those is the exact failure
78
+ the ledger exists to prevent: a repair can introduce a defect no existing entry points at.
79
+
80
+ Ground every finding in reachable code. Prefer a short list of real findings to a long list
81
+ of guesses, and do not report style differences that are merely different.
82
+
83
+ Do not broaden a focused pass because another lens looks interesting. If an obvious P0
84
+ appears outside the selected lens, record it as an out-of-lens critical risk, but do not
85
+ start searching that lens.
86
+
87
+ If a possible secret appears, never quote its value, paste the matching line, or include
88
+ raw output containing it. Report the redacted category, file, line, risk, and remediation.
89
+
90
+ ## Step 4 - write the ledger
91
+
92
+ `religion/context/findings.md` is the durable record. Chat does not survive a cleared
93
+ context; this does. Create it with a `# Findings` heading if it is missing.
94
+
95
+ One block per finding. The header line is the machine-readable part and keeps this exact
96
+ shape; the prose below it is for people.
97
+
98
+ ### F-03 [P0] open - Retained auth volumes carry the run label
99
+
100
+ **File:** ops/compose.yaml:86
101
+ **Found:** 2026-09-04 by audit (scope: current; lens: security)
102
+ **Why it matters:** ...
103
+ **Suggested fix:** ...
104
+ **Resolution:**
105
+
106
+ Identifiers are sequential within the ledger, one past the highest present, never reused
107
+ and never renumbered while their entries live there. Completion archives resolved entries
108
+ under a work-item prefix, and that prefixed form is the permanent reference.
109
+
110
+ | Status | Meaning | Blocks completion at P0/P1 |
111
+ | --- | --- | --- |
112
+ | `unverified` | Suspected, no confirming evidence yet | No |
113
+ | `open` | Confirmed, not yet repaired | Yes |
114
+ | `fixed` | Repaired, not yet re-reviewed | Yes |
115
+ | `closed` | Repaired and re-reviewed against the new code | No |
116
+ | `accepted` | Not fixing, by the user's explicit decision, reason recorded | No |
117
+ | `invalid` | Re-examination proved it wrong, evidence recorded | No |
118
+
119
+ After reviewing:
120
+
121
+ - Append each new confirmed finding as `open`.
122
+ - Record a lead worth tracking as `unverified`. It never gates anything.
123
+ - Update entries this pass re-examined, noting the evidence in **Resolution**.
124
+ - Move `fixed` to `closed` only when all three hold: this pass covered the finding's file,
125
+ re-examining the repaired code confirmed the defect is gone and nothing worse replaced
126
+ it, and the report names it as closed. An unrelated new problem in the same file gets its
127
+ own entry. Never close implicitly.
128
+ - Set `accepted` only on the user's explicit decision, with their reason. Never on their
129
+ behalf.
130
+ - Set `invalid` only when re-examination shows the finding was wrong, with that evidence.
131
+ It is a review verdict, never a shortcut past a gate.
132
+
133
+ `fixed` blocking completion is deliberate: a repair is done when a review has looked at the
134
+ result, not when the code changed.
135
+
136
+ ## Step 5 - report
137
+
138
+ Findings first, ordered by severity, using the identifiers just assigned.
139
+
140
+ Severity: **P0** data loss, a security break, or code that cannot ship. **P1** a likely bug,
141
+ a broken contract, a missing guard. **P2** a maintainability problem worth fixing before
142
+ the work closes. **P3** a small cleanup or follow-up.
143
+
144
+ P0 and P1 require a concrete code path, a violated contract or boundary, a failing command,
145
+ or reproducible behavior. Incomplete evidence goes under **Unverified risks** with what is
146
+ missing, never presented as a confirmed high-severity finding.
147
+
148
+ Then report: ledger changes by identifier; commands run and their results; the scope and
149
+ lens chosen; the commit range for `current`; the paths reviewed and excluded; checks that
150
+ could not run; and a suggested repair order.
151
+
152
+ A focused lens is not a broad review. Say what was not reviewed, and never imply the
153
+ omitted lenses passed. For `full`, say whether coverage was complete or partial.
154
+
155
+ ## Rules
156
+
157
+ - The ledger is the only file this skill writes.
158
+ - The ledger reports status; it never defines what the review looks at.
159
+ - Never reproduce a secret or sensitive value.
160
+ - Ground every finding in a file and line where possible.
161
+ - Recommend the smallest fix that removes the risk, not a rewrite.
162
+ - Respect the project's existing patterns over generic advice.
163
+ - The goal is code that is understandable, consistent, tested where it matters, and safe to
164
+ keep building on. Not perfection.
165
+
166
+ ## Formatting
167
+
168
+ Match the conventions in `religion/context/ai-interaction.md`: concise, scannable
169
+ markdown, lists for enumerations and tables for matrices rather than dense paragraphs.