bearings 0.2.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/README.md +92 -0
- package/dist/cli.js +477 -0
- package/package.json +33 -0
- package/templates/AGENTS.md +43 -0
- package/templates/CLAUDE.md +1 -0
- package/templates/agents/commands/current-state.md +163 -0
- package/templates/agents/commands/setup-repo.md +49 -0
- package/templates/agents/skills/commit-convention/SKILL.md +35 -0
- package/templates/agents/skills/defer-work/SKILL.md +35 -0
- package/templates/agents/skills/enforcement-gates/SKILL.md +35 -0
- package/templates/agents/skills/implementing-task/SKILL.md +36 -0
- package/templates/agents/skills/installing-dependencies/SKILL.md +36 -0
- package/templates/agents/skills/repo-navigation/SKILL.md +34 -0
- package/templates/agents/skills/resurface-deferred-work/SKILL.md +36 -0
- package/templates/agents/skills/secrets-handling/SKILL.md +36 -0
- package/templates/docs/CURRENT_STATE.md +38 -0
- package/templates/docs/adr/0000-template.md +17 -0
- package/templates/docs/conventions/current-state.md +9 -0
- package/templates/docs/conventions/doc-lifecycle.md +16 -0
- package/templates/docs/deferred/INDEX.md +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# bearings
|
|
2
|
+
|
|
3
|
+
`bearings` is a small TypeScript CLI that helps you give your agents their bearings: it scaffolds an agent-friendly repository setup, exposes shared skills and commands to supported harnesses, records the generated files in a manifest, and leaves project-specific tailoring to your AI agent via `/setup-repo`.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx bearings init
|
|
9
|
+
/setup-repo
|
|
10
|
+
bearings verify
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
1. `npx bearings init` writes the generic scaffold and prints the next step.
|
|
14
|
+
2. `/setup-repo` runs inside your AI agent to merge backups, fill placeholders, seed docs, and tailor skills.
|
|
15
|
+
3. `bearings verify` checks the scaffold for drift, missing files, broken exposures, and unresolved setup warnings.
|
|
16
|
+
|
|
17
|
+
## What Gets Generated
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
AGENTS.md
|
|
21
|
+
CLAUDE.md
|
|
22
|
+
docs/
|
|
23
|
+
CURRENT_STATE.md
|
|
24
|
+
conventions/
|
|
25
|
+
current-state.md
|
|
26
|
+
doc-lifecycle.md
|
|
27
|
+
adr/
|
|
28
|
+
0000-template.md
|
|
29
|
+
deferred/
|
|
30
|
+
INDEX.md
|
|
31
|
+
.agents/
|
|
32
|
+
bearings.json
|
|
33
|
+
commands/
|
|
34
|
+
current-state.md
|
|
35
|
+
setup-repo.md
|
|
36
|
+
skills/
|
|
37
|
+
repo-navigation/SKILL.md
|
|
38
|
+
implementing-task/SKILL.md
|
|
39
|
+
installing-dependencies/SKILL.md
|
|
40
|
+
secrets-handling/SKILL.md
|
|
41
|
+
enforcement-gates/SKILL.md
|
|
42
|
+
commit-convention/SKILL.md
|
|
43
|
+
defer-work/SKILL.md
|
|
44
|
+
resurface-deferred-work/SKILL.md
|
|
45
|
+
.claude/
|
|
46
|
+
commands/*
|
|
47
|
+
skills/*
|
|
48
|
+
.opencode/
|
|
49
|
+
commands/*
|
|
50
|
+
skills/*
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Existing scaffold targets are backed up as `<path>.bkp` before replacement. Re-running `init` skips unchanged managed files.
|
|
54
|
+
|
|
55
|
+
## The Model
|
|
56
|
+
|
|
57
|
+
Agent-friendly context is routed, not dumped.
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
user task
|
|
61
|
+
-> root agent seed
|
|
62
|
+
-> current-state map
|
|
63
|
+
-> situation-specific skills
|
|
64
|
+
-> task/runbook/spec docs when needed
|
|
65
|
+
-> executable guardrails before completion
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Commands Reference
|
|
69
|
+
|
|
70
|
+
### `bearings init`
|
|
71
|
+
|
|
72
|
+
Scaffold the agent-friendly setup in the current repository.
|
|
73
|
+
|
|
74
|
+
| Flag | Meaning |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `--harness <name...>` | Expose to `claude` and/or `opencode`. Defaults to detected harness dirs, or both in a fresh repo. |
|
|
77
|
+
| `--copy` | Copy exposures instead of symlinking them. `verify` checks copy drift. |
|
|
78
|
+
| `-y`, `--yes` | Accept defaults and do not prompt. |
|
|
79
|
+
|
|
80
|
+
### `bearings verify`
|
|
81
|
+
|
|
82
|
+
Check the manifest and harness exposures for mechanical breakage. Exit code `0` means no failures; exit code `1` means one or more failures.
|
|
83
|
+
|
|
84
|
+
| Code | Severity | Condition |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `no-manifest` | fail | `.agents/bearings.json` missing |
|
|
87
|
+
| `missing-file` | fail | manifest entry path doesn't exist |
|
|
88
|
+
| `missing-exposure` | fail | a child of `.agents/skills` or `.agents/commands` absent from a configured harness dir |
|
|
89
|
+
| `broken-symlink` | fail | harness entry is a symlink whose target doesn't resolve |
|
|
90
|
+
| `copy-drift` | fail | copy mode: harness file content differs from `.agents` source content |
|
|
91
|
+
| `unfilled-placeholder` | warn | a bearings-generated file still contains `<agent:` |
|
|
92
|
+
| `unreviewed-backup` | warn | manifest `backup` path still exists on disk |
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
8
|
+
|
|
9
|
+
// src/commands/init.ts
|
|
10
|
+
import { lstat as lstat2, readdir as readdir2 } from "fs/promises";
|
|
11
|
+
import { join as join6 } from "path";
|
|
12
|
+
|
|
13
|
+
// src/scanner.ts
|
|
14
|
+
import { access, symlink, rm } from "fs/promises";
|
|
15
|
+
import { join as join2 } from "path";
|
|
16
|
+
|
|
17
|
+
// src/paths.ts
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
import { dirname, join } from "path";
|
|
20
|
+
function templatesDir() {
|
|
21
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
22
|
+
}
|
|
23
|
+
var SKILLS = [
|
|
24
|
+
"repo-navigation",
|
|
25
|
+
"implementing-task",
|
|
26
|
+
"installing-dependencies",
|
|
27
|
+
"secrets-handling",
|
|
28
|
+
"enforcement-gates",
|
|
29
|
+
"commit-convention",
|
|
30
|
+
"defer-work",
|
|
31
|
+
"resurface-deferred-work"
|
|
32
|
+
];
|
|
33
|
+
var SCAFFOLD = [
|
|
34
|
+
{ template: "AGENTS.md", target: "AGENTS.md", owner: "agent" },
|
|
35
|
+
{ template: "CLAUDE.md", target: "CLAUDE.md", owner: "bearings" },
|
|
36
|
+
{ template: "docs/CURRENT_STATE.md", target: "docs/CURRENT_STATE.md", owner: "agent" },
|
|
37
|
+
{ template: "docs/conventions/current-state.md", target: "docs/conventions/current-state.md", owner: "bearings" },
|
|
38
|
+
{ template: "docs/conventions/doc-lifecycle.md", target: "docs/conventions/doc-lifecycle.md", owner: "bearings" },
|
|
39
|
+
{ template: "docs/adr/0000-template.md", target: "docs/adr/0000-template.md", owner: "bearings" },
|
|
40
|
+
{ template: "docs/deferred/INDEX.md", target: "docs/deferred/INDEX.md", owner: "agent" },
|
|
41
|
+
{ template: "agents/commands/current-state.md", target: ".agents/commands/current-state.md", owner: "bearings" },
|
|
42
|
+
{ template: "agents/commands/setup-repo.md", target: ".agents/commands/setup-repo.md", owner: "bearings" },
|
|
43
|
+
...SKILLS.map((s) => ({
|
|
44
|
+
template: `agents/skills/${s}/SKILL.md`,
|
|
45
|
+
target: `.agents/skills/${s}/SKILL.md`,
|
|
46
|
+
owner: s === "installing-dependencies" || s === "secrets-handling" ? "agent" : "bearings"
|
|
47
|
+
}))
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// src/scanner.ts
|
|
51
|
+
async function exists(p) {
|
|
52
|
+
try {
|
|
53
|
+
await access(p);
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function scan(repoDir) {
|
|
60
|
+
const collisions = [];
|
|
61
|
+
for (const e of SCAFFOLD) if (await exists(join2(repoDir, e.target))) collisions.push(e.target);
|
|
62
|
+
const harnessDirsPresent = [];
|
|
63
|
+
for (const h of ["claude", "opencode"])
|
|
64
|
+
if (await exists(join2(repoDir, `.${h}`))) harnessDirsPresent.push(h);
|
|
65
|
+
let symlinksSupported = true;
|
|
66
|
+
const probe = join2(repoDir, ".bearings-symlink-probe");
|
|
67
|
+
try {
|
|
68
|
+
await symlink(".", probe);
|
|
69
|
+
await rm(probe);
|
|
70
|
+
} catch {
|
|
71
|
+
symlinksSupported = false;
|
|
72
|
+
}
|
|
73
|
+
return { collisions, harnessDirsPresent, symlinksSupported };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/generator.ts
|
|
77
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, rename, access as access2 } from "fs/promises";
|
|
78
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
79
|
+
|
|
80
|
+
// src/manifest.ts
|
|
81
|
+
import { createHash } from "crypto";
|
|
82
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
83
|
+
import { join as join3 } from "path";
|
|
84
|
+
function sha256(content) {
|
|
85
|
+
return "sha256:" + createHash("sha256").update(content).digest("hex");
|
|
86
|
+
}
|
|
87
|
+
function manifestPath(repoDir) {
|
|
88
|
+
return join3(repoDir, ".agents", "bearings.json");
|
|
89
|
+
}
|
|
90
|
+
async function saveManifest(repoDir, m) {
|
|
91
|
+
await mkdir(join3(repoDir, ".agents"), { recursive: true });
|
|
92
|
+
await writeFile(manifestPath(repoDir), JSON.stringify(m, null, 2) + "\n");
|
|
93
|
+
}
|
|
94
|
+
async function loadManifest(repoDir) {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(await readFile(manifestPath(repoDir), "utf8"));
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/generator.ts
|
|
103
|
+
async function exists2(p) {
|
|
104
|
+
try {
|
|
105
|
+
await access2(p);
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function freeBackupPath(repoDir, target) {
|
|
112
|
+
let candidate = `${target}.bkp`;
|
|
113
|
+
for (let i = 1; await exists2(join4(repoDir, candidate)); i++) candidate = `${target}.bkp.${i}`;
|
|
114
|
+
return candidate;
|
|
115
|
+
}
|
|
116
|
+
async function generate(repoDir, bearingsVersion) {
|
|
117
|
+
const prior = await loadManifest(repoDir);
|
|
118
|
+
const result = { written: [], backedUp: [], skippedUnchanged: [], files: [] };
|
|
119
|
+
for (const entry of SCAFFOLD) {
|
|
120
|
+
const abs = join4(repoDir, entry.target);
|
|
121
|
+
const templateContent = await readFile2(join4(templatesDir(), entry.template), "utf8");
|
|
122
|
+
const priorEntry = prior?.files.find((f) => f.path === entry.target);
|
|
123
|
+
let backup;
|
|
124
|
+
if (await exists2(abs)) {
|
|
125
|
+
const current = await readFile2(abs, "utf8");
|
|
126
|
+
if (priorEntry && sha256(current) === priorEntry.hash) {
|
|
127
|
+
result.skippedUnchanged.push(entry.target);
|
|
128
|
+
result.files.push(priorEntry);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
backup = await freeBackupPath(repoDir, entry.target);
|
|
132
|
+
await rename(abs, join4(repoDir, backup));
|
|
133
|
+
result.backedUp.push({ path: entry.target, backup });
|
|
134
|
+
}
|
|
135
|
+
await mkdir2(dirname2(abs), { recursive: true });
|
|
136
|
+
await writeFile2(abs, templateContent);
|
|
137
|
+
result.written.push(entry.target);
|
|
138
|
+
result.files.push({
|
|
139
|
+
path: entry.target,
|
|
140
|
+
template: entry.template,
|
|
141
|
+
templateVersion: bearingsVersion,
|
|
142
|
+
hash: sha256(templateContent),
|
|
143
|
+
owner: entry.owner,
|
|
144
|
+
...backup ? { backup } : {}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/adapters.ts
|
|
151
|
+
import { mkdir as mkdir3, readdir, symlink as symlink2, lstat, readlink, rm as rm2, cp, readFile as readFile3 } from "fs/promises";
|
|
152
|
+
import { join as join5 } from "path";
|
|
153
|
+
var KINDS = ["skills", "commands"];
|
|
154
|
+
async function entriesMatch(src, dst) {
|
|
155
|
+
const srcStat = await lstat(src).catch(() => null);
|
|
156
|
+
const dstStat = await lstat(dst).catch(() => null);
|
|
157
|
+
if (!srcStat || !dstStat) return false;
|
|
158
|
+
if (srcStat.isSymbolicLink() || dstStat.isSymbolicLink()) {
|
|
159
|
+
return srcStat.isSymbolicLink() && dstStat.isSymbolicLink() && await readlink(src) === await readlink(dst);
|
|
160
|
+
}
|
|
161
|
+
if (srcStat.isDirectory() || dstStat.isDirectory()) {
|
|
162
|
+
if (!srcStat.isDirectory() || !dstStat.isDirectory()) return false;
|
|
163
|
+
const [srcEntries, dstEntries] = await Promise.all([readdir(src), readdir(dst)]);
|
|
164
|
+
if (srcEntries.length !== dstEntries.length) return false;
|
|
165
|
+
srcEntries.sort();
|
|
166
|
+
dstEntries.sort();
|
|
167
|
+
for (let i = 0; i < srcEntries.length; i++) {
|
|
168
|
+
if (srcEntries[i] !== dstEntries[i]) return false;
|
|
169
|
+
if (!await entriesMatch(join5(src, srcEntries[i]), join5(dst, dstEntries[i]))) return false;
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (srcStat.isFile() || dstStat.isFile()) {
|
|
174
|
+
return srcStat.isFile() && dstStat.isFile() && (await readFile3(src)).equals(await readFile3(dst));
|
|
175
|
+
}
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
async function expose(repoDir, harness, mode) {
|
|
179
|
+
const created = [];
|
|
180
|
+
for (const kind of KINDS) {
|
|
181
|
+
const srcDir = join5(repoDir, ".agents", kind);
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = await readdir(srcDir);
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const dstDir = join5(repoDir, `.${harness}`, kind);
|
|
189
|
+
await mkdir3(dstDir, { recursive: true });
|
|
190
|
+
for (const name of entries) {
|
|
191
|
+
const dst = join5(dstDir, name);
|
|
192
|
+
const adapterPath = join5(`.${harness}`, kind, name);
|
|
193
|
+
const relTarget = join5("..", "..", ".agents", kind, name);
|
|
194
|
+
const stat2 = await lstat(dst).catch(() => null);
|
|
195
|
+
if (mode === "symlink") {
|
|
196
|
+
if (stat2?.isSymbolicLink() && await readlink(dst) === relTarget) continue;
|
|
197
|
+
if (stat2 && !stat2.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
198
|
+
if (stat2) await rm2(dst, { recursive: true });
|
|
199
|
+
await symlink2(relTarget, dst);
|
|
200
|
+
} else {
|
|
201
|
+
const src = join5(srcDir, name);
|
|
202
|
+
if (stat2 && await entriesMatch(src, dst)) continue;
|
|
203
|
+
if (stat2 && !stat2.isSymbolicLink()) throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
204
|
+
if (stat2) await rm2(dst, { recursive: true });
|
|
205
|
+
await cp(src, dst, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
created.push(adapterPath);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return created;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/report.ts
|
|
214
|
+
function renderInitReport(r, exposed, m) {
|
|
215
|
+
const lines = ["bearings init complete.", ""];
|
|
216
|
+
lines.push(`Written (${r.written.length}):`, ...r.written.map((p) => ` + ${p}`));
|
|
217
|
+
if (r.backedUp.length) {
|
|
218
|
+
lines.push("", `Backed up (${r.backedUp.length}) \u2014 review during /setup-repo:`);
|
|
219
|
+
lines.push(...r.backedUp.map((b) => ` \u26A0 ${b.path} -> ${b.backup}`));
|
|
220
|
+
}
|
|
221
|
+
if (r.skippedUnchanged.length) {
|
|
222
|
+
lines.push(
|
|
223
|
+
"",
|
|
224
|
+
`Skipped, unchanged (${r.skippedUnchanged.length}):`,
|
|
225
|
+
...r.skippedUnchanged.map((p) => ` = ${p}`)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
lines.push(
|
|
229
|
+
"",
|
|
230
|
+
`Exposed to harnesses [${m.harnesses.join(", ")}] via ${m.exposure}:`,
|
|
231
|
+
...exposed.map((p) => ` ~ ${p}`)
|
|
232
|
+
);
|
|
233
|
+
lines.push(
|
|
234
|
+
"",
|
|
235
|
+
"Next step \u2014 finish setup with your AI agent:",
|
|
236
|
+
" Open your agent (Claude Code, OpenCode, ...) in this repo and run:",
|
|
237
|
+
" /setup-repo",
|
|
238
|
+
" It will review any backups, interview you, and tailor the setup.",
|
|
239
|
+
" Finish by running: bearings verify"
|
|
240
|
+
);
|
|
241
|
+
return lines.join("\n");
|
|
242
|
+
}
|
|
243
|
+
function renderVerifyReport(v) {
|
|
244
|
+
const lines = [];
|
|
245
|
+
for (const f of v.failures) lines.push(`FAIL ${f.code} ${f.path} \u2014 ${f.message}`);
|
|
246
|
+
for (const w of v.warnings) lines.push(`warn ${w.code} ${w.path} \u2014 ${w.message}`);
|
|
247
|
+
lines.push(v.failures.length ? `
|
|
248
|
+
${v.failures.length} failure(s).` : "\nbearings verify: OK");
|
|
249
|
+
return lines.join("\n");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/commands/init.ts
|
|
253
|
+
var VALID_HARNESSES = ["claude", "opencode"];
|
|
254
|
+
var KINDS2 = ["skills", "commands"];
|
|
255
|
+
function validateHarnesses(harnesses) {
|
|
256
|
+
if (!harnesses) return void 0;
|
|
257
|
+
for (const harness of harnesses) {
|
|
258
|
+
if (!VALID_HARNESSES.includes(harness)) {
|
|
259
|
+
throw new Error(`Invalid harness: ${String(harness)}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return harnesses;
|
|
263
|
+
}
|
|
264
|
+
function cancelInit(p) {
|
|
265
|
+
p.cancel("Init cancelled.");
|
|
266
|
+
throw new Error("Init cancelled.");
|
|
267
|
+
}
|
|
268
|
+
async function plannedAdapterSources(repoDir) {
|
|
269
|
+
const sources = { skills: /* @__PURE__ */ new Map(), commands: /* @__PURE__ */ new Map() };
|
|
270
|
+
for (const entry of SCAFFOLD) {
|
|
271
|
+
for (const kind of KINDS2) {
|
|
272
|
+
const prefix = `.agents/${kind}/`;
|
|
273
|
+
if (entry.target.startsWith(prefix)) {
|
|
274
|
+
const name = entry.target.slice(prefix.length).split("/")[0];
|
|
275
|
+
sources[kind].set(name, join6(templatesDir(), "agents", kind, name));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
for (const kind of KINDS2) {
|
|
280
|
+
const srcDir = join6(repoDir, ".agents", kind);
|
|
281
|
+
for (const name of await readdir2(srcDir).catch(() => [])) {
|
|
282
|
+
if (!sources[kind].has(name)) sources[kind].set(name, join6(srcDir, name));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return sources;
|
|
286
|
+
}
|
|
287
|
+
async function preflightAdapterCollisions(repoDir, harnesses, exposure) {
|
|
288
|
+
const sources = await plannedAdapterSources(repoDir);
|
|
289
|
+
for (const h of harnesses) {
|
|
290
|
+
for (const kind of KINDS2) {
|
|
291
|
+
for (const [name, source] of sources[kind]) {
|
|
292
|
+
const adapterPath = join6(`.${h}`, kind, name);
|
|
293
|
+
const target = join6(repoDir, adapterPath);
|
|
294
|
+
const stat2 = await lstat2(target).catch(() => null);
|
|
295
|
+
if (!stat2 || stat2.isSymbolicLink()) continue;
|
|
296
|
+
if (exposure === "copy" && await entriesMatch(source, target)) continue;
|
|
297
|
+
throw new Error(`Refusing to replace existing adapter entry: ${adapterPath}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function runInit(repoDir, flags, version) {
|
|
303
|
+
const s = await scan(repoDir);
|
|
304
|
+
let harnesses = validateHarnesses(flags.harnesses);
|
|
305
|
+
let exposure = flags.exposure;
|
|
306
|
+
if (!harnesses || !exposure) {
|
|
307
|
+
if (flags.yes || !process.stdin.isTTY) {
|
|
308
|
+
harnesses ??= s.harnessDirsPresent.length ? s.harnessDirsPresent : ["claude", "opencode"];
|
|
309
|
+
exposure ??= s.symlinksSupported ? "symlink" : "copy";
|
|
310
|
+
} else {
|
|
311
|
+
const p = await import("@clack/prompts");
|
|
312
|
+
if (!harnesses) {
|
|
313
|
+
const selectedHarnesses = await p.multiselect({
|
|
314
|
+
message: "Expose skills/commands to which harnesses?",
|
|
315
|
+
options: [
|
|
316
|
+
{ value: "claude", label: "Claude Code (.claude/)" },
|
|
317
|
+
{ value: "opencode", label: "OpenCode (.opencode/)" }
|
|
318
|
+
],
|
|
319
|
+
initialValues: s.harnessDirsPresent.length ? s.harnessDirsPresent : ["claude", "opencode"]
|
|
320
|
+
});
|
|
321
|
+
if (p.isCancel(selectedHarnesses)) {
|
|
322
|
+
cancelInit(p);
|
|
323
|
+
}
|
|
324
|
+
harnesses = validateHarnesses(selectedHarnesses);
|
|
325
|
+
}
|
|
326
|
+
if (!exposure && s.symlinksSupported) {
|
|
327
|
+
const selectedExposure = await p.select({
|
|
328
|
+
message: "Exposure mode?",
|
|
329
|
+
options: [
|
|
330
|
+
{ value: "symlink", label: "Symlinks (recommended)" },
|
|
331
|
+
{ value: "copy", label: "Copies (verify checks drift)" }
|
|
332
|
+
]
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(selectedExposure)) {
|
|
335
|
+
cancelInit(p);
|
|
336
|
+
}
|
|
337
|
+
exposure = selectedExposure;
|
|
338
|
+
} else {
|
|
339
|
+
exposure ??= "copy";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
await preflightAdapterCollisions(repoDir, harnesses, exposure);
|
|
344
|
+
const gen = await generate(repoDir, version);
|
|
345
|
+
const exposed = [];
|
|
346
|
+
for (const h of harnesses) exposed.push(...await expose(repoDir, h, exposure));
|
|
347
|
+
const manifest = {
|
|
348
|
+
version: 1,
|
|
349
|
+
bearingsVersion: version,
|
|
350
|
+
harnesses,
|
|
351
|
+
exposure,
|
|
352
|
+
files: gen.files
|
|
353
|
+
};
|
|
354
|
+
await saveManifest(repoDir, manifest);
|
|
355
|
+
return { manifest, report: renderInitReport(gen, exposed, manifest) };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/verifier.ts
|
|
359
|
+
import { access as access3, lstat as lstat3, readFile as readFile4, readdir as readdir3, readlink as readlink2, stat } from "fs/promises";
|
|
360
|
+
import { dirname as dirname3, join as join7, resolve } from "path";
|
|
361
|
+
var KINDS3 = ["skills", "commands"];
|
|
362
|
+
async function exists3(p) {
|
|
363
|
+
try {
|
|
364
|
+
await access3(p);
|
|
365
|
+
return true;
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function verify(repoDir) {
|
|
371
|
+
const failures = [];
|
|
372
|
+
const warnings = [];
|
|
373
|
+
const m = await loadManifest(repoDir);
|
|
374
|
+
if (!m) return { failures: [{ code: "no-manifest", path: ".agents/bearings.json", message: "run bearings init first" }], warnings };
|
|
375
|
+
for (const f of m.files) {
|
|
376
|
+
const abs = join7(repoDir, f.path);
|
|
377
|
+
if (!await exists3(abs)) {
|
|
378
|
+
failures.push({ code: "missing-file", path: f.path, message: "managed file deleted" });
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const content = await readFile4(abs, "utf8");
|
|
382
|
+
if (content.includes("<agent:")) {
|
|
383
|
+
warnings.push({ code: "unfilled-placeholder", path: f.path, message: "run /setup-repo to fill" });
|
|
384
|
+
}
|
|
385
|
+
if (f.backup && await exists3(join7(repoDir, f.backup))) {
|
|
386
|
+
warnings.push({ code: "unreviewed-backup", path: f.backup, message: "review during /setup-repo, then delete" });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const kind of KINDS3) {
|
|
390
|
+
const srcDir = join7(repoDir, ".agents", kind);
|
|
391
|
+
let entries = [];
|
|
392
|
+
try {
|
|
393
|
+
entries = await readdir3(srcDir);
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
for (const h of m.harnesses) {
|
|
397
|
+
for (const name of entries) {
|
|
398
|
+
const exposurePath = join7(`.${h}`, kind, name);
|
|
399
|
+
const dst = join7(repoDir, `.${h}`, kind, name);
|
|
400
|
+
const l = await lstat3(dst).catch(() => null);
|
|
401
|
+
if (!l) {
|
|
402
|
+
failures.push({ code: "missing-exposure", path: exposurePath, message: `not exposed to ${h}` });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (l.isSymbolicLink()) {
|
|
406
|
+
if (!await stat(dst).catch(() => null)) {
|
|
407
|
+
failures.push({ code: "broken-symlink", path: exposurePath, message: "symlink target missing" });
|
|
408
|
+
} else {
|
|
409
|
+
const target = await readlink2(dst);
|
|
410
|
+
if (resolve(dirname3(dst), target) !== resolve(srcDir, name)) {
|
|
411
|
+
failures.push({ code: "missing-exposure", path: exposurePath, message: `not exposed to ${h}` });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} else if (m.exposure === "symlink") {
|
|
415
|
+
failures.push({ code: "missing-exposure", path: exposurePath, message: `not exposed to ${h}` });
|
|
416
|
+
} else {
|
|
417
|
+
const src = join7(srcDir, name);
|
|
418
|
+
if (await copyDrifted(src, dst)) {
|
|
419
|
+
failures.push({ code: "copy-drift", path: exposurePath, message: "diverged from .agents source" });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return { failures, warnings };
|
|
426
|
+
}
|
|
427
|
+
async function copyDrifted(src, dst) {
|
|
428
|
+
const s = await stat(src);
|
|
429
|
+
if (s.isDirectory()) {
|
|
430
|
+
const [srcChildren, dstChildren] = await Promise.all([
|
|
431
|
+
readdir3(src),
|
|
432
|
+
readdir3(dst).catch(() => null)
|
|
433
|
+
]);
|
|
434
|
+
if (dstChildren === null) return true;
|
|
435
|
+
const srcNames = new Set(srcChildren);
|
|
436
|
+
for (const child of dstChildren) {
|
|
437
|
+
if (!srcNames.has(child)) return true;
|
|
438
|
+
}
|
|
439
|
+
for (const child of srcChildren) {
|
|
440
|
+
if (await copyDrifted(join7(src, child), join7(dst, child))) return true;
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const [a, b] = await Promise.all([
|
|
445
|
+
readFile4(src, "utf8"),
|
|
446
|
+
readFile4(dst, "utf8").catch(() => null)
|
|
447
|
+
]);
|
|
448
|
+
return b === null || sha256(a) !== sha256(b);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/commands/verify.ts
|
|
452
|
+
async function runVerify(repoDir) {
|
|
453
|
+
const v = await verify(repoDir);
|
|
454
|
+
console.log(renderVerifyReport(v));
|
|
455
|
+
return v.failures.length ? 1 : 0;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/cli.ts
|
|
459
|
+
var pkg = JSON.parse(
|
|
460
|
+
readFileSync(join8(dirname4(fileURLToPath2(import.meta.url)), "../package.json"), "utf8")
|
|
461
|
+
);
|
|
462
|
+
var program = new Command("bearings").version(pkg.version);
|
|
463
|
+
program.command("init").description("Scaffold the agent-friendly setup in the current repository").option("--harness <name...>", "claude and/or opencode").option("--copy", "copy instead of symlink").option("-y, --yes", "accept defaults, no prompts").action(async (o) => {
|
|
464
|
+
const { report } = await runInit(process.cwd(), {
|
|
465
|
+
harnesses: validateHarnesses(o.harness),
|
|
466
|
+
exposure: o.copy ? "copy" : void 0,
|
|
467
|
+
yes: o.yes
|
|
468
|
+
}, pkg.version);
|
|
469
|
+
console.log(report);
|
|
470
|
+
});
|
|
471
|
+
program.command("verify").description("Check the agent-friendly setup for drift and breakage").action(async () => {
|
|
472
|
+
process.exitCode = await runVerify(process.cwd());
|
|
473
|
+
});
|
|
474
|
+
program.parseAsync().catch((error) => {
|
|
475
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
476
|
+
process.exit(1);
|
|
477
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bearings",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Give your AI agents their bearings — scaffold an agent-friendly setup in any repository.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"bearings": "dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"templates"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"pretest": "npm run build",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"prepublishOnly": "npm run build && npm test"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@clack/prompts": "^0.11.0",
|
|
25
|
+
"commander": "^14.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^26.1.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.5.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# <agent: project name> - Agent Seed
|
|
2
|
+
|
|
3
|
+
Read this first. Load deeper context only when the task matches.
|
|
4
|
+
|
|
5
|
+
## Project
|
|
6
|
+
|
|
7
|
+
<agent: fill during handoff — Short summary of what this repo is, who uses it, and what it produces.>
|
|
8
|
+
|
|
9
|
+
## Where To Read What
|
|
10
|
+
|
|
11
|
+
| You need... | Read |
|
|
12
|
+
|---|---|
|
|
13
|
+
| What exists today and where things live | `docs/CURRENT_STATE.md` |
|
|
14
|
+
| Durable architecture and decisions | `docs/ARCHITECTURE.md`, `docs/adr/` |
|
|
15
|
+
| Deferred work | `docs/deferred/INDEX.md` |
|
|
16
|
+
|
|
17
|
+
## Load On Demand
|
|
18
|
+
|
|
19
|
+
| When you are... | Load skill |
|
|
20
|
+
|---|---|
|
|
21
|
+
| finding where code lives or which docs to read | `repo-navigation` |
|
|
22
|
+
| touching secrets, credentials, keys, tokens, or config containing secrets | `secrets-handling` |
|
|
23
|
+
| implementing a planned task | `implementing-task` |
|
|
24
|
+
| adding dependencies | `installing-dependencies` |
|
|
25
|
+
| fixing failed checks | `enforcement-gates` |
|
|
26
|
+
| about to commit | `commit-convention` |
|
|
27
|
+
| building something listed in deferred work | `resurface-deferred-work` |
|
|
28
|
+
|
|
29
|
+
## Hard Invariants
|
|
30
|
+
|
|
31
|
+
- <agent: fill during handoff — Invariant 1 with required skill/doc.>
|
|
32
|
+
- <agent: fill during handoff — Invariant 2 with required verification.>
|
|
33
|
+
|
|
34
|
+
## Always-On Rules
|
|
35
|
+
|
|
36
|
+
- Read `docs/CURRENT_STATE.md` before broad exploration.
|
|
37
|
+
- Do not read archived or generated folders unless explicitly needed.
|
|
38
|
+
- Do not silently rewrite durable docs during unrelated work.
|
|
39
|
+
- Before claiming completion, run the smallest relevant verification command or say why it could not run.
|
|
40
|
+
|
|
41
|
+
## Setup Status
|
|
42
|
+
|
|
43
|
+
Setup pending — run /setup-repo. <agent: remove this section when handoff is complete>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Generate or refresh docs/CURRENT_STATE.md and, if `docs/diagrams/` exists in this repo, docs/diagrams/c4-component.puml — the source-of-truth source map
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /current-state
|
|
6
|
+
|
|
7
|
+
Refresh `docs/CURRENT_STATE.md` and, if `docs/diagrams/` exists in this repo, `docs/diagrams/c4-component.puml` when component topology changes. Runs after each committed task, or standalone when the source map has drifted.
|
|
8
|
+
|
|
9
|
+
This command is the **only** writer for `CURRENT_STATE.md` and, if `docs/diagrams/` exists in this repo, `c4-component.puml`. Do not touch either during implementation.
|
|
10
|
+
|
|
11
|
+
## When to run
|
|
12
|
+
|
|
13
|
+
- After a task is implemented and committed — typical use, run in the same thread.
|
|
14
|
+
- Standalone, when the source map has drifted and needs catching up without a task in progress.
|
|
15
|
+
- First time, to seed `CURRENT_STATE.md` for an existing codebase or a freshly-skeletoned project.
|
|
16
|
+
|
|
17
|
+
## Required sections — CURRENT_STATE.md
|
|
18
|
+
|
|
19
|
+
Exactly these headings, in this order. Empty section: keep the heading with `(none)`. Do not remove.
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
# CURRENT_STATE
|
|
23
|
+
|
|
24
|
+
## Purpose
|
|
25
|
+
|
|
26
|
+
<1–2 sentences, caveman: what codebase does, for whom>
|
|
27
|
+
|
|
28
|
+
## Stack
|
|
29
|
+
|
|
30
|
+
<lang ⊕ framework ⊕ key libs; versions only when material>
|
|
31
|
+
|
|
32
|
+
## Shipped capabilities
|
|
33
|
+
|
|
34
|
+
<flat list, 1 line each — only end-user functionality working today>
|
|
35
|
+
|
|
36
|
+
## Source map
|
|
37
|
+
|
|
38
|
+
<folder/file tree, 1-line purpose per node — tech-lead pointer granularity>
|
|
39
|
+
|
|
40
|
+
## Design patterns in use
|
|
41
|
+
|
|
42
|
+
<pattern name → file/dir>
|
|
43
|
+
|
|
44
|
+
## Public interfaces
|
|
45
|
+
|
|
46
|
+
<HTTP routes / UI routes / CLI cmds / events / queues>
|
|
47
|
+
|
|
48
|
+
## Data model digest
|
|
49
|
+
|
|
50
|
+
<link to docs/diagrams/erd.puml ⊕ 1-line table list>
|
|
51
|
+
|
|
52
|
+
## Component diagram
|
|
53
|
+
|
|
54
|
+
<embed of rendered docs/diagrams/rendered/c4-component.svg, if `docs/diagrams/` exists in this repo>
|
|
55
|
+
|
|
56
|
+
## Known debt / TODOs
|
|
57
|
+
|
|
58
|
+
<short, current; deleted once addressed>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Caveman style — whole file
|
|
62
|
+
|
|
63
|
+
- Bullet fragments. Drop articles (`the`, `a`).
|
|
64
|
+
- Symbols: `→` leads-to · `⊕` and-also · `~` approximately · `Δ` change · `&` and-also.
|
|
65
|
+
- `file:line` over prose: `auth/login.ts:42` ✓ · "line 42 of auth/login.ts" ✗.
|
|
66
|
+
- No hedging (`might`, `could`, `perhaps`) — state or omit.
|
|
67
|
+
- No preamble, no meta, no restating the question.
|
|
68
|
+
- 1 line per entry where possible.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
## Shipped capabilities
|
|
74
|
+
- email/pw signup ⊕ verify
|
|
75
|
+
- JWT auth → 7d refresh
|
|
76
|
+
- profile edit (name, avatar)
|
|
77
|
+
|
|
78
|
+
## Source map
|
|
79
|
+
- src/auth/ — signup, login, JWT issuance
|
|
80
|
+
- login.ts:42 → token mint
|
|
81
|
+
- guards/ — route protection
|
|
82
|
+
- src/users/ — CRUD ⊕ profile
|
|
83
|
+
- src/db/ — Prisma client ⊕ migrations
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Inputs
|
|
87
|
+
|
|
88
|
+
Gather these in order — the diff is the truth source; task or issue context describes intent:
|
|
89
|
+
|
|
90
|
+
1. **`docs/CURRENT_STATE.md`** — baseline to preserve.
|
|
91
|
+
2. **If `docs/diagrams/` exists in this repo, `docs/diagrams/c4-component.puml`** — component baseline.
|
|
92
|
+
3. **Task or issue context** — identify from conversation context, linked issue/spec, or `git log -1 --format=%B`. Read named context docs for intended Δ when present.
|
|
93
|
+
4. **Adjacent planning docs** — read only if linked by the task, issue, spec, or commit message.
|
|
94
|
+
5. **`git diff HEAD~1`** — what actually shipped; trust this over intent docs if they diverge.
|
|
95
|
+
|
|
96
|
+
## Procedure A — generate from scratch (no file exists)
|
|
97
|
+
|
|
98
|
+
Covers (a) existing codebase adopting the methodology, (b) new project past design/skeleton phase.
|
|
99
|
+
|
|
100
|
+
1. **Stack** — read manifests: `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `tsconfig.json`, `vite.config.*`, `next.config.*`, `prisma/schema.prisma`, etc.
|
|
101
|
+
2. **Source map** — walk top-down. Per top-level folder: read `index.*` / `mod.rs` / `__init__.py`, the largest file, any `README.md`. Stop at tech-lead-pointer granularity (deep enough to route a dev to the right file, no deeper). Symbol-level entries only when the symbol _is_ the landmark.
|
|
102
|
+
3. **Public interfaces** — find route registrations (Express/NestJS/FastAPI/Django/etc.), CLI entry points (`bin/`, `cmd/`, `cli.*`), event/queue handlers.
|
|
103
|
+
4. **Data model digest** — read schema + migrations → 1-line table list. Link `docs/diagrams/erd.puml` if it exists; if not, link `prisma/schema.prisma` or equivalent. Do not inline schema.
|
|
104
|
+
5. **Design patterns in use** — recurring shapes by directory naming + import graph (Repository, Service, UseCase, Controller, ...). Pattern → file/dir.
|
|
105
|
+
6. **Shipped capabilities** — user-facing features actually working today. Skip in-flight or planned. If unsure what works, **ask the user**. Do not infer from task files or phase docs.
|
|
106
|
+
7. **Component diagram** — if `docs/diagrams/` exists in this repo and `docs/diagrams/c4-component.puml` does not exist, create a minimal skeleton from the source map's top-level boundaries. Render to SVG via `docker run --rm -v "$PWD:/data" plantuml/plantuml -tsvg docs/diagrams/c4-component.puml`. Embed the rendered SVG path in the section. If `docs/diagrams/` does not exist in this repo, write `(none)` in the section.
|
|
107
|
+
8. **Known debt / TODOs** — grep `TODO|FIXME|HACK|XXX`. Add obvious gaps spotted during the walk.
|
|
108
|
+
9. **Purpose** — 1–2 caveman sentences. If unclear, **ask the user**. Do not guess.
|
|
109
|
+
10. Apply caveman style end-to-end. Re-read once. Cut every word with no signal.
|
|
110
|
+
|
|
111
|
+
After drafting: surface to user, ask for corrections. The first version is a draft, not the answer.
|
|
112
|
+
|
|
113
|
+
## Procedure B — update an existing file
|
|
114
|
+
|
|
115
|
+
Trigger: a task was just committed and `/current-state` is called in the same thread, or standalone drift correction.
|
|
116
|
+
|
|
117
|
+
1. Read `docs/CURRENT_STATE.md` fully — establish the baseline.
|
|
118
|
+
2. If `docs/diagrams/` exists in this repo, read `docs/diagrams/c4-component.puml` — establish the component baseline.
|
|
119
|
+
3. Identify the completed task or issue from conversation context or `git log -1 --format=%B`. Read named context docs for intended Δ when present.
|
|
120
|
+
4. Run `git diff HEAD~1` — confirm what actually shipped. This is the truth source.
|
|
121
|
+
5. Map change to affected sections — most tasks touch 1–3:
|
|
122
|
+
- new dep → **Stack**
|
|
123
|
+
- new user-facing feature working today → **Shipped capabilities**
|
|
124
|
+
- new/renamed/removed file or folder, or purpose shift → **Source map**
|
|
125
|
+
- new endpoint/CLI/event → **Public interfaces**
|
|
126
|
+
- new migration → **Data model digest**
|
|
127
|
+
- new component or container relation → **Component diagram** if `docs/diagrams/` exists in this repo (edit `c4-component.puml`, re-render)
|
|
128
|
+
- new pattern adopted ⊕ old pattern retired → **Design patterns in use**
|
|
129
|
+
- debt resolved → **prune** from Known debt; new debt → add
|
|
130
|
+
- **Purpose** — touch only if scope actually shifted
|
|
131
|
+
6. **Weigh new entries against the existing baseline.** A freshly-shipped task does not automatically replace older, more important entries. If the file is growing past one screen, prune low-signal entries first.
|
|
132
|
+
7. Apply changes with the **Edit tool only** — surgical replacements. Never regenerate the file. Untouched sections stay byte-identical.
|
|
133
|
+
8. **Prune obsolete entries.** File removed → delete source-map line. Pattern retired → delete entry. Debt resolved → delete item. Stale entries are worse than missing ones.
|
|
134
|
+
9. New entries match surrounding density. Caveman throughout.
|
|
135
|
+
10. If `docs/diagrams/` exists in this repo and `c4-component.puml` changed, re-render: `docker run --rm -v "$PWD:/data" plantuml/plantuml -tsvg docs/diagrams/c4-component.puml`. Update the embed path in the Component diagram section if the rendered filename changed.
|
|
136
|
+
11. Re-read after editing. Cut redundancy the diff introduced.
|
|
137
|
+
|
|
138
|
+
After updating: surface the diff of `CURRENT_STATE.md` to the user, flag any conflicts, ask for corrections. Once the user approves (or no corrections are needed), commit with a concise docs message such as:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
docs: update CURRENT_STATE
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Include only project-required trailers. Stage only `docs/CURRENT_STATE.md` and, if `docs/diagrams/` exists in this repo and changed, `docs/diagrams/c4-component.puml` plus its rendered SVG.
|
|
145
|
+
|
|
146
|
+
## Conflict handling
|
|
147
|
+
|
|
148
|
+
- **Task file says X, diff shows Y** → trust the diff. Note the divergence to the user.
|
|
149
|
+
- **Manual human edit in `CURRENT_STATE.md` conflicts with new change** → trust the new change but **flag the conflict before overwriting**. The human edit may encode context the agent lacks.
|
|
150
|
+
|
|
151
|
+
## Anti-bloat rules
|
|
152
|
+
|
|
153
|
+
- No narrative. No restating code comments.
|
|
154
|
+
- No planned, in-flight, or "next release" features. Only what works today.
|
|
155
|
+
- No duplication of content that lives elsewhere — link instead (`→ prisma/schema.prisma`, `→ docs/diagrams/erd.puml`).
|
|
156
|
+
- File grows with **source-tree structure**, not task count. Growing per-task → wrong content going in; rework the entry until it describes structure, not the task.
|
|
157
|
+
- No business detail — that lives in `docs/features/`. `CURRENT_STATE.md` is a source map, not a feature catalog.
|
|
158
|
+
- No history — `CURRENT_STATE.md` answers "what is"; `CHANGELOG.md` answers "how we got here". They never overlap.
|
|
159
|
+
|
|
160
|
+
## Out of scope
|
|
161
|
+
|
|
162
|
+
- Appending to `CHANGELOG.md`.
|
|
163
|
+
- Editing unrelated requirements, prototypes, class diagrams, ERDs, context diagrams, or container diagrams.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: One-time (re-runnable) project tailoring after `bearings init`. Interviews the developer, explores the repo, and completes the agent-friendly setup.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /setup-repo
|
|
6
|
+
|
|
7
|
+
You are completing the agent-friendly setup that `bearings init` scaffolded.
|
|
8
|
+
The scaffold is generic; your job is everything project-specific. Interview
|
|
9
|
+
the developer relentlessly — one question at a time, with a recommended
|
|
10
|
+
answer per question — and explore the code before asking anything the code
|
|
11
|
+
can answer.
|
|
12
|
+
|
|
13
|
+
## Required workflow
|
|
14
|
+
|
|
15
|
+
1. Read `AGENTS.md`, `docs/conventions/doc-lifecycle.md`,
|
|
16
|
+
`docs/conventions/current-state.md`, and `.agents/bearings.json`.
|
|
17
|
+
2. Review Backup Files: for every manifest entry with a `backup`, read the
|
|
18
|
+
backup, summarise what it contained, and ask the developer per file:
|
|
19
|
+
merge into the new scaffold / keep parts / discard. Apply their choice,
|
|
20
|
+
then delete the `.bkp` file once resolved.
|
|
21
|
+
3. Explore the repo: stack, package manager, build/test/lint commands, CI,
|
|
22
|
+
generated files, secret/config paths, deployment surfaces.
|
|
23
|
+
4. Interview the developer, one question at a time, covering at minimum:
|
|
24
|
+
- repository purpose and primary users/operators
|
|
25
|
+
- hard invariants that must never break
|
|
26
|
+
- off-limits paths
|
|
27
|
+
- what counts as verification ("smallest command that proves a change")
|
|
28
|
+
- concern detection: persisted data? secrets? IaC? frontend? release flow?
|
|
29
|
+
5. Fill every `<agent: ...>` placeholder in `AGENTS.md` and the starter
|
|
30
|
+
skills. Keep `AGENTS.md` a router — deep content goes in docs/skills.
|
|
31
|
+
6. For each detected concern with no matching skill, create
|
|
32
|
+
`.agents/skills/<name>/SKILL.md` (follow the shape of the starter
|
|
33
|
+
skills) and add a "Load on demand" row in `AGENTS.md`.
|
|
34
|
+
7. Seed `docs/CURRENT_STATE.md` by running `/current-state`.
|
|
35
|
+
8. Mirror any skills/commands you created into each harness dir the same
|
|
36
|
+
way existing ones are exposed (symlink or copy — check `.agents/bearings.json`).
|
|
37
|
+
9. Run `bearings verify`. Fix every failure. Report remaining warnings to
|
|
38
|
+
the developer with a recommendation each.
|
|
39
|
+
10. Remove the `## Setup Status` section from `AGENTS.md`.
|
|
40
|
+
|
|
41
|
+
## Rules
|
|
42
|
+
|
|
43
|
+
- Do not overwrite developer decisions silently — every merge/discard of a
|
|
44
|
+
backup is the developer's call.
|
|
45
|
+
- Do not edit `.agents/bearings.json` except where this command requires it.
|
|
46
|
+
- Do not invent invariants — every Hard Invariant line must come from the
|
|
47
|
+
developer or from clear evidence in the code (cite the path).
|
|
48
|
+
- Re-running this command later is allowed: skip completed steps, focus on
|
|
49
|
+
drift between docs and reality.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use before creating a git commit, writing a commit message, changelog entry, or release-affecting change.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Commit Convention
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Keep history searchable and release automation predictable.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- You are about to commit.
|
|
14
|
+
- You are choosing a commit type, scope, footer, or release note.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Inspect `git status` and `git diff` -> stage only intended files.
|
|
19
|
+
2. Choose the commit type and scope -> match the project's release rules.
|
|
20
|
+
3. Write the commit message -> use the required format exactly.
|
|
21
|
+
4. Commit without co-author trailers unless the developer asked for them.
|
|
22
|
+
|
|
23
|
+
## Rules
|
|
24
|
+
|
|
25
|
+
- Do: use `<agent: fill during handoff — commit message format and allowed types>`.
|
|
26
|
+
- Do not: commit unrelated files, secrets, generated noise, or unverified work.
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
- Run `<agent: fill during handoff — pre-commit verification command>` before committing when available.
|
|
31
|
+
|
|
32
|
+
## References
|
|
33
|
+
|
|
34
|
+
- `<agent: fill during handoff — release/changelog config path>` - release semantics.
|
|
35
|
+
- `<agent: fill during handoff — contribution or commit policy path>` - local commit rules.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when the developer explicitly parks, defers, postpones, skips-for-now, or chooses an interim solution instead of a fuller plan.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Defer Work
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Record intentional "yes, later" work so future agents do not silently build or forget it.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- The developer says work is deferred, parked, postponed, YAGNI, or skipped for now.
|
|
14
|
+
- A simple interim solution ships instead of the fuller planned solution.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Confirm the deferred item -> get explicit developer approval to record it.
|
|
19
|
+
2. Capture business need, parked approach, reason, and revisit trigger -> make the future decision self-contained.
|
|
20
|
+
3. Create `docs/deferred/DEF-NNN-topic.md` -> assign the next available ID.
|
|
21
|
+
4. Add a row to `docs/deferred/INDEX.md` -> include ID, parked item, revisit trigger, and detail path.
|
|
22
|
+
|
|
23
|
+
## Rules
|
|
24
|
+
|
|
25
|
+
- Do: record only currently parked work with a concrete revisit trigger.
|
|
26
|
+
- Do not: record rejected-forever work or create deferred entries without developer confirmation.
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
- Run `<agent: fill during handoff — docs verification command>` when deferred docs change.
|
|
31
|
+
|
|
32
|
+
## References
|
|
33
|
+
|
|
34
|
+
- `docs/deferred/INDEX.md` - registry of parked work.
|
|
35
|
+
- `docs/conventions/doc-lifecycle.md` - deferred-work lifecycle.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when a local hook, lint, typecheck, test, build, validation, or CI gate fails.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Enforcement Gates
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Diagnose failed gates from evidence, fix the underlying issue, and avoid bypassing safety checks.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- A pre-commit, pre-push, CI, lint, typecheck, test, build, validation, or policy gate fails.
|
|
14
|
+
- You are tempted to skip or weaken a check.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Capture the failing command and first relevant error -> identify the gate, not a symptom.
|
|
19
|
+
2. Fix the smallest underlying cause -> keep the gate intact.
|
|
20
|
+
3. Re-run the failed gate -> confirm it passes.
|
|
21
|
+
4. Run adjacent verification if the fix changes behavior -> prevent regressions.
|
|
22
|
+
|
|
23
|
+
## Rules
|
|
24
|
+
|
|
25
|
+
- Do: preserve strict gate settings and cite the command/output in the report.
|
|
26
|
+
- Do not: bypass hooks, lower thresholds, delete tests, or silence errors without a documented reason.
|
|
27
|
+
|
|
28
|
+
## Verification
|
|
29
|
+
|
|
30
|
+
- Run `<agent: fill during handoff — standard gate command>` after fixing any gate failure.
|
|
31
|
+
|
|
32
|
+
## References
|
|
33
|
+
|
|
34
|
+
- `<agent: fill during handoff — CI config path>` - canonical gate list.
|
|
35
|
+
- `<agent: fill during handoff — local hook config path>` - developer gate list.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when implementing planned work from a task, issue, spec, or phase document.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Implementing Task
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Execute planned work without drifting from scope, skipping verification, or mixing unrelated changes.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- You are implementing a task/spec/issue with explicit requirements.
|
|
14
|
+
- You are changing code under a feature or phase plan.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Read the task source and linked durable docs -> identify exact scope and exclusions.
|
|
19
|
+
2. Write or update the smallest failing test first -> confirm the expected RED failure.
|
|
20
|
+
3. Implement the minimal change -> keep unrelated files untouched.
|
|
21
|
+
4. Run the required verification -> record command and outcome.
|
|
22
|
+
5. Update current-state only through `/current-state` when shipped behavior or source map changed.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Do: preserve exact values, paths, and commands from the task source.
|
|
27
|
+
- Do not: pull future-phase work forward or silently expand scope.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- Run `<agent: fill during handoff — smallest command that proves a planned task>` before reporting completion.
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- `docs/CURRENT_STATE.md` - current source map.
|
|
36
|
+
- `docs/conventions/doc-lifecycle.md` - durable vs planning document lifecycle.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when adding, removing, or updating dependencies, package-manager metadata, lockfiles, or workspace package links.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Installing Dependencies
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Keep dependency changes reproducible, scoped to the correct package, and aligned with the project's package manager.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- Adding, removing, or upgrading an external package.
|
|
14
|
+
- Linking an internal workspace package.
|
|
15
|
+
- Editing package manifests or lockfiles.
|
|
16
|
+
|
|
17
|
+
## Required Workflow
|
|
18
|
+
|
|
19
|
+
1. Identify package manager and workspace shape -> `<agent: fill during handoff — package manager and workspace command rules>`.
|
|
20
|
+
2. Identify the owning package/app -> avoid installing at the wrong level.
|
|
21
|
+
3. Run the package manager command -> update manifest and lockfile together.
|
|
22
|
+
4. Run dependency-sensitive verification -> prove install and imports work.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Do: use `<agent: fill during handoff — approved package manager command>` for dependency changes.
|
|
27
|
+
- Do not: edit lockfiles by hand or mix unrelated dependency upgrades.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- Run `<agent: fill during handoff — install/build/test command after dependency changes>` when dependency metadata changes.
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- `<agent: fill during handoff — package manifest path>` - dependency owner.
|
|
36
|
+
- `<agent: fill during handoff — lockfile path>` - resolved dependency graph.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when exploring the repo, locating code, or deciding which docs and paths to read before making changes.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Repo Navigation
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Explore efficiently without crawling irrelevant, archived, generated, or project-specific material.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- You need to understand where code, docs, commands, or ownership rules live.
|
|
14
|
+
- You are about to do broad repository exploration.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Read `docs/CURRENT_STATE.md` -> use it as the first source map.
|
|
19
|
+
2. Read only the paths relevant to the task -> keep exploration proportional.
|
|
20
|
+
3. If the source map is stale -> report the mismatch and continue from direct evidence.
|
|
21
|
+
|
|
22
|
+
## Rules
|
|
23
|
+
|
|
24
|
+
- Do: prefer durable docs, manifests, and entry points before deep file walks.
|
|
25
|
+
- Do not: crawl archived, generated, vendored, or build-output folders unless the task explicitly needs them.
|
|
26
|
+
|
|
27
|
+
## Verification
|
|
28
|
+
|
|
29
|
+
- Run `<agent: fill during handoff — smallest command that proves navigation-sensitive changes>` when exploration changes generated docs or routing metadata.
|
|
30
|
+
|
|
31
|
+
## References
|
|
32
|
+
|
|
33
|
+
- `docs/CURRENT_STATE.md` - source map and current shipped shape.
|
|
34
|
+
- `docs/conventions/current-state.md` - update policy and staleness rules.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use before building, planning, or designing work that matches an entry in docs/deferred/INDEX.md.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Resurface Deferred Work
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Prevent agents from silently building over parked decisions without confirming the need and checking drift.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- A request matches a row in `docs/deferred/INDEX.md`.
|
|
14
|
+
- You are about to plan or build something that was intentionally deferred.
|
|
15
|
+
|
|
16
|
+
## Required Workflow
|
|
17
|
+
|
|
18
|
+
1. Read the matching registry row and detail doc -> understand need, reason, and trigger.
|
|
19
|
+
2. Confirm the developer still wants it -> do not expose stale implementation detail before confirming.
|
|
20
|
+
3. Drift-check the parked plan against current code -> classify none, small, or too much drift.
|
|
21
|
+
4. Present the viable path -> proceed, restructure, re-park, or reject forever.
|
|
22
|
+
5. If shipped, remove the registry row and detail doc -> keep deferred work current.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Do: scan deferred work before planning new features.
|
|
27
|
+
- Do not: silently build a parked item or trust an old plan without drift-checking it.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- Run `<agent: fill during handoff — docs/code verification command>` when resurfaced work changes docs or code.
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- `docs/deferred/INDEX.md` - parked-work registry.
|
|
36
|
+
- `docs/conventions/doc-lifecycle.md` - lifecycle for deferred plans.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use when touching credentials, tokens, keys, secret references, config containing secrets, or secret redaction paths.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Secrets Handling
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Prevent secrets from being committed, logged, exposed through APIs, or stored outside the approved secret store.
|
|
10
|
+
|
|
11
|
+
## Triggers
|
|
12
|
+
|
|
13
|
+
- Storing, reading, rotating, deleting, or displaying credentials.
|
|
14
|
+
- Editing config, environment files, CI secrets, tokens, keys, or redaction logic.
|
|
15
|
+
- Adding a new integration that needs a secret.
|
|
16
|
+
|
|
17
|
+
## Required Workflow
|
|
18
|
+
|
|
19
|
+
1. Identify the secret and owner -> `<agent: fill during handoff — secret store/provider and ownership rule>`.
|
|
20
|
+
2. Store only references in code/config where possible -> keep raw values in the approved store.
|
|
21
|
+
3. Verify redaction boundaries -> responses, logs, errors, docs, and tests must not reveal secret values.
|
|
22
|
+
4. Rotate or revoke exposed values -> treat accidental disclosure as a security incident.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Do: use `<agent: fill during handoff — approved secret reference pattern>` for secret lookup and storage.
|
|
27
|
+
- Do not: commit raw secrets, print them, include them in snapshots, or expose secret references where the project forbids it.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
- Run `<agent: fill during handoff — secret/config validation command>` when secret handling changes.
|
|
32
|
+
|
|
33
|
+
## References
|
|
34
|
+
|
|
35
|
+
- `<agent: fill during handoff — secret management doc/path>` - approved store and redaction contract.
|
|
36
|
+
- `<agent: fill during handoff — config/env schema path>` - allowed secret references.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# CURRENT_STATE
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
<agent: fill via /current-state>
|
|
6
|
+
|
|
7
|
+
## Stack
|
|
8
|
+
|
|
9
|
+
- <agent: fill via /current-state>
|
|
10
|
+
- <agent: fill via /current-state>
|
|
11
|
+
|
|
12
|
+
## Shipped capabilities
|
|
13
|
+
|
|
14
|
+
- <agent: fill via /current-state>
|
|
15
|
+
|
|
16
|
+
## Source map
|
|
17
|
+
|
|
18
|
+
- `<agent: fill via /current-state>` - <agent: fill via /current-state>
|
|
19
|
+
|
|
20
|
+
## Design patterns in use
|
|
21
|
+
|
|
22
|
+
- <agent: fill via /current-state> -> <agent: fill via /current-state>
|
|
23
|
+
|
|
24
|
+
## Public interfaces
|
|
25
|
+
|
|
26
|
+
- <agent: fill via /current-state> -> <agent: fill via /current-state>
|
|
27
|
+
|
|
28
|
+
## Data model digest
|
|
29
|
+
|
|
30
|
+
- <agent: fill via /current-state> - <agent: fill via /current-state>
|
|
31
|
+
|
|
32
|
+
## Component diagram
|
|
33
|
+
|
|
34
|
+
- <agent: fill via /current-state>
|
|
35
|
+
|
|
36
|
+
## Known debt / TODOs
|
|
37
|
+
|
|
38
|
+
- <agent: fill via /current-state>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# ADR-0000: <title>
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
State whether this decision is proposed, accepted, superseded, or rejected.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Describe the forces, constraints, and facts that make a decision necessary.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Record the chosen approach and the scope where it applies.
|
|
14
|
+
|
|
15
|
+
## Consequences
|
|
16
|
+
|
|
17
|
+
List the tradeoffs, follow-up work, and verification impact.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Current State Policy
|
|
2
|
+
|
|
3
|
+
## Policy
|
|
4
|
+
|
|
5
|
+
- Agents read it before broad exploration.
|
|
6
|
+
- If it answers the question, agents should not re-crawl the repo.
|
|
7
|
+
- It should be updated only by an explicit command or maintenance workflow.
|
|
8
|
+
- If stale, agents should report staleness instead of silently rewriting it during unrelated work.
|
|
9
|
+
- Only `/current-state` writes `docs/CURRENT_STATE.md`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Documentation Lifecycle
|
|
2
|
+
|
|
3
|
+
## Common categories
|
|
4
|
+
|
|
5
|
+
| Category | Examples | Lifecycle |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Durable | architecture, ADRs, glossary, operations, current-state conventions | Long-lived |
|
|
8
|
+
| Planning | roadmap, phases, feature plans, task docs | Archived or removed after shipped |
|
|
9
|
+
| Runbooks | deployment, rollback, manual checks, provider setup | Updated when operations change |
|
|
10
|
+
| Deferred work | parked plans and revisit triggers | Removed when built or rejected |
|
|
11
|
+
|
|
12
|
+
## Why this matters
|
|
13
|
+
|
|
14
|
+
- Prevents agents from treating stale plans as shipped behavior.
|
|
15
|
+
- Prevents durable docs from depending on soon-archived docs.
|
|
16
|
+
- Gives agents a clear source of truth for scope disputes.
|