create-religion 0.2.0 → 0.4.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.
- package/dist/bin/religion.js +37 -2
- package/dist/lib/doctor.js +5 -1
- package/dist/lib/install.js +51 -2
- package/dist/lib/merge.js +119 -0
- package/package.json +1 -1
- package/template/.agents/skills/audit/SKILL.md +1 -0
- package/template/.agents/skills/auto/SKILL.md +75 -8
- package/template/.agents/skills/capture/SKILL.md +65 -0
- package/template/.agents/skills/complete/SKILL.md +26 -3
- package/template/.agents/skills/doctor/SKILL.md +1 -0
- package/template/.agents/skills/feature/SKILL.md +16 -0
- package/template/.agents/skills/fix/SKILL.md +6 -1
- package/template/.agents/skills/implement/SKILL.md +50 -5
- package/template/.agents/skills/refactor/SKILL.md +6 -0
- package/template/.agents/skills/rollback/SKILL.md +35 -1
- package/template/.agents/skills/spike/SKILL.md +92 -0
- package/template/.agents/skills/status/SKILL.md +1 -0
- package/template/.agents/skills/try/SKILL.md +1 -0
- package/template/.claude/hooks/guard-rendered-output.mjs +33 -4
- package/template/.claude/hooks/guard-tier-one.mjs +37 -5
- package/template/.claude/hooks/scan-untrusted-input.mjs +114 -0
- package/template/.claude/skills/audit/SKILL.md +1 -0
- package/template/.claude/skills/auto/SKILL.md +75 -8
- package/template/.claude/skills/capture/SKILL.md +65 -0
- package/template/.claude/skills/complete/SKILL.md +26 -3
- package/template/.claude/skills/doctor/SKILL.md +1 -0
- package/template/.claude/skills/feature/SKILL.md +16 -0
- package/template/.claude/skills/fix/SKILL.md +6 -1
- package/template/.claude/skills/implement/SKILL.md +50 -5
- package/template/.claude/skills/refactor/SKILL.md +6 -0
- package/template/.claude/skills/rollback/SKILL.md +35 -1
- package/template/.claude/skills/spike/SKILL.md +92 -0
- package/template/.claude/skills/status/SKILL.md +1 -0
- package/template/.claude/skills/try/SKILL.md +1 -0
- package/template/AGENTS.md +29 -2
- package/template/CLAUDE.md +26 -2
- package/template/religion/.state/settings-template.json +29 -3
- package/template/religion/config.json +8 -2
- package/template/religion/context/ai-interaction.md +15 -2
- package/template/religion/context/inbox.md +10 -0
- package/template/religion/context/untrusted-input.md +57 -0
- package/template/religion/history/spikes/README.md +9 -0
package/dist/bin/religion.js
CHANGED
|
@@ -86,16 +86,33 @@ async function runInstall(options) {
|
|
|
86
86
|
create: plan.filter((p) => p.action === "create").length,
|
|
87
87
|
update: plan.filter((p) => p.action === "update").length,
|
|
88
88
|
conflict: plan.filter((p) => p.action === "conflict").length,
|
|
89
|
-
kept: plan.filter((p) => p.action === "seed-skip").length
|
|
89
|
+
kept: plan.filter((p) => p.action === "seed-skip").length,
|
|
90
|
+
merge: plan.filter((p) => p.action === "merge").length,
|
|
91
|
+
remerge: plan.filter((p) => p.action === "remerge").length
|
|
90
92
|
};
|
|
91
93
|
console.log(`\nAdapters: ${adapters.map((a) => ADAPTERS[a].label).join(", ")}`);
|
|
92
94
|
console.log(` create ${counts.create}`);
|
|
93
95
|
console.log(` update ${counts.update}`);
|
|
94
96
|
console.log(` keep ${counts.kept} (your files, never overwritten)`);
|
|
95
97
|
console.log(` conflict ${counts.conflict}`);
|
|
98
|
+
if (counts.merge > 0)
|
|
99
|
+
console.log(` merge ${counts.merge} (your file, awaiting a decision)`);
|
|
100
|
+
if (counts.remerge > 0)
|
|
101
|
+
console.log(` merged ${counts.remerge} (your sections kept)`);
|
|
96
102
|
for (const entry of plan.filter((p) => p.action === "conflict")) {
|
|
97
103
|
console.log(` ${entry.relative}`);
|
|
98
104
|
}
|
|
105
|
+
const mergeable = plan.filter((p) => p.action === "merge").map((p) => p.relative);
|
|
106
|
+
let merge = false;
|
|
107
|
+
if (mergeable.length > 0) {
|
|
108
|
+
console.log(`\nThese already exist and were written by you:`);
|
|
109
|
+
for (const relative of mergeable)
|
|
110
|
+
console.log(` ${relative}`);
|
|
111
|
+
console.log("\nReligion can append its own sections to them inside markers, keeping everything" +
|
|
112
|
+
"\nyou wrote exactly where it is. Later updates then replace only what is between" +
|
|
113
|
+
"\nthose markers. The originals are backed up either way.");
|
|
114
|
+
merge = await confirmMerge(options.yes);
|
|
115
|
+
}
|
|
99
116
|
if (options.dryRun) {
|
|
100
117
|
console.log("\nDry run. Nothing written.");
|
|
101
118
|
return;
|
|
@@ -103,9 +120,12 @@ async function runInstall(options) {
|
|
|
103
120
|
if (plan.length === 0) {
|
|
104
121
|
throw new Error(`No template files found under ${templateRoot}. Nothing would be installed, which is never correct.`);
|
|
105
122
|
}
|
|
106
|
-
const result = await applyInstall(templateRoot, target, plan, { force: options.force });
|
|
123
|
+
const result = await applyInstall(templateRoot, target, plan, { force: options.force, merge });
|
|
107
124
|
await writeManifest(target, await version(), adapters, templateRoot, previous, result.conflicts);
|
|
108
125
|
console.log(`\nWrote ${result.written.length} file(s).`);
|
|
126
|
+
if (result.merged.length > 0) {
|
|
127
|
+
console.log(`Merged ${result.merged.length} entry file(s), keeping what you wrote.`);
|
|
128
|
+
}
|
|
109
129
|
if (result.backups.length > 0)
|
|
110
130
|
console.log(`Backed up ${result.backups.length} conflicting file(s).`);
|
|
111
131
|
if (result.conflicts.length > 0) {
|
|
@@ -126,6 +146,21 @@ async function runInstall(options) {
|
|
|
126
146
|
console.log("\nNext: run the setup skill in your AI tool, then fill in the two plans.");
|
|
127
147
|
}
|
|
128
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Ask before rewriting a file the user wrote.
|
|
151
|
+
*
|
|
152
|
+
* Defaults to yes because the merge is additive and reversible: nothing of theirs moves and
|
|
153
|
+
* the original is backed up first. Declining leaves the file untouched and reported as a
|
|
154
|
+
* conflict, which is the old behaviour.
|
|
155
|
+
*/
|
|
156
|
+
async function confirmMerge(assumeYes) {
|
|
157
|
+
if (assumeYes || !process.stdin.isTTY)
|
|
158
|
+
return assumeYes;
|
|
159
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
160
|
+
const answer = (await rl.question("\nMerge them? [Y/n] ")).trim().toLowerCase();
|
|
161
|
+
rl.close();
|
|
162
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
163
|
+
}
|
|
129
164
|
async function chooseAdapters(assumeYes) {
|
|
130
165
|
const all = Object.keys(ADAPTERS);
|
|
131
166
|
if (assumeYes || !process.stdin.isTTY)
|
package/dist/lib/doctor.js
CHANGED
|
@@ -14,17 +14,21 @@ const REQUIRED = [
|
|
|
14
14
|
"build-plan.md",
|
|
15
15
|
"context/project-overview.md",
|
|
16
16
|
"context/coding-standards.md",
|
|
17
|
+
"context/untrusted-input.md",
|
|
17
18
|
"context/ai-interaction.md",
|
|
18
19
|
"context/current-work.md",
|
|
19
20
|
"context/findings.md",
|
|
21
|
+
"context/inbox.md",
|
|
20
22
|
"history/features",
|
|
21
23
|
"history/fixes",
|
|
22
24
|
"history/rollbacks",
|
|
25
|
+
"history/refactors",
|
|
26
|
+
"history/spikes",
|
|
23
27
|
"reference"
|
|
24
28
|
];
|
|
25
29
|
const ALLOWED = {
|
|
26
30
|
"workflow.stepReview": ["every", "item"],
|
|
27
|
-
"git.mode": ["trunk", "branch-per-item"],
|
|
31
|
+
"git.mode": ["trunk", "branch-per-item", "pull-request"],
|
|
28
32
|
"git.checkpoints": ["none", "every-step", "squash"],
|
|
29
33
|
"verification.logicTests": ["when-configured", "required"],
|
|
30
34
|
"verification.uiEvidence": ["when-available", "required"],
|
package/dist/lib/install.js
CHANGED
|
@@ -10,6 +10,7 @@ import crypto from "node:crypto";
|
|
|
10
10
|
import fs from "node:fs/promises";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { exists, STATE_DIR } from "./paths.js";
|
|
13
|
+
import { hasDamagedMarkers, hasMarkers, replaceManagedBlock, spliceEntry } from "./merge.js";
|
|
13
14
|
export const ADAPTERS = {
|
|
14
15
|
claude: { label: "Claude Code", trees: [".claude/skills", ".claude/hooks"], entry: ["CLAUDE.md"] },
|
|
15
16
|
codex: { label: "Codex", trees: [".agents/skills"], entry: ["AGENTS.md"] },
|
|
@@ -24,8 +25,13 @@ export function hash(contents) {
|
|
|
24
25
|
export function isManaged(relative) {
|
|
25
26
|
return !relative.startsWith(`${STATE_DIR}/`);
|
|
26
27
|
}
|
|
28
|
+
/** The entry files for the chosen adapters, which are the only files merging applies to. */
|
|
29
|
+
function entryFiles(adapters) {
|
|
30
|
+
return new Set(adapters.flatMap((a) => ADAPTERS[a].entry));
|
|
31
|
+
}
|
|
27
32
|
export async function planInstall(templateRoot, target, adapters, previous) {
|
|
28
33
|
const wanted = wantedPaths(adapters);
|
|
34
|
+
const entries = entryFiles(adapters);
|
|
29
35
|
const plan = [];
|
|
30
36
|
for (const relative of await walk(templateRoot)) {
|
|
31
37
|
if (!wanted(relative))
|
|
@@ -50,7 +56,22 @@ export async function planInstall(templateRoot, target, adapters, previous) {
|
|
|
50
56
|
// installed. Anything else is a local edit, and overwriting it silently is the failure
|
|
51
57
|
// this manifest exists to prevent.
|
|
52
58
|
const recorded = previous?.managed[relative];
|
|
53
|
-
|
|
59
|
+
if (recorded && recorded === hash(current)) {
|
|
60
|
+
plan.push({ relative, action: "update" });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// An entry file is the one place where "differs from the template" is the normal case
|
|
64
|
+
// rather than a problem: a repository that already tells agents how to work has one, and
|
|
65
|
+
// it is usually the most carefully written file in the project.
|
|
66
|
+
if (entries.has(relative)) {
|
|
67
|
+
const text = current.toString("utf8");
|
|
68
|
+
// A damaged marker pair is a conflict, never a fresh merge: splicing a second block
|
|
69
|
+
// into a file that already has one is worse than the damage.
|
|
70
|
+
const action = hasMarkers(text) ? "remerge" : hasDamagedMarkers(text) ? "conflict" : "merge";
|
|
71
|
+
plan.push({ relative, action });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
plan.push({ relative, action: "conflict" });
|
|
54
75
|
}
|
|
55
76
|
return plan;
|
|
56
77
|
}
|
|
@@ -58,6 +79,7 @@ export async function applyInstall(templateRoot, target, plan, options) {
|
|
|
58
79
|
const written = [];
|
|
59
80
|
const conflicts = [];
|
|
60
81
|
const backups = [];
|
|
82
|
+
const merged = [];
|
|
61
83
|
for (const entry of plan) {
|
|
62
84
|
if (entry.action === "unchanged" || entry.action === "seed-skip")
|
|
63
85
|
continue;
|
|
@@ -65,6 +87,33 @@ export async function applyInstall(templateRoot, target, plan, options) {
|
|
|
65
87
|
conflicts.push(entry.relative);
|
|
66
88
|
continue;
|
|
67
89
|
}
|
|
90
|
+
// Replacing only what is between the markers is the whole point of having merged: the
|
|
91
|
+
// user's own prose sits outside them and is never read, let alone rewritten.
|
|
92
|
+
if (entry.action === "remerge" || (entry.action === "merge" && options.merge)) {
|
|
93
|
+
const destination = path.join(target, entry.relative);
|
|
94
|
+
const template = await fs.readFile(path.join(templateRoot, entry.relative), "utf8");
|
|
95
|
+
const current = await fs.readFile(destination, "utf8");
|
|
96
|
+
if (entry.action === "merge") {
|
|
97
|
+
const backup = path.join(target, STATE_DIR, ".state", "backups", entry.relative);
|
|
98
|
+
await fs.mkdir(path.dirname(backup), { recursive: true });
|
|
99
|
+
await fs.copyFile(destination, backup);
|
|
100
|
+
backups.push(entry.relative);
|
|
101
|
+
}
|
|
102
|
+
const next = entry.action === "merge" ? spliceEntry(current, template) : replaceManagedBlock(current, template);
|
|
103
|
+
if (next === null) {
|
|
104
|
+
// Markers went missing between planning and writing, or were edited into an order
|
|
105
|
+
// this cannot reason about. Leaving it alone is the only safe answer.
|
|
106
|
+
conflicts.push(entry.relative);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
await fs.writeFile(destination, next, "utf8");
|
|
110
|
+
merged.push(entry.relative);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (entry.action === "merge") {
|
|
114
|
+
conflicts.push(entry.relative);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
68
117
|
const destination = path.join(target, entry.relative);
|
|
69
118
|
if (entry.action === "conflict") {
|
|
70
119
|
const backup = path.join(target, STATE_DIR, ".state", "backups", entry.relative);
|
|
@@ -76,7 +125,7 @@ export async function applyInstall(templateRoot, target, plan, options) {
|
|
|
76
125
|
await fs.copyFile(path.join(templateRoot, entry.relative), destination);
|
|
77
126
|
written.push(entry.relative);
|
|
78
127
|
}
|
|
79
|
-
return { written, conflicts, backups };
|
|
128
|
+
return { written, conflicts, backups, merged };
|
|
80
129
|
}
|
|
81
130
|
/**
|
|
82
131
|
* Records what was *installed*, not what is currently on disk.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merging Religion's entry-file sections into a file the project already had.
|
|
3
|
+
*
|
|
4
|
+
* A repository that already carries agent instructions is the normal case, not the odd one,
|
|
5
|
+
* and the previous behaviour left those installs with the workflow on disk but nothing
|
|
6
|
+
* telling the agent it existed. Overwriting was never an option: those files are often the
|
|
7
|
+
* most carefully written thing in the repository.
|
|
8
|
+
*
|
|
9
|
+
* So the user's file stays exactly where it is and Religion's sections are appended inside
|
|
10
|
+
* markers. The markers are what make the result maintainable rather than a one-time paste:
|
|
11
|
+
* a later update replaces what is between them and never reads what is outside.
|
|
12
|
+
*
|
|
13
|
+
* Two sections are deliberately left outside the block. "What this is" is what the user's own
|
|
14
|
+
* file already says, and Commands is filled in by the setup skill with the project's real
|
|
15
|
+
* commands. Both would be destroyed by an update that replaced them.
|
|
16
|
+
*/
|
|
17
|
+
export const MANAGED_START = "<!-- religion:managed:start -->";
|
|
18
|
+
export const MANAGED_END = "<!-- religion:managed:end -->";
|
|
19
|
+
/** Sections the user owns. They never enter the managed block. */
|
|
20
|
+
const USER_OWNED = new Set(["What this is", "Commands"]);
|
|
21
|
+
const BLOCK_NOTE = "Everything between these markers is generated by Religion and replaced on update.\n" +
|
|
22
|
+
"Edit the files it points at, or the sections outside this block, rather than editing here.";
|
|
23
|
+
/** Split a rendered entry file into its leading lines and its `## ` sections. */
|
|
24
|
+
function parse(entry) {
|
|
25
|
+
const lines = entry.split(/\r?\n/);
|
|
26
|
+
const lead = [];
|
|
27
|
+
const sections = [];
|
|
28
|
+
let current = null;
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const heading = /^## (.+)$/.exec(line);
|
|
31
|
+
if (heading?.[1]) {
|
|
32
|
+
current = { heading: heading[1].trim(), body: "" };
|
|
33
|
+
sections.push(current);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (current)
|
|
37
|
+
current.body += line + "\n";
|
|
38
|
+
else
|
|
39
|
+
lead.push(line);
|
|
40
|
+
}
|
|
41
|
+
return { lead, sections };
|
|
42
|
+
}
|
|
43
|
+
function render(sections) {
|
|
44
|
+
return sections.map((s) => `## ${s.heading}\n${s.body.replace(/\n+$/, "")}\n`).join("\n");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The part of a rendered entry file that Religion owns.
|
|
48
|
+
*
|
|
49
|
+
* Includes the import lines, which is what makes a merged install keep receiving new context
|
|
50
|
+
* files: leaving them outside the block would freeze the set of loaded files at whatever
|
|
51
|
+
* shipped on the day of the merge.
|
|
52
|
+
*/
|
|
53
|
+
export function managedBlock(templateEntry) {
|
|
54
|
+
const { lead, sections } = parse(templateEntry);
|
|
55
|
+
const imports = lead.filter((line) => line.startsWith("@")).join("\n");
|
|
56
|
+
const owned = sections.filter((s) => !USER_OWNED.has(s.heading));
|
|
57
|
+
return [
|
|
58
|
+
MANAGED_START,
|
|
59
|
+
"",
|
|
60
|
+
BLOCK_NOTE,
|
|
61
|
+
"",
|
|
62
|
+
...(imports ? [imports, ""] : []),
|
|
63
|
+
render(owned).replace(/\n+$/, ""),
|
|
64
|
+
"",
|
|
65
|
+
MANAGED_END
|
|
66
|
+
].join("\n");
|
|
67
|
+
}
|
|
68
|
+
/** The sections the user owns, taken from the template as a starting point. */
|
|
69
|
+
export function userOwnedSections(templateEntry) {
|
|
70
|
+
const { sections } = parse(templateEntry);
|
|
71
|
+
const owned = sections.filter((s) => USER_OWNED.has(s.heading) && s.heading !== "What this is");
|
|
72
|
+
return render(owned).replace(/\n+$/, "");
|
|
73
|
+
}
|
|
74
|
+
export function hasMarkers(contents) {
|
|
75
|
+
return contents.includes(MANAGED_START) && contents.includes(MANAGED_END);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A file carrying one marker but not both, or them in the wrong order.
|
|
79
|
+
*
|
|
80
|
+
* This must be told apart from a file that was never merged. Treating it as unmerged splices
|
|
81
|
+
* a second block into a file that already had one, which is a worse state than the damage it
|
|
82
|
+
* was trying to repair.
|
|
83
|
+
*/
|
|
84
|
+
export function hasDamagedMarkers(contents) {
|
|
85
|
+
const start = contents.indexOf(MANAGED_START);
|
|
86
|
+
const end = contents.indexOf(MANAGED_END);
|
|
87
|
+
if (start < 0 && end < 0)
|
|
88
|
+
return false;
|
|
89
|
+
return start < 0 || end < 0 || end < start;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the merged file: their content, then Religion's block, then the sections they own.
|
|
93
|
+
*
|
|
94
|
+
* Nothing of theirs moves. That is the property that makes this safe to accept without
|
|
95
|
+
* reading the diff, and the reason the block is appended rather than woven in.
|
|
96
|
+
*/
|
|
97
|
+
export function spliceEntry(existing, templateEntry) {
|
|
98
|
+
const body = existing.replace(/\n+$/, "");
|
|
99
|
+
const commands = userOwnedSections(templateEntry);
|
|
100
|
+
const parts = [body, "", managedBlock(templateEntry)];
|
|
101
|
+
// Only add Commands when their file has no section by that name; theirs is more accurate
|
|
102
|
+
// than the placeholder, and setup will find either one.
|
|
103
|
+
if (commands && !/^## Commands\s*$/m.test(existing))
|
|
104
|
+
parts.push("", commands);
|
|
105
|
+
return parts.join("\n") + "\n";
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Replace the managed block in a merged file, leaving everything outside it untouched.
|
|
109
|
+
*
|
|
110
|
+
* Returns null when the markers are missing or out of order, which is treated as a conflict
|
|
111
|
+
* rather than repaired: a file whose markers were edited is one whose owner had a reason.
|
|
112
|
+
*/
|
|
113
|
+
export function replaceManagedBlock(existing, templateEntry) {
|
|
114
|
+
const start = existing.indexOf(MANAGED_START);
|
|
115
|
+
const end = existing.indexOf(MANAGED_END);
|
|
116
|
+
if (start < 0 || end < 0 || end < start)
|
|
117
|
+
return null;
|
|
118
|
+
return existing.slice(0, start) + managedBlock(templateEntry) + existing.slice(end + MANAGED_END.length);
|
|
119
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-religion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nate",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: audit
|
|
3
3
|
summary: review the code itself across four lenses, recording findings in the ledger
|
|
4
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
|
+
allowed-tools: Read, Grep, Glob, Bash, Agent, Write, Edit
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# audit - review the code, and record what you find
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: auto
|
|
3
3
|
summary: run the loop unattended, within explicit bounds
|
|
4
|
-
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Use only when the user runs $auto or explicitly asks for an unattended run."
|
|
4
|
+
description: "Run the reviewed loop without stopping between steps, within bounds you set. With no argument it takes one work item and stops before completing it; `all` runs the remaining build plan; a number runs that many items. Specs, implements, verifies, applies the configured gates, repairs confirmed high-severity findings, and completes each item. Its invocation grants committing, dependency installs, and remote reads for the run, and grants nothing else: it still stops to ask before any merge. Under git.mode pull-request it lands every item on one shared integration branch and ends by opening a single aggregate review packet it never merges, after stating that whole remote budget up front and being told yes. Use only when the user runs $auto or explicitly asks for an unattended run."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# auto - the same loop, without the pauses
|
|
@@ -29,14 +29,32 @@ produced.
|
|
|
29
29
|
Running this authorizes, for this run only: committing, installing dependencies, and
|
|
30
30
|
network reads. That is the second authority tier, and the invocation is the approval.
|
|
31
31
|
|
|
32
|
-
It grants **nothing** in the first tier. Merging, pushing
|
|
33
|
-
data, and rewriting history each still stop and ask, every
|
|
34
|
-
remain.
|
|
32
|
+
It grants **nothing** in the first tier. Merging into the default branch, pushing to it,
|
|
33
|
+
deploying, publishing, deleting data, and rewriting history each still stop and ask, every
|
|
34
|
+
time, no matter how many items remain.
|
|
35
35
|
|
|
36
36
|
Under `git.mode: trunk` there is no merge, so a run completes items and keeps going. Under
|
|
37
37
|
`branch-per-item` it stops once per item to ask. That is not a flaw in the mode; landing
|
|
38
38
|
work on a shared branch is a decision, and it stays one.
|
|
39
39
|
|
|
40
|
+
Under `pull-request` nothing lands on the default branch, so there is no merge to stop for.
|
|
41
|
+
What the run does need is the one grant the authority rules allow to be given ahead of time,
|
|
42
|
+
and it is asked for **before any work starts**, in full:
|
|
43
|
+
|
|
44
|
+
This run will, without asking again:
|
|
45
|
+
create and push the integration branch <integrationBranchPrefix><date>
|
|
46
|
+
create and push one branch per item
|
|
47
|
+
open a pull request from each item into the integration branch
|
|
48
|
+
merge those pull requests into the integration branch
|
|
49
|
+
open one aggregate pull request into <default branch>
|
|
50
|
+
|
|
51
|
+
It will not merge or push <default branch>, and it will not merge
|
|
52
|
+
the aggregate pull request.
|
|
53
|
+
|
|
54
|
+
Print that, naming the real branches, and wait. **The enumeration is the approval, and it is
|
|
55
|
+
the whole remote budget.** A general yes to running is not it, and neither is silence. If the
|
|
56
|
+
answer is no, the run does not start.
|
|
57
|
+
|
|
40
58
|
## Step 1 - preflight once
|
|
41
59
|
|
|
42
60
|
Before the first item:
|
|
@@ -48,6 +66,21 @@ Before the first item:
|
|
|
48
66
|
than one that never began.
|
|
49
67
|
- The working tree is clean, or the only dirt belongs to work being resumed.
|
|
50
68
|
- The plan has unchecked items.
|
|
69
|
+
- Under `pull-request`, the repository can host one: a remote exists and the host command
|
|
70
|
+
works. A repository that cannot is a stop, not a reason to fall back to local merges. The
|
|
71
|
+
mode exists to produce review gates, and silently producing a different history instead
|
|
72
|
+
would be worse than not running.
|
|
73
|
+
|
|
74
|
+
Record the default branch's current commit. It bounds what the aggregate pull request will
|
|
75
|
+
carry, and the final report refers to it.
|
|
76
|
+
|
|
77
|
+
Under `pull-request`, then establish the run's integration branch, named from
|
|
78
|
+
`git.integrationBranchPrefix` and the date. Create it from that recorded commit and push it.
|
|
79
|
+
Every item branches off it and lands back into it, which is what lets item three build on
|
|
80
|
+
items one and two. On a resume, reuse the branch of that name rather than opening a second;
|
|
81
|
+
if one exists that does not descend from the recorded commit, stop, because the run's base
|
|
82
|
+
moved and that needs a person. Never rebase, reset, or force-push it: it is append-only for
|
|
83
|
+
the run.
|
|
51
84
|
|
|
52
85
|
State the bounds, the gates in force, and how many items are in range. Then start.
|
|
53
86
|
|
|
@@ -66,9 +99,39 @@ For every item, in plan order:
|
|
|
66
99
|
the finding recorded rather than trying variations.
|
|
67
100
|
5. **Complete it**, following $complete, which archives, ticks the plan, prunes the
|
|
68
101
|
ledger, and commits. Ask before any merge.
|
|
69
|
-
6. **
|
|
102
|
+
6. **Land it**, under `pull-request` only: push the item's branch, open its pull request
|
|
103
|
+
**into the integration branch** rather than the default branch, and merge it there once
|
|
104
|
+
its checks are green. Merge only a pull request this run opened, only into the integration
|
|
105
|
+
branch. A failing or conflicted one is a stop, never something to force. Then delete the
|
|
106
|
+
item branch, confirming the work commit is in the integration branch first, since a
|
|
107
|
+
squash leaves no merge parent for `git branch -d` to find. Return to the integration
|
|
108
|
+
branch clean before the next item.
|
|
109
|
+
7. **Report one line** and move on.
|
|
110
|
+
|
|
111
|
+
An item counts against `auto.maxItems` once it has landed, not once it is built.
|
|
112
|
+
|
|
113
|
+
## Step 3 - close the run
|
|
114
|
+
|
|
115
|
+
Under `trunk` and `branch-per-item` there is nothing to close: the items are done and the
|
|
116
|
+
run reports.
|
|
117
|
+
|
|
118
|
+
Under `pull-request`, open one aggregate pull request from the integration branch into the
|
|
119
|
+
default branch. Title it for the run rather than for any one item. Its body is the run's
|
|
120
|
+
review packet, and it is the artefact a person actually reads:
|
|
121
|
+
|
|
122
|
+
- one section per completed item, in plan order, naming what it delivered, its merged pull
|
|
123
|
+
request, and the checks that proved it
|
|
124
|
+
- findings still open or fixed, by identifier, and anything deliberately left out
|
|
125
|
+
- what the run did not reach, and why it stopped
|
|
126
|
+
|
|
127
|
+
Report the URL and stop. **Never merge it.** It is the review gate the mode exists to reach.
|
|
128
|
+
|
|
129
|
+
Open it even when the run stopped early with completed items behind it, saying plainly in
|
|
130
|
+
the body that it stopped short: work that passed every gate deserves to be reviewable, and a
|
|
131
|
+
packet describing three of six items beats an integration branch nobody knows about. When
|
|
132
|
+
the run completed no items at all, open nothing and say so.
|
|
70
133
|
|
|
71
|
-
## Step
|
|
134
|
+
## Step 4 - stop
|
|
72
135
|
|
|
73
136
|
Stop immediately, keeping all state intact, when any of these happens:
|
|
74
137
|
|
|
@@ -84,9 +147,11 @@ Stop immediately, keeping all state intact, when any of these happens:
|
|
|
84
147
|
A stopped run leaves the branch, the spec, and the ticked steps exactly as they are.
|
|
85
148
|
`$auto resume` picks up from there, because the state is in files rather than in the run.
|
|
86
149
|
|
|
87
|
-
## Step
|
|
150
|
+
## Step 5 - report
|
|
88
151
|
|
|
89
152
|
- items completed, by name, and what each delivered
|
|
153
|
+
- under `pull-request`, the recorded starting commit, the integration branch and its head,
|
|
154
|
+
each item's merged pull request, and the aggregate pull request URL or why none was opened
|
|
90
155
|
- what stopped the run, exactly, and the state it stopped in
|
|
91
156
|
- findings raised, repaired, and still open
|
|
92
157
|
- checks run, and any that could not run
|
|
@@ -100,7 +165,9 @@ the part it got wrong.
|
|
|
100
165
|
|
|
101
166
|
- Explicit invocation only.
|
|
102
167
|
- The gates are the same gates. Unattended is not a reason to lower one.
|
|
103
|
-
- Never
|
|
168
|
+
- Never write to the default branch, in any mode. Never merge the aggregate pull request,
|
|
169
|
+
and never force-push anything, the integration branch included.
|
|
170
|
+
- Never deploy, publish, or delete without asking, however long the queue is.
|
|
104
171
|
- Never widen an item's scope to make its gate pass.
|
|
105
172
|
- Stop on ambiguity. There is nobody to ask mid-run, so a guess compounds silently.
|
|
106
173
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: capture
|
|
3
|
+
summary: note something for later without breaking the one thing in progress
|
|
4
|
+
description: "Write a one-line note into religion/context/inbox.md and carry on, so noticing something worth doing does not cost the work item in progress. Takes the note as an argument, records it dated with where it came from, and stops. It never specs, never plans, and never starts work: $fix and $feature read the inbox when choosing what to build next. Use when the user runs $capture, says to note, park, remember or jot something down, mentions something worth doing later or some other time, or spots a problem mid-build that is not the thing being built."
|
|
5
|
+
allowed-tools: Read, Grep, Glob, Bash, Write, Edit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# capture - note it, keep building
|
|
9
|
+
|
|
10
|
+
This exists because of a rule elsewhere: one work item at a time. That rule is what makes a
|
|
11
|
+
cleared context cheap, and it is also what makes noticing something mid-build awkward, since
|
|
12
|
+
there is nowhere to put it. Without an answer, a good observation either derails the work or
|
|
13
|
+
is lost.
|
|
14
|
+
|
|
15
|
+
The answer is a line in a file and no ceremony at all.
|
|
16
|
+
|
|
17
|
+
## Input
|
|
18
|
+
|
|
19
|
+
The note, as an argument:
|
|
20
|
+
|
|
21
|
+
$capture "the rate limiter drops the first request after an idle period"
|
|
22
|
+
|
|
23
|
+
With no argument, ask what to note. Do not guess from the conversation, and do not sweep up
|
|
24
|
+
everything recently mentioned: a note nobody chose is a note nobody wants.
|
|
25
|
+
|
|
26
|
+
## What it does
|
|
27
|
+
|
|
28
|
+
Append one line to `religion/context/inbox.md` with the date and, when there is one, the file
|
|
29
|
+
or area it concerns. Then stop, and return to whatever was happening.
|
|
30
|
+
|
|
31
|
+
That is the whole skill. It is deliberately small: anything more, and using it costs more
|
|
32
|
+
attention than the thought was worth, which is the failure mode that makes people stop
|
|
33
|
+
noting things.
|
|
34
|
+
|
|
35
|
+
## What it does not do
|
|
36
|
+
|
|
37
|
+
- **It does not spec.** A note is not a work item. $fix turns one into a spec when you
|
|
38
|
+
decide to.
|
|
39
|
+
- **It does not start work**, touch the active spec, or tick anything.
|
|
40
|
+
- **It does not record code defects found by review.** Those are findings, they carry
|
|
41
|
+
severities and block completion, and $audit owns them. A note is something you
|
|
42
|
+
thought of; a finding is something a review proved.
|
|
43
|
+
- **It does not judge.** Write down what was said. Triage happens when the note is read, not
|
|
44
|
+
when it is written.
|
|
45
|
+
|
|
46
|
+
## How a note leaves the inbox
|
|
47
|
+
|
|
48
|
+
$fix and $feature read the inbox when they start, and offer what is in it
|
|
49
|
+
alongside the build plan. Choosing one specs it and removes the line; that is the only way a
|
|
50
|
+
note leaves, other than you deleting it.
|
|
51
|
+
|
|
52
|
+
An inbox nobody reads is a list of things nobody did, so the reading is the point rather
|
|
53
|
+
than the writing.
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
- One line per note. If it needs a paragraph, it needs a spec, and that is $fix.
|
|
58
|
+
- Never interrupt the work in progress to discuss the note.
|
|
59
|
+
- Never write to the findings ledger.
|
|
60
|
+
- Never let the inbox become a plan. The build plan is the plan.
|
|
61
|
+
|
|
62
|
+
## Formatting
|
|
63
|
+
|
|
64
|
+
Match the conventions in `religion/context/ai-interaction.md`: concise, scannable markdown,
|
|
65
|
+
lists for enumerations and tables for matrices rather than dense paragraphs.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: complete
|
|
3
3
|
summary: run the final gate, archive the work, commit it, and close it out
|
|
4
|
-
description: "Close out a finished feature, fix, rollback, or refactor campaign. Runs the full Verify command and the final safety pass, applies the configured quality gates, archives the spec under religion/history/, ticks the build plan, prunes resolved findings into the archive, resets the active spec, and makes the work commit. Under branch-per-item it squash-merges only with explicit approval, then asks separately before pushing. Ends with a manual walkthrough of what landed. Use when the user runs $complete, or asks to finish, wrap up, close out, or merge the current work item once it is built and reviewed."
|
|
4
|
+
description: "Close out a finished feature, fix, rollback, or refactor campaign. Runs the full Verify command and the final safety pass, applies the configured quality gates, archives the spec under religion/history/, ticks the build plan, prunes resolved findings into the archive, resets the active spec, and makes the work commit. Under branch-per-item it squash-merges only with explicit approval, then asks separately before pushing. Under pull-request it pushes the branch and opens a pull request into the default branch, merging nothing. Ends with a manual walkthrough of what landed. Use when the user runs $complete, or asks to finish, wrap up, close out, or merge the current work item once it is built and reviewed."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# complete - close out the finished work
|
|
@@ -152,10 +152,33 @@ Under **`branch-per-item`**:
|
|
|
152
152
|
2. Delete the branch after a clean merge.
|
|
153
153
|
3. Stop.
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
In those two modes, ask separately whether to push. **Approval to merge is not approval to
|
|
156
|
+
push.** Push only after an explicit yes to pushing, in this conversation. If there is no
|
|
157
157
|
remote, say so rather than guessing.
|
|
158
158
|
|
|
159
|
+
Under **`pull-request`** nothing is merged here and the default branch is never written to.
|
|
160
|
+
The branch goes up, the pull request describes it, and the merge is the user's, on the host:
|
|
161
|
+
|
|
162
|
+
1. Confirm the repository can host one: a remote exists and the host command works. If it
|
|
163
|
+
cannot, stop and say so plainly. The work is committed on its branch and the user can
|
|
164
|
+
open the pull request themselves. Never invent a remote, and never quietly fall back to
|
|
165
|
+
a local merge, which would swap the history model without saying so.
|
|
166
|
+
2. Push the branch and set its upstream, only with an explicit yes in this conversation.
|
|
167
|
+
**That yes covers pushing this branch and nothing else.** Neither running this skill nor
|
|
168
|
+
"looks good" is that yes.
|
|
169
|
+
3. Open the pull request from the branch into the default branch. Title it from the work
|
|
170
|
+
commit. The body carries what changed and why, the checks that ran with the exact command
|
|
171
|
+
or output proving each, the manual walkthrough from Step 5, and anything left open:
|
|
172
|
+
unresolved findings, deliberate omissions, follow-ups.
|
|
173
|
+
4. Report the pull request URL and stop. Do not merge it, do not wait or poll for it, and do
|
|
174
|
+
not touch the default branch.
|
|
175
|
+
|
|
176
|
+
Deleting the branch is a separate, later action, because the merge happens outside this
|
|
177
|
+
session. When the user says it merged: check out the default branch, fast-forward it,
|
|
178
|
+
confirm the work commit is actually in it, and only then delete the branch locally and on
|
|
179
|
+
the remote. A squash-merge leaves no merge parent, so `git branch -d` will refuse; verify
|
|
180
|
+
the content landed before reaching for `-D`.
|
|
181
|
+
|
|
159
182
|
## Step 5 - say how to try it
|
|
160
183
|
|
|
161
184
|
Always end with the manual walkthrough for what just landed: what to start, where to go,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: doctor
|
|
3
3
|
summary: check the setup is healthy and explain anything that is not
|
|
4
4
|
description: "Read-only health check of the installation: required files present, adapter trees in sync, the entry file's commands accurate, configuration valid, plans usable, overview fresh, ignore rules sane, and git state workable. Runs the religion command-line tool for the deterministic checks when it is installed, then explains what any failure means and offers the fix. Use when the user runs $doctor, asks whether the setup is correct, wants a health check, or says something feels wrong before starting or resuming work."
|
|
5
|
+
allowed-tools: Read, Grep, Glob, Bash
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# doctor - is this set up correctly
|
|
@@ -22,6 +22,11 @@ lines into something buildable is this skill's whole job. It plans; it never bui
|
|
|
22
22
|
- **A description with no match in the plan** - goes through **New work intake** below.
|
|
23
23
|
- **`preview`** appended to any of the above - a read-only briefing, writing nothing.
|
|
24
24
|
|
|
25
|
+
Read `religion/context/inbox.md` before choosing. When it holds anything and no argument
|
|
26
|
+
named a specific item, list what is in it alongside the plan item you would otherwise take,
|
|
27
|
+
and let the user pick. A note that is specced leaves the inbox with the line removed; the
|
|
28
|
+
rest stay. An inbox nobody reads is a list of things nobody did.
|
|
29
|
+
|
|
25
30
|
## Step 0 - make sure the ground is solid
|
|
26
31
|
|
|
27
32
|
Two checks before anything else.
|
|
@@ -127,9 +132,20 @@ visual spec from words alone when an image could exist.
|
|
|
127
132
|
|
|
128
133
|
**Build steps** are the core of the file. Each is a small diff, ends with something that
|
|
129
134
|
works, depends only on earlier steps, and carries an outcome that can actually be observed.
|
|
135
|
+
|
|
136
|
+
Where two steps genuinely do not depend on each other, mark the later one `(with N)`. It is
|
|
137
|
+
an assertion, not a hint: that these steps touch different files and neither needs the
|
|
138
|
+
other's result. Mark nothing you are unsure of. An unmarked spec is sequential, which is
|
|
139
|
+
always correct and never wrong, and a project with `workflow.parallelSteps` off ignores the
|
|
140
|
+
markers entirely.
|
|
130
141
|
Read the Commands section of the entry file while writing the Testing section, so predicted
|
|
131
142
|
coverage matches the gate that is actually on.
|
|
132
143
|
|
|
144
|
+
When writing the spec needed a broad read of the codebase first, run that read in a subagent
|
|
145
|
+
and have it return what it found. A survey large enough to be worth doing is large enough to
|
|
146
|
+
crowd out the context that has to write the spec from it. The subagent reads and returns; this
|
|
147
|
+
skill writes, as it always does.
|
|
148
|
+
|
|
133
149
|
This is a draft. Do not present it yet.
|
|
134
150
|
|
|
135
151
|
## Step 4 - red-team the draft, then tighten
|
|
@@ -16,7 +16,12 @@ numbered, it ticks nothing, and it archives on its own.
|
|
|
16
16
|
something wrong.
|
|
17
17
|
- **A finding identifier** - `$fix F-03` specs the repair of that ledger entry.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
- **Nothing** - read `religion/context/inbox.md` and offer what is there. Those notes were
|
|
20
|
+
taken mid-build precisely because they were not worth stopping for at the time, and this is
|
|
21
|
+
the moment they are worth something. Specced notes leave the inbox; the rest stay.
|
|
22
|
+
|
|
23
|
+
Without any of those, ask what is wrong. Do not guess, and do not scan the project hoping to
|
|
24
|
+
find something.
|
|
20
25
|
|
|
21
26
|
## Step 1 - understand the problem
|
|
22
27
|
|