@open-tomato/rafa 0.17.0 → 0.19.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/NOTICE +9 -1
- package/README.md +40 -26
- package/dist/PROMPT.md +2 -1
- package/dist/bundled/agents/build-error-resolver.md +144 -0
- package/dist/bundled/agents/code-reviewer.md +335 -0
- package/dist/bundled/agents/doc-updater.md +108 -0
- package/dist/bundled/agents/loop-implementer.md +57 -0
- package/dist/bundled/agents/qa-bug-reporter.md +164 -0
- package/dist/bundled/agents/tdd-guide.md +121 -0
- package/dist/bundled/bin/ts-symbols +272 -0
- package/dist/{SKILL.md → bundled/skills/dev-planner/SKILL.md} +7 -5
- package/dist/bundled/skills/documentation/SKILL.md +93 -0
- package/dist/bundled/skills/git-workflow/SKILL.md +242 -0
- package/dist/bundled/skills/ts-symbols/SKILL.md +82 -0
- package/dist/cli.js +6952 -4721
- package/dist/{describe-wbn759vf.js → describe-cenk70n5.js} +4 -3
- package/dist/effort/store/index.js +6 -4
- package/dist/{index-64wec0k7.js → index-09p41s0n.js} +11 -224
- package/dist/{index-qzhpqmfy.js → index-c5cecsga.js} +2 -2
- package/dist/{index-4qd537z6.js → index-ca0fccvr.js} +3527 -1402
- package/dist/{index-n3rw8rhr.js → index-cedghpva.js} +78 -2
- package/dist/index-d6d0942q.js +218 -0
- package/dist/{index-7t0qz29r.js → index-gb51x34c.js} +1020 -58
- package/dist/{index-r7cnr44m.js → index-mh5hyw8g.js} +1681 -3101
- package/dist/index-pqrpcfaz.js +1020 -0
- package/dist/{index-p901zs2p.js → index-vg7ycs94.js} +1 -1
- package/dist/index-wp8c28fd.js +18 -0
- package/dist/index.js +13 -11
- package/dist/learning/index.js +25 -0
- package/dist/plan/index.js +8 -7
- package/dist/plan-prompt.md +2 -0
- package/package.json +4 -3
- package/dist/index-ps5sac9p.js +0 -531
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/tools/ts-symbols/cli.ts
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
var USAGE = `usage: ts-symbols <command> <target> [--json]
|
|
9
|
+
outline <file> exported + top-level symbols with kinds and lines
|
|
10
|
+
def <file>:<line>:<col> definition location of the symbol at position (1-based)
|
|
11
|
+
refs <file>:<line>:<col> references across the nearest tsconfig project
|
|
12
|
+
type <file>:<line>:<col> resolved type text at position (--full to expand)`;
|
|
13
|
+
var COMMANDS = new Set(["outline", "def", "refs", "type"]);
|
|
14
|
+
var NO_TYPESCRIPT_EXIT = 3;
|
|
15
|
+
function fail(msg) {
|
|
16
|
+
console.error(`ts-symbols: ${msg}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
function noResults(msg) {
|
|
20
|
+
console.error(`ts-symbols: ${msg}`);
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
function readRequest(argv) {
|
|
24
|
+
const json = argv.includes("--json");
|
|
25
|
+
const full = argv.includes("--full");
|
|
26
|
+
const args = argv.filter((a) => !a.startsWith("--"));
|
|
27
|
+
const [command, rawTarget] = args;
|
|
28
|
+
if (!command || command === "help") {
|
|
29
|
+
console.log(USAGE);
|
|
30
|
+
process.exit(command ? 0 : 1);
|
|
31
|
+
}
|
|
32
|
+
if (!rawTarget)
|
|
33
|
+
fail(`missing target
|
|
34
|
+
${USAGE}`);
|
|
35
|
+
if (!COMMANDS.has(command))
|
|
36
|
+
fail(`unknown command "${command}"
|
|
37
|
+
${USAGE}`);
|
|
38
|
+
return { command, rawTarget, json, full };
|
|
39
|
+
}
|
|
40
|
+
function typescriptDirectory(dir) {
|
|
41
|
+
const candidate = path.join(dir, "node_modules", "typescript");
|
|
42
|
+
if (existsSync(path.join(candidate, "package.json")))
|
|
43
|
+
return candidate;
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
return parent === dir ? undefined : typescriptDirectory(parent);
|
|
46
|
+
}
|
|
47
|
+
function loadTypeScript(root) {
|
|
48
|
+
const directory = typescriptDirectory(root);
|
|
49
|
+
if (!directory) {
|
|
50
|
+
console.error(`ts-symbols: no typescript under ${root}: add it to the project`);
|
|
51
|
+
process.exit(NO_TYPESCRIPT_EXIT);
|
|
52
|
+
}
|
|
53
|
+
return createRequire(import.meta.url)(directory);
|
|
54
|
+
}
|
|
55
|
+
var request = readRequest(process.argv.slice(2));
|
|
56
|
+
var ts = loadTypeScript(process.cwd());
|
|
57
|
+
function parseTarget(raw) {
|
|
58
|
+
const m = /^(.+):(\d+):(\d+)$/.exec(raw);
|
|
59
|
+
if (!m || m[1] === undefined)
|
|
60
|
+
fail(`expected <file>:<line>:<col> (1-based), got "${raw}"`);
|
|
61
|
+
const file = path.resolve(m[1]);
|
|
62
|
+
if (!ts.sys.fileExists(file))
|
|
63
|
+
fail(`file not found: ${file}`);
|
|
64
|
+
return { file, line: Number(m[2]), col: Number(m[3]) };
|
|
65
|
+
}
|
|
66
|
+
function relPath(file) {
|
|
67
|
+
const rel = path.relative(process.cwd(), file);
|
|
68
|
+
return rel.startsWith("..") ? file : rel;
|
|
69
|
+
}
|
|
70
|
+
var LOOSE_OPTIONS = {
|
|
71
|
+
allowJs: true,
|
|
72
|
+
target: ts.ScriptTarget.ES2022,
|
|
73
|
+
module: ts.ModuleKind.ESNext,
|
|
74
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler
|
|
75
|
+
};
|
|
76
|
+
function createProject(absFile, singleFile) {
|
|
77
|
+
let configPath;
|
|
78
|
+
let rootNames = [absFile];
|
|
79
|
+
let options = LOOSE_OPTIONS;
|
|
80
|
+
let projectDir = path.dirname(absFile);
|
|
81
|
+
if (!singleFile) {
|
|
82
|
+
configPath = ts.findConfigFile(path.dirname(absFile), ts.sys.fileExists, "tsconfig.json");
|
|
83
|
+
if (configPath) {
|
|
84
|
+
projectDir = path.dirname(configPath);
|
|
85
|
+
const jsonFile = ts.readJsonConfigFile(configPath, ts.sys.readFile);
|
|
86
|
+
const parsed = ts.parseJsonSourceFileConfigFileContent(jsonFile, ts.sys, projectDir);
|
|
87
|
+
options = parsed.options;
|
|
88
|
+
rootNames = parsed.fileNames.slice();
|
|
89
|
+
if (!rootNames.some((f) => path.resolve(f) === absFile))
|
|
90
|
+
rootNames.push(absFile);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const host = {
|
|
94
|
+
getScriptFileNames: () => rootNames,
|
|
95
|
+
getScriptVersion: () => "0",
|
|
96
|
+
getScriptSnapshot: (file) => {
|
|
97
|
+
const text = ts.sys.readFile(file);
|
|
98
|
+
return text === undefined ? undefined : ts.ScriptSnapshot.fromString(text);
|
|
99
|
+
},
|
|
100
|
+
getCurrentDirectory: () => projectDir,
|
|
101
|
+
getCompilationSettings: () => options,
|
|
102
|
+
getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts),
|
|
103
|
+
fileExists: ts.sys.fileExists,
|
|
104
|
+
readFile: ts.sys.readFile,
|
|
105
|
+
readDirectory: ts.sys.readDirectory,
|
|
106
|
+
directoryExists: ts.sys.directoryExists,
|
|
107
|
+
getDirectories: ts.sys.getDirectories
|
|
108
|
+
};
|
|
109
|
+
return { service: ts.createLanguageService(host, ts.createDocumentRegistry()), configPath };
|
|
110
|
+
}
|
|
111
|
+
function sourceFileOf(project, file) {
|
|
112
|
+
const program = project.service.getProgram();
|
|
113
|
+
const fromProgram = program?.getSourceFile(file);
|
|
114
|
+
if (fromProgram)
|
|
115
|
+
return fromProgram;
|
|
116
|
+
const text = ts.sys.readFile(file) ?? "";
|
|
117
|
+
return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
|
|
118
|
+
}
|
|
119
|
+
function offsetOf(sf, target) {
|
|
120
|
+
try {
|
|
121
|
+
return sf.getPositionOfLineAndCharacter(target.line - 1, target.col - 1);
|
|
122
|
+
} catch {
|
|
123
|
+
return fail(`position ${target.line}:${target.col} is outside ${relPath(target.file)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function locOf(project, file, start) {
|
|
127
|
+
const sf = sourceFileOf(project, file);
|
|
128
|
+
const lc = sf.getLineAndCharacterOfPosition(start);
|
|
129
|
+
const starts = sf.getLineStarts();
|
|
130
|
+
const end = lc.line + 1 < starts.length ? starts[lc.line + 1] : sf.text.length;
|
|
131
|
+
return {
|
|
132
|
+
file,
|
|
133
|
+
line: lc.line + 1,
|
|
134
|
+
col: lc.character + 1,
|
|
135
|
+
lineText: sf.text.slice(starts[lc.line], end).trim()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
var CONTAINER_KINDS = new Set(["class", "interface", "enum", "module", "type"]);
|
|
139
|
+
function navToOutline(sf, node, depth) {
|
|
140
|
+
const span = node.nameSpan ?? node.spans[0];
|
|
141
|
+
if (!span)
|
|
142
|
+
fail(`no source span for symbol "${node.text}"`);
|
|
143
|
+
const lc = sf.getLineAndCharacterOfPosition(span.start);
|
|
144
|
+
const item = {
|
|
145
|
+
name: node.text,
|
|
146
|
+
kind: node.kind,
|
|
147
|
+
line: lc.line + 1,
|
|
148
|
+
col: lc.character + 1,
|
|
149
|
+
exported: node.kindModifiers.split(",").includes("export")
|
|
150
|
+
};
|
|
151
|
+
if (depth > 0 && node.childItems?.length && CONTAINER_KINDS.has(node.kind)) {
|
|
152
|
+
item.children = node.childItems.map((c) => navToOutline(sf, c, depth - 1)).sort((a, b) => a.line - b.line || a.col - b.col);
|
|
153
|
+
}
|
|
154
|
+
return item;
|
|
155
|
+
}
|
|
156
|
+
function printOutline(items, indent) {
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
const marker = item.exported ? "export " : "";
|
|
159
|
+
console.log(`${item.line}: ${indent}${marker}${item.kind} ${item.name}`);
|
|
160
|
+
if (item.children)
|
|
161
|
+
printOutline(item.children, indent + " ");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function cmdOutline(rawFile, json) {
|
|
165
|
+
const absFile = path.resolve(rawFile);
|
|
166
|
+
if (!ts.sys.fileExists(absFile))
|
|
167
|
+
fail(`file not found: ${absFile}`);
|
|
168
|
+
const project = createProject(absFile, true);
|
|
169
|
+
const tree = project.service.getNavigationTree(absFile);
|
|
170
|
+
const sf = sourceFileOf(project, absFile);
|
|
171
|
+
const items = (tree.childItems ?? []).filter((c) => c.kind !== ts.ScriptElementKind.alias).map((c) => navToOutline(sf, c, 1)).sort((a, b) => a.line - b.line || a.col - b.col);
|
|
172
|
+
if (json) {
|
|
173
|
+
console.log(JSON.stringify({ file: relPath(absFile), symbols: items }, null, 2));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (items.length === 0)
|
|
177
|
+
noResults(`no top-level symbols in ${relPath(absFile)}`);
|
|
178
|
+
printOutline(items, "");
|
|
179
|
+
}
|
|
180
|
+
function cmdDef(target, json) {
|
|
181
|
+
const project = createProject(target.file, false);
|
|
182
|
+
const pos = offsetOf(sourceFileOf(project, target.file), target);
|
|
183
|
+
const defs = project.service.getDefinitionAtPosition(target.file, pos) ?? [];
|
|
184
|
+
if (defs.length === 0)
|
|
185
|
+
noResults(`no definition found at ${relPath(target.file)}:${target.line}:${target.col}`);
|
|
186
|
+
const rows = defs.map((d) => {
|
|
187
|
+
const loc = locOf(project, d.fileName, d.textSpan.start);
|
|
188
|
+
return { file: relPath(d.fileName), line: loc.line, col: loc.col, kind: d.kind, name: d.name, lineText: loc.lineText };
|
|
189
|
+
});
|
|
190
|
+
if (json) {
|
|
191
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
for (const r of rows)
|
|
195
|
+
console.log(`${r.file}:${r.line}:${r.col} ${r.kind} ${r.name} \u2014 ${r.lineText}`);
|
|
196
|
+
}
|
|
197
|
+
function cmdRefs(target, json) {
|
|
198
|
+
const project = createProject(target.file, false);
|
|
199
|
+
const pos = offsetOf(sourceFileOf(project, target.file), target);
|
|
200
|
+
const refs = project.service.getReferencesAtPosition(target.file, pos) ?? [];
|
|
201
|
+
if (refs.length === 0)
|
|
202
|
+
noResults(`no references found at ${relPath(target.file)}:${target.line}:${target.col}`);
|
|
203
|
+
const defSpans = new Set((project.service.getDefinitionAtPosition(target.file, pos) ?? []).map((d) => `${d.fileName}:${d.textSpan.start}`));
|
|
204
|
+
const rows = refs.map((r) => {
|
|
205
|
+
const loc = locOf(project, r.fileName, r.textSpan.start);
|
|
206
|
+
const role = defSpans.has(`${r.fileName}:${r.textSpan.start}`) ? "def" : r.isWriteAccess ? "write" : "use";
|
|
207
|
+
return { file: relPath(r.fileName), line: loc.line, col: loc.col, role, lineText: loc.lineText };
|
|
208
|
+
}).sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.col - b.col);
|
|
209
|
+
if (json) {
|
|
210
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
for (const r of rows)
|
|
214
|
+
console.log(`${r.file}:${r.line}:${r.col} [${r.role}] ${r.lineText}`);
|
|
215
|
+
console.error(`ts-symbols: ${rows.length} reference(s)${project.configPath ? ` in project ${relPath(project.configPath)}` : " (no tsconfig found \u2014 searched target file and its imports only)"}`);
|
|
216
|
+
}
|
|
217
|
+
function tokenAt(sf, pos) {
|
|
218
|
+
function descend(node) {
|
|
219
|
+
for (const child of node.getChildren(sf)) {
|
|
220
|
+
if (pos >= child.getStart(sf) && pos < child.getEnd())
|
|
221
|
+
return descend(child);
|
|
222
|
+
}
|
|
223
|
+
return node;
|
|
224
|
+
}
|
|
225
|
+
return descend(sf);
|
|
226
|
+
}
|
|
227
|
+
function cmdType(target, json, full) {
|
|
228
|
+
const project = createProject(target.file, false);
|
|
229
|
+
const sf = sourceFileOf(project, target.file);
|
|
230
|
+
const pos = offsetOf(sf, target);
|
|
231
|
+
const info = project.service.getQuickInfoAtPosition(target.file, pos);
|
|
232
|
+
if (!info)
|
|
233
|
+
noResults(`no symbol at ${relPath(target.file)}:${target.line}:${target.col}`);
|
|
234
|
+
const declared = ts.displayPartsToString(info.displayParts);
|
|
235
|
+
const docs = ts.displayPartsToString(info.documentation ?? []);
|
|
236
|
+
let expanded;
|
|
237
|
+
if (full) {
|
|
238
|
+
const program = project.service.getProgram();
|
|
239
|
+
const checker = program?.getTypeChecker();
|
|
240
|
+
if (checker) {
|
|
241
|
+
const node = tokenAt(sf, pos);
|
|
242
|
+
const type = checker.getTypeAtLocation(node);
|
|
243
|
+
expanded = checker.typeToString(type, node, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (json) {
|
|
247
|
+
console.log(JSON.stringify({ kind: info.kind, declared, expanded, documentation: docs || undefined }, null, 2));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
console.log(declared);
|
|
251
|
+
if (expanded && expanded !== declared)
|
|
252
|
+
console.log(`expanded: ${expanded}`);
|
|
253
|
+
if (docs)
|
|
254
|
+
console.log(`doc: ${docs.split(`
|
|
255
|
+
`)[0]}`);
|
|
256
|
+
}
|
|
257
|
+
function main({ command, rawTarget, json, full }) {
|
|
258
|
+
switch (command) {
|
|
259
|
+
case "outline":
|
|
260
|
+
return cmdOutline(rawTarget, json);
|
|
261
|
+
case "def":
|
|
262
|
+
return cmdDef(parseTarget(rawTarget), json);
|
|
263
|
+
case "refs":
|
|
264
|
+
return cmdRefs(parseTarget(rawTarget), json);
|
|
265
|
+
case "type":
|
|
266
|
+
return cmdType(parseTarget(rawTarget), json, full);
|
|
267
|
+
default:
|
|
268
|
+
fail(`unknown command "${command}"
|
|
269
|
+
${USAGE}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
main(request);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dev-planner
|
|
3
3
|
description: "Use when writing or parsing a PLAN-<stub>.md for the rafa loop — block syntax, task lines, and report format."
|
|
4
|
+
provenance: first-party
|
|
4
5
|
prevents: "plan tasks the loop's parser silently skips because they are shaped wrong"
|
|
5
6
|
signal: silent
|
|
6
7
|
when_to_use: "You are generating a plan document for the rafa loop, or reading one the loop will parse. Prevents: plan tasks the loop's parser silently skips because they are shaped wrong"
|
|
@@ -222,6 +223,7 @@ findings:
|
|
|
222
223
|
resolution: "wrap append in try/finally"
|
|
223
224
|
artifact: "EMFILE: too many open files"
|
|
224
225
|
signal: loud
|
|
226
|
+
domain: debugging
|
|
225
227
|
- trigger: "SQLite schema migration timing"
|
|
226
228
|
kind: pattern
|
|
227
229
|
what: "migrations run on every store init, not just schema changes"
|
|
@@ -261,6 +263,7 @@ Finding entry fields:
|
|
|
261
263
|
| `resolution` | string | no | How to fix or work around it |
|
|
262
264
|
| `artifact` | string | no | Error message, file path, or code snippet that signals the finding |
|
|
263
265
|
| `signal` | string | yes | `loud` (it surfaced as a failure) or `silent` (it passed while wrong) |
|
|
266
|
+
| `domain` | string | no | The area of work the finding's lesson belongs to: `code-style`, `testing`, `git`, `debugging`, `workflow`, `security` or `general-best-practices`. A value outside the set is reported as an issue and read as absent |
|
|
264
267
|
|
|
265
268
|
Change note entry fields:
|
|
266
269
|
|
|
@@ -346,11 +349,10 @@ The strip happens where the loop reads the next task, `findNextTask` in `src/uti
|
|
|
346
349
|
prompt, the operator log and the commit message: a declaration is a
|
|
347
350
|
planning annotation, never an instruction.
|
|
348
351
|
* Pick the agent by the task's SHAPE — not its subject or its verb —
|
|
349
|
-
from the
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
keys instead of an agent.
|
|
352
|
+
from the task routing table below in this prompt. Prefer a row whose
|
|
353
|
+
third column names a tracked `.claude/agents/<name>.md` file, since a
|
|
354
|
+
`user-level` row does not travel with a fresh clone. Where no row fits,
|
|
355
|
+
declare the granular keys instead of an agent.
|
|
354
356
|
|
|
355
357
|
There is no default agent. A task with no declaration passes no routing flag and runs at the loop's defaults, as does one whose every value failed to parse.
|
|
356
358
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: documentation
|
|
3
|
+
description: "Use when writing TSDoc, wiring TypeDoc, documenting a route with OpenAPI, or choosing between `.docs/` and `docs/`."
|
|
4
|
+
provenance: first-party
|
|
5
|
+
tags:
|
|
6
|
+
- tsdoc
|
|
7
|
+
- typedoc
|
|
8
|
+
- openapi
|
|
9
|
+
- swagger
|
|
10
|
+
- express
|
|
11
|
+
- mcp
|
|
12
|
+
- react
|
|
13
|
+
prevents: undocumented code and a docs command that wipes the tracked docs tree while exiting clean
|
|
14
|
+
signal: silent
|
|
15
|
+
when_to_use: "You are writing TSDoc, wiring documentation tooling, or deciding where a new document belongs. Prevents: undocumented code and a docs command that wipes the tracked docs tree while exiting clean"
|
|
16
|
+
stack:
|
|
17
|
+
- typescript
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
> Scope: file paths in this document are relative to `packages/service/` (the `@ar/service` package), except `.claude/`, the plans directory, the specs directory, and `tools/`, which live at the umbrella repo root.
|
|
21
|
+
|
|
22
|
+
# Documentation Skill
|
|
23
|
+
|
|
24
|
+
This skill defines how to document code in this repo.
|
|
25
|
+
Two kinds of documentation are maintained:
|
|
26
|
+
|
|
27
|
+
| Kind | Tool | Output | When to generate |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| **Code (TypeDoc)** | `typedoc` | typedoc's default `out`, which resolves to the TRACKED `docs/` tree | `bun run docs:generate` |
|
|
30
|
+
| **API (Swagger)** | `@asteasolutions/zod-to-openapi` + `swagger-ui-express` | `openapi.json` + served behind `ctx.requireAuth` at `GET /docs/` | `bun run docs:openapi` |
|
|
31
|
+
|
|
32
|
+
> Both rows are INSTALLED in `@ar/service` — the OpenAPI half is no longer a
|
|
33
|
+
> convention to adopt on some later day, and [`rules/openapi.md`](rules/openapi.md)
|
|
34
|
+
> records what is there rather than a setup to perform. The two scripts are
|
|
35
|
+
> SIBLINGS sharing no output tree, which is worth stating because the pair of
|
|
36
|
+
> names suggests they do: `docs:openapi` writes the gitignored `.docs/swagger/`,
|
|
37
|
+
> while `docs:generate` is a bare `typedoc` carrying no `typedoc.json` and no
|
|
38
|
+
> `typedocOptions` key in either `package.json` or `tsconfig.json` — all three
|
|
39
|
+
> measured absent — so it takes typedoc's own default `out`, the TRACKED `docs/`
|
|
40
|
+
> tree beside it, with `cleanOutputDir` defaulting true. Running it therefore
|
|
41
|
+
> DELETES that tree before writing (measured once at typedoc 0.28.20: 14 tracked
|
|
42
|
+
> files removed, at exit 0, for a site documenting nothing).
|
|
43
|
+
|
|
44
|
+
Manual documentation (ADRs, design notes) lives in `docs/` and is never gitignored.
|
|
45
|
+
Auto-generated outputs go in `.docs/` (dotfolder) and are always gitignored.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Rules index
|
|
50
|
+
|
|
51
|
+
| Rule file | When to use |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| [`rules/tsdoc.md`](rules/tsdoc.md) | Writing TSDoc comments on any TypeScript symbol |
|
|
54
|
+
| [`rules/openapi.md`](rules/openapi.md) | Documenting Express routes with OpenAPI/Swagger |
|
|
55
|
+
| [`rules/typedoc-setup.md`](rules/typedoc-setup.md) | Adding TypeDoc infra to the repo (or a project derived from it) |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Workflow
|
|
60
|
+
|
|
61
|
+
1. **Determine kind before touching**: identify whether documentation is manual or auto-generated before editing. Manual docs live in `docs/`; auto-generated outputs live in `.docs/`.
|
|
62
|
+
2. **Manual docs**: include author and date in the file header. Never gitignore `docs/`.
|
|
63
|
+
3. **Auto-generated docs**: outputs go in `.docs/` (dotfolder). If adding a new documentation tool, add its output folder to `.gitignore`.
|
|
64
|
+
4. **Update in parallel**: update documentation before or in parallel with code changes — do not defer docs to a follow-up PR.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick checklist
|
|
69
|
+
|
|
70
|
+
Before marking a documentation task done:
|
|
71
|
+
|
|
72
|
+
- [ ] Every exported function, class, and interface has a TSDoc block with `@param` and `@returns`.
|
|
73
|
+
- [ ] Every file has a `@packageDocumentation` comment (or is covered by a module-level JSDoc).
|
|
74
|
+
- [ ] Every Express router factory is documented with `@remarks` listing its HTTP endpoints.
|
|
75
|
+
- [ ] `typedoc.json`, `tsconfig.docs.json`, and a `docs:generate` script exist (see [`rules/typedoc-setup.md`](rules/typedoc-setup.md)).
|
|
76
|
+
- [ ] `openapi.ts` exists and every route is registered in it — `openapi-coverage.test.ts` is the gate that says so, and a route added without a binding-table entry reddens it.
|
|
77
|
+
- [ ] `.docs/` is already gitignored twice over and needs no new entry: once at the repo root (`.gitignore`) and again at the package (`packages/service/.gitignore`), both files tracked. Running `git check-ignore -v` on the generated `openapi.json` names the PACKAGE rule, the nearer file winning — take the source off that output rather than predicting it.
|
|
78
|
+
- [ ] `docs/` (manual ADRs) is **not** gitignored.
|
|
79
|
+
- [ ] `bun run docs:openapi` passes with 0 errors. Do NOT reach for `bun run docs:generate` as a verification: it exits 0 while WIPING the tracked `docs/` tree (see the note under the table above).
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Toolchain
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
typedoc → HTML docs from TSDoc comments → the TRACKED docs/ tree
|
|
87
|
+
@asteasolutions/zod-to-openapi → OpenAPI document from the Zod schemas the routes parse with
|
|
88
|
+
swagger-ui-express → serves that document behind ctx.requireAuth at GET /docs/
|
|
89
|
+
export-openapi.ts → writes openapi.json, via bun run docs:openapi
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Dependencies go in `devDependencies` where the toolchain is only ever a test's or a generator's — `@seriousme/openapi-schema-validator`, `@types/swagger-ui-express` and `typedoc` are all there.
|
|
93
|
+
BOTH OpenAPI runtime halves sit in `dependencies` instead, and not just `swagger-ui-express`: `src/index.ts` imports `generateOpenApiDocument` at boot to build the document it serves, so `@asteasolutions/zod-to-openapi` is a runtime dependency too.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: git-workflow
|
|
3
|
+
description: "Use when pushing a branch, opening or updating a PR, naming a branch, rebasing, or force-pushing after a rebase."
|
|
4
|
+
provenance: first-party
|
|
5
|
+
prevents: duplicate PRs and outbound PR bodies published with leaked references and wrong diff counts
|
|
6
|
+
signal: silent
|
|
7
|
+
when_to_use: "You are naming a branch, pushing it, opening or updating a pull request, or recovering from a rebase. Prevents: duplicate PRs and outbound PR bodies published with leaked references and wrong diff counts"
|
|
8
|
+
tags:
|
|
9
|
+
- git
|
|
10
|
+
- workflow
|
|
11
|
+
- javascript
|
|
12
|
+
- shell
|
|
13
|
+
stack:
|
|
14
|
+
- javascript
|
|
15
|
+
- shell
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Git Workflow
|
|
19
|
+
|
|
20
|
+
## Branch, commit, and PR conventions
|
|
21
|
+
|
|
22
|
+
**Branch names:** `<type>/<issue-number>-<slug>` when the work is tied to a
|
|
23
|
+
GitHub issue — e.g. `feat/42-user-auth` — and plain `<type>/<slug>` otherwise.
|
|
24
|
+
`<type>` is one of the conventional-commit types (`feat fix chore docs
|
|
25
|
+
refactor test perf ci spike`).
|
|
26
|
+
|
|
27
|
+
**Commits** follow conventional commits: `type(scope): subject`.
|
|
28
|
+
|
|
29
|
+
**PR bodies** link the issue with GitHub's closing keywords: `Closes #42` for
|
|
30
|
+
a PR that finishes the issue, or `Refs #42` for one that only partially
|
|
31
|
+
addresses it. This distinction is load-bearing: only `Closes` triggers
|
|
32
|
+
GitHub's auto-close on merge. Working without an issue is fine — such a PR
|
|
33
|
+
simply carries no issue link; do not invent one to satisfy the convention.
|
|
34
|
+
|
|
35
|
+
### rafa project naming convention
|
|
36
|
+
|
|
37
|
+
For the rafa project specifically, issue numbers are prefixed with `rafa-` and
|
|
38
|
+
reflected throughout the spec and branch workflow:
|
|
39
|
+
|
|
40
|
+
- **Issue reference** (in prose): `#rafa-<n>` — e.g., `#rafa-20`
|
|
41
|
+
- **Spec file**: `.rafa/specs/rafa-<n>-<slug>.md` — e.g., `.rafa/specs/rafa-20-pr-commands.md`
|
|
42
|
+
- **Plan stub** (in prose): `rafa-<n>-<slug>` — e.g., `rafa-20-pr-commands`
|
|
43
|
+
- **Branch name**: `feat/rafa-<n>-<slug>` — e.g., `feat/rafa-20-pr-commands`
|
|
44
|
+
- **PR title**: `rafa-<n>: <title>` — e.g., `rafa-20: Add PR commands`
|
|
45
|
+
- **PR body closing line**: `Closes #<n>` — e.g., `Closes #20` (GitHub issue #20, not
|
|
46
|
+
`#rafa-20`; the prefix is a rafa naming convention, not a GitHub issue alias)
|
|
47
|
+
|
|
48
|
+
## Pushing and opening PRs
|
|
49
|
+
|
|
50
|
+
When pushing changes and creating PRs:
|
|
51
|
+
|
|
52
|
+
1. Always check if the branch has been pushed before. If not, use `git push -u` to set the upstream tracking branch on the first push. This ensures that future pushes and `gh pr create` commands work smoothly without needing to specify the remote/branch each time.
|
|
53
|
+
2. If the branch already has an associated PR, push to whichever remote the branch is tracking.
|
|
54
|
+
3. If the branch hasn't been pushed before, default to pushing to `origin`.
|
|
55
|
+
|
|
56
|
+
## PR-body close-out discipline (measured, this repo)
|
|
57
|
+
|
|
58
|
+
A PR body is the one piece of prose here that is OUTBOUND, unreviewable by any
|
|
59
|
+
gate, and irreversible once `gh pr create` returns. Four checks, all cheap:
|
|
60
|
+
|
|
61
|
+
1. **Sweep it for origin needles before the push.** `findForbiddenMatches(content,
|
|
62
|
+
path)` takes CONTENT, so one /tmp `.mjs` under bun can feed it the body file
|
|
63
|
+
alongside `git ls-files`, with a fragment-built planted control in the same run.
|
|
64
|
+
Re-run it immediately before `gh pr create` — a close-out sweep ordered ahead
|
|
65
|
+
of the tasks that finish the body swept a draft nobody will publish.
|
|
66
|
+
2. **Pre-flight with a control.** `gh pr list --head <branch> --state all` returning
|
|
67
|
+
nothing is a zero-hit scan; bare `--state open`, or `gh pr view`, both miss a
|
|
68
|
+
CLOSED prior PR on the same head. Send a known-existing head through the
|
|
69
|
+
IDENTICAL command shape in the same call so the empty result is readable.
|
|
70
|
+
Without it, a broken filter is indistinguishable from "no PR yet" — and the
|
|
71
|
+
consequence is a duplicate PR. The control's PR must be CLOSED or MERGED, not
|
|
72
|
+
merely one that HAS a PR: `--state all` exists precisely because the open-only
|
|
73
|
+
form misses a non-open prior PR, so an OPEN control proves only that the
|
|
74
|
+
command can find something and says nothing about the half whose failure
|
|
75
|
+
produces the duplicate. Measured opening one PR, the subject printed `<none>`
|
|
76
|
+
beside `25 MERGED` and `11 CLOSED` from two known heads.
|
|
77
|
+
3. **Re-read the REMOTE after creating.** The sweep proves the FILE is clean; only
|
|
78
|
+
`gh pr view <n> --json body -q .body` diffed against the swept file (normalise
|
|
79
|
+
CRLF and the trailing newline) proves the swept bytes are the published ones.
|
|
80
|
+
It is the only check that catches a `--body-file` pointed at a stale draft.
|
|
81
|
+
4. **Hold the four numbers three-dot, against the REMOTE base.** GitHub computes
|
|
82
|
+
`additions`/`deletions`/`changedFiles` from the base branch as the REMOTE has
|
|
83
|
+
it, so the local side is
|
|
84
|
+
`git diff --numstat $(git merge-base origin/main HEAD)..HEAD` summed after a
|
|
85
|
+
fetch — never `main..HEAD` two-dot, which drifts the moment main moves, and
|
|
86
|
+
never local `main` without checking. PRINT the local and remote merge-bases
|
|
87
|
+
against each other rather than assuming they agree: measured at one tip, local
|
|
88
|
+
`main` was 62 commits BEHIND `origin/main` (an ancestor, not diverged) while
|
|
89
|
+
both merge-bases with HEAD still resolved to the SAME commit — a coincidence
|
|
90
|
+
that would not survive the local branch moving past the fork point, and a
|
|
91
|
+
diverged local `main` otherwise attributes somebody else's commits to this
|
|
92
|
+
PR's diff with every number still looking plausible. Sum `--numstat`, not
|
|
93
|
+
`--shortstat`, so a binary file shows as `-` instead of silently contributing
|
|
94
|
+
zero, and report the binary count beside the totals. Read the commits list's
|
|
95
|
+
LAST oid against `git rev-parse HEAD` beside the counts: a count can agree
|
|
96
|
+
while the head differs.
|
|
97
|
+
5. **A CONFLICTING PR schedules NO workflow run at all**, so `gh pr checks <n>`
|
|
98
|
+
printing `no checks reported` is ambiguous between a run that has not
|
|
99
|
+
scheduled yet and one that never will. GitHub builds a `pull_request` run
|
|
100
|
+
against `refs/pull/<n>/merge`, which it cannot create for a PR that does not
|
|
101
|
+
merge cleanly — so the reading is a ref existence check, not a guess:
|
|
102
|
+
`git ls-remote origin 'refs/pull/<n>/*'` answers `head` alone where a
|
|
103
|
+
MERGEABLE control PR answers `head` AND `merge`. That ref check is the
|
|
104
|
+
WHOLE reading and `mergeable` is only a corroborator, because GitHub
|
|
105
|
+
computes mergeability lazily and `gh` does not poll for it: measured
|
|
106
|
+
opening one PR, the subject answered `CONFLICTING` while BOTH mergeable
|
|
107
|
+
control PRs answered `UNKNOWN` and still exposed `refs/pull/<n>/merge`.
|
|
108
|
+
So `UNKNOWN` beside a present merge ref is the HEALTHY shape, not a
|
|
109
|
+
second finding — never let it stand in for the ref check. Name the
|
|
110
|
+
conflict set with
|
|
111
|
+
`git merge-tree --write-tree --name-only origin/main HEAD`, which
|
|
112
|
+
changes no worktree state. Parse it carefully: its FIRST line is the
|
|
113
|
+
written TREE OID, so a probe reading the whole block as paths reports a
|
|
114
|
+
40-hex string as a conflicting file. The name list is the CONFLICT set
|
|
115
|
+
and not the changed-file set (the narrative below it carries
|
|
116
|
+
`Auto-merging <path>` lines for files that merged cleanly), and exit 1
|
|
117
|
+
is the signal rather than an error. Exit 1 has TWO causes though, a real
|
|
118
|
+
conflict or a failed invocation, so a CLEAN exit 0 is only worth trusting
|
|
119
|
+
beside a liveness control: build a two-sided conflict from loose objects
|
|
120
|
+
in a scratch repository (`git hash-object -w`, `git mktree`,
|
|
121
|
+
`git commit-tree` — no ref moved, no working tree touched), read it
|
|
122
|
+
through the same command, and confirm it answers exit 1 with a `CONFLICT`
|
|
123
|
+
line before believing the subject's exit 0. Do NOT rebase to clear it as an
|
|
124
|
+
afterthought inside a PR task: every commit oid moves, and a body whose
|
|
125
|
+
suite figures were measured at the pre-rebase tip stops describing the
|
|
126
|
+
head it is attached to.
|
|
127
|
+
|
|
128
|
+
**Every suite figure in the body must come from a run at the PR's OWN head.** A
|
|
129
|
+
loop that commits per task makes all plausible sources disagree and each was
|
|
130
|
+
correct when written (measured 679, then 690, then 692 cases across one plan, no
|
|
131
|
+
regression). Read the `pretest` stamp in the same capture, so the run is
|
|
132
|
+
simultaneously the artifact-freshness check and the proof the tree is publishable.
|
|
133
|
+
|
|
134
|
+
**Where the branch is carried-in red**, the hosted gate confirms the body's red
|
|
135
|
+
claim and is simultaneously NOT evidence about its suite figures: `back.yml` runs
|
|
136
|
+
the type-check before the suite under `bash -e`, so the job halts and vitest never
|
|
137
|
+
runs. Report both halves, or a reader seeing a red `test` job assumes the suite
|
|
138
|
+
is what failed.
|
|
139
|
+
|
|
140
|
+
## `gh pr create` branch detection
|
|
141
|
+
|
|
142
|
+
If `gh pr create` says `you must first push the current branch to a remote` even though `git push -u` succeeded, create the PR with an explicit head ref:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
gh pr create --head <owner>:<branch> ...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## `gh issue close --reason` takes two words
|
|
149
|
+
|
|
150
|
+
`--reason` accepts only `completed` and `not planned`, and `not planned` is
|
|
151
|
+
two words: written bare, `gh` parses `planned` as a positional argument and
|
|
152
|
+
the close fails. Quote it.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
gh issue close <n> --reason "not planned" --comment "<why>"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## GITHUB_TOKEN and workflow chaining
|
|
159
|
+
|
|
160
|
+
Actions performed using the default `GITHUB_TOKEN` (including labels added by `github-actions[bot]` via `actions/github-script`) do **not** trigger `pull_request_target` or other workflow events. This is a GitHub limitation to prevent infinite loops. If one workflow adds a label that should trigger another workflow (e.g., a labeling workflow adds `cc:rebase` to trigger a rebase workflow), the label-adding step must use a **PAT** or **GitHub App token** (e.g., a `PR_RW_GITHUB_TOKEN` secret) instead of `GITHUB_TOKEN`.
|
|
161
|
+
|
|
162
|
+
## GitHub API calls with special characters
|
|
163
|
+
|
|
164
|
+
When using `gh api` to post comments or replies containing backticks, `$()`, or other shell metacharacters, security hooks may block the command. Instead of passing the body inline with `-f body="..."`, write a JSON file and use `--input`:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Write JSON body to a scratch file (use the Write tool, not echo/cat),
|
|
168
|
+
# somewhere untracked such as reply_body.json under a tmp/ directory
|
|
169
|
+
# {"body": "Your comment with `backticks` and special chars"}
|
|
170
|
+
|
|
171
|
+
gh api repos/${GH_ORG}/${GH_REPO_NAME}/pulls/123/comments/456/replies --input reply_body.json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Force-pushing after rebase with changes ahead and behind head
|
|
175
|
+
|
|
176
|
+
When rebasing a PR branch that has both new commits in the PR and new commits in upstream/main (i.e., the branch is both ahead and behind), the rebase will create a new commit history that doesn't match the existing remote branch. In this case, you must force-push to update the PR branch with the rebased commits. Use `--force-with-lease` to avoid overwriting others' work, but if the branch is tracking a different remote (e.g., `origin` instead of `upstream`), and the solution is plain `--force` do not execute. The allowed force push command in this scenario is:
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
git push --force-with-lease
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Note:** Plain `--force` can overwrite others' remote commits. Inform the team when this scenario comes up, and `--force-with-lease` cannot work. In normal setups, always prefer `--force-with-lease`.
|
|
184
|
+
|
|
185
|
+
## Rebase workflow and conflict resolution
|
|
186
|
+
|
|
187
|
+
### Handling unstaged changes during rebase
|
|
188
|
+
|
|
189
|
+
If `git rebase` fails with "You have unstaged changes" (common with spurious `package-lock.json` changes):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git stash push -m "Stashing changes before rebase"
|
|
193
|
+
git rebase origin/main
|
|
194
|
+
git stash pop
|
|
195
|
+
```
|
|
196
|
+
The stashed changes will be automatically merged back after the rebase completes.
|
|
197
|
+
|
|
198
|
+
**Note:** **Always** use `git stash pop` to restore a single stash after the rebase, rather than `git stash apply`, to avoid accidentally applying stash leftovers or the same stash multiple times if you need to rebase again.
|
|
199
|
+
|
|
200
|
+
## Rebasing with uncommitted changes
|
|
201
|
+
|
|
202
|
+
If you need to rebase but have uncommitted changes (e.g., package-lock.json from startup npm install):
|
|
203
|
+
|
|
204
|
+
1. Stash changes: `git stash push -m "Stash changes before rebase"`
|
|
205
|
+
2. Rebase: `git rebase origin/main` (resolve conflicts if needed)
|
|
206
|
+
3. After rebase completes, review stashed changes: `git stash show -p`
|
|
207
|
+
4. If stashed changes are spurious (e.g., package-lock.json peer markers when package.json conflicts were resolved during rebase), drop the stash: `git stash drop`
|
|
208
|
+
5. Otherwise, pop stash: `git stash pop` and discard spurious changes: `git restore package-lock.json` (if package.json unchanged)
|
|
209
|
+
|
|
210
|
+
This prevents rebase conflicts from uncommitted changes while preserving any work in progress.
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
### Conflict resolution tips
|
|
214
|
+
|
|
215
|
+
- **Modify/delete conflicts**: When a rebase shows `CONFLICT (modify/delete): <file> deleted in <commit> and modified in HEAD`, use `git rm <file>` (not `git add`) to resolve by confirming the deletion. Use `git add <file>` only when you want to keep the modified version instead.
|
|
216
|
+
- **Before rebasing:** If `bun install` modified `package-lock.json` (common in CI/local), discard changes with `git restore package-lock.json` to avoid "unstaged changes" errors
|
|
217
|
+
- When resolving import conflicts (e.g., `<<<<<<< HEAD` with different imports), keep **both** imports if both are valid and needed by the component
|
|
218
|
+
- If both sides of a conflict have valid imports/hooks, keep both and remove any duplicate constant redefinitions
|
|
219
|
+
- **Complementary additions**: When both sides added new sections at the end of a file (e.g., both added different documentation tips), keep both sections rather than choosing one — they're not truly conflicting, just different additions
|
|
220
|
+
- **Preserve variable declarations used in common code**: When one side of a conflict declares a variable (e.g., `const iframe = po.previewPanel.getPreviewIframeElement()`) that is referenced in non-conflicting code between or after conflict markers, keep the declaration even when adopting the other side's verification approach — the variable is needed regardless of which style you choose
|
|
221
|
+
- **React component wrapper conflicts**: When rebasing UI changes that conflict on wrapper div classes (e.g., `flex items-start space-x-2` vs `flex items-end gap-1`), keep the newer styling from the incoming commit but preserve any functional components (like dialogs or modals) that exist in HEAD but not in the incoming change
|
|
222
|
+
- **Refactoring conflicts**: When incoming commits refactor code (e.g., extracting inline logic into helper functions), and HEAD has new features in the same area, integrate HEAD's features into the new structure. Example: if incoming code moves streaming logic to `runSingleStreamPass()` and HEAD adds mid-turn compaction to the inline code, add compaction support to the new function rather than keeping the old inline version
|
|
223
|
+
|
|
224
|
+
## Git orphan branch pitfalls
|
|
225
|
+
|
|
226
|
+
- **`git checkout -` does not work after `git checkout --orphan`** — git does not record the pre-orphan branch in a way that `-` can resolve. Always capture the current branch before switching: `PREV=$(git branch --show-current)`, then return explicitly with `git checkout "$PREV"`.
|
|
227
|
+
- **Orphan commits break pre-commit hooks that reference `HEAD^1`** — a hook that diffs or lints against the parent commit fails with `fatal: ambiguous argument 'HEAD^1'` on a root commit. Hooks that require a parent commit are incompatible with orphan branches.
|
|
228
|
+
- **`git checkout <branch>` after staging on a side branch discards untracked working-tree files** — when you commit untracked files on branch B and then `git checkout A`, git restores the working tree to branch A's HEAD, removing any files that were committed on B but never existed on A. This affects both orphan branches and regular side branches used as checkpoint mechanisms — all accumulated task output files vanish on checkout-back.
|
|
229
|
+
|
|
230
|
+
## Shallow clone push limitations
|
|
231
|
+
|
|
232
|
+
- **"shallow update not allowed" when pushing to an empty remote** — a shallow clone can only push to a remote that already has the shallow boundary commit in its history. Pushing a tag or branch from `git clone --depth 1` to a bare repo with no objects fails. Fix: use the project's existing `origin` (which has full history), not a new empty remote. `git fetch --unshallow` makes the clone full but is expensive and defeats the depth-1 optimization.
|
|
233
|
+
- **All git refs in an ephemeral shallow clone are ephemeral** — if a cleanup step destroys the clone's directory, every pack object, tag, and branch goes with it. Refs must be pushed to the origin remote (which has the shallow boundary) before teardown if they need to survive.
|
|
234
|
+
|
|
235
|
+
## Tracking Tasks
|
|
236
|
+
|
|
237
|
+
1. **Commit often** with clear, descriptive messages following conventional commits format.
|
|
238
|
+
2. **Do not** include AI attribution in commit messages.
|
|
239
|
+
3. **Do not** create PRs without passing tests and lint checks — run all relevant tools first.
|
|
240
|
+
4. Follow [Branch, commit, and PR conventions](#branch-commit-and-pr-conventions)
|
|
241
|
+
above for branch names and PR bodies — nothing automates it here, so write
|
|
242
|
+
the `Closes #n` / `Refs #n` line by hand when the work is tied to an issue.
|