@rse/specbook 0.9.5 → 0.9.7

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 (61) hide show
  1. package/README.md +108 -328
  2. package/dst/specbook-api.d.ts +13 -20
  3. package/dst/specbook-api.js +43 -39
  4. package/dst/specbook-cli.js +42 -55
  5. package/dst/specbook-cmd-describe.d.ts +5 -2
  6. package/dst/specbook-cmd-describe.js +30 -91
  7. package/dst/specbook-cmd-export.d.ts +3 -3
  8. package/dst/specbook-cmd-export.js +23 -19
  9. package/dst/specbook-cmd-init.d.ts +3 -3
  10. package/dst/specbook-cmd-init.js +38 -15
  11. package/dst/specbook-cmd-lint.d.ts +5 -6
  12. package/dst/specbook-cmd-lint.js +61 -24
  13. package/dst/specbook-config.d.ts +2 -2
  14. package/dst/specbook-config.js +82 -73
  15. package/dst/specbook-diagnostic.js +4 -1
  16. package/dst/specbook-diagram.d.ts +6 -5
  17. package/dst/specbook-diagram.js +94 -104
  18. package/dst/specbook-export-ast.d.ts +3 -3
  19. package/dst/specbook-export-ast.js +1 -1
  20. package/dst/specbook-export-common.d.ts +11 -9
  21. package/dst/specbook-export-common.js +34 -22
  22. package/dst/specbook-export-html-search.js +10 -14
  23. package/dst/specbook-export-html.css +37 -32
  24. package/dst/specbook-export-html.d.ts +4 -4
  25. package/dst/specbook-export-html.js +153 -107
  26. package/dst/specbook-export-logo-dark.svg +35 -0
  27. package/dst/specbook-export-logo-light.svg +35 -0
  28. package/dst/specbook-export-md.d.ts +3 -3
  29. package/dst/specbook-export-md.js +50 -24
  30. package/dst/specbook-export-pdf.d.ts +4 -2
  31. package/dst/specbook-export-pdf.js +48 -38
  32. package/dst/{specbook-struct-schema.d.ts → specbook-format-schema.d.ts} +3 -2
  33. package/dst/{specbook-struct-schema.js → specbook-format-schema.js} +8 -4
  34. package/dst/specbook-format-spec.d.ts +31 -0
  35. package/dst/{specbook-struct-spec.js → specbook-format-spec.js} +10 -10
  36. package/dst/specbook-format.md +750 -0
  37. package/dst/specbook-link.d.ts +3 -2
  38. package/dst/specbook-link.js +7 -4
  39. package/dst/specbook-mcp.d.ts +2 -1
  40. package/dst/specbook-mcp.js +30 -62
  41. package/dst/specbook-parse-common.d.ts +5 -3
  42. package/dst/specbook-parse-common.js +8 -0
  43. package/dst/specbook-parse-semantic.d.ts +6 -4
  44. package/dst/specbook-parse-semantic.js +98 -106
  45. package/dst/specbook-parse-syntax.d.ts +2 -2
  46. package/dst/specbook-parse-syntax.js +175 -144
  47. package/dst/specbook-parse-value.d.ts +1 -1
  48. package/dst/specbook-parse-value.js +6 -3
  49. package/dst/specbook-parse.d.ts +4 -7
  50. package/dst/specbook-parse.js +28 -35
  51. package/dst/specbook-verbose.d.ts +2 -0
  52. package/dst/specbook-verbose.js +17 -0
  53. package/package.json +3 -7
  54. package/dst/specbook-cmd-edit.d.ts +0 -9
  55. package/dst/specbook-cmd-edit.js +0 -74
  56. package/dst/specbook-cmd-import.d.ts +0 -9
  57. package/dst/specbook-cmd-import.js +0 -78
  58. package/dst/specbook-export-logo.svg +0 -35
  59. package/dst/specbook-llm.d.ts +0 -13
  60. package/dst/specbook-llm.js +0 -51
  61. package/dst/specbook-struct-spec.d.ts +0 -31
