benjamin-docs 0.1.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +352 -0
  3. package/dist/src/anchors.d.ts +1 -0
  4. package/dist/src/anchors.js +50 -0
  5. package/dist/src/anchors.js.map +1 -0
  6. package/dist/src/cli.d.ts +2 -0
  7. package/dist/src/cli.js +231 -0
  8. package/dist/src/cli.js.map +1 -0
  9. package/dist/src/constants.d.ts +12 -0
  10. package/dist/src/constants.js +13 -0
  11. package/dist/src/constants.js.map +1 -0
  12. package/dist/src/export.d.ts +1 -0
  13. package/dist/src/export.js +70 -0
  14. package/dist/src/export.js.map +1 -0
  15. package/dist/src/frontmatter.d.ts +3 -0
  16. package/dist/src/frontmatter.js +119 -0
  17. package/dist/src/frontmatter.js.map +1 -0
  18. package/dist/src/fsx.d.ts +14 -0
  19. package/dist/src/fsx.js +129 -0
  20. package/dist/src/fsx.js.map +1 -0
  21. package/dist/src/info.d.ts +3 -0
  22. package/dist/src/info.js +65 -0
  23. package/dist/src/info.js.map +1 -0
  24. package/dist/src/init.d.ts +12 -0
  25. package/dist/src/init.js +145 -0
  26. package/dist/src/init.js.map +1 -0
  27. package/dist/src/next.d.ts +2 -0
  28. package/dist/src/next.js +65 -0
  29. package/dist/src/next.js.map +1 -0
  30. package/dist/src/project-config.d.ts +6 -0
  31. package/dist/src/project-config.js +41 -0
  32. package/dist/src/project-config.js.map +1 -0
  33. package/dist/src/scopes.d.ts +1 -0
  34. package/dist/src/scopes.js +52 -0
  35. package/dist/src/scopes.js.map +1 -0
  36. package/dist/src/status.d.ts +1 -0
  37. package/dist/src/status.js +38 -0
  38. package/dist/src/status.js.map +1 -0
  39. package/dist/src/templates.d.ts +19 -0
  40. package/dist/src/templates.js +82 -0
  41. package/dist/src/templates.js.map +1 -0
  42. package/dist/src/types.d.ts +51 -0
  43. package/dist/src/types.js +2 -0
  44. package/dist/src/types.js.map +1 -0
  45. package/dist/src/validate.d.ts +5 -0
  46. package/dist/src/validate.js +542 -0
  47. package/dist/src/validate.js.map +1 -0
  48. package/package.json +45 -0
  49. package/skills/benjamin-docs/SKILL.md +129 -0
