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.
Files changed (41) hide show
  1. package/README.md +46 -24
  2. package/dist/apispec/apispec.js +1 -1
  3. package/dist/apispec/apispec.test.js +2 -2
  4. package/dist/check/run.js +1 -1
  5. package/dist/check/run.test.js +6 -6
  6. package/dist/check/suggest.js +1 -1
  7. package/dist/check/tokens.js +1 -1
  8. package/dist/cmd/main.js +17 -17
  9. package/dist/cmd/main.test.js +13 -13
  10. package/dist/codeq/errors.js +2 -2
  11. package/dist/codeq/index.js +1 -1
  12. package/dist/codeq/resolver.test.js +1 -1
  13. package/dist/config/config.js +2 -2
  14. package/dist/config/config.test.js +5 -5
  15. package/dist/config/fs.js +1 -1
  16. package/dist/config/glob.js +1 -1
  17. package/dist/config/glob.test.js +1 -1
  18. package/dist/dedup/configload.js +3 -3
  19. package/dist/dedup/configload.test.js +5 -5
  20. package/dist/dedup/dedup.index.test.js +9 -9
  21. package/dist/dedup/dedup.js +5 -5
  22. package/dist/dedup/dedup.test.js +4 -4
  23. package/dist/dedup/dedupcfg/config.js +2 -2
  24. package/dist/dedup/embedder/cache.js +4 -4
  25. package/dist/dedup/embedder/cache.test.js +13 -13
  26. package/dist/dedup/embedder/embedder.js +2 -2
  27. package/dist/dedup/embedder/embedder.mock.test.js +1 -1
  28. package/dist/dedup/embedder/embedder.test.js +4 -4
  29. package/dist/dedup/embedder/session.test.js +1 -1
  30. package/dist/dedup/gitignore.js +5 -5
  31. package/dist/dedup/gitignore.test.js +2 -2
  32. package/dist/dedup/indexdb/indexdb.js +2 -2
  33. package/dist/repo/exists.test.js +7 -7
  34. package/dist/repo/index.js +1 -1
  35. package/dist/repo/overlay.test.js +6 -6
  36. package/dist/repo/repo.js +10 -10
  37. package/dist/repo/repo.test.js +35 -35
  38. package/dist/repo/testutil.js +1 -1
  39. package/dist/repo/write.test.js +12 -12
  40. package/dist/violation/types.js +1 -1
  41. package/package.json +2 -2
@@ -62,7 +62,7 @@ function writeFiles(repoRoot, files) {
62
62
  }
63
63
  }
64
64
  function dbPathOf(repoRoot) {
65
- return join(repoRoot, ".docgov", "dedup", "index.db");
65
+ return join(repoRoot, ".docsgov", "dedup", "index.db");
66
66
  }
67
67
  const noProgress = () => { };
