@ghostbyte/agent-docs-kit 5.0.0 → 5.0.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 +13 -0
- package/docs/ARCHITECTURE.md +3 -1
- package/docs/RELEASE_PROCESS.md +1 -1
- package/docs/SECURITY_MODEL.md +2 -0
- package/package.json +9 -3
- package/scripts/smoke-package.mjs +24 -0
- package/scripts/validate-content.mjs +12 -0
- package/src/core/files.mjs +25 -4
- package/tests/cli.test.mjs +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Notable user-visible changes are recorded here. Versions follow semantic versioning.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Local documentation workspaces and generated audit records are excluded from the npm archive by an explicit public-document allowlist.
|
|
10
|
+
- Package validation and smoke installation now reject missing declared entry points and unexpected documentation files.
|
|
11
|
+
- The legacy `main` field resolves to the same public API used by `exports`.
|
|
12
|
+
- Forced scaffold replacement stages content before swapping the owned file into place.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- GitHub workflows use the reviewed, SHA-pinned checkout v7.0.1, setup-node v7.1.0, configure-pages v6.0.0, upload-pages-artifact v5.0.0, and deploy-pages v5.0.1 releases.
|
|
17
|
+
|
|
5
18
|
## 5.0.0 - 2026-10-14
|
|
6
19
|
|
|
7
20
|
### Added
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -43,4 +43,6 @@ Agent_Docs_Kit/
|
|
|
43
43
|
|
|
44
44
|
`content/` is the single canonical library for all three workflows. `agent-docs setup` creates `.agent-docs/config.json` and a project documentation tree. Reports are written beneath configured directories; the reference library stays in the npm package and installed skill rather than being duplicated into each project.
|
|
45
45
|
|
|
46
|
-
Generated outputs under `build/`, package archives, dependencies, and the maintainer’s local monthly AI prompt are ignored by Git.
|
|
46
|
+
Generated outputs under `build/`, package archives, dependencies, and the maintainer’s local monthly AI prompt are ignored by Git. Local consumer-style documentation under the configured `docs/` workspace is also ignored in this repository.
|
|
47
|
+
|
|
48
|
+
Git ignore rules are not treated as a publishing boundary. `package.json` explicitly lists the six public maintainer guides, `docs/.npmignore` excludes local workspace records, and the packed-installation smoke test rejects every unexpected `docs/` path and every missing declared entry point.
|
package/docs/RELEASE_PROCESS.md
CHANGED
|
@@ -4,7 +4,7 @@ Published npm versions are immutable. A matching version-tag push can publish a
|
|
|
4
4
|
|
|
5
5
|
1. Update canonical content and run `npm run build` to refresh progressive skill references and the site.
|
|
6
6
|
2. Run `npm run release:check`. This includes tests, validation, builds, a clean tarball installation, and `npm audit`.
|
|
7
|
-
3. Confirm the package archive contains no credentials, local reports, nested repositories, or generated consumer data.
|
|
7
|
+
3. Confirm the package archive contains no credentials, local reports, nested repositories, or generated consumer data. `npm pack --dry-run --json` must show only the six approved public files under `docs/`, and every `main`, `bin`, and `exports` target must exist in the archive.
|
|
8
8
|
4. Commit the package changes locally.
|
|
9
9
|
5. Update the version with `npm version patch`, `minor`, or `major` according to compatibility.
|
|
10
10
|
6. Push the commit and tag. A matching `v*` tag on `main` runs `.github/workflows/publish-npm.yml`, verifies the version and ancestry, and publishes through npm trusted publishing.
|
package/docs/SECURITY_MODEL.md
CHANGED
|
@@ -4,6 +4,8 @@ The CLI is inert on installation: `package.json` defines no `preinstall`, `insta
|
|
|
4
4
|
|
|
5
5
|
Project setup skips existing files by default. `--force` is required to replace files owned by a command, and `--dry-run` previews writes. The package does not install Git hooks automatically, execute network scans, read credentials, or contact a telemetry service.
|
|
6
6
|
|
|
7
|
+
Forced scaffold replacement is staged in the destination directory before the owned file is swapped into place. npm packaging uses an explicit public-document allowlist plus a packed-manifest check; local documentation, generated findings, and audit evidence are rejected from the archive even when they exist in the working tree.
|
|
8
|
+
|
|
7
9
|
Audit reports begin as `Unverified`. The tool scaffolds the review; it does not claim that generated text proves a control. Agents and reviewers must attach repository paths, line numbers, reproducible command output, or authorized external evidence.
|
|
8
10
|
|
|
9
11
|
Publishing uses npm provenance through GitHub trusted publishing when configured. Protect the GitHub release environment, require reviews as appropriate, and never store a long-lived npm token when OIDC is available.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostbyte/agent-docs-kit",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Documentation governance, evidence-based security audits, and engineering-quality reviews for software projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
"content/",
|
|
16
16
|
"templates/",
|
|
17
17
|
"skills/",
|
|
18
|
-
"docs/",
|
|
18
|
+
"docs/ARCHITECTURE.md",
|
|
19
|
+
"docs/CONTENT_MAINTENANCE.md",
|
|
20
|
+
"docs/GITHUB_AND_NPM_SETUP.md",
|
|
21
|
+
"docs/PURPOSE.md",
|
|
22
|
+
"docs/RELEASE_PROCESS.md",
|
|
23
|
+
"docs/SECURITY_MODEL.md",
|
|
19
24
|
"scripts/",
|
|
20
25
|
"site/",
|
|
21
26
|
"tests/",
|
|
@@ -62,5 +67,6 @@
|
|
|
62
67
|
"directories": {
|
|
63
68
|
"doc": "docs",
|
|
64
69
|
"test": "tests"
|
|
65
|
-
}
|
|
70
|
+
},
|
|
71
|
+
"main": "./src/api/index.mjs"
|
|
66
72
|
}
|
|
@@ -13,11 +13,35 @@ try {
|
|
|
13
13
|
const cache = resolve(scratch, "npm-cache");
|
|
14
14
|
const packed = JSON.parse(runNpm(["pack", "--json", "--cache", cache, "--pack-destination", scratch], { cwd: root, encoding: "utf8" }));
|
|
15
15
|
const archivePaths = new Set(packed[0].files.map((entry) => entry.path.replaceAll("\\", "/")));
|
|
16
|
+
const publicDocs = new Set([
|
|
17
|
+
"docs/ARCHITECTURE.md",
|
|
18
|
+
"docs/CONTENT_MAINTENANCE.md",
|
|
19
|
+
"docs/GITHUB_AND_NPM_SETUP.md",
|
|
20
|
+
"docs/PURPOSE.md",
|
|
21
|
+
"docs/RELEASE_PROCESS.md",
|
|
22
|
+
"docs/SECURITY_MODEL.md"
|
|
23
|
+
]);
|
|
16
24
|
for (const required of ["scripts/validate-content.mjs", "site/index.html", "tests/cli.test.mjs", "skills/manage-project-quality/references/security-protocol.md", "skills/manage-project-quality/references/engineering-protocol.md", "skills/manage-project-quality/references/documentation-governance.md"]) {
|
|
17
25
|
if (!archivePaths.has(required)) throw new Error(`Packed archive is missing ${required}.`);
|
|
18
26
|
}
|
|
27
|
+
for (const publicDoc of publicDocs) {
|
|
28
|
+
if (!archivePaths.has(publicDoc)) throw new Error(`Packed archive is missing public documentation: ${publicDoc}`);
|
|
29
|
+
}
|
|
19
30
|
for (const path of archivePaths) {
|
|
20
31
|
if (/^(?:\.git|\.agent-docs|\.npm-cache|build|node_modules)(?:\/|$)|MONTHLY_MAINTENANCE_AI\.md$|\.tgz$/i.test(path)) throw new Error(`Private or generated path leaked into package: ${path}`);
|
|
32
|
+
if (path.startsWith("docs/") && !publicDocs.has(path)) throw new Error(`Local documentation leaked into package: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
const packageMetadata = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
35
|
+
const declaredTargets = new Set([packageMetadata.main, ...Object.values(packageMetadata.bin ?? {})]);
|
|
36
|
+
const collectExportTargets = (value) => {
|
|
37
|
+
if (typeof value === "string") declaredTargets.add(value);
|
|
38
|
+
else if (Array.isArray(value)) value.forEach(collectExportTargets);
|
|
39
|
+
else if (value && typeof value === "object") Object.values(value).forEach(collectExportTargets);
|
|
40
|
+
};
|
|
41
|
+
collectExportTargets(packageMetadata.exports);
|
|
42
|
+
for (const target of declaredTargets) {
|
|
43
|
+
const normalized = target?.replace(/^\.\//, "").replaceAll("\\", "/");
|
|
44
|
+
if (normalized && !archivePaths.has(normalized)) throw new Error(`Declared package entry point is missing from the archive: ${target}`);
|
|
21
45
|
}
|
|
22
46
|
const tarball = resolve(scratch, packed[0].filename);
|
|
23
47
|
const consumer = resolve(scratch, "consumer");
|
|
@@ -74,6 +74,18 @@ for (const directory of metadata.files ?? []) {
|
|
|
74
74
|
try { await access(resolve(root, directory)); }
|
|
75
75
|
catch { errors.push(`package.json files entry does not exist: ${directory}`); }
|
|
76
76
|
}
|
|
77
|
+
const declaredTargets = new Set([metadata.main, ...Object.values(metadata.bin ?? {})]);
|
|
78
|
+
const collectExportTargets = (value) => {
|
|
79
|
+
if (typeof value === "string") declaredTargets.add(value);
|
|
80
|
+
else if (Array.isArray(value)) value.forEach(collectExportTargets);
|
|
81
|
+
else if (value && typeof value === "object") Object.values(value).forEach(collectExportTargets);
|
|
82
|
+
};
|
|
83
|
+
collectExportTargets(metadata.exports);
|
|
84
|
+
for (const target of declaredTargets) {
|
|
85
|
+
if (!target) continue;
|
|
86
|
+
try { await access(resolve(root, target)); }
|
|
87
|
+
catch { errors.push(`Declared package entry point does not exist: ${target}`); }
|
|
88
|
+
}
|
|
77
89
|
|
|
78
90
|
const site = await readFile(resolve(root, "site/index.html"), "utf8");
|
|
79
91
|
const ids = [...site.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]);
|
package/src/core/files.mjs
CHANGED
|
@@ -16,10 +16,31 @@ export async function writeSafe(path, contents, { force = false, dryRun = false
|
|
|
16
16
|
if (!force && await exists(path)) return { path, status: "skipped" };
|
|
17
17
|
if (!dryRun) {
|
|
18
18
|
await mkdir(dirname(path), { recursive: true });
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if (!force) {
|
|
20
|
+
try { await writeFile(path, contents, { encoding: "utf8", flag: "wx" }); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code === "EEXIST") return { path, status: "skipped" };
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
const nonce = `${process.pid}-${Date.now()}-${randomUUID()}`;
|
|
27
|
+
const staged = `${path}.agent-docs-stage-${nonce}`;
|
|
28
|
+
const backup = `${path}.agent-docs-backup-${nonce}`;
|
|
29
|
+
await writeFile(staged, contents, { encoding: "utf8", flag: "wx" });
|
|
30
|
+
const replacing = await exists(path);
|
|
31
|
+
let movedOriginal = false;
|
|
32
|
+
try {
|
|
33
|
+
if (replacing) {
|
|
34
|
+
await rename(path, backup);
|
|
35
|
+
movedOriginal = true;
|
|
36
|
+
}
|
|
37
|
+
await rename(staged, path);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
await rm(staged, { force: true });
|
|
40
|
+
if (movedOriginal && !(await exists(path))) await rename(backup, path);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
if (movedOriginal) await rm(backup, { force: true });
|
|
23
44
|
}
|
|
24
45
|
}
|
|
25
46
|
return { path, status: dryRun ? "planned" : "created" };
|
package/tests/cli.test.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { access, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { setupProject } from "../src/commands/setup.mjs";
|
|
@@ -29,6 +29,22 @@ test("setup is explicit, non-destructive, and repeatable", async () => {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
test("forced setup atomically replaces owned files without leaving staging artifacts", async () => {
|
|
33
|
+
const root = await mkdtemp(join(tmpdir(), "agent-docs-kit-"));
|
|
34
|
+
try {
|
|
35
|
+
await setupProject({ cwd: root });
|
|
36
|
+
const overview = join(root, "docs", "01-system", "overview.md");
|
|
37
|
+
await writeFile(overview, "temporary contents\n");
|
|
38
|
+
const replaced = await setupProject({ cwd: root, force: true });
|
|
39
|
+
assert.ok(replaced.every((item) => item.status === "created"));
|
|
40
|
+
assert.match(await readFile(overview, "utf8"), /^# System overview/);
|
|
41
|
+
const names = await readdir(join(root, "docs", "01-system"));
|
|
42
|
+
assert.ok(names.every((name) => !name.includes(".agent-docs-stage-") && !name.includes(".agent-docs-backup-")));
|
|
43
|
+
} finally {
|
|
44
|
+
await rm(root, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
32
48
|
test("security and engineering reports use distinct semantics", async () => {
|
|
33
49
|
const root = await mkdtemp(join(tmpdir(), "agent-docs-kit-"));
|
|
34
50
|
try {
|