docsgov 0.1.0 → 0.1.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/README.md +46 -24
- package/dist/apispec/apispec.js +1 -1
- package/dist/apispec/apispec.test.js +2 -2
- package/dist/check/run.js +1 -1
- package/dist/check/run.test.js +6 -6
- package/dist/check/suggest.js +1 -1
- package/dist/check/tokens.js +1 -1
- package/dist/cmd/main.js +17 -17
- package/dist/cmd/main.test.js +13 -13
- package/dist/codeq/errors.js +2 -2
- package/dist/codeq/index.js +1 -1
- package/dist/codeq/resolver.test.js +1 -1
- package/dist/config/config.js +2 -2
- package/dist/config/config.test.js +5 -5
- package/dist/config/fs.js +1 -1
- package/dist/config/glob.js +1 -1
- package/dist/config/glob.test.js +1 -1
- package/dist/dedup/configload.js +3 -3
- package/dist/dedup/configload.test.js +5 -5
- package/dist/dedup/dedup.index.test.js +9 -9
- package/dist/dedup/dedup.js +5 -5
- package/dist/dedup/dedup.test.js +4 -4
- package/dist/dedup/dedupcfg/config.js +2 -2
- package/dist/dedup/embedder/cache.js +4 -4
- package/dist/dedup/embedder/cache.test.js +13 -13
- package/dist/dedup/embedder/embedder.js +2 -2
- package/dist/dedup/embedder/embedder.mock.test.js +1 -1
- package/dist/dedup/embedder/embedder.test.js +4 -4
- package/dist/dedup/embedder/session.test.js +1 -1
- package/dist/dedup/gitignore.js +5 -5
- package/dist/dedup/gitignore.test.js +2 -2
- package/dist/dedup/indexdb/indexdb.js +2 -2
- package/dist/repo/exists.test.js +7 -7
- package/dist/repo/index.js +1 -1
- package/dist/repo/overlay.test.js +6 -6
- package/dist/repo/repo.js +10 -10
- package/dist/repo/repo.test.js +35 -35
- package/dist/repo/testutil.js +1 -1
- package/dist/repo/write.test.js +12 -12
- package/dist/violation/types.js +1 -1
- package/package.json +2 -2
package/dist/repo/repo.test.js
CHANGED
|
@@ -5,27 +5,27 @@ import { find } from "./repo.js";
|
|
|
5
5
|
import { makeDir } from "./testutil.js";
|
|
6
6
|
const dec = new TextDecoder();
|
|
7
7
|
describe("find: repo-root discovery", () => {
|
|
8
|
-
// WHY: when a .
|
|
8
|
+
// WHY: when a .docsgov/ exists in the ancestor chain, that ancestor is the
|
|
9
9
|
// repo root — governance config lives relative to it.
|
|
10
|
-
test("returns the ancestor that owns .
|
|
11
|
-
const root = await makeDir(".
|
|
10
|
+
test("returns the ancestor that owns .docsgov/ when CWD is the root", async () => {
|
|
11
|
+
const root = await makeDir(".docsgov");
|
|
12
12
|
const r = await find(root);
|
|
13
13
|
expect(r.root()).toBe(root);
|
|
14
14
|
// The FS must be usable — list the sentinel dir through it.
|
|
15
15
|
const entries = await r.fs().readDir(".");
|
|
16
|
-
expect(entries.map((e) => e.name())).toContain(".
|
|
16
|
+
expect(entries.map((e) => e.name())).toContain(".docsgov");
|
|
17
17
|
});
|
|
18
|
-
// WHY: gen/add must be able to bootstrap a repo that has no .
|
|
18
|
+
// WHY: gen/add must be able to bootstrap a repo that has no .docsgov/ yet;
|
|
19
19
|
// falling back to the supplied dir is what makes that possible.
|
|
20
|
-
test("falls back to the supplied dir when no .
|
|
21
|
-
const root = await makeDir(); // no .
|
|
20
|
+
test("falls back to the supplied dir when no .docsgov/ exists (bootstrapping)", async () => {
|
|
21
|
+
const root = await makeDir(); // no .docsgov
|
|
22
22
|
const r = await find(root);
|
|
23
23
|
expect(r.root()).toBe(root);
|
|
24
24
|
});
|
|
25
25
|
// WHY: a check run from a nested working directory must resolve the same repo
|
|
26
26
|
// root as a run from the top — the walk-up is what guarantees that.
|
|
27
|
-
test("walks up from a nested directory to the .
|
|
28
|
-
const root = await makeDir(".
|
|
27
|
+
test("walks up from a nested directory to the .docsgov/ owner", async () => {
|
|
28
|
+
const root = await makeDir(".docsgov", "a/b/c");
|
|
29
29
|
const nested = path.join(root, "a", "b", "c");
|
|
30
30
|
const r = await find(nested);
|
|
31
31
|
expect(r.root()).toBe(root);
|
|
@@ -35,7 +35,7 @@ describe("find: repo-root discovery", () => {
|
|
|
35
35
|
// boundary keeps every downstream parser CRLF-agnostic and reports
|
|
36
36
|
// byte-identical across platforms.
|
|
37
37
|
test("readFile normalises CRLF to LF so callers never see CR", async () => {
|
|
38
|
-
const root = await makeDir(".
|
|
38
|
+
const root = await makeDir(".docsgov");
|
|
39
39
|
await nodefs.writeFile(path.join(root, "test.txt"), "line one\r\nline two\r\n");
|
|
40
40
|
const r = await find(root);
|
|
41
41
|
const got = dec.decode(await r.readFile("test.txt"));
|
|
@@ -47,7 +47,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
|
|
|
47
47
|
// WHY: the docs guard validates file references; a real regular file at the
|
|
48
48
|
// exact path must report present so a valid reference is not flagged broken.
|
|
49
49
|
test("returns true for an existing regular file (exact case)", async () => {
|
|
50
|
-
const root = await makeDir(".
|
|
50
|
+
const root = await makeDir(".docsgov", "docs/adr");
|
|
51
51
|
const r = await find(root);
|
|
52
52
|
await r.writeFile("docs/adr/0001.md", enc.encode("x\n"));
|
|
53
53
|
expect(await r.exists("docs/adr/0001.md")).toBe(true);
|
|
@@ -55,7 +55,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
|
|
|
55
55
|
// WHY: host filesystems case-fold on macOS/Windows; exists() must NOT, or a
|
|
56
56
|
// wrong-case reference ("Docs/...") would be wrongly accepted and ship broken.
|
|
57
57
|
test("returns false when a path segment differs only in case", async () => {
|
|
58
|
-
const root = await makeDir(".
|
|
58
|
+
const root = await makeDir(".docsgov", "docs/adr");
|
|
59
59
|
const r = await find(root);
|
|
60
60
|
await r.writeFile("docs/adr/0001.md", enc.encode("x\n"));
|
|
61
61
|
expect(await r.exists("Docs/adr/0001.md")).toBe(false);
|
|
@@ -64,14 +64,14 @@ describe("exists: exact-case file presence for the docs guard", () => {
|
|
|
64
64
|
// WHY: the guard checks file references, not directory references; a reference
|
|
65
65
|
// that resolves to a directory must be rejected as not-a-file.
|
|
66
66
|
test("returns false when the final segment is a directory", async () => {
|
|
67
|
-
const root = await makeDir(".
|
|
67
|
+
const root = await makeDir(".docsgov", "docs/adr");
|
|
68
68
|
const r = await find(root);
|
|
69
69
|
expect(await r.exists("docs/adr")).toBe(false);
|
|
70
70
|
});
|
|
71
71
|
// WHY: a reference whose interior segment is a file (not a dir) cannot be
|
|
72
72
|
// descended into; treating it as present would mask a malformed reference.
|
|
73
73
|
test("returns false when an interior segment is a file, not a directory", async () => {
|
|
74
|
-
const root = await makeDir(".
|
|
74
|
+
const root = await makeDir(".docsgov", "docs");
|
|
75
75
|
const r = await find(root);
|
|
76
76
|
await r.writeFile("docs/a.md", enc.encode("x\n"));
|
|
77
77
|
// a.md is a file, so "a.md/inner.md" cannot descend.
|
|
@@ -80,14 +80,14 @@ describe("exists: exact-case file presence for the docs guard", () => {
|
|
|
80
80
|
// WHY: a missing leaf in an existing directory is the common "broken
|
|
81
81
|
// reference" case the guard must catch — it must report absent, not error.
|
|
82
82
|
test("returns false for a missing final segment", async () => {
|
|
83
|
-
const root = await makeDir(".
|
|
83
|
+
const root = await makeDir(".docsgov", "docs");
|
|
84
84
|
const r = await find(root);
|
|
85
85
|
expect(await r.exists("docs/missing.md")).toBe(false);
|
|
86
86
|
});
|
|
87
87
|
// WHY: an empty or root-only path is never a file reference; returning false
|
|
88
88
|
// keeps the guard from treating "." as a present file.
|
|
89
89
|
test("returns false for the root / empty path", async () => {
|
|
90
|
-
const root = await makeDir(".
|
|
90
|
+
const root = await makeDir(".docsgov");
|
|
91
91
|
const r = await find(root);
|
|
92
92
|
expect(await r.exists("/")).toBe(false);
|
|
93
93
|
expect(await r.exists("")).toBe(false);
|
|
@@ -95,7 +95,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
|
|
|
95
95
|
// WHY: a leading slash is a slash-relative reference, not an absolute OS path;
|
|
96
96
|
// it must be stripped so the lookup still resolves under the repo root.
|
|
97
97
|
test("strips a leading slash before resolving", async () => {
|
|
98
|
-
const root = await makeDir(".
|
|
98
|
+
const root = await makeDir(".docsgov", "docs");
|
|
99
99
|
const r = await find(root);
|
|
100
100
|
await r.writeFile("docs/a.md", enc.encode("x\n"));
|
|
101
101
|
expect(await r.exists("/docs/a.md")).toBe(true);
|
|
@@ -105,7 +105,7 @@ describe("fileExists / dirExists: write-with-backup precondition checks", () =>
|
|
|
105
105
|
// WHY: the write-with-backup layer asks "is there already a file here?"; a
|
|
106
106
|
// genuine file must report true so it backs up instead of clobbering.
|
|
107
107
|
test("fileExists is true for a file and false for a directory or missing path", async () => {
|
|
108
|
-
const root = await makeDir(".
|
|
108
|
+
const root = await makeDir(".docsgov", "docs");
|
|
109
109
|
const r = await find(root);
|
|
110
110
|
await r.writeFile("docs/a.md", enc.encode("x\n"));
|
|
111
111
|
expect(await r.fileExists("docs/a.md")).toBe(true);
|
|
@@ -116,14 +116,14 @@ describe("fileExists / dirExists: write-with-backup precondition checks", () =>
|
|
|
116
116
|
// so the caller falls through to the write that surfaces a richer error; an
|
|
117
117
|
// escaping path must therefore be reported absent, not throw here.
|
|
118
118
|
test("fileExists returns false for a path that escapes the repo root", async () => {
|
|
119
|
-
const root = await makeDir(".
|
|
119
|
+
const root = await makeDir(".docsgov");
|
|
120
120
|
const r = await find(root);
|
|
121
121
|
expect(await r.fileExists("../outside.md")).toBe(false);
|
|
122
122
|
});
|
|
123
123
|
// WHY: dirExists gates directory operations (rename targets, group dirs); a
|
|
124
124
|
// real directory must report true and a file or missing path false.
|
|
125
125
|
test("dirExists is true for a directory and false for a file or missing path", async () => {
|
|
126
|
-
const root = await makeDir(".
|
|
126
|
+
const root = await makeDir(".docsgov", "docs");
|
|
127
127
|
const r = await find(root);
|
|
128
128
|
await r.writeFile("docs/a.md", enc.encode("x\n"));
|
|
129
129
|
expect(await r.dirExists("docs")).toBe(true);
|
|
@@ -133,16 +133,16 @@ describe("fileExists / dirExists: write-with-backup precondition checks", () =>
|
|
|
133
133
|
// WHY: same swallow-faults contract — an escaping directory path must be
|
|
134
134
|
// reported absent rather than throwing, so callers branch on a boolean.
|
|
135
135
|
test("dirExists returns false for a path that escapes the repo root", async () => {
|
|
136
|
-
const root = await makeDir(".
|
|
136
|
+
const root = await makeDir(".docsgov");
|
|
137
137
|
const r = await find(root);
|
|
138
138
|
expect(await r.dirExists("../outside")).toBe(false);
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
141
|
describe("writeFile: the sole owner of OS writes", () => {
|
|
142
|
-
// WHY:
|
|
142
|
+
// WHY: docsgov generates files into nested doc dirs that may not exist yet;
|
|
143
143
|
// writeFile must create parents so generation does not fail on a fresh repo.
|
|
144
144
|
test("creates missing parent directories", async () => {
|
|
145
|
-
const root = await makeDir(".
|
|
145
|
+
const root = await makeDir(".docsgov");
|
|
146
146
|
const r = await find(root);
|
|
147
147
|
await r.writeFile("docs/deep/nested/a.md", enc.encode("hello\n"));
|
|
148
148
|
expect(dec.decode(await r.readFile("docs/deep/nested/a.md"))).toBe("hello\n");
|
|
@@ -151,7 +151,7 @@ describe("writeFile: the sole owner of OS writes", () => {
|
|
|
151
151
|
// root must be refused (via toAbs) so generation can never scribble outside
|
|
152
152
|
// the repo. The error names the offending path for diagnosis.
|
|
153
153
|
test("refuses a path that escapes the repo root", async () => {
|
|
154
|
-
const root = await makeDir(".
|
|
154
|
+
const root = await makeDir(".docsgov");
|
|
155
155
|
const r = await find(root);
|
|
156
156
|
await expect(r.writeFile("../escape.md", enc.encode("x"))).rejects.toThrow(/escapes repository root/);
|
|
157
157
|
});
|
|
@@ -160,7 +160,7 @@ describe("renameDir / removeAll: directory lifecycle", () => {
|
|
|
160
160
|
// WHY: rename is used to move a generated group dir into place; it must move
|
|
161
161
|
// the tree atomically so a half-renamed dir never appears.
|
|
162
162
|
test("renameDir moves a directory tree to a new name", async () => {
|
|
163
|
-
const root = await makeDir(".
|
|
163
|
+
const root = await makeDir(".docsgov", "old");
|
|
164
164
|
const r = await find(root);
|
|
165
165
|
await r.writeFile("old/a.md", enc.encode("x\n"));
|
|
166
166
|
await r.renameDir("old", "new");
|
|
@@ -170,14 +170,14 @@ describe("renameDir / removeAll: directory lifecycle", () => {
|
|
|
170
170
|
// WHY: a failed rename (e.g. missing source) must throw with context naming
|
|
171
171
|
// both paths so the operator can see which move broke — Rule 12 fail loud.
|
|
172
172
|
test("renameDir throws with both paths when the source is missing", async () => {
|
|
173
|
-
const root = await makeDir(".
|
|
173
|
+
const root = await makeDir(".docsgov");
|
|
174
174
|
const r = await find(root);
|
|
175
175
|
await expect(r.renameDir("missing", "dest")).rejects.toThrow(/repo\.renameDir/);
|
|
176
176
|
});
|
|
177
177
|
// WHY: removeAll backs the stale-.bak cleanup; it must delete an existing tree
|
|
178
178
|
// so a leftover backup dir does not block the next write.
|
|
179
179
|
test("removeAll deletes an existing directory tree", async () => {
|
|
180
|
-
const root = await makeDir(".
|
|
180
|
+
const root = await makeDir(".docsgov", "junk");
|
|
181
181
|
const r = await find(root);
|
|
182
182
|
await r.writeFile("junk/a.md", enc.encode("x\n"));
|
|
183
183
|
await r.removeAll("junk");
|
|
@@ -186,7 +186,7 @@ describe("renameDir / removeAll: directory lifecycle", () => {
|
|
|
186
186
|
// WHY: the cleanup use-case calls removeAll even when nothing is there; it
|
|
187
187
|
// must be a no-op (force:true), never throw, so cleanup is idempotent.
|
|
188
188
|
test("removeAll is a no-op for a missing path", async () => {
|
|
189
|
-
const root = await makeDir(".
|
|
189
|
+
const root = await makeDir(".docsgov");
|
|
190
190
|
const r = await find(root);
|
|
191
191
|
await expect(r.removeAll("nope")).resolves.toBeUndefined();
|
|
192
192
|
});
|
|
@@ -196,11 +196,11 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
196
196
|
// and is atomic; if the temp landed in the OS temp dir the rename could cross
|
|
197
197
|
// devices and silently degrade to a non-atomic copy.
|
|
198
198
|
test("writes a sibling temp file, not one in the OS temp dir", async () => {
|
|
199
|
-
const root = await makeDir(".
|
|
199
|
+
const root = await makeDir(".docsgov", "docs");
|
|
200
200
|
const r = await find(root);
|
|
201
201
|
const pending = await r.atomicWriteFile("docs/a.md", enc.encode("new\n"));
|
|
202
202
|
const tmp = pending.tmpAbsForTest();
|
|
203
|
-
expect(tmp).toBe(path.join(root, "docs", "a.md.
|
|
203
|
+
expect(tmp).toBe(path.join(root, "docs", "a.md.docsgov-flip.tmp"));
|
|
204
204
|
// The target itself is not yet written before commit.
|
|
205
205
|
expect(await r.fileExists("docs/a.md")).toBe(false);
|
|
206
206
|
await pending.commit(true);
|
|
@@ -208,7 +208,7 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
208
208
|
// WHY: commit(true) is what makes the new content visible; after it the target
|
|
209
209
|
// holds the new bytes and the temp is gone (renamed into place).
|
|
210
210
|
test("commit(true) renames the temp into place", async () => {
|
|
211
|
-
const root = await makeDir(".
|
|
211
|
+
const root = await makeDir(".docsgov", "docs");
|
|
212
212
|
const r = await find(root);
|
|
213
213
|
const pending = await r.atomicWriteFile("docs/a.md", enc.encode("committed\n"));
|
|
214
214
|
await pending.commit(true);
|
|
@@ -218,7 +218,7 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
218
218
|
// WHY: commit(false) is the rollback path; it must remove the temp and leave
|
|
219
219
|
// any original untouched, so an aborted flip changes nothing on disk.
|
|
220
220
|
test("commit(false) removes the temp and leaves the original untouched", async () => {
|
|
221
|
-
const root = await makeDir(".
|
|
221
|
+
const root = await makeDir(".docsgov", "docs");
|
|
222
222
|
const r = await find(root);
|
|
223
223
|
await r.writeFile("docs/a.md", enc.encode("original\n"));
|
|
224
224
|
const pending = await r.atomicWriteFile("docs/a.md", enc.encode("new\n"));
|
|
@@ -230,7 +230,7 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
230
230
|
// must swallow the ENOENT (not re-throw), so a double-rollback never crashes
|
|
231
231
|
// the cleanup path.
|
|
232
232
|
test("commit(false) is a no-op when the temp is already removed", async () => {
|
|
233
|
-
const root = await makeDir(".
|
|
233
|
+
const root = await makeDir(".docsgov", "docs");
|
|
234
234
|
const r = await find(root);
|
|
235
235
|
const pending = await r.atomicWriteFile("docs/a.md", enc.encode("x\n"));
|
|
236
236
|
await nodefs.rm(pending.tmpAbsForTest()); // remove it out from under commit
|
|
@@ -239,7 +239,7 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
239
239
|
// WHY: atomicWriteFile must create the target's parent dirs, mirroring
|
|
240
240
|
// writeFile, so a flip into a not-yet-existing doc dir succeeds.
|
|
241
241
|
test("creates missing parent directories for the temp", async () => {
|
|
242
|
-
const root = await makeDir(".
|
|
242
|
+
const root = await makeDir(".docsgov");
|
|
243
243
|
const r = await find(root);
|
|
244
244
|
const pending = await r.atomicWriteFile("docs/new/a.md", enc.encode("y\n"));
|
|
245
245
|
await pending.commit(true);
|
|
@@ -248,7 +248,7 @@ describe("atomicWriteFile + PendingWrite: crash-safe flips", () => {
|
|
|
248
248
|
// WHY: atomicWriteFile is also a guarded write; an escaping path must be
|
|
249
249
|
// refused before any temp is created.
|
|
250
250
|
test("refuses a path that escapes the repo root", async () => {
|
|
251
|
-
const root = await makeDir(".
|
|
251
|
+
const root = await makeDir(".docsgov");
|
|
252
252
|
const r = await find(root);
|
|
253
253
|
await expect(r.atomicWriteFile("../escape.md", enc.encode("x"))).rejects.toThrow(/escapes repository root/);
|
|
254
254
|
});
|
package/dist/repo/testutil.js
CHANGED
|
@@ -13,7 +13,7 @@ afterEach(async () => {
|
|
|
13
13
|
}
|
|
14
14
|
});
|
|
15
15
|
export async function makeDir(...dirs) {
|
|
16
|
-
const root = await nodefs.mkdtemp(path.join(os.tmpdir(), "
|
|
16
|
+
const root = await nodefs.mkdtemp(path.join(os.tmpdir(), "docsgov-repo-"));
|
|
17
17
|
// Resolve symlinks (macOS /var -> /private/var) so toAbs prefix checks and
|
|
18
18
|
// root equality assertions compare canonical paths.
|
|
19
19
|
const realRoot = await nodefs.realpath(root);
|
package/dist/repo/write.test.js
CHANGED
|
@@ -22,7 +22,7 @@ describe("write capability", () => {
|
|
|
22
22
|
// WHY: gen writes to nested paths; without parent creation every subdirectory
|
|
23
23
|
// write fails and gen can never produce a multi-file tree.
|
|
24
24
|
test("writeFile creates all missing parent directories", async () => {
|
|
25
|
-
const root = await makeDir(".
|
|
25
|
+
const root = await makeDir(".docsgov");
|
|
26
26
|
const r = await find(root);
|
|
27
27
|
const content = enc.encode("hello\n");
|
|
28
28
|
await r.writeFile(".claude/skills/write-docs/adr/adr.md", content);
|
|
@@ -32,7 +32,7 @@ describe("write capability", () => {
|
|
|
32
32
|
// WHY: a slash path must map onto the OS separator so files land at the right
|
|
33
33
|
// location on every platform.
|
|
34
34
|
test("writeFile maps a slash path to the nested OS path", async () => {
|
|
35
|
-
const root = await makeDir(".
|
|
35
|
+
const root = await makeDir(".docsgov");
|
|
36
36
|
const r = await find(root);
|
|
37
37
|
await r.writeFile("subdir/file.txt", enc.encode("data\n"));
|
|
38
38
|
const got = await nodefs.readFile(path.join(root, "subdir", "file.txt"));
|
|
@@ -41,14 +41,14 @@ describe("write capability", () => {
|
|
|
41
41
|
// WHY: allowing ".." traversal would let gen overwrite arbitrary files on the
|
|
42
42
|
// user's system — a security and correctness boundary.
|
|
43
43
|
test("writeFile refuses a path that escapes the repo root", async () => {
|
|
44
|
-
const root = await makeDir(".
|
|
44
|
+
const root = await makeDir(".docsgov");
|
|
45
45
|
const r = await find(root);
|
|
46
46
|
await expect(r.writeFile("../escape.txt", enc.encode("should not be written"))).rejects.toThrow();
|
|
47
47
|
});
|
|
48
48
|
// WHY: write-with-backup renames the existing skill dir to <skill>.bak; if
|
|
49
49
|
// renameDir is broken the backup protocol can't protect the user's tree.
|
|
50
50
|
test("renameDir moves a directory and its contents", async () => {
|
|
51
|
-
const root = await makeDir(".
|
|
51
|
+
const root = await makeDir(".docsgov", ".claude/skills/write-docs");
|
|
52
52
|
await nodefs.writeFile(path.join(root, ".claude", "skills", "write-docs", "sentinel.txt"), "x");
|
|
53
53
|
const r = await find(root);
|
|
54
54
|
await r.renameDir(".claude/skills/write-docs", ".claude/skills/write-docs.bak");
|
|
@@ -58,7 +58,7 @@ describe("write capability", () => {
|
|
|
58
58
|
// WHY: rollback deletes the partial skill dir before restoring the backup; an
|
|
59
59
|
// incomplete removeAll leaves partial state in the user's repository.
|
|
60
60
|
test("removeAll removes a directory tree", async () => {
|
|
61
|
-
const root = await makeDir(".
|
|
61
|
+
const root = await makeDir(".docsgov", ".claude/skills/partial/nested");
|
|
62
62
|
await nodefs.writeFile(path.join(root, ".claude", "skills", "partial", "nested", "f.txt"), "y");
|
|
63
63
|
const r = await find(root);
|
|
64
64
|
await r.removeAll(".claude/skills/partial");
|
|
@@ -67,7 +67,7 @@ describe("write capability", () => {
|
|
|
67
67
|
// WHY: stale-bak cleanup calls removeAll unconditionally on first run when no
|
|
68
68
|
// .bak exists — it must silently succeed.
|
|
69
69
|
test("removeAll on a nonexistent path is a no-op", async () => {
|
|
70
|
-
const root = await makeDir(".
|
|
70
|
+
const root = await makeDir(".docsgov");
|
|
71
71
|
const r = await find(root);
|
|
72
72
|
await expect(r.removeAll(".claude/skills/does-not-exist.bak")).resolves.toBeUndefined();
|
|
73
73
|
});
|
|
@@ -75,7 +75,7 @@ describe("write capability", () => {
|
|
|
75
75
|
// deciding to back up; a wrong answer skips a needed backup or crashes a
|
|
76
76
|
// rename of a nonexistent dir.
|
|
77
77
|
test("dirExists reports true for an existing dir and false otherwise", async () => {
|
|
78
|
-
const root = await makeDir(".
|
|
78
|
+
const root = await makeDir(".docsgov", ".claude/skills/write-docs");
|
|
79
79
|
const r = await find(root);
|
|
80
80
|
expect(await r.dirExists(".claude/skills/write-docs")).toBe(true);
|
|
81
81
|
expect(await r.dirExists(".claude/skills/no-such-dir")).toBe(false);
|
|
@@ -86,17 +86,17 @@ describe("write capability", () => {
|
|
|
86
86
|
// mutates the original or leaves a stray temp file breaks that invariant.
|
|
87
87
|
describe("atomicWriteFile two-phase commit", () => {
|
|
88
88
|
test("commit(false) rolls back leaving the original untouched and no temp", async () => {
|
|
89
|
-
const root = await makeDir(".
|
|
89
|
+
const root = await makeDir(".docsgov", "docs");
|
|
90
90
|
const r = await find(root);
|
|
91
91
|
const target = "docs/a.md";
|
|
92
92
|
await r.writeFile(target, enc.encode("ORIGINAL\n"));
|
|
93
93
|
const pw = await r.atomicWriteFile(target, enc.encode("NEW\n"));
|
|
94
94
|
await pw.commit(false);
|
|
95
95
|
expect(dec.decode(await r.readFile(target))).toBe("ORIGINAL\n");
|
|
96
|
-
expect(await statSafe(path.join(root, "docs", "a.md.
|
|
96
|
+
expect(await statSafe(path.join(root, "docs", "a.md.docsgov-flip.tmp"))).toBe(false);
|
|
97
97
|
});
|
|
98
98
|
test("commit(true) atomically renames the temp into place", async () => {
|
|
99
|
-
const root = await makeDir(".
|
|
99
|
+
const root = await makeDir(".docsgov", "docs");
|
|
100
100
|
const r = await find(root);
|
|
101
101
|
const target = "docs/a.md";
|
|
102
102
|
await r.writeFile(target, enc.encode("ORIGINAL\n"));
|
|
@@ -110,7 +110,7 @@ describe("atomicWriteFile two-phase commit", () => {
|
|
|
110
110
|
// mid-flight. The real temp path comes from PendingWrite, so this genuinely
|
|
111
111
|
// fails if the impl changes to use the OS temp dir.
|
|
112
112
|
test("the temp file is a sibling of the target, not in the OS temp dir", async () => {
|
|
113
|
-
const root = await makeDir(".
|
|
113
|
+
const root = await makeDir(".docsgov", "docs");
|
|
114
114
|
const r = await find(root);
|
|
115
115
|
const target = "docs/a.md";
|
|
116
116
|
await r.writeFile(target, enc.encode("ORIGINAL\n"));
|
|
@@ -119,7 +119,7 @@ describe("atomicWriteFile two-phase commit", () => {
|
|
|
119
119
|
const targetDir = path.join(root, "docs");
|
|
120
120
|
expect(path.dirname(actualTmpPath)).toBe(targetDir);
|
|
121
121
|
// The temp file must exist on disk before commit is called.
|
|
122
|
-
expect(await statSafe(path.join(targetDir, "a.md.
|
|
122
|
+
expect(await statSafe(path.join(targetDir, "a.md.docsgov-flip.tmp"))).toBe(true);
|
|
123
123
|
await pw.commit(false); // rollback
|
|
124
124
|
});
|
|
125
125
|
});
|
package/dist/violation/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Package violation defines the Violation Record and the closed Rule enum for
|
|
2
|
-
// the
|
|
2
|
+
// the docsgov check surface. It is a leaf package: it imports nothing internal.
|
|
3
3
|
// Every rule the check surface can emit is listed in the Rule set; the set is
|
|
4
4
|
// closed and must exactly match the rule registry in docs/flows/check.md §5.
|
|
5
5
|
// Rules is the closed set of all rule identifiers the check surface can emit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docsgov",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Config-driven documentation governance: checks that {{code:…}} and {{api:…}} references in your Markdown docs still resolve, that doc links exist, and (via dedup) flags near-duplicate concepts.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"node": ">=22"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"
|
|
11
|
+
"docsgov": "dist/cmd/main.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist"
|