68
68
  beforeEach(() => {
@@ -71,16 +71,16 @@ beforeEach(() => {
71
71
  // Index() calls Embedder.newEmbedder() with no explicit cacheDir, which would
72
72
  // resolve to the host ~/.cache. Point it at a throwaway temp dir so the test
73
73
  // never writes to the real home cache.
74
- savedModelCache = process.env.DOCGOV_MODEL_CACHE;
74
+ savedModelCache = process.env.docsgov_MODEL_CACHE;
75
75
  const modelCache = mkdtempSync(join(tmpdir(), "dedup-index-model-"));
76
76
  tmpDirs.push(modelCache);
77
- process.env.DOCGOV_MODEL_CACHE = modelCache;
77
+ process.env.docsgov_MODEL_CACHE = modelCache;
78
78
  });
79
79
  afterEach(() => {
80
80
  if (savedModelCache === undefined)
81
- delete process.env.DOCGOV_MODEL_CACHE;
81
+ delete process.env.docsgov_MODEL_CACHE;
82
82
  else
83
- process.env.DOCGOV_MODEL_CACHE = savedModelCache;
83
+ process.env.docsgov_MODEL_CACHE = savedModelCache;
84
84
  for (const d of tmpDirs.splice(0))
85
85
  rmSync(d, { recursive: true, force: true });
86
86
  });
@@ -140,7 +140,7 @@ describe("dedup.Index error wiring", () => {
140
140
  const repoRoot = newRepoRoot();
141
141
  writeFiles(repoRoot, {
142
142
  // thresh_high out of (0,1) range → validate() throws inside Load.
143
- ".docgov/dedup/config.yml": "analyzer:\n thresh_high: 5\n",
143
+ ".docsgov/dedup/config.yml": "analyzer:\n thresh_high: 5\n",
144
144
  "docs/a.md": "## A\n\nThis is a sufficiently long paragraph of prose used purely to make the section eligible.\n",
145
145
  });
146
146
  await expect(Index(repoRoot, noProgress)).rejects.toThrow(/dedup\.Index: load config/);
@@ -162,7 +162,7 @@ describe("dedup.Index error wiring", () => {
162
162
  it("wraps an open-db failure on a corrupt index file", async () => {
163
163
  const repoRoot = newRepoRoot();
164
164
  writeFiles(repoRoot, {
165
- ".docgov/dedup/index.db": "this is not a sqlite database",
165
+ ".docsgov/dedup/index.db": "this is not a sqlite database",
166
166
  "docs/a.md": "## A\n\nThis is a sufficiently long paragraph of prose used purely to make the section eligible.\n",
167
167
  });
168
168
  await expect(Index(repoRoot, noProgress)).rejects.toThrow(/dedup\.Index: open db/);
@@ -186,7 +186,7 @@ describe("dedup.Analyze error wiring", () => {
186
186
  });
187
187
  await Index(repoRoot, noProgress);
188
188
  writeFiles(repoRoot, {
189
- ".docgov/dedup/config.yml": "analyzer:\n thresh_high: 5\n",
189
+ ".docsgov/dedup/config.yml": "analyzer:\n thresh_high: 5\n",
190
190
  });
191
191
  await expect(Analyze(repoRoot)).rejects.toThrow(/dedup\.Analyze: load config/);
192
192
  });
@@ -196,7 +196,7 @@ describe("dedup.Analyze error wiring", () => {
196
196
  it("wraps an open-db failure on a corrupt index file", async () => {
197
197
  const repoRoot = newRepoRoot();
198
198
  writeFiles(repoRoot, {
199
- ".docgov/dedup/index.db": "this is not a sqlite database",
199
+ ".docsgov/dedup/index.db": "this is not a sqlite database",
200
200
  });
201
201
  await expect(Analyze(repoRoot)).rejects.toThrow(/dedup\.Analyze: open db/);
202
202
  });
@@ -1,9 +1,9 @@
1
- // Top-level facade for the docgov dedup subsystem: the Index and Analyze entry
1
+ // Top-level facade for the docsgov dedup subsystem: the Index and Analyze entry
2
2
  // points the CLI calls.
3
3
  //
4
4
  // Ported from internal/dedup/dedup.go. Index walks <repoRoot>/docs/, extracts
5
5
  // eligible sections, embeds new/changed ones, and persists the index to
6
- // .docgov/dedup/index.db. Analyze loads that index and runs the layered
6
+ // .docsgov/dedup/index.db. Analyze loads that index and runs the layered
7
7
  // duplicate-detection algorithm, returning a structured Report.
8
8
  //
9
9
  // Reconciling the mixed sync/async stack vs Go:
@@ -24,11 +24,11 @@ import { Load } from "./configload.js";
24
24
  import { ensureDedupGitignore } from "./gitignore.js";
25
25
  /** dbPathOf returns the index DB path under repoRoot (Go inlines this twice). */
26
26
  function dbPathOf(repoRoot) {
27
- return path.join(repoRoot, ".docgov", "dedup", "index.db");
27
+ return path.join(repoRoot, ".docsgov", "dedup", "index.db");
28
28
  }
29
29
  /**
30
30
  * Index walks <repoRoot>/docs/, extracts eligible sections, embeds new and
31
- * changed ones, and persists the index to .docgov/dedup/index.db.
31
+ * changed ones, and persists the index to .docsgov/dedup/index.db.
32
32
  *
33
33
  * progress receives one-line status messages; pass a no-op for silence (Go's
34
34
  * io.Discard) or one that writes to stderr from the CLI.
@@ -92,7 +92,7 @@ export async function Index(repoRoot, progress) {
92
92
  }
93
93
  }
94
94
  /**
95
- * Analyze loads the index from .docgov/dedup/index.db, runs the layered
95
+ * Analyze loads the index from .docsgov/dedup/index.db, runs the layered
96
96
  * duplicate-detection algorithm, and returns a structured Report.
97
97
  *
98
98
  * Throws ErrIndexMissing (wrapped) when the index DB does not exist.
@@ -76,7 +76,7 @@ function writeFiles(repoRoot, files) {
76
76
  }
77
77
  /** dbPathOf mirrors the facade's index DB location. */
78
78
  function dbPathOf(repoRoot) {
79
- return join(repoRoot, ".docgov", "dedup", "index.db");
79
+ return join(repoRoot, ".docsgov", "dedup", "index.db");
80
80
  }
81
81
  /** createEmptyIndexDB opens (and immediately closes) a valid empty index DB. */
82
82
  function createEmptyIndexDB(repoRoot) {
@@ -90,7 +90,7 @@ describe("dedup.Analyze", () => {
90
90
  // WHY: a missing index must be a distinct, matchable error so the CLI can
91
91
  // prompt the user to index first rather than reporting a generic failure.
92
92
  it("throws ErrIndexMissing when the index DB does not exist", async () => {
93
- const repoRoot = newRepoRoot(); // empty, no .docgov/dedup/index.db
93
+ const repoRoot = newRepoRoot(); // empty, no .docsgov/dedup/index.db
94
94
  await expect(Analyze(repoRoot)).rejects.toBeInstanceOf(ErrIndexMissing);
95
95
  });
96
96
  // WHY: an existing but empty index is a valid "no duplicates" state — Analyze
@@ -189,9 +189,9 @@ describe("dedup facade end-to-end (fake embedder)", () => {
189
189
  });
190
190
  describe("dedup.Index (real embedder)", () => {
191
191
  // WHY: the Index path itself (real embedder, real walk, real persist) is only
192
- // meaningful with the model. Gated behind DOCGOV_E2E so CI never downloads
192
+ // meaningful with the model. Gated behind docsgov_E2E so CI never downloads
193
193
  // ~1GB — the single allowed env-skip per the porting conventions.
194
- const runE2E = process.env["DOCGOV_E2E"] === "1";
194
+ const runE2E = process.env["docsgov_E2E"] === "1";
195
195
  it.skipIf(!runE2E)("indexes a corpus and persists sections", async () => {
196
196
  const repoRoot = newRepoRoot();
197
197
  writeFiles(repoRoot, {
@@ -3,7 +3,7 @@
3
3
  // indexer can import it without creating an import cycle with the top-level
4
4
  // dedup facade.
5
5
  //
6
- // The YAML overlay (Default() merged with .docgov/dedup/config.yml) lives in
6
+ // The YAML overlay (Default() merged with .docsgov/dedup/config.yml) lives in
7
7
  // the parent dedup package; this leaf only declares the shapes and defaults.
8
8
  // Because that overlay deserializes raw YAML keys onto these shapes, every
9
9
  // property name here keeps its EXACT YAML serialized spelling (snake_case),
@@ -44,7 +44,7 @@ export function defaultConfig() {
44
44
  "build",
45
45
  ".next",
46
46
  ".cache",
47
- ".docgov",
47
+ ".docsgov",
48
48
  "dedup-poc",
49
49
  ".venv",
50
50
  ],
@@ -4,8 +4,8 @@ import { Model } from "./constants.js";
4
4
  /**
5
5
  * cacheDir returns the resolved model cache directory in precedence order:
6
6
  * 1. explicit (the WithCacheDir option equivalent)
7
- * 2. DOCGOV_MODEL_CACHE env var
8
- * 3. ~/.cache/docgov/models/<sanitized-model-name>/
7
+ * 2. docsgov_MODEL_CACHE env var
8
+ * 3. ~/.cache/docsgov/models/<sanitized-model-name>/
9
9
  *
10
10
  * The returned path is not guaranteed to exist; the caller creates it.
11
11
  */
@@ -13,11 +13,11 @@ export function cacheDir(explicit) {
13
13
  if (explicit !== "") {
14
14
  return explicit;
15
15
  }
16
- const env = process.env.DOCGOV_MODEL_CACHE;
16
+ const env = process.env.docsgov_MODEL_CACHE;
17
17
  if (env !== undefined && env !== "") {
18
18
  return env;
19
19
  }
20
20
  // Sanitize the model name: replace / with _ for filesystem safety.
21
21
  const sanitized = Model.replaceAll("/", "_");
22
- return join(homedir(), ".cache", "docgov", "models", sanitized);
22
+ return join(homedir(), ".cache", "docsgov", "models", sanitized);
23
23
  }
@@ -3,40 +3,40 @@ import { join } from "node:path";
3
3
  import { afterEach, beforeEach, expect, test } from "vitest";
4
4
  import { cacheDir } from "./cache.js";
5
5
  import { Model } from "./constants.js";
6
- // Snapshot/restore DOCGOV_MODEL_CACHE so tests don't leak into each other or
6
+ // Snapshot/restore docsgov_MODEL_CACHE so tests don't leak into each other or
7
7
  // the host env.
8
8
  let savedEnv;
9
9
  beforeEach(() => {
10
- savedEnv = process.env.DOCGOV_MODEL_CACHE;
11
- delete process.env.DOCGOV_MODEL_CACHE;
10
+ savedEnv = process.env.docsgov_MODEL_CACHE;
11
+ delete process.env.docsgov_MODEL_CACHE;
12
12
  });
13
13
  afterEach(() => {
14
14
  if (savedEnv === undefined) {
15
- delete process.env.DOCGOV_MODEL_CACHE;
15
+ delete process.env.docsgov_MODEL_CACHE;
16
16
  }
17
17
  else {
18
- process.env.DOCGOV_MODEL_CACHE = savedEnv;
18
+ process.env.docsgov_MODEL_CACHE = savedEnv;
19
19
  }
20
20
  });
21
21
  // WHY: tests pass an explicit cache dir (a temp dir) so CI never races on the
22
22
  // shared host cache. The explicit option MUST win over both the env var and
23
23
  // the default, or that isolation guarantee breaks.
24
24
  test("explicit cache dir wins over env var and default", () => {
25
- process.env.DOCGOV_MODEL_CACHE = "/from/env";
25
+ process.env.docsgov_MODEL_CACHE = "/from/env";
26
26
  expect(cacheDir("/explicit/dir")).toBe("/explicit/dir");
27
27
  });
28
- // WHY: DOCGOV_MODEL_CACHE lets operators relocate the multi-hundred-MB model
28
+ // WHY: docsgov_MODEL_CACHE lets operators relocate the multi-hundred-MB model
29
29
  // off the home partition. It must be honored when no explicit dir is given.
30
- test("DOCGOV_MODEL_CACHE is used when no explicit dir is given", () => {
31
- process.env.DOCGOV_MODEL_CACHE = "/from/env";
30
+ test("docsgov_MODEL_CACHE is used when no explicit dir is given", () => {
31
+ process.env.docsgov_MODEL_CACHE = "/from/env";
32
32
  expect(cacheDir("")).toBe("/from/env");
33
33
  });
34
34
  // WHY: an empty env var must NOT shadow the default (Go checks `env != ""`).
35
- // A stray `DOCGOV_MODEL_CACHE=` in a shell would otherwise route the cache to
35
+ // A stray `docsgov_MODEL_CACHE=` in a shell would otherwise route the cache to
36
36
  // the empty path.
37
- test("empty DOCGOV_MODEL_CACHE falls through to the default", () => {
38
- process.env.DOCGOV_MODEL_CACHE = "";
39
- const want = join(homedir(), ".cache", "docgov", "models", Model.replaceAll("/", "_"));
37
+ test("empty docsgov_MODEL_CACHE falls through to the default", () => {
38
+ process.env.docsgov_MODEL_CACHE = "";
39
+ const want = join(homedir(), ".cache", "docsgov", "models", Model.replaceAll("/", "_"));
40
40
  expect(cacheDir("")).toBe(want);
41
41
  });
42
42
  // WHY: the default path sanitizes the model name by replacing "/" with "_".
@@ -3,8 +3,8 @@
3
3
  // sentence-transformers/paraphrase-multilingual-mpnet-base-v2 model.
4
4
  //
5
5
  // The embedder model is auto-downloaded on first use to a configurable cache
6
- // directory (see NewOptions.cacheDir, the DOCGOV_MODEL_CACHE env var, or the
7
- // default ~/.cache/docgov/models/<sanitized-model-name>/).
6
+ // directory (see NewOptions.cacheDir, the docsgov_MODEL_CACHE env var, or the
7
+ // default ~/.cache/docsgov/models/<sanitized-model-name>/).
8
8
  import { cacheDir } from "./cache.js";
9
9
  import { Dimension, Model } from "./constants.js";
10
10
  import { InferenceError } from "./errors.js";
@@ -1,6 +1,6 @@
1
1
  // Behavior tests for Embedder, the public facade over Session, with the model
2
2
  // mocked so no ~1GB download happens. The real-model parity/golden gate stays
3
- // in embedder.test.ts (DOCGOV_EMBED_E2E) and is intentionally untouched.
3
+ // in embedder.test.ts (docsgov_EMBED_E2E) and is intentionally untouched.
4
4
  //
5
5
  // vi.mock is hoisted above the imports and intercepts the dynamic
6
6
  // `await import("@huggingface/transformers")` inside session.ts, so
@@ -26,15 +26,15 @@ function cosine(a, b) {
26
26
  dot += (a[i] ?? 0) * (b[i] ?? 0);
27
27
  return dot;
28
28
  }
29
- // E2E inference is gated by DOCGOV_EMBED_E2E because it downloads the model
29
+ // E2E inference is gated by docsgov_EMBED_E2E because it downloads the model
30
30
  // (~1GB). Bit-parity vs Go is already proven (maxAbsDiff 3.6e-8 in the spike),
31
31
  // so default CI runs only the fast unit suites above and below.
32
- const e2e = process.env.DOCGOV_EMBED_E2E ? describe : describe.skip;
33
- e2e("real inference (DOCGOV_EMBED_E2E)", () => {
32
+ const e2e = process.env.docsgov_EMBED_E2E ? describe : describe.skip;
33
+ e2e("real inference (docsgov_EMBED_E2E)", () => {
34
34
  let emb;
35
35
  let dir;
36
36
  beforeEach(async () => {
37
- dir = join(tmpdir(), `docgov-embed-${process.pid}-${Date.now()}`);
37
+ dir = join(tmpdir(), `docsgov-embed-${process.pid}-${Date.now()}`);
38
38
  emb = await Embedder.newEmbedder({ cacheDir: dir });
39
39
  });
40
40
  afterEach(async () => {
@@ -1,6 +1,6 @@
1
1
  // Behavior tests for Session, the transformers.js pipeline wrapper, with the
2
2
  // model mocked so no ~1GB download happens. The real-model parity gate lives in
3
- // embedder.test.ts (DOCGOV_EMBED_E2E) and is intentionally untouched.
3
+ // embedder.test.ts (docsgov_EMBED_E2E) and is intentionally untouched.
4
4
  //
5
5
  // vi.mock is hoisted above the imports and intercepts BOTH the static and the
6
6
  // dynamic `await import("@huggingface/transformers")` that session.ts performs,
@@ -1,4 +1,4 @@
1
- // Manages the docgov-owned block in .docgov/dedup/.gitignore that keeps the
1
+ // Manages the docsgov-owned block in .docsgov/dedup/.gitignore that keeps the
2
2
  // local SQLite index cache out of git.
3
3
  //
4
4
  // Ported from internal/dedup/gitignore.go. Go's os.ReadFile/WriteFile become
@@ -9,8 +9,8 @@
9
9
  // are preserved verbatim.
10
10
  import { readFile, writeFile } from "node:fs/promises";
11
11
  import * as path from "node:path";
12
- /** dedupGitignoreHeader labels the block docgov manages in .docgov/dedup/.gitignore. */
13
- export const dedupGitignoreHeader = "# docgov dedup index cache — local, rebuilt by `docgov dedup`.";
12
+ /** dedupGitignoreHeader labels the block docsgov manages in .docsgov/dedup/.gitignore. */
13
+ export const dedupGitignoreHeader = "# docsgov dedup index cache — local, rebuilt by `docsgov dedup`.";
14
14
  /**
15
15
  * dedupGitignoreRules are the patterns that keep the local SQLite index cache out
16
16
  * of git: the DB plus its WAL-mode sidecars. config.yml and the .gitignore itself
@@ -24,7 +24,7 @@ function isNotExist(err) {
24
24
  err.code === "ENOENT");
25
25
  }
26
26
  /**
27
- * ensureDedupGitignore makes sure .docgov/dedup/.gitignore excludes the index
27
+ * ensureDedupGitignore makes sure .docsgov/dedup/.gitignore excludes the index
28
28
  * cache. It is a no-op when every rule is already present (matched as an exact,
29
29
  * trimmed line — comments and blanks ignored); otherwise it creates the file or
30
30
  * appends only the missing rules, leaving any existing content intact so a user's
@@ -75,7 +75,7 @@ export function gitignoreLineSet(body) {
75
75
  return set;
76
76
  }
77
77
  /**
78
- * appendGitignoreBlock returns existing followed by the docgov header and the
78
+ * appendGitignoreBlock returns existing followed by the docsgov header and the
79
79
  * missing rules. When existing is empty the block stands alone; otherwise it is
80
80
  * separated from prior content by a blank line, and a trailing newline is added
81
81
  * first if needed so the header never glues onto a user's last line.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Behavior-encoding tests for the docgov-owned .docgov/dedup/.gitignore block,
2
+ * Behavior-encoding tests for the docsgov-owned .docsgov/dedup/.gitignore block,
3
3
  * ported from internal/dedup/gitignore_test.go.
4
4
  *
5
5
  * WHY each case matters: this file decides what stays out of git. The exact
@@ -49,7 +49,7 @@ describe("ensureDedupGitignore", () => {
49
49
  const dir = newDir();
50
50
  await ensureDedupGitignore(dir);
51
51
  const got = readGitignore(dir);
52
- const want = "# docgov dedup index cache — local, rebuilt by `docgov dedup`.\n" +
52
+ const want = "# docsgov dedup index cache — local, rebuilt by `docsgov dedup`.\n" +
53
53
  "index.db\nindex.db-wal\nindex.db-shm\n";
54
54
  expect(got).toBe(want);
55
55
  const set = gitignoreLineSet(got);
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Manages the dedup section index stored in a SQLite database.
3
3
  *
4
- * The DB lives at <repo_root>/.docgov/dedup/index.db. Callers resolve the repo
4
+ * The DB lives at <repo_root>/.docsgov/dedup/index.db. Callers resolve the repo
5
5
  * root and pass the absolute DB path to open — this package never walks for
6
- * .docgov or touches the sentinel constant (that stays in internal/repo).
6
+ * .docsgov or touches the sentinel constant (that stays in internal/repo).
7
7
  *
8
8
  * Ported from internal/dedup/indexdb. node:sqlite is synchronous, so Go's
9
9
  * context.Context / database/sql async plumbing is dropped: open/close/queries
@@ -8,7 +8,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
8
8
  // WHY: the docs guard calls exists to verify a referenced file is present.
9
9
  // A false negative would flag a valid reference as a violation.
10
10
  test("returns true for a file present at the exact-case path", async () => {
11
- const root = await makeDir(".docgov", "docs/adr");
11
+ const root = await makeDir(".docsgov", "docs/adr");
12
12
  await nodefs.writeFile(path.join(root, "docs", "adr", "0001-foo.md"), "hello");
13
13
  const r = await find(root);
14
14
  expect(await r.exists("docs/adr/0001-foo.md")).toBe(true);
@@ -16,7 +16,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
16
16
  // WHY: a reference to a non-existent file is the most common docs guard
17
17
  // violation; exists must detect absence.
18
18
  test("returns false for a missing file", async () => {
19
- const root = await makeDir(".docgov", "docs/adr");
19
+ const root = await makeDir(".docsgov", "docs/adr");
20
20
  const r = await find(root);
21
21
  expect(await r.exists("docs/adr/nonexistent.md")).toBe(false);
22
22
  });
@@ -24,7 +24,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
24
24
  // pass case-mismatched references. The segment-by-segment walk matches each
25
25
  // entry by exact case so reports are byte-identical across platforms.
26
26
  test("rejects a wrong-case filename segment", async () => {
27
- const root = await makeDir(".docgov", "docs/adr");
27
+ const root = await makeDir(".docsgov", "docs/adr");
28
28
  await nodefs.writeFile(path.join(root, "docs", "adr", "0001-foo.md"), "hello");
29
29
  const r = await find(root);
30
30
  expect(await r.exists("docs/adr/0001-FOO.md")).toBe(false);
@@ -32,7 +32,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
32
32
  // WHY: the exact-case rule must hold at EVERY segment, not just the filename;
33
33
  // a wrong-case directory prefix must fail too.
34
34
  test("rejects a wrong-case directory segment", async () => {
35
- const root = await makeDir(".docgov", "docs/adr");
35
+ const root = await makeDir(".docsgov", "docs/adr");
36
36
  await nodefs.writeFile(path.join(root, "docs", "adr", "0001-foo.md"), "hello");
37
37
  const r = await find(root);
38
38
  expect(await r.exists("Docs/adr/0001-foo.md")).toBe(false);
@@ -40,7 +40,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
40
40
  // WHY: the docs guard checks FILE references; a directory path is not a valid
41
41
  // file reference and must not pass.
42
42
  test("returns false for a directory path", async () => {
43
- const root = await makeDir(".docgov", "docs/adr");
43
+ const root = await makeDir(".docsgov", "docs/adr");
44
44
  const r = await find(root);
45
45
  expect(await r.exists("docs/adr")).toBe(false);
46
46
  });
@@ -48,7 +48,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
48
48
  // would abort on every reference to a file that doesn't exist yet, which is
49
49
  // the guard's core purpose.
50
50
  test("treats a missing path as false without throwing", async () => {
51
- const root = await makeDir(".docgov");
51
+ const root = await makeDir(".docsgov");
52
52
  const r = await find(root);
53
53
  await expect(r.exists("does/not/exist.md")).resolves.toBe(false);
54
54
  });
@@ -58,7 +58,7 @@ describe("exists: exact-case file presence for the docs guard", () => {
58
58
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
59
59
  const ioErrorTest = isRoot || os.platform() === "win32" ? test.skip : test;
60
60
  ioErrorTest("propagates a real I/O error rather than swallowing it as absent", async () => {
61
- const root = await makeDir(".docgov", "docs");
61
+ const root = await makeDir(".docsgov", "docs");
62
62
  const r = await find(root);
63
63
  const docsPath = path.join(root, "docs");
64
64
  await nodefs.chmod(docsPath, 0o000);
@@ -1,4 +1,4 @@
1
- // Public surface of the repo package — the OS boundary for docgov.
1
+ // Public surface of the repo package — the OS boundary for docsgov.
2
2
  export { Repo, PendingWrite, find } from "./repo.js";
3
3
  export { RealFS, isNotExist } from "./fs.js";
4
4
  export { OverlayFS } from "./overlay.js";
@@ -12,7 +12,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
12
12
  // Core contract: the check sees the flipped bytes even though the real file
13
13
  // on disk still holds the original.
14
14
  test("overlaid path returns the injected content, not the on-disk bytes", async () => {
15
- const root = await makeDir(".docgov", "docs");
15
+ const root = await makeDir(".docsgov", "docs");
16
16
  const r = await find(root);
17
17
  await r.writeFile("docs/a.md", enc.encode("---\nstatus: planning\n---\n"));
18
18
  const injected = enc.encode("---\nstatus: implemented\n---\n");
@@ -22,7 +22,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
22
22
  // The resolver reads source files (non-overlaid); if the overlay intercepted
23
23
  // those, the resolver would see wrong data.
24
24
  test("non-overlaid path delegates to the base on-disk content", async () => {
25
- const root = await makeDir(".docgov", "docs", "pkg");
25
+ const root = await makeDir(".docsgov", "docs", "pkg");
26
26
  const r = await find(root);
27
27
  await r.writeFile("docs/a.md", enc.encode("original\n"));
28
28
  await r.writeFile("pkg/foo.go", enc.encode("package foo\n"));
@@ -32,7 +32,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
32
32
  // The check pipeline reads files via fs() too (direct reads and the markdown
33
33
  // walk); the overlay FS must agree with overlay readFile.
34
34
  test("fs() serves the injected content for the overlaid path", async () => {
35
- const root = await makeDir(".docgov", "docs");
35
+ const root = await makeDir(".docsgov", "docs");
36
36
  const r = await find(root);
37
37
  await r.writeFile("docs/a.md", enc.encode("on-disk\n"));
38
38
  const injected = enc.encode("overlay\n");
@@ -42,7 +42,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
42
42
  // If withOverlay mutated the base repo's FS, the real file would be
43
43
  // effectively overwritten in memory — the exact problem being avoided.
44
44
  test("creating an overlay does not mutate the base repo", async () => {
45
- const root = await makeDir(".docgov", "docs");
45
+ const root = await makeDir(".docsgov", "docs");
46
46
  const r = await find(root);
47
47
  await r.writeFile("docs/a.md", enc.encode("status: planning\n"));
48
48
  r.withOverlay("docs/a.md", enc.encode("status: implemented\n"));
@@ -53,7 +53,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
53
53
  // real on-disk tree (including the overlaid file). If readDir intercepted
54
54
  // anything, the walk would miss or duplicate files.
55
55
  test("readDir delegates to the base so the overlaid file is still enumerated", async () => {
56
- const root = await makeDir(".docgov", "docs");
56
+ const root = await makeDir(".docsgov", "docs");
57
57
  const r = await find(root);
58
58
  await r.writeFile("docs/a.md", enc.encode("on-disk\n"));
59
59
  await r.writeFile("docs/b.md", enc.encode("other\n"));
@@ -68,7 +68,7 @@ describe("withOverlay: single-path in-memory intercept", () => {
68
68
  // the overlay's content rule — a sub() that diverged from base would make a
69
69
  // nested read return the wrong bytes for non-overlaid files.
70
70
  test("sub descends into the base subtree", async () => {
71
- const root = await makeDir(".docgov", "docs", "pkg");
71
+ const root = await makeDir(".docsgov", "docs", "pkg");
72
72
  const r = await find(root);
73
73
  await r.writeFile("docs/a.md", enc.encode("on-disk\n"));
74
74
  await r.writeFile("pkg/foo.go", enc.encode("package foo\n"));
package/dist/repo/repo.js CHANGED
@@ -1,10 +1,10 @@
1
- // Package repo is the OS boundary for docgov. It is the only package that
1
+ // Package repo is the OS boundary for docsgov. It is the only package that
2
2
  // touches the real filesystem directly and owns OS path handling. Everything
3
3
  // else works with slash paths and the FS interface.
4
4
  //
5
5
  // Responsibilities:
6
- // - Walk CWD up to the nearest ancestor containing a .docgov/ directory.
7
- // - Fall back to the supplied directory when no .docgov/ exists (gen/add
6
+ // - Walk CWD up to the nearest ancestor containing a .docsgov/ directory.
7
+ // - Fall back to the supplied directory when no .docsgov/ exists (gen/add
8
8
  // bootstrapping mode).
9
9
  // - Return a Repo that exposes an FS and the OS root path.
10
10
  // - CRLF -> LF normalisation on every file read.
@@ -12,8 +12,8 @@ import * as nodefs from "node:fs/promises";
12
12
  import * as path from "node:path";
13
13
  import { isNotExist, RealFS } from "./fs.js";
14
14
  import { OverlayFS } from "./overlay.js";
15
- // sentinel is the directory name that marks a docgov repository root.
16
- const sentinel = ".docgov";
15
+ // sentinel is the directory name that marks a docsgov repository root.
16
+ const sentinel = ".docsgov";
17
17
  /**
18
18
  * Holds the resolved repository root. It is the sole owner of the OS path; all
19
19
  * callers work through the {@link FS} interface or the slash-path readFile
@@ -209,7 +209,7 @@ export class Repo {
209
209
  }
210
210
  /**
211
211
  * Writes `content` to a temp file that is a sibling of the file at
212
- * `slashPath` (same directory, ".docgov-flip.tmp" suffix), then returns a
212
+ * `slashPath` (same directory, ".docsgov-flip.tmp" suffix), then returns a
213
213
  * {@link PendingWrite}. The caller calls commit to either rename the temp
214
214
  * into place (ok=true) or remove it (ok=false). The temp file is always in
215
215
  * the same directory as the target so the rename stays atomic on a single
@@ -223,7 +223,7 @@ export class Repo {
223
223
  catch (err) {
224
224
  throw new Error(`repo.atomicWriteFile: mkdir parents for ${JSON.stringify(slashPath)}: ${errMsg(err)}`);
225
225
  }
226
- const tmpAbs = abs + ".docgov-flip.tmp";
226
+ const tmpAbs = abs + ".docsgov-flip.tmp";
227
227
  try {
228
228
  await nodefs.writeFile(tmpAbs, content);
229
229
  }
@@ -298,9 +298,9 @@ export class PendingWrite {
298
298
  }
299
299
  }
300
300
  /**
301
- * Walks up from `dir` to locate the nearest ancestor containing a .docgov/
301
+ * Walks up from `dir` to locate the nearest ancestor containing a .docsgov/
302
302
  * directory. If none is found it falls back to `dir` itself (so gen and add can
303
- * bootstrap a repo that has no .docgov/ yet). `dir` is resolved to an absolute
303
+ * bootstrap a repo that has no .docsgov/ yet). `dir` is resolved to an absolute
304
304
  * OS path first.
305
305
  */
306
306
  export async function find(dir) {
@@ -322,7 +322,7 @@ export async function find(dir) {
322
322
  }
323
323
  const parent = path.dirname(current);
324
324
  if (parent === current) {
325
- // Reached the filesystem root without finding .docgov/. Fall back to the
325
+ // Reached the filesystem root without finding .docsgov/. Fall back to the
326
326
  // original dir (bootstrapping mode).
327
327
  return new Repo(abs, new RealFS(abs));
328
328
  }