docspec 0.2.0 → 0.4.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.
- package/README.md +64 -142
- package/dist/__tests__/changed.test.d.ts +2 -0
- package/dist/__tests__/changed.test.d.ts.map +1 -0
- package/dist/__tests__/changed.test.js +98 -0
- package/dist/__tests__/changed.test.js.map +1 -0
- package/dist/__tests__/cli.test.js +48 -145
- package/dist/__tests__/cli.test.js.map +1 -1
- package/dist/__tests__/create.test.d.ts +2 -0
- package/dist/__tests__/create.test.d.ts.map +1 -0
- package/dist/__tests__/{generator.test.js → create.test.js} +31 -27
- package/dist/__tests__/create.test.js.map +1 -0
- package/dist/__tests__/generate.test.d.ts +2 -0
- package/dist/__tests__/generate.test.d.ts.map +1 -0
- package/dist/__tests__/generate.test.js +152 -0
- package/dist/__tests__/generate.test.js.map +1 -0
- package/dist/__tests__/path-utils.test.d.ts +2 -0
- package/dist/__tests__/path-utils.test.d.ts.map +1 -0
- package/dist/__tests__/path-utils.test.js +49 -0
- package/dist/__tests__/path-utils.test.js.map +1 -0
- package/dist/__tests__/template.test.d.ts +2 -0
- package/dist/__tests__/template.test.d.ts.map +1 -0
- package/dist/__tests__/template.test.js +95 -0
- package/dist/__tests__/template.test.js.map +1 -0
- package/dist/changed.d.ts +25 -0
- package/dist/changed.d.ts.map +1 -0
- package/dist/changed.js +209 -0
- package/dist/changed.js.map +1 -0
- package/dist/cli.js +89 -72
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +118 -15
- package/dist/constants.js.map +1 -1
- package/dist/create.d.ts +14 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +97 -0
- package/dist/create.js.map +1 -0
- package/dist/generate.d.ts +26 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +176 -0
- package/dist/generate.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +74 -0
- package/dist/logger.js.map +1 -0
- package/dist/path-utils.d.ts +17 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +76 -0
- package/dist/path-utils.js.map +1 -0
- package/dist/{format-parser.d.ts → template.d.ts} +5 -8
- package/dist/template.d.ts.map +1 -0
- package/dist/{format-parser.js → template.js} +33 -57
- package/dist/template.js.map +1 -0
- package/package.json +1 -1
- package/README.docspec.md +0 -180
- package/dist/__tests__/generator.test.d.ts +0 -2
- package/dist/__tests__/generator.test.d.ts.map +0 -1
- package/dist/__tests__/generator.test.js.map +0 -1
- package/dist/__tests__/validator.test.d.ts +0 -2
- package/dist/__tests__/validator.test.d.ts.map +0 -1
- package/dist/__tests__/validator.test.js +0 -331
- package/dist/__tests__/validator.test.js.map +0 -1
- package/dist/format-parser.d.ts.map +0 -1
- package/dist/format-parser.js.map +0 -1
- package/dist/generator.d.ts +0 -11
- package/dist/generator.d.ts.map +0 -1
- package/dist/generator.js +0 -66
- package/dist/generator.js.map +0 -1
- package/dist/types.d.ts +0 -16
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/validator.d.ts +0 -7
- package/dist/validator.d.ts.map +0 -1
- package/dist/validator.js +0 -178
- package/dist/validator.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
# docspec
|
|
2
2
|
|
|
3
|
-
Docspec is a specification format and toolchain for documentation that is maintained by agents.
|
|
3
|
+
Docspec is a specification format and toolchain for documentation that is maintained by agents. **Docspec does not run an LLM**—it produces prompt output that you feed into your own LLM CLI (e.g. Claude, Codex).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Docspec files live under **`.docspec/`**. For a markdown file `README.md` or `docs/deploy.md`, the docspec is `.docspec/README.docspec.md` or `.docspec/docs/deploy.docspec.md` respectively.
|
|
6
|
+
|
|
7
|
+
The **format template** is fully up to you: it lives at **`.docspec/docspec.md`**. If you run any docspec command and `.docspec/docspec.md` does not exist, it is seeded from the bundled default (the content of `docspec-format.md` in this repo). Edit `.docspec/docspec.md` to define your own structure.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## The Docspec Format
|
|
8
10
|
|
|
9
|
-
The format
|
|
10
|
-
1. **Document Purpose** - What this document exists to explain or enable
|
|
11
|
-
2. **Update Triggers** - What kinds of changes should cause this document to be updated
|
|
12
|
-
3. **Expected Structure** - The sections this document should contain
|
|
13
|
-
4. **Editing Guidelines** - How edits to this document should be made
|
|
14
|
-
5. **Intentional Omissions** - What this document deliberately does not cover
|
|
11
|
+
Each `*.docspec.md` file is a specification for another document. The **default** format (used when seeding) is defined in [`docspec-format.md`](docspec-format.md). After seeding, your project uses `.docspec/docspec.md`, which you can change.
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
- Each section differs from the template boilerplate text
|
|
18
|
-
- Content is not just whitespace differences from boilerplate
|
|
19
|
-
- Each section has at least 50 characters of meaningful content
|
|
13
|
+
The default includes 5 sections: Document Purpose, Update Triggers, Expected Structure, Editing Guidelines, Intentional Omissions. Customize the template in `.docspec/docspec.md` to match your needs.
|
|
20
14
|
|
|
21
15
|
## Installation
|
|
22
16
|
|
|
@@ -34,162 +28,91 @@ npm install -g docspec
|
|
|
34
28
|
|
|
35
29
|
### CLI Commands
|
|
36
30
|
|
|
37
|
-
####
|
|
31
|
+
#### Generate a docspec file (default)
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
Pass a markdown file path to create (or overwrite) its docspec under `.docspec/`:
|
|
40
34
|
|
|
41
35
|
```bash
|
|
42
|
-
docspec
|
|
36
|
+
docspec README.md
|
|
37
|
+
docspec docs/deploy.md
|
|
43
38
|
```
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
docspec validate
|
|
49
|
-
```
|
|
40
|
+
This creates `.docspec/README.docspec.md` and `.docspec/docs/deploy.docspec.md` using the template at `.docspec/docspec.md` (seeded from the default on first run).
|
|
50
41
|
|
|
51
|
-
|
|
42
|
+
#### docspec changed (prompt for syncing docs after changes)
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
Produce a prompt file that instructs an LLM to sync markdown files with their docspecs given a list of changed files or a git diff:
|
|
54
45
|
|
|
55
46
|
```bash
|
|
56
|
-
docspec
|
|
47
|
+
docspec changed --base <base_sha> --merge <merge_sha> --output prompt.txt
|
|
48
|
+
docspec changed --changed-files "src/foo.ts,README.md" --output prompt.txt
|
|
57
49
|
```
|
|
58
50
|
|
|
59
|
-
|
|
51
|
+
Options: `--max-docspecs`, `--max-diff-chars`. Default output file: `prompt.txt`.
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
import { validateDocspec, generateDocspec } from "docspec";
|
|
65
|
-
import type { ValidationResult, DocspecSection } from "docspec";
|
|
53
|
+
#### docspec generate (docspec + prompt for LLM)
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
const result: ValidationResult = await validateDocspec("path/to/file.docspec.md");
|
|
69
|
-
if (!result.valid) {
|
|
70
|
-
console.error("Validation errors:", result.errors);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Generate a new docspec file
|
|
74
|
-
await generateDocspec("path/to/README.docspec.md");
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
The library exports:
|
|
78
|
-
- `validateDocspec()` - Validate a docspec file
|
|
79
|
-
- `generateDocspec()` - Generate a new docspec file
|
|
80
|
-
- `generateDocspecContent()` - Generate docspec content as a string
|
|
81
|
-
- `ValidationResult` - Type for validation results
|
|
82
|
-
- `DocspecSection` - Type for docspec sections
|
|
83
|
-
- `REQUIRED_SECTIONS` - Array of required section names
|
|
84
|
-
- `SECTION_BOILERPLATE` - Boilerplate text for each section
|
|
85
|
-
|
|
86
|
-
## Pre-commit Integration
|
|
87
|
-
|
|
88
|
-
To use docspec with [pre-commit](https://pre-commit.com/), see [`.pre-commit-config.yaml`](.pre-commit-config.yaml) for the configuration. The hook uses `docspec validate` as the entry point, targets `\.docspec\.md$` files, and passes filenames to the validate command.
|
|
89
|
-
|
|
90
|
-
Then install the pre-commit hooks:
|
|
55
|
+
Generate a new docspec for a markdown file and write a prompt you can feed to your LLM to fill or improve it:
|
|
91
56
|
|
|
92
57
|
```bash
|
|
93
|
-
|
|
58
|
+
docspec generate README.md --output-prompt prompt.txt
|
|
59
|
+
docspec generate docs/deploy.md --overwrite --output-prompt prompt.txt
|
|
94
60
|
```
|
|
95
61
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
## GitHub Action Integration
|
|
99
|
-
|
|
100
|
-
Docspec includes two GitHub Actions for different use cases:
|
|
101
|
-
|
|
102
|
-
1. **Post-merge documentation updates** - Automatically syncs markdown files after PR merges (workflow file: `.github/workflows/docspec-check.yml`, workflow name: "Docspec PR check")
|
|
103
|
-
2. **Manual docspec generation** - Manually triggered workflow to generate and improve docspec files (workflow file: `.github/workflows/docspec-generate.yml`, workflow name: "Docspec generate")
|
|
104
|
-
|
|
105
|
-
### Post-Merge Documentation Updates
|
|
106
|
-
|
|
107
|
-
This action automatically updates markdown files based on `*.docspec.md` files after PR merges. It uses Claude Code CLI (not the Anthropic API directly) to explore the repository and generate unified diff patches for documentation updates.
|
|
108
|
-
|
|
109
|
-
#### Setup
|
|
62
|
+
Use `--overwrite` to replace an existing docspec. Optionally `--output-plan <file>` to write a separate plan prompt.
|
|
110
63
|
|
|
111
|
-
|
|
64
|
+
Add the `--verbose` flag to any command for detailed logging.
|
|
112
65
|
|
|
113
|
-
|
|
114
|
-
- Add `ANTHROPIC_API_KEY` to your repository secrets (Settings → Secrets and variables → Actions)
|
|
115
|
-
- `GITHUB_TOKEN` is automatically provided by GitHub Actions
|
|
116
|
-
|
|
117
|
-
#### How It Works
|
|
118
|
-
|
|
119
|
-
1. When a PR is merged, the workflow triggers
|
|
120
|
-
2. The action discovers relevant `*.docspec.md` files using a three-part discovery strategy:
|
|
121
|
-
- Files that changed directly in the PR (docspec files modified in the merge)
|
|
122
|
-
- Files in the same directory as any changed file (sibling docspecs)
|
|
123
|
-
- Files in parent directories, walking up to the repository root (ancestor docspecs)
|
|
124
|
-
3. For each discovered docspec, Claude Code CLI is invoked with built-in tools to explore the repository and understand the codebase context
|
|
125
|
-
4. Claude generates a unified diff patch to update the target markdown file based on the code changes and docspec requirements
|
|
126
|
-
5. Unified diff patches are validated and applied to update the markdown files
|
|
127
|
-
6. A new PR is opened with the documentation updates
|
|
128
|
-
|
|
129
|
-
**File naming convention**: The action uses the pattern `filename.docspec.md` → `filename.md`:
|
|
130
|
-
- `README.docspec.md` → targets `README.md`
|
|
131
|
-
- `docs/guide.docspec.md` → targets `docs/guide.md`
|
|
132
|
-
|
|
133
|
-
#### Configuration Options
|
|
134
|
-
|
|
135
|
-
The action supports optional inputs:
|
|
136
|
-
|
|
137
|
-
- `max_docspecs` (default: `10`) - Maximum number of docspec files to process per merge
|
|
138
|
-
- `max_diff_chars` (default: `120000`) - Maximum characters in PR diff before truncation
|
|
139
|
-
- `anthropic_model` (default: `claude-sonnet-4-5`) - Anthropic model to use (short alias for the Claude Sonnet 4.5 model)
|
|
140
|
-
|
|
141
|
-
See [`.github/workflows/docspec-check.yml`](.github/workflows/docspec-check.yml) for the complete workflow file and [`action.yml`](action.yml) for all available configuration options.
|
|
142
|
-
|
|
143
|
-
**Note**: For this repository's own workflow files, you can use the local reference `uses: ./` (at the action level in the step) instead of the published action reference. This applies to both the post-merge workflow and the manual improvement workflow.
|
|
144
|
-
|
|
145
|
-
#### Safety Features
|
|
146
|
-
|
|
147
|
-
The action includes multiple guardrails to ensure safe operation:
|
|
148
|
-
|
|
149
|
-
- **Max files limit**: Prevents processing too many files in a single run (default: 10)
|
|
150
|
-
- **Diff truncation**: Large PR diffs are truncated to stay within token limits (default: 120,000 characters)
|
|
151
|
-
- **Unified diff validation**: Only accepts properly formatted patches that start with `diff --git` or `--- ` markers
|
|
152
|
-
- **Path validation**: Patches must reference the expected file path
|
|
153
|
-
- **No new files**: Patches cannot create new files
|
|
154
|
-
- **No non-markdown modifications**: Only markdown files can be modified
|
|
155
|
-
- **Concurrency control**: Prevents multiple workflow runs from conflicting
|
|
156
|
-
- **Controlled environment**: Claude Code CLI runs with built-in tools in a controlled filesystem environment
|
|
157
|
-
|
|
158
|
-
### Manual Docspec Generation
|
|
159
|
-
|
|
160
|
-
The docspec-generate workflow allows you to manually trigger generation and improvements to a docspec file and its associated markdown file. This is useful when you want to:
|
|
161
|
-
|
|
162
|
-
- Update a docspec to better reflect the current state of the markdown
|
|
163
|
-
- Discover gaps in documentation that aren't triggered by code changes
|
|
164
|
-
- Regenerate a docspec from scratch
|
|
165
|
-
|
|
166
|
-
#### Setup
|
|
66
|
+
### Library Usage
|
|
167
67
|
|
|
168
|
-
|
|
68
|
+
```typescript
|
|
69
|
+
import {
|
|
70
|
+
generateDocspec,
|
|
71
|
+
buildDocspecChangedPrompt,
|
|
72
|
+
buildDocspecGeneratePrompts,
|
|
73
|
+
markdownToDocspecPath,
|
|
74
|
+
docspecToMarkdownPath,
|
|
75
|
+
} from "docspec";
|
|
76
|
+
|
|
77
|
+
// Generate a docspec for a markdown file (writes to .docspec/<path>.docspec.md)
|
|
78
|
+
await generateDocspec("README.md");
|
|
79
|
+
|
|
80
|
+
// Build prompt for docspec changed (e.g. for CI)
|
|
81
|
+
const { prompt, outputPath } = await buildDocspecChangedPrompt({
|
|
82
|
+
base: "abc123",
|
|
83
|
+
merge: "def456",
|
|
84
|
+
outputPath: "prompt.txt",
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Build prompts for docspec generate
|
|
88
|
+
const { implPrompt } = await buildDocspecGeneratePrompts({
|
|
89
|
+
markdownPath: "README.md",
|
|
90
|
+
outputPromptPath: "prompt.txt",
|
|
91
|
+
});
|
|
92
|
+
```
|
|
169
93
|
|
|
170
|
-
|
|
94
|
+
The library also exports: `generateDocspecContent()`, `REQUIRED_SECTIONS`, `SECTION_BOILERPLATE`, `logger`, `LogLevel`, `isDocspecPath`, and types `DocspecChangedOptions`, `DocspecGenerateOptions`.
|
|
171
95
|
|
|
172
|
-
|
|
96
|
+
## GitHub Actions
|
|
173
97
|
|
|
174
|
-
|
|
175
|
-
- Missing information in the docspec (what should be documented but isn't)
|
|
176
|
-
- Irrelevant or incorrect information in the docspec (what doesn't match reality)
|
|
177
|
-
- Missing information in the markdown file (gaps in documentation)
|
|
98
|
+
Docspec’s actions **only produce prompt files**; they do not run an LLM or require API keys.
|
|
178
99
|
|
|
179
|
-
|
|
100
|
+
- **docspec-changed** (`.github/actions/docspec-check`) – Runs `docspec changed` and writes a prompt file. Outputs `prompt_file` and `has_prompt`.
|
|
101
|
+
- **docspec-generate** (`.github/actions/docspec-generate`) – Runs `docspec generate <markdown_file>` and writes a prompt file.
|
|
180
102
|
|
|
181
|
-
|
|
182
|
-
- Generates or overwrites the docspec file using `docspec generate`
|
|
183
|
-
- Validates the updated docspec file using `docspec validate` after changes are made
|
|
103
|
+
### Example: run docspec changed then Claude
|
|
184
104
|
|
|
185
|
-
|
|
105
|
+
This repo’s [`.github/workflows/docspec-check.yml`](.github/workflows/docspec-check.yml) runs when a PR is merged: it prepares the prompt with `docspec changed`, then runs the [official Claude Code Action](https://github.com/anthropics/claude-code-action) with that prompt. Add `ANTHROPIC_API_KEY` to your repository secrets if you want the Claude step to run.
|
|
186
106
|
|
|
187
|
-
|
|
188
|
-
2. Click "Run workflow"
|
|
189
|
-
3. Enter the path to the markdown file (e.g., `README.md`)
|
|
190
|
-
4. The workflow will generate/update the corresponding docspec file and create a PR with improvements
|
|
107
|
+
### Example: docspec generate (prompt only)
|
|
191
108
|
|
|
192
|
-
|
|
109
|
+
```yaml
|
|
110
|
+
- uses: actions/checkout@v4
|
|
111
|
+
- uses: docspec-ai/docspec/.github/actions/docspec-generate@main
|
|
112
|
+
with:
|
|
113
|
+
markdown_file: README.md
|
|
114
|
+
overwrite: false
|
|
115
|
+
```
|
|
193
116
|
|
|
194
117
|
## Development
|
|
195
118
|
|
|
@@ -214,4 +137,3 @@ npm run build
|
|
|
214
137
|
## License
|
|
215
138
|
|
|
216
139
|
MIT
|
|
217
|
-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changed.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/changed.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("fs/promises"));
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const changed_1 = require("../changed");
|
|
40
|
+
describe("changed", () => {
|
|
41
|
+
let tempDir;
|
|
42
|
+
let originalCwd;
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "docspec-changed-test-"));
|
|
45
|
+
originalCwd = process.cwd();
|
|
46
|
+
process.chdir(tempDir);
|
|
47
|
+
});
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
process.chdir(originalCwd);
|
|
50
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
it("returns empty prompt when no docspecs match changed files", async () => {
|
|
53
|
+
await fs.mkdir(path.join(tempDir, ".docspec", "sub"), { recursive: true });
|
|
54
|
+
await fs.writeFile(path.join(tempDir, ".docspec", "sub", "bar.docspec.md"), "# DOCSPEC: bar\n\n## 1. Purpose\n\n", "utf-8");
|
|
55
|
+
await fs.mkdir(path.join(tempDir, "sub"), { recursive: true });
|
|
56
|
+
await fs.writeFile(path.join(tempDir, "sub", "bar.md"), "# Bar", "utf-8");
|
|
57
|
+
await fs.writeFile(path.join(tempDir, "root-only.js"), "code", "utf-8");
|
|
58
|
+
const { prompt } = await (0, changed_1.buildDocspecChangedPrompt)({
|
|
59
|
+
changedFiles: ["root-only.js"],
|
|
60
|
+
repoRoot: tempDir,
|
|
61
|
+
});
|
|
62
|
+
expect(prompt).toBe("");
|
|
63
|
+
});
|
|
64
|
+
it("builds prompt when changed file is the target markdown of a docspec", async () => {
|
|
65
|
+
await fs.mkdir(path.join(tempDir, ".docspec"), { recursive: true });
|
|
66
|
+
const docspecContent = "# DOCSPEC: [foo.md](/foo.md)\n\n## 1. Purpose\n\nDescribe foo.";
|
|
67
|
+
await fs.writeFile(path.join(tempDir, ".docspec", "foo.docspec.md"), docspecContent, "utf-8");
|
|
68
|
+
await fs.writeFile(path.join(tempDir, "foo.md"), "# Foo content", "utf-8");
|
|
69
|
+
const { prompt, outputPath } = await (0, changed_1.buildDocspecChangedPrompt)({
|
|
70
|
+
changedFiles: ["foo.md"],
|
|
71
|
+
repoRoot: tempDir,
|
|
72
|
+
});
|
|
73
|
+
expect(prompt).toContain("<diff>");
|
|
74
|
+
expect(prompt).toContain("## Docspec: .docspec/foo.docspec.md");
|
|
75
|
+
expect(prompt).toContain("Target markdown: foo.md");
|
|
76
|
+
expect(prompt).toContain("<docspec>");
|
|
77
|
+
expect(prompt).toContain(docspecContent);
|
|
78
|
+
expect(prompt).toContain("<markdown>");
|
|
79
|
+
expect(prompt).toContain("# Foo content");
|
|
80
|
+
expect(prompt).toContain("Task:");
|
|
81
|
+
expect(outputPath).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
it("writes prompt to outputPath when provided", async () => {
|
|
84
|
+
await fs.mkdir(path.join(tempDir, ".docspec"), { recursive: true });
|
|
85
|
+
await fs.writeFile(path.join(tempDir, ".docspec", "bar.docspec.md"), "# DOCSPEC: bar\n\n## 1. Purpose\n\n", "utf-8");
|
|
86
|
+
await fs.writeFile(path.join(tempDir, "bar.md"), "# Bar", "utf-8");
|
|
87
|
+
const { prompt, outputPath } = await (0, changed_1.buildDocspecChangedPrompt)({
|
|
88
|
+
changedFiles: ["bar.md"],
|
|
89
|
+
repoRoot: tempDir,
|
|
90
|
+
outputPath: "out/prompt.txt",
|
|
91
|
+
});
|
|
92
|
+
expect(prompt.length).toBeGreaterThan(0);
|
|
93
|
+
expect(outputPath).toBe(path.join(tempDir, "out", "prompt.txt"));
|
|
94
|
+
const written = await fs.readFile(path.join(tempDir, "out", "prompt.txt"), "utf-8");
|
|
95
|
+
expect(written).toBe(prompt);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=changed.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changed.test.js","sourceRoot":"","sources":["../../src/__tests__/changed.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,7 +38,6 @@ const path = __importStar(require("path"));
|
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const child_process_1 = require("child_process");
|
|
40
40
|
const util_1 = require("util");
|
|
41
|
-
const generator_1 = require("../generator");
|
|
42
41
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
43
42
|
describe("CLI", () => {
|
|
44
43
|
let tempDir;
|
|
@@ -48,14 +47,25 @@ describe("CLI", () => {
|
|
|
48
47
|
originalCwd = process.cwd();
|
|
49
48
|
process.chdir(tempDir);
|
|
50
49
|
});
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
const cliPath = path.resolve(process.cwd(), "dist", "cli.js");
|
|
52
|
+
try {
|
|
53
|
+
await fs.access(cliPath);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error("CLI tests require dist/cli.js. Run 'npm run build' before 'npm test'.");
|
|
57
|
+
}
|
|
58
|
+
});
|
|
51
59
|
afterEach(async () => {
|
|
52
60
|
process.chdir(originalCwd);
|
|
53
61
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
54
62
|
});
|
|
55
63
|
const runCli = async (args) => {
|
|
56
|
-
const cliPath = path.
|
|
64
|
+
const cliPath = path.resolve(originalCwd, "dist", "cli.js");
|
|
57
65
|
try {
|
|
58
|
-
const { stdout, stderr } = await execAsync(`node ${cliPath} ${args}
|
|
66
|
+
const { stdout, stderr } = await execAsync(`node "${cliPath}" ${args}`, {
|
|
67
|
+
cwd: tempDir,
|
|
68
|
+
});
|
|
59
69
|
return { stdout: stdout || "", stderr: stderr || "", code: 0 };
|
|
60
70
|
}
|
|
61
71
|
catch (error) {
|
|
@@ -66,179 +76,72 @@ describe("CLI", () => {
|
|
|
66
76
|
};
|
|
67
77
|
}
|
|
68
78
|
};
|
|
69
|
-
describe("
|
|
70
|
-
it("should
|
|
71
|
-
const
|
|
72
|
-
const validContent = `# DOCSPEC: Test
|
|
73
|
-
|
|
74
|
-
## 1. Document Purpose
|
|
75
|
-
|
|
76
|
-
This document serves as a test case for the CLI validation command. It contains custom content that is different from the boilerplate template and sufficient to pass validation.
|
|
77
|
-
|
|
78
|
-
## 2. Update Triggers
|
|
79
|
-
|
|
80
|
-
This document should be updated when testing CLI validation functionality. The content is meaningful and customized for testing purposes.
|
|
81
|
-
|
|
82
|
-
## 3. Expected Structure
|
|
83
|
-
|
|
84
|
-
This section describes the structure of the test document. It includes all required sections with adequate content to pass validation checks.
|
|
85
|
-
|
|
86
|
-
## 4. Editing Guidelines
|
|
87
|
-
|
|
88
|
-
The style for this test document is straightforward and technical. It focuses on clarity and precision in describing test scenarios. Do: Ensure all sections have adequate content. Don't: Use boilerplate text or leave sections empty. Always provide meaningful test data.
|
|
89
|
-
|
|
90
|
-
## 5. Intentional Omissions
|
|
91
|
-
|
|
92
|
-
There are no known gaps in this test document. All sections are complete and properly formatted with sufficient content.
|
|
93
|
-
`;
|
|
94
|
-
await fs.writeFile(filePath, validContent, "utf-8");
|
|
95
|
-
const result = await runCli(`validate ${filePath}`);
|
|
96
|
-
expect(result.code).toBe(0);
|
|
97
|
-
expect(result.stdout).toContain("✅");
|
|
98
|
-
});
|
|
99
|
-
it("should reject an invalid docspec file", async () => {
|
|
100
|
-
const filePath = path.join(tempDir, "invalid.docspec.md");
|
|
101
|
-
await (0, generator_1.generateDocspec)(filePath); // This creates boilerplate-only content
|
|
102
|
-
const result = await runCli(`validate ${filePath}`);
|
|
103
|
-
expect(result.code).toBe(1);
|
|
104
|
-
const output = result.stdout + result.stderr;
|
|
105
|
-
expect(output).toContain("❌");
|
|
106
|
-
expect(output).toContain("boilerplate");
|
|
107
|
-
});
|
|
108
|
-
it("should validate multiple files", async () => {
|
|
109
|
-
const file1 = path.join(tempDir, "file1.docspec.md");
|
|
110
|
-
const file2 = path.join(tempDir, "file2.docspec.md");
|
|
111
|
-
const validContent = `# DOCSPEC: Test
|
|
112
|
-
|
|
113
|
-
## 1. Document Purpose
|
|
114
|
-
|
|
115
|
-
This document serves as a test case for validating multiple docspec files. It contains custom content that is different from the boilerplate template.
|
|
116
|
-
|
|
117
|
-
## 2. Update Triggers
|
|
118
|
-
|
|
119
|
-
This document should be updated when testing multiple file validation. The content is meaningful and customized for testing purposes.
|
|
120
|
-
|
|
121
|
-
## 3. Expected Structure
|
|
122
|
-
|
|
123
|
-
This section describes the structure of the test document. It includes all required sections with adequate content to pass validation checks.
|
|
124
|
-
|
|
125
|
-
## 4. Editing Guidelines
|
|
126
|
-
|
|
127
|
-
The style for this test document is straightforward and technical. It focuses on clarity and precision in describing test scenarios. Do: Ensure all sections have adequate content. Don't: Use boilerplate text or leave sections empty. Always provide meaningful test data.
|
|
128
|
-
|
|
129
|
-
## 5. Intentional Omissions
|
|
130
|
-
|
|
131
|
-
There are no known gaps in this test document. All sections are complete and properly formatted with sufficient content.
|
|
132
|
-
`;
|
|
133
|
-
await fs.writeFile(file1, validContent, "utf-8");
|
|
134
|
-
await fs.writeFile(file2, validContent, "utf-8");
|
|
135
|
-
const result = await runCli(`validate ${file1} ${file2}`);
|
|
136
|
-
expect(result.code).toBe(0);
|
|
137
|
-
expect(result.stdout).toContain("file1.docspec.md");
|
|
138
|
-
expect(result.stdout).toContain("file2.docspec.md");
|
|
139
|
-
});
|
|
140
|
-
it("should find all docspec files when no paths provided", async () => {
|
|
141
|
-
const file1 = path.join(tempDir, "file1.docspec.md");
|
|
142
|
-
const file2 = path.join(tempDir, "nested", "file2.docspec.md");
|
|
143
|
-
await fs.mkdir(path.join(tempDir, "nested"), { recursive: true });
|
|
144
|
-
const validContent = `# DOCSPEC: Test
|
|
145
|
-
|
|
146
|
-
## 1. Document Purpose
|
|
147
|
-
|
|
148
|
-
This document serves as a test case for validating multiple docspec files. It contains custom content that is different from the boilerplate template.
|
|
149
|
-
|
|
150
|
-
## 2. Update Triggers
|
|
151
|
-
|
|
152
|
-
This document should be updated when testing multiple file validation. The content is meaningful and customized for testing purposes.
|
|
153
|
-
|
|
154
|
-
## 3. Expected Structure
|
|
155
|
-
|
|
156
|
-
This section describes the structure of the test document. It includes all required sections with adequate content to pass validation checks.
|
|
157
|
-
|
|
158
|
-
## 4. Editing Guidelines
|
|
159
|
-
|
|
160
|
-
The style for this test document is straightforward and technical. It focuses on clarity and precision in describing test scenarios. Do: Ensure all sections have adequate content. Don't: Use boilerplate text or leave sections empty. Always provide meaningful test data.
|
|
161
|
-
|
|
162
|
-
## 5. Intentional Omissions
|
|
163
|
-
|
|
164
|
-
There are no known gaps in this test document. All sections are complete and properly formatted with sufficient content.
|
|
165
|
-
`;
|
|
166
|
-
await fs.writeFile(file1, validContent, "utf-8");
|
|
167
|
-
await fs.writeFile(file2, validContent, "utf-8");
|
|
168
|
-
const result = await runCli("validate");
|
|
79
|
+
describe("default command (docspec <markdown_path>)", () => {
|
|
80
|
+
it("should seed .docspec/docspec.md from default when missing", async () => {
|
|
81
|
+
const result = await runCli("seed-test.md");
|
|
169
82
|
expect(result.code).toBe(0);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
expect(
|
|
176
|
-
const output = result.stdout + result.stderr;
|
|
177
|
-
expect(output).toContain("Failed to read file");
|
|
178
|
-
});
|
|
179
|
-
it("should skip node_modules and .git directories", async () => {
|
|
180
|
-
await fs.mkdir(path.join(tempDir, "node_modules"), { recursive: true });
|
|
181
|
-
await fs.mkdir(path.join(tempDir, ".git"), { recursive: true });
|
|
182
|
-
const fileInNodeModules = path.join(tempDir, "node_modules", "test.docspec.md");
|
|
183
|
-
const fileInGit = path.join(tempDir, ".git", "test.docspec.md");
|
|
184
|
-
await fs.writeFile(fileInNodeModules, "test", "utf-8");
|
|
185
|
-
await fs.writeFile(fileInGit, "test", "utf-8");
|
|
186
|
-
const result = await runCli("validate");
|
|
187
|
-
// Should not find files in node_modules or .git
|
|
188
|
-
expect(result.stdout).not.toContain("node_modules");
|
|
189
|
-
expect(result.stdout).not.toContain(".git");
|
|
83
|
+
const templatePath = path.join(tempDir, ".docspec", "docspec.md");
|
|
84
|
+
const templateExists = await fs.access(templatePath).then(() => true).catch(() => false);
|
|
85
|
+
expect(templateExists).toBe(true);
|
|
86
|
+
const content = await fs.readFile(templatePath, "utf-8");
|
|
87
|
+
expect(content).toContain("Document Purpose");
|
|
88
|
+
expect(content).toContain("{{TARGET_FILE}}");
|
|
190
89
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
it("should generate a new docspec file", async () => {
|
|
194
|
-
const filePath = path.join(tempDir, "new.docspec.md");
|
|
195
|
-
const result = await runCli(`generate ${filePath}`);
|
|
90
|
+
it("should generate a new docspec file under .docspec/ for markdown path", async () => {
|
|
91
|
+
const result = await runCli("new.md");
|
|
196
92
|
expect(result.code).toBe(0);
|
|
197
93
|
expect(result.stdout).toContain("✅");
|
|
198
|
-
expect(result.stdout).toContain("new.docspec.md");
|
|
94
|
+
expect(result.stdout).toContain(".docspec/new.docspec.md");
|
|
95
|
+
const filePath = path.join(tempDir, ".docspec", "new.docspec.md");
|
|
199
96
|
const exists = await fs.access(filePath).then(() => true).catch(() => false);
|
|
200
97
|
expect(exists).toBe(true);
|
|
201
98
|
});
|
|
202
99
|
it("should generate file with correct content", async () => {
|
|
203
|
-
|
|
204
|
-
|
|
100
|
+
await runCli("test.md");
|
|
101
|
+
const filePath = path.join(tempDir, ".docspec", "test.docspec.md");
|
|
205
102
|
const content = await fs.readFile(filePath, "utf-8");
|
|
206
103
|
expect(content).toContain("# DOCSPEC: [test.md](/test.md)");
|
|
207
104
|
expect(content).toContain("Document Purpose");
|
|
208
105
|
});
|
|
209
|
-
it("should
|
|
210
|
-
const
|
|
211
|
-
await runCli(`generate ${filePath}`);
|
|
212
|
-
const fullPath = filePath + ".docspec.md";
|
|
213
|
-
const exists = await fs.access(fullPath).then(() => true).catch(() => false);
|
|
214
|
-
expect(exists).toBe(true);
|
|
215
|
-
});
|
|
216
|
-
it("should create nested directories", async () => {
|
|
217
|
-
const filePath = path.join(tempDir, "nested", "deep", "test.docspec.md");
|
|
218
|
-
const result = await runCli(`generate ${filePath}`);
|
|
106
|
+
it("should create nested directories under .docspec/", async () => {
|
|
107
|
+
const result = await runCli("nested/deep/test.md");
|
|
219
108
|
expect(result.code).toBe(0);
|
|
109
|
+
const filePath = path.join(tempDir, ".docspec", "nested", "deep", "test.docspec.md");
|
|
220
110
|
const exists = await fs.access(filePath).then(() => true).catch(() => false);
|
|
221
111
|
expect(exists).toBe(true);
|
|
222
112
|
});
|
|
223
113
|
it("should generate link to target markdown file", async () => {
|
|
224
|
-
|
|
225
|
-
|
|
114
|
+
await runCli("my-awesome-doc.md");
|
|
115
|
+
const filePath = path.join(tempDir, ".docspec", "my-awesome-doc.docspec.md");
|
|
226
116
|
const content = await fs.readFile(filePath, "utf-8");
|
|
227
117
|
expect(content).toContain("# DOCSPEC: [my-awesome-doc.md](/my-awesome-doc.md)");
|
|
228
118
|
});
|
|
229
119
|
});
|
|
120
|
+
describe("generate subcommand (file + prompt)", () => {
|
|
121
|
+
it("should generate docspec and write prompt file", async () => {
|
|
122
|
+
await fs.writeFile(path.join(tempDir, "README.md"), "# Hello", "utf-8");
|
|
123
|
+
const result = await runCli("generate README.md --output-prompt prompt.txt");
|
|
124
|
+
expect(result.code).toBe(0);
|
|
125
|
+
expect(result.stdout).toContain(".docspec/README.docspec.md");
|
|
126
|
+
expect(result.stdout).toContain("prompt");
|
|
127
|
+
const promptPath = path.join(tempDir, "prompt.txt");
|
|
128
|
+
const exists = await fs.access(promptPath).then(() => true).catch(() => false);
|
|
129
|
+
expect(exists).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
230
132
|
describe("help and version", () => {
|
|
231
133
|
it("should show help message", async () => {
|
|
232
134
|
const result = await runCli("--help");
|
|
233
135
|
expect(result.code).toBe(0);
|
|
234
136
|
expect(result.stdout).toContain("Usage:");
|
|
235
|
-
expect(result.stdout).toContain("
|
|
137
|
+
expect(result.stdout).toContain("changed");
|
|
236
138
|
expect(result.stdout).toContain("generate");
|
|
139
|
+
expect(result.stdout).toContain("markdown_path");
|
|
237
140
|
});
|
|
238
141
|
it("should show version", async () => {
|
|
239
142
|
const result = await runCli("--version");
|
|
240
143
|
expect(result.code).toBe(0);
|
|
241
|
-
expect(result.stdout).toContain("0.
|
|
144
|
+
expect(result.stdout).toContain("0.3.0");
|
|
242
145
|
});
|
|
243
146
|
});
|
|
244
147
|
});
|