@@ -1,74 +0,0 @@
1
- /*
2
- ** Specification Book (SpecBook)
3
- ** Copyright (c) 2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
- ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
- */
6
- import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import textframe from "textframe";
9
- import { describeConfiguration } from "./specbook-cmd-describe.js";
10
- import { complete, timestamp, renderFileBlock, parseFileBlocks } from "./specbook-llm.js";
11
- /* the LLM instruction for the edit command */
12
- const instruction = (description, now) => textframe `
13
- You are the edit engine of SpecBook, a Markdown-based specification
14
- format. Apply the given edit request to the Markdown specification
15
- artifact files of the project. Strictly honor the following rules:
16
-
17
- - Surgical changes: change only what the edit request actually
18
- demands; do not rewrite unrelated parts of an artifact.
19
- - No fabrication: never invent content the edit request does not
20
- support; leave gaps open instead of guessing.
21
- - Format conformance: strictly follow the specification format
22
- described below.
23
- - Markdown generation: MIRROR the format each existing object already
24
- uses (keep Complex Format objects complex and Concise Format
25
- objects concise); for entirely new objects emit the Complex Format
26
- on levels 1-3 ("#"/"##"/"###") and the Concise Format on level 4
27
- and deeper.
28
- - Timestamps: refresh the "Modified:" frontmatter of every changed
29
- file to "${now}"; keep "Created:" untouched.
30
- - Files: update ONLY the configured artifact files listed in the
31
- format description below -- never any other file.
32
- - Output: return ONLY the actually changed files, each as a block of
33
- the exact form below, and no other text at all:
34
-
35
- <<<FILE: <name>>>>
36
- <content>
37
- <<<END-FILE>>>
38
-
39
- ${description}
40
- `;
41
- /* apply an edit request to the specification artifact files */
42
- export const editSpecification = async (options) => {
43
- const basedir = options.basedir ?? ".";
44
- const files = options.config.map((artifact) => artifact.file).filter((file) => file !== undefined);
45
- /* ensure a non-empty edit request was given */
46
- if (options.query.trim() === "")
47
- throw new Error("no edit request given");
48
- /* gather the existing artifact files */
49
- const existing = files
50
- .filter((file) => fs.existsSync(path.join(basedir, file)))
51
- .map((file) => renderFileBlock(file, fs.readFileSync(path.join(basedir, file), "utf8")));
52
- if (existing.length === 0)
53
- throw new Error(`no configured artifact files found in "${basedir}"`);
54
- /* let the LLM apply the edit request */
55
- options.verbose(`editing artifact files in "${basedir}"`);
56
- const system = instruction(describeConfiguration(options.config), timestamp());
57
- const prompt = "EXISTING ARTIFACT FILES:\n\n" + existing.join("\n\n") +
58
- "\n\nEDIT REQUEST:\n\n" + options.query;
59
- const response = await complete(options, system, prompt);
60
- /* write back all returned artifact files */
61
- const written = new Array();
62
- for (const block of parseFileBlocks(response)) {
63
- if (!files.includes(block.name)) {
64
- options.verbose(`ignoring unconfigured file "${block.name}"`);
65
- continue;
66
- }
67
- const file = path.join(basedir, block.name);
68
- fs.mkdirSync(path.dirname(file), { recursive: true });
69
- fs.writeFileSync(file, block.content, "utf8");
70
- options.verbose(`wrote artifact file "${block.name}"`);
71
- written.push(block.name);
72
- }
73
- return written;
74
- };
@@ -1,9 +0,0 @@
1
- import { type SchemaSpecification } from "./specbook-struct-schema.js";
2
- import { type AiOptions } from "./specbook-llm.js";
3
- export interface ImportOptions extends AiOptions {
4
- config: SchemaSpecification;
5
- basedir?: string;
6
- inputs: string[];
7
- verbose: (msg: string) => void;
8
- }
9
- export declare const importSpecification: (options: ImportOptions) => Promise<string[]>;
@@ -1,78 +0,0 @@
1
- /*
2
- ** Specification Book (SpecBook)
3
- ** Copyright (c) 2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
- ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
- */
6
- import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import textframe from "textframe";
9
- import { describeConfiguration } from "./specbook-cmd-describe.js";
10
- import { complete, timestamp, renderFileBlock, parseFileBlocks } from "./specbook-llm.js";
11
- /* the LLM instruction for the import command */
12
- const instruction = (description, now) => textframe `
13
- You are the import engine of SpecBook, a Markdown-based specification
14
- format. Import the information of the given foreign sources into the
15
- Markdown specification artifact files of the project, generating or
16
- updating the artifact files so they faithfully reflect the imported
17
- information. Strictly honor the following rules:
18
-
19
- - Faithful reflection: the artifacts must reflect the imported
20
- sources exactly -- no more, no less.
21
- - No fabrication: never invent content the sources do not support;
22
- leave gaps open instead of guessing.
23
- - Surgical updates: when updating an existing artifact, change only
24
- what the imported information actually requires.
25
- - Level-appropriate translation: re-express the imported facts at the
26
- artifact's level of abstraction; do not copy verbatim.
27
- - Format conformance: strictly follow the specification format
28
- described below.
29
- - Markdown generation: emit the Complex Format on levels 1-3
30
- ("#"/"##"/"###") and the Concise Format on level 4 and deeper.
31
- - Timestamps: use "${now}" for the "Created:" and "Modified:"
32
- frontmatter of generated files, and refresh "Modified:" to
33
- "${now}" on updated files.
34
- - Files: generate or update ONLY the configured artifact files listed
35
- in the format description below -- never any other file.
36
- - Output: return ONLY the generated or updated files, each as a block
37
- of the exact form below, and no other text at all:
38
-
39
- <<<FILE: <name>>>>
40
- <content>
41
- <<<END-FILE>>>
42
-
43
- ${description}
44
- `;
45
- /* import foreign sources into the specification artifact files */
46
- export const importSpecification = async (options) => {
47
- const basedir = options.basedir ?? ".";
48
- const files = options.config.map((artifact) => artifact.file).filter((file) => file !== undefined);
49
- /* ensure at least one foreign source was given */
50
- if (options.inputs.length === 0)
51
- throw new Error("no foreign sources given to import");
52
- /* gather the existing artifact files and the foreign sources */
53
- const existing = files
54
- .filter((file) => fs.existsSync(path.join(basedir, file)))
55
- .map((file) => renderFileBlock(file, fs.readFileSync(path.join(basedir, file), "utf8")));
56
- const sources = options.inputs
57
- .map((input) => renderFileBlock(input, fs.readFileSync(input, "utf8")));
58
- /* let the LLM generate or update the artifact files */
59
- options.verbose(`importing ${options.inputs.length} foreign source(s) into "${basedir}"`);
60
- const system = instruction(describeConfiguration(options.config), timestamp());
61
- const prompt = "EXISTING ARTIFACT FILES:\n\n" + (existing.length > 0 ? existing.join("\n\n") : "(none)") +
62
- "\n\nFOREIGN SOURCES TO IMPORT:\n\n" + sources.join("\n\n");
63
- const response = await complete(options, system, prompt);
64
- /* write back all returned artifact files */
65
- const written = new Array();
66
- for (const block of parseFileBlocks(response)) {
67
- if (!files.includes(block.name)) {
68
- options.verbose(`ignoring unconfigured file "${block.name}"`);
69
- continue;
70
- }
71
- const file = path.join(basedir, block.name);
72
- fs.mkdirSync(path.dirname(file), { recursive: true });
73
- fs.writeFileSync(file, block.content, "utf8");
74
- options.verbose(`wrote artifact file "${block.name}"`);
75
- written.push(block.name);
76
- }
77
- return written;
78
- };
@@ -1,35 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 260.81 110.54">
3
- <!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
4
- <defs>
5
- <style>
6
- .st0 {
7
- fill: none;
8
- }
9
-
10
- .st1 {
11
- fill: #231f20;
12
- }
13
-
14
- .st2 {
15
- fill: #666766;
16
- }
17
- </style>
18
- </defs>
19
- <g>
20
- <rect class="st0" x="128.02" y="5.89" width="297.69" height="245.38"/>
21
- <path class="st1" d="M132.56,41.28c2.62,1.34,6.66,2.69,10.82,2.69,4.48,0,6.85-1.86,6.85-4.67s-2.05-4.22-7.23-6.08c-7.17-2.5-11.84-6.46-11.84-12.73,0-7.36,6.14-12.99,16.32-12.99,4.86,0,8.45,1.02,11.01,2.18l-2.18,7.87c-1.73-.83-4.8-2.05-9.02-2.05s-6.27,1.92-6.27,4.16c0,2.75,2.43,3.97,8,6.08,7.62,2.82,11.2,6.78,11.2,12.86,0,7.23-5.57,13.38-17.41,13.38-4.93,0-9.79-1.28-12.22-2.62l1.98-8.06Z"/>
22
- <path class="st1" d="M163.28,30.53c0-4.1-.13-7.62-.26-10.5h8.45l.45,4.35h.13c2.3-3.33,5.89-5.06,10.43-5.06,6.85,0,12.99,5.95,12.99,15.93,0,11.39-7.23,16.77-14.21,16.77-3.78,0-6.72-1.54-8.13-3.58h-.13v15.55h-9.73V30.53ZM173.01,37.89c0,.77.06,1.47.19,2.05.64,2.62,2.88,4.61,5.7,4.61,4.22,0,6.72-3.52,6.72-8.83,0-4.99-2.24-8.77-6.59-8.77-2.75,0-5.18,2.05-5.82,4.93-.13.51-.19,1.15-.19,1.73v4.29Z"/>
23
- <path class="st1" d="M206.03,38.78c.32,4.03,4.29,5.95,8.83,5.95,3.33,0,6.02-.45,8.64-1.28l1.28,6.59c-3.2,1.28-7.1,1.92-11.33,1.92-10.62,0-16.7-6.14-16.7-15.94,0-7.94,4.93-16.7,15.81-16.7,10.11,0,13.95,7.87,13.95,15.61,0,1.66-.19,3.14-.32,3.84h-20.16ZM217.48,32.13c0-2.37-1.02-6.34-5.5-6.34-4.1,0-5.76,3.71-6.02,6.34h11.52Z"/>
24
- <path class="st1" d="M252.62,50.56c-1.73.77-4.99,1.41-8.7,1.41-10.11,0-16.57-6.14-16.57-16,0-9.15,6.27-16.64,17.92-16.64,2.56,0,5.38.45,7.42,1.22l-1.54,7.23c-1.15-.51-2.88-.96-5.44-.96-5.12,0-8.45,3.65-8.38,8.77,0,5.76,3.84,8.77,8.58,8.77,2.3,0,4.1-.38,5.57-.96l1.15,7.17Z"/>
25
- </g>
26
- <path d="M25.37,18.54h0c-1.03,0-2,.4-2.73,1.13-.74.74-1.15,1.72-1.15,2.77v58.42c0,2.14,1.75,3.89,3.9,3.9,9.07.02,24.26,1.91,34.75,12.88v-61.14c0-.73-.19-1.41-.54-1.97-8.6-13.85-25.14-15.96-34.23-15.98Z"/>
27
- <path d="M104.82,80.86V22.43c0-1.05-.41-2.03-1.15-2.77-.73-.73-1.7-1.13-2.73-1.13h0c-9.09.02-25.62,2.13-34.23,15.98-.35.56-.54,1.25-.54,1.97v61.14c10.48-10.97,25.68-12.86,34.75-12.88,2.15,0,3.9-1.75,3.9-3.89Z"/>
28
- <path class="st2" d="M113.7,32.01h-2.83v48.85c0,5.47-4.46,9.93-9.94,9.94-7.69.02-20.38,1.52-29.36,10.02,15.54-3.8,31.91-1.33,41.25.8,1.17.27,2.37,0,3.3-.75.93-.74,1.46-1.85,1.46-3.04v-61.92c0-2.15-1.75-3.9-3.9-3.9Z"/>
29
- <path class="st2" d="M15.43,80.86v-48.85h-2.83c-2.15,0-3.9,1.75-3.9,3.9v61.92c0,1.19.53,2.3,1.46,3.04.93.74,2.14,1.02,3.3.75,9.33-2.13,25.71-4.6,41.25-.8-8.98-8.5-21.67-10.01-29.36-10.02-5.48-.01-9.94-4.47-9.94-9.94Z"/>
30
- <g>
31
- <path class="st2" d="M131.67,55.5c2.63-.55,6.77-.97,10.99-.97,6.01,0,9.88,1.04,12.78,3.39,2.42,1.8,3.87,4.56,3.87,8.22,0,4.49-2.97,8.43-7.88,10.23v.14c4.42,1.11,9.6,4.77,9.6,11.68,0,4.01-1.59,7.05-3.94,9.33-3.25,2.97-8.5,4.35-16.1,4.35-4.15,0-7.32-.28-9.33-.55v-45.81ZM137.68,74.57h5.46c6.36,0,10.09-3.32,10.09-7.81,0-5.46-4.15-7.6-10.23-7.6-2.76,0-4.35.21-5.32.41v14.99ZM137.68,96.89c1.17.21,2.9.28,5.04.28,6.22,0,11.95-2.28,11.95-9.05,0-6.36-5.46-8.98-12.02-8.98h-4.97v17.76Z"/>
32
- <path class="st2" d="M206.14,66.9c-9.26,0-16.58,6.56-16.58,17.75,0,.12.01.24.01.36h-.04c-.27,7.16-4.51,12.28-10.13,12.28-5.94,0-10.16-5.53-10.16-12.85,0-6.36,3.11-12.99,10.29-12.99,4.29,0,7.02,2.57,8.53,5.85.79-2.15,1.94-3.99,3.37-5.51-2.83-3.1-6.89-4.9-11.76-4.9-9.26,0-16.58,6.56-16.58,17.75,0,10.57,6.98,17.2,16.03,17.2,7.88,0,16.19-5.14,16.61-16.84h0c0-.19-.02-.38-.02-.57,0-6.36,3.11-12.99,10.29-12.99s10.02,7.19,10.02,12.85c0,7.53-4.35,12.99-10.15,12.99-4.05,0-7.29-2.57-8.94-6.51-.75,2.31-1.89,4.24-3.29,5.8,2.9,3.37,7.13,5.27,11.96,5.27,8.08,0,16.65-5.39,16.65-17.75,0-10.22-6.49-17.2-16.1-17.2Z"/>
33
- <path class="st2" d="M230.65,83.76h.14c.83-1.17,2-2.63,2.97-3.8l9.81-11.54h7.32l-12.92,13.75,14.72,19.69h-7.39l-11.54-16.03-3.11,3.45v12.57h-6.01v-49.06h6.01v30.95Z"/>
34
- </g>
35
- </svg>
@@ -1,13 +0,0 @@
1
- import { type LanguageModel } from "ai";
2
- export interface AiOptions {
3
- provider?: string;
4
- model?: string;
5
- }
6
- export declare const resolveModel: (options: AiOptions) => Promise<LanguageModel>;
7
- export declare const complete: (options: AiOptions, system: string, prompt: string) => Promise<string>;
8
- export declare const timestamp: () => string;
9
- export declare const renderFileBlock: (name: string, content: string) => string;
10
- export declare const parseFileBlocks: (text: string) => {
11
- name: string;
12
- content: string;
13
- }[];
@@ -1,51 +0,0 @@
1
- /*
2
- ** Specification Book (SpecBook)
3
- ** Copyright (c) 2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
4
- ** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
5
- */
6
- /* the supported AI providers */
7
- const providers = {
8
- anthropic: { model: "claude-sonnet-5",
9
- create: async (id) => (await import("@ai-sdk/anthropic")).createAnthropic()(id) },
10
- openai: { model: "gpt-5.6-luna",
11
- create: async (id) => (await import("@ai-sdk/openai")).createOpenAI()(id) },
12
- openrouter: { model: "anthropic/claude-sonnet-5",
13
- create: async (id) => (await import("@openrouter/ai-sdk-provider")).createOpenRouter()(id) },
14
- ollama: { model: "qwen3",
15
- create: async (id) => (await import("ollama-ai-provider-v2")).createOllama()(id) }
16
- };
17
- /* resolve the AI provider and model into a language model handle */
18
- export const resolveModel = async (options) => {
19
- const name = options.provider ?? process.env.SPECBOOK_AI_PROVIDER ?? "anthropic";
20
- const provider = providers[name];
21
- if (provider === undefined)
22
- throw new Error(`unknown AI provider "${name}" (expected ` +
23
- Object.keys(providers).map((n) => `"${n}"`).join(", ") + ")");
24
- const model = options.model ?? process.env.SPECBOOK_AI_MODEL ?? provider.model;
25
- return provider.create(model);
26
- };
27
- /* perform a single LLM completion */
28
- export const complete = async (options, system, prompt) => {
29
- const { generateText } = await import("ai");
30
- const model = await resolveModel(options);
31
- const result = await generateText({ model, system, prompt });
32
- return result.text;
33
- };
34
- /* the current time in the frontmatter timestamp format */
35
- export const timestamp = () => {
36
- const d = new Date();
37
- const pad = (n) => String(n).padStart(2, "0");
38
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
39
- `${pad(d.getHours())}:${pad(d.getMinutes())}`;
40
- };
41
- /* render a named file as a block of the LLM file exchange protocol */
42
- export const renderFileBlock = (name, content) => `<<<FILE: ${name}>>>\n${content.replace(/\n$/, "")}\n<<<END-FILE>>>`;
43
- /* parse all file blocks of the LLM file exchange protocol */
44
- export const parseFileBlocks = (text) => {
45
- const blocks = new Array();
46
- const re = /<<<FILE:\s*([^>]+?)\s*>>>\r?\n([\s\S]*?)<<<END-FILE>>>/g;
47
- let m;
48
- while ((m = re.exec(text)) !== null)
49
- blocks.push({ name: m[1], content: m[2].replace(/\n*$/, "\n") });
50
- return blocks;
51
- };
@@ -1,31 +0,0 @@
1
- import * as v from "valibot";
2
- export type Specification = {
3
- artifacts: Artifact[];
4
- };
5
- export type Artifact = {
6
- created: Date;
7
- modified: Date;
8
- objects: Object[];
9
- };
10
- export type Object = {
11
- kind: string;
12
- id: string;
13
- anchor?: string;
14
- paren?: string;
15
- name: string;
16
- primary?: boolean;
17
- description?: Description;
18
- properties: Property[];
19
- childs: Object[];
20
- };
21
- export type Description = {
22
- description: string;
23
- rationale?: string;
24
- embedding?: string[];
25
- };
26
- export type Property = {
27
- key: string;
28
- value: string;
29
- embedding?: string[];
30
- };
31
- export declare const Specification: v.GenericSchema<Specification>;