@@ -0,0 +1,12 @@
1
+ export declare const DEFAULT_DOCS_ROOT = "benjamin-docs";
2
+ export declare const CONFIG_DIR = ".benjamin-docs";
3
+ export declare const MANIFEST_FILE = "manifest.json";
4
+ export declare const SCOPES_FILE = "scopes.json";
5
+ export declare const ANCHORS_FILE = "anchors.json";
6
+ export declare const CONFIG_FILE = "config.json";
7
+ export declare const KNOWN_SCOPES: readonly ["project", "feature", "release", "handoff"];
8
+ export declare const KNOWN_FOCUS_TYPES: readonly ["project", "codebase", "feature"];
9
+ export declare const KNOWN_AUDIENCES: readonly ["developer", "designer", "agent", "business", "public", "user", "advisor"];
10
+ export declare const KNOWN_STATUSES: readonly ["draft", "review", "approved", "stale", "archived"];
11
+ export declare const KNOWN_VISIBILITIES: readonly ["private", "unlisted", "public"];
12
+ export declare const KNOWN_SOURCES: readonly ["session-capture", "manual", "codebase-scan", "release-sync"];
@@ -0,0 +1,13 @@
1
+ export const DEFAULT_DOCS_ROOT = "benjamin-docs";
2
+ export const CONFIG_DIR = ".benjamin-docs";
3
+ export const MANIFEST_FILE = "manifest.json";
4
+ export const SCOPES_FILE = "scopes.json";
5
+ export const ANCHORS_FILE = "anchors.json";
6
+ export const CONFIG_FILE = "config.json";
7
+ export const KNOWN_SCOPES = ["project", "feature", "release", "handoff"];
8
+ export const KNOWN_FOCUS_TYPES = ["project", "codebase", "feature"];
9
+ export const KNOWN_AUDIENCES = ["developer", "designer", "agent", "business", "public", "user", "advisor"];
10
+ export const KNOWN_STATUSES = ["draft", "review", "approved", "stale", "archived"];
11
+ export const KNOWN_VISIBILITIES = ["private", "unlisted", "public"];
12
+ export const KNOWN_SOURCES = ["session-capture", "manual", "codebase-scan", "release-sync"];
13
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC;AACjD,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAC;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAC7C,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AACzC,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAU,CAAC;AAClF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAU,CAAC;AACpH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC;AAC5F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,iBAAiB,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,CAAU,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function exportAudience(root: string, audience: string): string[];
@@ -0,0 +1,70 @@
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, rmSync } from "node:fs";
2
+ import { relative, sep } from "node:path";
3
+ import { KNOWN_AUDIENCES } from "./constants.js";
4
+ import { assertGeneratedPathSafe, ensureGeneratedDir, lstatIfExists, rootPath, writeGeneratedText } from "./fsx.js";
5
+ import { parseMarkdown } from "./frontmatter.js";
6
+ import { readConfig } from "./project-config.js";
7
+ import { validateProject } from "./validate.js";
8
+ export function exportAudience(root, audience) {
9
+ const selectedAudience = parseAudience(audience);
10
+ const validation = validateProject(root);
11
+ if (validation.errors.length > 0) {
12
+ throw new Error(["Cannot export while validation has errors:", ...validation.errors.map((error) => `- ${error}`)].join("\n"));
13
+ }
14
+ const config = readConfig(root);
15
+ const docsRoot = rootPath(root, config.docsRoot);
16
+ const docs = findMarkdownFiles(docsRoot);
17
+ const bundleRelativeRoot = `exports/${selectedAudience}`;
18
+ prepareCleanBundleDirectory(root, selectedAudience);
19
+ const written = [];
20
+ for (const docPath of docs) {
21
+ const content = readFileSync(docPath, "utf8");
22
+ const parsed = parseMarkdown(content);
23
+ if (!parsed.frontmatter.audience.includes(selectedAudience))
24
+ continue;
25
+ const relativePath = relative(docsRoot, docPath).split(sep).join("/");
26
+ const targetRelativePath = `${bundleRelativeRoot}/${relativePath}`;
27
+ const targetPath = rootPath(root, ...targetRelativePath.split("/"));
28
+ writeGeneratedText(root, targetRelativePath, content);
29
+ written.push(targetPath);
30
+ }
31
+ return written;
32
+ }
33
+ function parseAudience(audience) {
34
+ if (!KNOWN_AUDIENCES.includes(audience)) {
35
+ throw new Error(`Unknown audience: ${audience}. Expected one of: ${KNOWN_AUDIENCES.join(", ")}`);
36
+ }
37
+ return audience;
38
+ }
39
+ function prepareCleanBundleDirectory(root, audience) {
40
+ const parts = ["exports", audience];
41
+ const relativePath = parts.join("/");
42
+ const bundleRoot = rootPath(root, ...parts);
43
+ assertGeneratedPathSafe(root, parts, "Generated output path", "directory");
44
+ const stat = lstatIfExists(bundleRoot);
45
+ if (stat) {
46
+ rmSync(bundleRoot, { recursive: true, force: true });
47
+ }
48
+ ensureGeneratedDir(root, relativePath);
49
+ return bundleRoot;
50
+ }
51
+ function findMarkdownFiles(dir) {
52
+ if (!existsSync(dir))
53
+ return [];
54
+ const files = [];
55
+ for (const entry of readdirSync(dir)) {
56
+ const fullPath = rootPath(dir, entry);
57
+ const stat = lstatSync(fullPath);
58
+ if (stat.isSymbolicLink()) {
59
+ if (fullPath.endsWith(".md"))
60
+ files.push(fullPath);
61
+ continue;
62
+ }
63
+ if (stat.isDirectory())
64
+ files.push(...findMarkdownFiles(fullPath));
65
+ if (stat.isFile() && fullPath.endsWith(".md"))
66
+ files.push(fullPath);
67
+ }
68
+ return files.sort();
69
+ }
70
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { DocFrontmatter, ParsedMarkdown } from "./types.js";
2
+ export declare function parseMarkdown(markdown: string): ParsedMarkdown;
3
+ export declare function serializeMarkdown(frontmatter: DocFrontmatter, body: string): string;
@@ -0,0 +1,119 @@
1
+ import { KNOWN_AUDIENCES, KNOWN_SCOPES, KNOWN_SOURCES, KNOWN_STATUSES, KNOWN_VISIBILITIES } from "./constants.js";
2
+ const ORDER = [
3
+ "title",
4
+ "scope",
5
+ "scope_id",
6
+ "audience",
7
+ "status",
8
+ "visibility",
9
+ "updated",
10
+ "source",
11
+ ];
12
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
13
+ export function parseMarkdown(markdown) {
14
+ const normalized = normalizeMarkdown(markdown);
15
+ if (!normalized.startsWith("---\n")) {
16
+ throw new Error("Markdown file is missing frontmatter");
17
+ }
18
+ const end = normalized.indexOf("\n---\n", 4);
19
+ if (end === -1) {
20
+ throw new Error("Markdown frontmatter is not closed");
21
+ }
22
+ const raw = normalized.slice(4, end);
23
+ const body = normalized.slice(end + 5);
24
+ const frontmatter = {};
25
+ for (const line of raw.split("\n")) {
26
+ if (!line.trim())
27
+ continue;
28
+ const colon = line.indexOf(":");
29
+ if (colon === -1)
30
+ throw new Error(`Invalid frontmatter line: ${line}`);
31
+ const key = line.slice(0, colon).trim();
32
+ const value = line.slice(colon + 1).trim();
33
+ frontmatter[key] = parseValue(value);
34
+ }
35
+ return { frontmatter: validateFrontmatter(frontmatter), body };
36
+ }
37
+ export function serializeMarkdown(frontmatter, body) {
38
+ const lines = ORDER.map((key) => `${key}: ${serializeValue(frontmatter[key])}`);
39
+ return `---\n${lines.join("\n")}\n---\n\n${body.replace(/^\n+/, "")}`;
40
+ }
41
+ function parseValue(value) {
42
+ if (value.startsWith("[") && value.endsWith("]")) {
43
+ const inner = value.slice(1, -1).trim();
44
+ if (!inner)
45
+ return [];
46
+ return inner.split(",").map((item) => item.trim());
47
+ }
48
+ return value;
49
+ }
50
+ function serializeValue(value) {
51
+ if (Array.isArray(value))
52
+ return `[${value.join(", ")}]`;
53
+ return value;
54
+ }
55
+ function normalizeMarkdown(markdown) {
56
+ return markdown.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
57
+ }
58
+ function validateFrontmatter(frontmatter) {
59
+ const scope = knownValue("scope", requiredString(frontmatter, "scope"), KNOWN_SCOPES);
60
+ const status = knownValue("status", requiredString(frontmatter, "status"), KNOWN_STATUSES);
61
+ const visibility = knownValue("visibility", requiredString(frontmatter, "visibility"), KNOWN_VISIBILITIES);
62
+ const source = knownValue("source", requiredString(frontmatter, "source"), KNOWN_SOURCES);
63
+ const audience = requiredAudience(frontmatter);
64
+ return {
65
+ title: requiredString(frontmatter, "title"),
66
+ scope,
67
+ scope_id: requiredString(frontmatter, "scope_id"),
68
+ audience,
69
+ status,
70
+ visibility,
71
+ updated: requiredDate(frontmatter),
72
+ source,
73
+ };
74
+ }
75
+ function requiredString(frontmatter, key) {
76
+ const value = frontmatter[key];
77
+ if (value === undefined) {
78
+ throw new Error(`Missing required frontmatter field: ${key}`);
79
+ }
80
+ if (typeof value !== "string") {
81
+ throw new Error(`Frontmatter field ${key} must be a string`);
82
+ }
83
+ return value;
84
+ }
85
+ function requiredAudience(frontmatter) {
86
+ const value = frontmatter.audience;
87
+ if (value === undefined) {
88
+ throw new Error("Missing required frontmatter field: audience");
89
+ }
90
+ if (!Array.isArray(value)) {
91
+ throw new Error("Frontmatter field audience must be an array");
92
+ }
93
+ const audiences = [];
94
+ for (const audience of value) {
95
+ if (typeof audience !== "string") {
96
+ throw new Error("Frontmatter field audience must contain only strings");
97
+ }
98
+ audiences.push(knownValue("audience", audience, KNOWN_AUDIENCES));
99
+ }
100
+ if (audiences.length === 0) {
101
+ throw new Error("Frontmatter field audience must include at least one value");
102
+ }
103
+ return audiences;
104
+ }
105
+ function requiredDate(frontmatter) {
106
+ const value = requiredString(frontmatter, "updated");
107
+ const date = new Date(`${value}T00:00:00.000Z`);
108
+ if (!DATE_PATTERN.test(value) || Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== value) {
109
+ throw new Error("Frontmatter field updated must be YYYY-MM-DD");
110
+ }
111
+ return value;
112
+ }
113
+ function knownValue(field, value, knownValues) {
114
+ if (!knownValues.includes(value)) {
115
+ throw new Error(`Unknown frontmatter ${field}: ${value}`);
116
+ }
117
+ return value;
118
+ }
119
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/frontmatter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { lstatSync } from "node:fs";
2
+ type GeneratedTarget = "any" | "directory" | "file";
3
+ export declare function pathExists(path: string): boolean;
4
+ export declare function readJson<T>(path: string): T;
5
+ export declare function readGeneratedJson<T>(root: string, relativePath: string, label?: string): T;
6
+ export declare function ensureGeneratedDir(root: string, relativePath: string, label?: string): void;
7
+ export declare function writeGeneratedJson(root: string, relativePath: string, value: unknown, label?: string): void;
8
+ export declare function writeGeneratedText(root: string, relativePath: string, value: string, label?: string): void;
9
+ export declare function writeGeneratedJsonIfMissing(root: string, relativePath: string, value: unknown, label?: string): boolean;
10
+ export declare function writeGeneratedTextIfMissing(root: string, relativePath: string, value: string, label?: string): boolean;
11
+ export declare function assertGeneratedPathSafe(root: string, parts: string[], label?: string, target?: GeneratedTarget): void;
12
+ export declare function lstatIfExists(path: string): ReturnType<typeof lstatSync> | undefined;
13
+ export declare function rootPath(root: string, ...parts: string[]): string;
14
+ export {};
@@ -0,0 +1,129 @@
1
+ import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve, sep, win32 } from "node:path";
3
+ const DEFAULT_GENERATED_LABEL = "Generated output path";
4
+ export function pathExists(path) {
5
+ return existsSync(path);
6
+ }
7
+ export function readJson(path) {
8
+ return JSON.parse(readFileSync(path, "utf8"));
9
+ }
10
+ export function readGeneratedJson(root, relativePath, label = DEFAULT_GENERATED_LABEL) {
11
+ const parts = generatedPathParts(relativePath, label);
12
+ assertGeneratedPathSafe(root, parts, label, "file");
13
+ return readJson(rootPath(root, ...parts));
14
+ }
15
+ export function ensureGeneratedDir(root, relativePath, label = DEFAULT_GENERATED_LABEL) {
16
+ const parts = generatedPathParts(relativePath, label);
17
+ const fullPath = rootPath(root, ...parts);
18
+ assertGeneratedPathSafe(root, parts, label, "directory");
19
+ mkdirSync(fullPath, { recursive: true });
20
+ assertGeneratedPathSafe(root, parts, label, "directory");
21
+ }
22
+ export function writeGeneratedJson(root, relativePath, value, label = DEFAULT_GENERATED_LABEL) {
23
+ writeGeneratedText(root, relativePath, `${JSON.stringify(value, null, 2)}\n`, label);
24
+ }
25
+ export function writeGeneratedText(root, relativePath, value, label = DEFAULT_GENERATED_LABEL) {
26
+ const parts = generatedPathParts(relativePath, label);
27
+ const fullPath = prepareGeneratedFile(root, parts, label);
28
+ writeFileSync(fullPath, value, "utf8");
29
+ }
30
+ export function writeGeneratedJsonIfMissing(root, relativePath, value, label = DEFAULT_GENERATED_LABEL) {
31
+ return writeGeneratedTextIfMissing(root, relativePath, `${JSON.stringify(value, null, 2)}\n`, label);
32
+ }
33
+ export function writeGeneratedTextIfMissing(root, relativePath, value, label = DEFAULT_GENERATED_LABEL) {
34
+ const parts = generatedPathParts(relativePath, label);
35
+ assertGeneratedPathSafe(root, parts, label, "file");
36
+ const fullPath = rootPath(root, ...parts);
37
+ if (lstatIfExists(fullPath))
38
+ return false;
39
+ prepareGeneratedParent(root, parts, label);
40
+ assertGeneratedPathSafe(root, parts, label, "file");
41
+ writeFileSync(fullPath, value, "utf8");
42
+ return true;
43
+ }
44
+ export function assertGeneratedPathSafe(root, parts, label = DEFAULT_GENERATED_LABEL, target = "any") {
45
+ const realRoot = realpathSync(root);
46
+ for (const [index] of parts.entries()) {
47
+ const currentParts = parts.slice(0, index + 1);
48
+ const current = rootPath(root, ...currentParts);
49
+ const displayPath = currentParts.join("/");
50
+ const stat = lstatIfExists(current);
51
+ if (!stat)
52
+ return;
53
+ if (stat.isSymbolicLink()) {
54
+ throw new Error(`${label} must not be a symlink: ${displayPath}`);
55
+ }
56
+ const realCurrent = realpathSync(current);
57
+ if (!isInsideRoot(realRoot, realCurrent)) {
58
+ throw new Error(`${label} must remain inside project root: ${displayPath}`);
59
+ }
60
+ if (index < parts.length - 1 && !stat.isDirectory()) {
61
+ throw new Error(`${label} parent must be a directory: ${displayPath}`);
62
+ }
63
+ if (index === parts.length - 1) {
64
+ if (target === "directory" && !stat.isDirectory()) {
65
+ throw new Error(`${label} must be a directory: ${displayPath}`);
66
+ }
67
+ if (target === "file" && !stat.isFile()) {
68
+ throw new Error(`${label} must be a file: ${displayPath}`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ export function lstatIfExists(path) {
74
+ try {
75
+ return lstatSync(path);
76
+ }
77
+ catch (error) {
78
+ if (isNotFoundError(error))
79
+ return undefined;
80
+ throw error;
81
+ }
82
+ }
83
+ export function rootPath(root, ...parts) {
84
+ const resolvedRoot = resolve(root);
85
+ for (const part of parts) {
86
+ if (isAbsolute(part)) {
87
+ throw new Error(`Refusing absolute path segment outside root: ${part}`);
88
+ }
89
+ }
90
+ const resolvedPath = resolve(resolvedRoot, ...parts);
91
+ const relativePath = relative(resolvedRoot, resolvedPath);
92
+ if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
93
+ throw new Error(`Resolved path is outside root: ${resolvedPath}`);
94
+ }
95
+ return resolvedPath;
96
+ }
97
+ function prepareGeneratedFile(root, parts, label) {
98
+ assertGeneratedPathSafe(root, parts, label, "file");
99
+ prepareGeneratedParent(root, parts, label);
100
+ assertGeneratedPathSafe(root, parts, label, "file");
101
+ return rootPath(root, ...parts);
102
+ }
103
+ function prepareGeneratedParent(root, parts, label) {
104
+ const parentParts = parts.slice(0, -1);
105
+ if (parentParts.length === 0)
106
+ return;
107
+ const parentPath = rootPath(root, ...parentParts);
108
+ assertGeneratedPathSafe(root, parentParts, label, "directory");
109
+ mkdirSync(parentPath, { recursive: true });
110
+ assertGeneratedPathSafe(root, parentParts, label, "directory");
111
+ }
112
+ function generatedPathParts(relativePath, label) {
113
+ if (!relativePath || isAbsolute(relativePath) || win32.isAbsolute(relativePath) || relativePath.includes("\\")) {
114
+ throw new Error(`${label} must be a relative project path: ${relativePath}`);
115
+ }
116
+ const parts = relativePath.split("/");
117
+ if (parts.some((part) => part === "" || part === "." || part === ".." || isAbsolute(part) || win32.isAbsolute(part))) {
118
+ throw new Error(`${label} must be a relative project path: ${relativePath}`);
119
+ }
120
+ return parts;
121
+ }
122
+ function isNotFoundError(error) {
123
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
124
+ }
125
+ function isInsideRoot(root, target) {
126
+ const relativePath = relative(root, target);
127
+ return relativePath === "" || (!relativePath.startsWith(`..${sep}`) && relativePath !== ".." && !isAbsolute(relativePath));
128
+ }
129
+ //# sourceMappingURL=fsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fsx.js","sourceRoot":"","sources":["../../src/fsx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function getPackageVersion(): string;
2
+ export declare function getHelpText(): string;
3
+ export declare function getIntroductionText(): string;
@@ -0,0 +1,65 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ export function getPackageVersion() {
5
+ const currentDir = dirname(fileURLToPath(import.meta.url));
6
+ const packagePath = join(currentDir, "..", "..", "package.json");
7
+ const pkg = JSON.parse(readFileSync(packagePath, "utf8"));
8
+ return pkg.version;
9
+ }
10
+ export function getHelpText() {
11
+ return [
12
+ "benjamin-docs",
13
+ "",
14
+ "Repo-local project memory for humans and AI agents.",
15
+ "",
16
+ "What it does:",
17
+ " Creates a small docs workspace in your project.",
18
+ " Gives your AI agent a clear place to capture decisions, plans, open questions, and handoffs.",
19
+ " Keeps those docs local unless you choose to share them.",
20
+ "",
21
+ "If you are here with an AI agent:",
22
+ " 1. Run: benjamin-docs introduce",
23
+ " 2. If you only have a chat, ask the agent to create a project folder from this chat.",
24
+ " A good default is ~/Documents/Benjamin Docs/<Project Name>.",
25
+ " 3. Otherwise run: benjamin-docs init",
26
+ " 4. Ask: Capture the current project baseline with benjamin-docs in plain language.",
27
+ "",
28
+ "Common commands:",
29
+ " benjamin-docs --version",
30
+ " benjamin-docs introduce",
31
+ " benjamin-docs init",
32
+ " benjamin-docs init --mode codebase",
33
+ " benjamin-docs init --mode feature --feature booking-capacity",
34
+ " benjamin-docs next",
35
+ " benjamin-docs status",
36
+ " benjamin-docs validate",
37
+ " benjamin-docs scope create feature booking-capacity",
38
+ " benjamin-docs anchor add booking-capacity-rules src/features/booking/capacity.ts",
39
+ " benjamin-docs export --audience developer",
40
+ " benjamin-docs promote --to codebase",
41
+ "",
42
+ "Start here:",
43
+ " benjamin-docs introduce",
44
+ " benjamin-docs init",
45
+ " benjamin-docs next",
46
+ ].join("\n");
47
+ }
48
+ export function getIntroductionText() {
49
+ return [
50
+ "benjamin-docs turns planning and build conversations into durable project memory.",
51
+ "",
52
+ "In plain language: it gives your project a local notebook that both humans and AI agents can read later.",
53
+ "",
54
+ "Your agent can use that notebook to capture what the project is, what was decided, what was rejected, what is still unclear, and what should happen next.",
55
+ "",
56
+ "The docs live inside your project, close to the work, so they can be versioned, reviewed, and reused by future sessions.",
57
+ "",
58
+ "Your docs are not uploaded or published by the CLI. The repo-local docs are the source of truth.",
59
+ "",
60
+ "If you only have a chat, ask your AI agent to create a new project from the chat with benjamin-docs. A good default location is ~/Documents/Benjamin Docs/<Project Name>.",
61
+ "",
62
+ "If you already have a project folder, ask your AI agent to run `benjamin-docs init`, then capture the current project baseline in plain language.",
63
+ ].join("\n");
64
+ }
65
+ //# sourceMappingURL=info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info.js","sourceRoot":"","sources":["../../src/info.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,iBAAiB;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAwB,CAAC;IACjF,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,eAAe;QACf,EAAE;QACF,qDAAqD;QACrD,EAAE;QACF,eAAe;QACf,mDAAmD;QACnD,gGAAgG;QAChG,2DAA2D;QAC3D,EAAE;QACF,mCAAmC;QACnC,mCAAmC;QACnC,wFAAwF;QACxF,kEAAkE;QAClE,wCAAwC;QACxC,sFAAsF;QACtF,EAAE;QACF,kBAAkB;QAClB,2BAA2B;QAC3B,2BAA2B;QAC3B,sBAAsB;QACtB,sCAAsC;QACtC,gEAAgE;QAChE,sBAAsB;QACtB,wBAAwB;QACxB,0BAA0B;QAC1B,uDAAuD;QACvD,oFAAoF;QACpF,6CAA6C;QAC7C,uCAAuC;QACvC,EAAE;QACF,aAAa;QACb,2BAA2B;QAC3B,sBAAsB;QACtB,sBAAsB;KACvB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,mFAAmF;QACnF,EAAE;QACF,0GAA0G;QAC1G,EAAE;QACF,2JAA2J;QAC3J,EAAE;QACF,0HAA0H;QAC1H,EAAE;QACF,kGAAkG;QAClG,EAAE;QACF,2KAA2K;QAC3K,EAAE;QACF,mJAAmJ;KACpJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { BenjaminDocsConfig, FocusType } from "./types.js";
2
+ export interface InitProjectOptions {
3
+ setup?: FocusType;
4
+ feature?: string;
5
+ docsRoot?: string;
6
+ }
7
+ export interface InitProjectResult {
8
+ written: string[];
9
+ config: BenjaminDocsConfig;
10
+ }
11
+ export declare function initProject(root: string, options?: InitProjectOptions): InitProjectResult;
12
+ export declare function promoteToCodebase(root: string): string[];
@@ -0,0 +1,145 @@
1
+ import { existsSync } from "node:fs";
2
+ import { ANCHORS_FILE, CONFIG_DIR, CONFIG_FILE, DEFAULT_DOCS_ROOT, MANIFEST_FILE, SCOPES_FILE } from "./constants.js";
3
+ import { ensureGeneratedDir, readGeneratedJson, writeGeneratedJson, writeGeneratedJsonIfMissing, writeGeneratedTextIfMissing, } from "./fsx.js";
4
+ import { codebaseDocs, featureDocs, workspaceDocs } from "./templates.js";
5
+ import { assertSafeDocsRoot, defaultConfig, normalizeConfig } from "./project-config.js";
6
+ const METADATA_LABEL = "Metadata path";
7
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
8
+ export function initProject(root, options = {}) {
9
+ const written = [];
10
+ const docsRoot = options.docsRoot ?? DEFAULT_DOCS_ROOT;
11
+ assertSafeDocsRoot(docsRoot);
12
+ const setup = options.setup ?? "project";
13
+ const feature = options.feature;
14
+ if (setup === "feature" && (!feature || !SLUG_PATTERN.test(feature))) {
15
+ throw new Error("Usage: benjamin-docs init --mode feature --feature <slug>");
16
+ }
17
+ const mode = setup === "codebase" || (setup === "feature" && looksLikeCodebase(root)) ? "codebase" : "planning";
18
+ const config = defaultConfig({ mode, docsRoot, focus: setup, feature });
19
+ ensureGeneratedDir(root, docsRoot);
20
+ ensureGeneratedDir(root, CONFIG_DIR, METADATA_LABEL);
21
+ const docs = workspaceDocs(docsRoot);
22
+ const featureFiles = setup === "feature" && feature ? featureDocs(feature, docsRoot) : [];
23
+ for (const doc of [...docs, ...featureFiles]) {
24
+ if (writeGeneratedTextIfMissing(root, doc.path, doc.content)) {
25
+ written.push(doc.path);
26
+ }
27
+ }
28
+ writeConfig(root, config, written);
29
+ writeManifest(root, [...docs, ...featureFiles].map((item) => item.path), written);
30
+ writeScopes(root, docsRoot, feature, written);
31
+ writeJsonIfMissing(root, [CONFIG_DIR, ANCHORS_FILE], { version: 1, anchors: {} }, written);
32
+ return { written, config };
33
+ }
34
+ export function promoteToCodebase(root) {
35
+ const written = [];
36
+ const config = normalizeConfig(readGeneratedJson(root, `${CONFIG_DIR}/${CONFIG_FILE}`, METADATA_LABEL));
37
+ assertSafeDocsRoot(config.docsRoot);
38
+ for (const codebaseDoc of codebaseDocs(config.docsRoot)) {
39
+ if (writeGeneratedTextIfMissing(root, codebaseDoc.path, codebaseDoc.content)) {
40
+ written.push(codebaseDoc.path);
41
+ }
42
+ }
43
+ config.mode = "codebase";
44
+ config.focus = "codebase";
45
+ writeGeneratedJson(root, `${CONFIG_DIR}/${CONFIG_FILE}`, config, METADATA_LABEL);
46
+ updateManifest(root, config.docsRoot);
47
+ updateScopes(root, config.docsRoot);
48
+ return written;
49
+ }
50
+ function writeConfig(root, config, written) {
51
+ const path = `${CONFIG_DIR}/${CONFIG_FILE}`;
52
+ if (writeGeneratedJsonIfMissing(root, path, config, METADATA_LABEL)) {
53
+ written.push(path);
54
+ return;
55
+ }
56
+ const existing = normalizeConfig(readGeneratedJson(root, path, METADATA_LABEL));
57
+ const next = {
58
+ ...existing,
59
+ docsRoot: existing.docsRoot || config.docsRoot,
60
+ mode: config.mode,
61
+ focus: config.focus,
62
+ feature: config.feature,
63
+ };
64
+ writeGeneratedJson(root, path, next, METADATA_LABEL);
65
+ }
66
+ function writeManifest(root, docs, written) {
67
+ const path = `${CONFIG_DIR}/${MANIFEST_FILE}`;
68
+ const initial = { version: 1, docs };
69
+ if (writeGeneratedJsonIfMissing(root, path, initial, METADATA_LABEL)) {
70
+ written.push(path);
71
+ return;
72
+ }
73
+ const manifest = readGeneratedJson(root, path, METADATA_LABEL);
74
+ for (const doc of docs) {
75
+ if (!manifest.docs.includes(doc))
76
+ manifest.docs.push(doc);
77
+ }
78
+ writeGeneratedJson(root, path, manifest, METADATA_LABEL);
79
+ }
80
+ function writeScopes(root, docsRoot, feature, written) {
81
+ const path = `${CONFIG_DIR}/${SCOPES_FILE}`;
82
+ const records = baseScopes(docsRoot, feature);
83
+ const initial = { version: 1, scopes: records };
84
+ if (writeGeneratedJsonIfMissing(root, path, initial, METADATA_LABEL)) {
85
+ written.push(path);
86
+ return;
87
+ }
88
+ const scopes = readGeneratedJson(root, path, METADATA_LABEL);
89
+ for (const record of records) {
90
+ if (!scopes.scopes.some((scope) => scope.id === record.id))
91
+ scopes.scopes.push(record);
92
+ }
93
+ writeGeneratedJson(root, path, scopes, METADATA_LABEL);
94
+ }
95
+ function baseScopes(docsRoot, feature) {
96
+ return [
97
+ { id: "project", kind: "project", title: "Project", path: `${docsRoot}/project`, status: "draft" },
98
+ { id: "human-brief", kind: "handoff", title: "Human Brief", path: `${docsRoot}/handoff/human-brief.md`, status: "draft" },
99
+ { id: "agent-brief", kind: "handoff", title: "Agent Brief", path: `${docsRoot}/handoff/agent-brief.md`, status: "draft" },
100
+ { id: "release", kind: "release", title: "Release", path: `${docsRoot}/releases/changelog.md`, status: "draft" },
101
+ ...(feature
102
+ ? [{ id: feature, kind: "feature", title: titleFromSlug(feature), path: `${docsRoot}/features/${feature}`, status: "draft" }]
103
+ : []),
104
+ ];
105
+ }
106
+ function writeJsonIfMissing(root, parts, value, written) {
107
+ const path = parts.join("/");
108
+ if (writeGeneratedJsonIfMissing(root, path, value, METADATA_LABEL)) {
109
+ written.push(path);
110
+ }
111
+ }
112
+ function updateManifest(root, docsRoot) {
113
+ const manifest = readGeneratedJson(root, `${CONFIG_DIR}/${MANIFEST_FILE}`, METADATA_LABEL);
114
+ for (const codebaseDoc of codebaseDocs(docsRoot)) {
115
+ if (!manifest.docs.includes(codebaseDoc.path))
116
+ manifest.docs.push(codebaseDoc.path);
117
+ }
118
+ writeGeneratedJson(root, `${CONFIG_DIR}/${MANIFEST_FILE}`, manifest, METADATA_LABEL);
119
+ }
120
+ function updateScopes(root, docsRoot) {
121
+ const scopes = readGeneratedJson(root, `${CONFIG_DIR}/${SCOPES_FILE}`, METADATA_LABEL);
122
+ if (scopes.scopes.some((scope) => scope.id === "release")) {
123
+ return;
124
+ }
125
+ const releaseScope = {
126
+ id: "release",
127
+ kind: "release",
128
+ title: "Release",
129
+ path: `${docsRoot}/releases/changelog.md`,
130
+ status: "draft",
131
+ };
132
+ scopes.scopes.push(releaseScope);
133
+ writeGeneratedJson(root, `${CONFIG_DIR}/${SCOPES_FILE}`, scopes, METADATA_LABEL);
134
+ }
135
+ function looksLikeCodebase(root) {
136
+ return ["package.json", "src", "app", "pages", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml"].some((path) => existsSync(`${root}/${path}`));
137
+ }
138
+ function titleFromSlug(slug) {
139
+ return slug
140
+ .split("-")
141
+ .filter(Boolean)
142
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`)
143
+ .join(" ");
144
+ }
145
+ //# sourceMappingURL=init.js.map