agent-docs-bootstrap 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +6 -5
- package/install.mjs +51 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follo
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.4.1] - 2026-10-27
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- A `.DS_Store` or other OS file inside an installed folder counted as a change: the first run skipped the path and dropped its marker line, and later runs called it "not installed by this kit", so updates stopped for the whole team once the marker was committed. Dot files, `Thumbs.db` and `desktop.ini` are now left out of content hashes; a folder without them hashes exactly as in 0.4.0.
|
|
11
|
+
- A path that exists without a marker line but holds content a kit version wrote, byte for byte, is recognised, updated and listed again, so installs stuck on 0.4.0 recover with a normal run.
|
|
12
|
+
- A shipped path the user changed keeps its marker line and old hash, so every run reports the same thing about it instead of "changed" first and "not installed by this kit" afterwards.
|
|
13
|
+
- The installer exits non-zero and lists the paths it could not install when a shipped path is skipped, so an agent or CI notices an incomplete install. A changed path the kit no longer ships is only a warning.
|
|
14
|
+
|
|
7
15
|
## [0.4.0] - 2026-10-27
|
|
8
16
|
|
|
9
17
|
### Changed
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ You need Node.js 18+ and a git repository.
|
|
|
17
17
|
1. From your project root, install the skills and subagents:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx agent-docs-bootstrap@0.4.
|
|
20
|
+
npx agent-docs-bootstrap@0.4.1 --target claude
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Replace `claude` with `codex`, `opencode`, a list such as `claude,codex`, or `all`.
|
|
@@ -103,19 +103,19 @@ All options run the same installer from your project root. Pin the version: the
|
|
|
103
103
|
**From npm** (recommended):
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
|
-
npx agent-docs-bootstrap@0.4.
|
|
106
|
+
npx agent-docs-bootstrap@0.4.1 --target claude
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
**From a GitHub release tag**, when the npm registry is blocked or unavailable:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
npx github:TraneDev/agent-docs-bootstrap#v0.4.
|
|
112
|
+
npx github:TraneDev/agent-docs-bootstrap#v0.4.1 --target claude
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
**From a clone**, to read or change the kit first:
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
git clone --branch v0.4.
|
|
118
|
+
git clone --branch v0.4.1 https://github.com/TraneDev/agent-docs-bootstrap.git ~/agent-docs-bootstrap
|
|
119
119
|
node ~/agent-docs-bootstrap/install.mjs --target claude
|
|
120
120
|
```
|
|
121
121
|
|
|
@@ -134,7 +134,8 @@ Two skills are installed, `docs-bootstrap` and `docs-check`. The guides by other
|
|
|
134
134
|
`.agent-docs-bootstrap` lists every installed path with a hash of what was written. The installer replaces or removes a path only if it is listed there and its content still matches, so it never touches files it did not create or that you changed:
|
|
135
135
|
|
|
136
136
|
- a path that exists but is not listed is skipped with a warning;
|
|
137
|
-
- a listed path whose content you changed, for example a skill you replaced with your own version,
|
|
137
|
+
- a listed path whose content you changed is left in place, with a warning, and the installer exits non-zero; a changed path the kit no longer ships, for example a skill you replaced with your own version, becomes yours and drops out of the marker;
|
|
138
|
+
- files the OS adds to the kit's folders, such as `.DS_Store`, `Thumbs.db` or `desktop.ini`, do not count as a change;
|
|
138
139
|
- a marker line not shaped like a path the kit installs (for example `src` or `../x`, added by a bad merge) is ignored with a warning.
|
|
139
140
|
|
|
140
141
|
### Other agents
|
package/install.mjs
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
// codex skills → .agents/skills/ agents → .codex/agents/*.toml
|
|
12
12
|
// opencode skills → .agents/skills/ agents → .opencode/agents/*.md commands → .opencode/commands/*.md
|
|
13
13
|
// Installed paths are listed in .agent-docs-bootstrap with a hash of what was written. A path is replaced or removed
|
|
14
|
-
// only if
|
|
15
|
-
//
|
|
16
|
-
// longer ships are removed under the
|
|
14
|
+
// only if its line has the shape of a path the kit writes (<layout folder>/<kit name>) and its content still matches
|
|
15
|
+
// that hash or something a version of the kit wrote; OS files such as .DS_Store do not count. A changed path is left
|
|
16
|
+
// in place and the run exits non-zero. On an update, listed paths this version no longer ships are removed under the
|
|
17
|
+
// same rule, or left to the user if changed.
|
|
17
18
|
// No network access, no changes to harness settings, no admin rights.
|
|
18
19
|
|
|
19
20
|
import fs from 'node:fs';
|
|
@@ -73,8 +74,19 @@ const LEGACY = new Set([
|
|
|
73
74
|
'c87450584ed42c8b', 'cf2f8a40c91f3c45', 'dac09810ceb351f7', 'dc418221c7363caa', 'f741ba5711364c55',
|
|
74
75
|
'fceff4c3b3159c80', 'fd975138dcda6b38', 'fe412d3c3c312f7a',
|
|
75
76
|
]);
|
|
77
|
+
|
|
78
|
+
const skills = skillDirs();
|
|
79
|
+
const agents = AGENTS.map(n => parseAgent(path.join(KIT, 'agents', `${n}.md`)));
|
|
80
|
+
const models = { 'docs-scout': a['scout-model'], 'docs-reader': a['reader-model'] };
|
|
81
|
+
const COMMANDS = [['docs-bootstrap', "Build the project's reference documentation"], ['docs-check', "Optional: test the project's documentation with a fresh reader"]];
|
|
82
|
+
const commandBody = (name, what) => `---\ndescription: ${JSON.stringify(`${what} (${name})`)}\n---\nLoad the \`${name}\` skill and follow it. Arguments from the developer, if any: $ARGUMENTS\n`;
|
|
83
|
+
// What this version writes when no model is pinned. Content that matches it or LEGACY byte for byte is the kit's,
|
|
84
|
+
// even where the marker lost its line (0.4.0 counted a .DS_Store as a change and dropped the line).
|
|
85
|
+
const CURRENT = new Set([...skills.map(d => contentHash(d)),
|
|
86
|
+
...TARGETS.flatMap(t => agents.map(ag => textHash(render(t, ag)))), ...COMMANDS.map(([n, w]) => textHash(commandBody(n, w)))]);
|
|
87
|
+
const isKitContent = (h) => LEGACY.has(h) || CURRENT.has(h);
|
|
76
88
|
// The path is the kit's and nobody changed it since it was installed.
|
|
77
|
-
const isUnchanged = (r) => { const h = contentHash(abs(r)); return
|
|
89
|
+
const isUnchanged = (r) => { const h = contentHash(abs(r)); return h === old.get(r) || isKitContent(h); };
|
|
78
90
|
const changedNote = (r) => `${r} changed since it was installed, so it is left in place as yours`;
|
|
79
91
|
if (a.uninstall) { uninstall(); process.exit(0); }
|
|
80
92
|
|
|
@@ -90,11 +102,18 @@ if (!GLOBAL) {
|
|
|
90
102
|
|
|
91
103
|
const installed = new Map();
|
|
92
104
|
const released = new Set();
|
|
105
|
+
const shipped = new Set();
|
|
106
|
+
const skipped = [];
|
|
107
|
+
// A shipped path the user changed keeps its marker line and old hash, so every run says the same thing about it.
|
|
93
108
|
function writeOwned(r, fn) {
|
|
94
|
-
if (
|
|
109
|
+
if (shipped.has(r)) return false;
|
|
110
|
+
shipped.add(r);
|
|
95
111
|
const dst = abs(r);
|
|
96
|
-
if (exists(dst)
|
|
97
|
-
|
|
112
|
+
if (exists(dst)) {
|
|
113
|
+
const why = !old.has(r) && !isKitContent(contentHash(dst)) ? 'exists and was not installed by this kit'
|
|
114
|
+
: old.has(r) && !isUnchanged(r) ? 'changed since it was installed' : '';
|
|
115
|
+
if (why) { warn(`skipped ${r}: ${why}`); skipped.push(r); return false; }
|
|
116
|
+
}
|
|
98
117
|
fs.rmSync(dst, { recursive: true, force: true });
|
|
99
118
|
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
100
119
|
fn(dst);
|
|
@@ -102,10 +121,6 @@ function writeOwned(r, fn) {
|
|
|
102
121
|
return true;
|
|
103
122
|
}
|
|
104
123
|
|
|
105
|
-
const skills = skillDirs();
|
|
106
|
-
const agents = AGENTS.map(n => parseAgent(path.join(KIT, 'agents', `${n}.md`)));
|
|
107
|
-
const models = { 'docs-scout': a['scout-model'], 'docs-reader': a['reader-model'] };
|
|
108
|
-
|
|
109
124
|
for (const t of targets) {
|
|
110
125
|
const L = LAYOUT[t];
|
|
111
126
|
console.log(`\n${t}`);
|
|
@@ -118,29 +133,36 @@ for (const t of targets) {
|
|
|
118
133
|
const r = `${L.agents}/${ag.name}.${ext}`;
|
|
119
134
|
if (writeOwned(r, dst => fs.writeFileSync(dst, render(t, ag, models[ag.name])))) log(`agent ${r}`);
|
|
120
135
|
}
|
|
121
|
-
if (L.commands) for (const [name, what] of
|
|
136
|
+
if (L.commands) for (const [name, what] of COMMANDS) {
|
|
122
137
|
const r = `${L.commands}/${name}.md`;
|
|
123
|
-
|
|
124
|
-
if (writeOwned(r, dst => fs.writeFileSync(dst, body))) log(`command /${name}`);
|
|
138
|
+
if (writeOwned(r, dst => fs.writeFileSync(dst, commandBody(name, what)))) log(`command /${name}`);
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
// Paths an earlier version installed into these targets' folders and this version no longer ships.
|
|
129
143
|
const ownDirs = new Set(targets.flatMap(t => Object.values(LAYOUT[t])));
|
|
130
144
|
for (const r of old.keys()) {
|
|
131
|
-
if (
|
|
145
|
+
if (shipped.has(r) || !ownDirs.has(path.dirname(r)) || !exists(abs(r))) continue;
|
|
132
146
|
if (isUnchanged(r)) { fs.rmSync(abs(r), { recursive: true, force: true }); log(`removed ${r} (no longer shipped)`); }
|
|
133
147
|
else { warn(`not removed: ${changedNote(r)}`); released.add(r); }
|
|
134
148
|
}
|
|
135
149
|
|
|
136
|
-
// Keep entries of earlier installs (e.g. another target) that are still on disk; a changed
|
|
150
|
+
// Keep entries of earlier installs (e.g. another target) and skipped shipped paths that are still on disk; a changed
|
|
151
|
+
// path this version no longer ships is the user's and is no longer listed.
|
|
137
152
|
const keep = [...old].filter(([r]) => !installed.has(r) && !released.has(r) && exists(abs(r)));
|
|
138
153
|
fs.writeFileSync(MARKER, [`version=${VERSION}`, ...[...keep, ...installed].map(([r, h]) => h ? `${r} ${h}` : r)].join('\n') + '\n');
|
|
139
154
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
if (skipped.length) {
|
|
156
|
+
console.error(`\nIncomplete: agent-docs-bootstrap ${VERSION} could not install ${skipped.length} path(s):`);
|
|
157
|
+
for (const r of skipped) console.error(` ${r}`);
|
|
158
|
+
console.error('They hold content this kit did not write. To get this version, move or delete them, then run the installer again.');
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
} else {
|
|
161
|
+
console.log(`\nDone: agent-docs-bootstrap ${VERSION}, ${targets.join(', ')}${GLOBAL ? ' (global)' : ''}.`);
|
|
162
|
+
if (!GLOBAL) log(`commit the installed folders and .agent-docs-bootstrap so the whole team gets the same version`);
|
|
163
|
+
log('restart the agent session so it picks up the new skills and subagents');
|
|
164
|
+
log('start with the docs-bootstrap skill (Claude Code /docs-bootstrap, Codex $docs-bootstrap, OpenCode /docs-bootstrap)');
|
|
165
|
+
}
|
|
144
166
|
|
|
145
167
|
// ---------- rendering
|
|
146
168
|
function render(target, ag, model) {
|
|
@@ -220,15 +242,22 @@ function usage(code, msg) {
|
|
|
220
242
|
process.exit(code);
|
|
221
243
|
}
|
|
222
244
|
// Content hash of an installed file or folder: relative names and text with line endings normalised, so a
|
|
223
|
-
// checkout with CRLF line endings still matches.
|
|
245
|
+
// checkout with CRLF line endings still matches. Files the OS adds (dot files such as .DS_Store, Thumbs.db,
|
|
246
|
+
// desktop.ini) are left out; the kit ships none (check.mjs makes sure). The format must not change: markers and
|
|
247
|
+
// LEGACY depend on it.
|
|
224
248
|
function contentHash(p) {
|
|
225
249
|
const h = createHash('sha256');
|
|
226
250
|
const files = fs.lstatSync(p).isDirectory() ? listFiles(p).sort() : [''];
|
|
227
251
|
for (const f of files) h.update(`${f}\0${read(path.join(p, f)).replace(/\r\n/g, '\n')}\0`);
|
|
228
252
|
return h.digest('hex').slice(0, 16);
|
|
229
253
|
}
|
|
254
|
+
function isOsFile(name) { return name.startsWith('.') || /^(thumbs\.db|desktop\.ini)$/i.test(name); }
|
|
255
|
+
// The same hash for text about to be written as a file.
|
|
256
|
+
function textHash(text) {
|
|
257
|
+
return createHash('sha256').update(`\0${text.replace(/\r\n/g, '\n')}\0`).digest('hex').slice(0, 16);
|
|
258
|
+
}
|
|
230
259
|
function listFiles(dir, base = dir) {
|
|
231
|
-
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(e => {
|
|
260
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter(e => !isOsFile(e.name)).flatMap(e => {
|
|
232
261
|
const p = path.join(dir, e.name);
|
|
233
262
|
return e.isDirectory() ? listFiles(p, base) : [path.relative(base, p).split(path.sep).join('/')];
|
|
234
263
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-docs-bootstrap",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Agent skills that build reference documentation for a codebase (glossary, ADRs, architecture, how-tos, AGENTS.md) through interviews with the team, with an optional check by a fresh agent. Works with any agent that runs skills; the installer sets up Claude Code, Codex and OpenCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": { "type": "git", "url": "git+https://github.com/TraneDev/agent-docs-bootstrap.git" },
|