harness-auto-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.
- package/.nvmrc +1 -0
- package/AGENTS.md +69 -0
- package/ARCHITECTURE.md +123 -0
- package/README.md +52 -0
- package/dist/ai/anthropic.d.ts +7 -0
- package/dist/ai/anthropic.js +20 -0
- package/dist/ai/interface.d.ts +3 -0
- package/dist/ai/interface.js +1 -0
- package/dist/ai/minimax.d.ts +7 -0
- package/dist/ai/minimax.js +21 -0
- package/dist/ai/openai.d.ts +7 -0
- package/dist/ai/openai.js +16 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +103 -0
- package/dist/core/diff.d.ts +17 -0
- package/dist/core/diff.js +46 -0
- package/dist/core/generator.d.ts +10 -0
- package/dist/core/generator.js +238 -0
- package/dist/core/relevance.d.ts +3 -0
- package/dist/core/relevance.js +29 -0
- package/dist/core/writer.d.ts +2 -0
- package/dist/core/writer.js +23 -0
- package/dist/providers/github.d.ts +13 -0
- package/dist/providers/github.js +43 -0
- package/dist/providers/gitlab.d.ts +9 -0
- package/dist/providers/gitlab.js +6 -0
- package/dist/providers/interface.d.ts +8 -0
- package/dist/providers/interface.js +1 -0
- package/docs/DESIGN.md +94 -0
- package/docs/QUALITY_SCORE.md +74 -0
- package/docs/design-docs/core-beliefs.md +71 -0
- package/docs/design-docs/index.md +32 -0
- package/docs/exec-plans/tech-debt-tracker.md +26 -0
- package/docs/product-specs/index.md +39 -0
- package/docs/references/anthropic-sdk-llms.txt +40 -0
- package/docs/references/octokit-rest-llms.txt +44 -0
- package/docs/references/openai-sdk-llms.txt +38 -0
- package/docs/superpowers/plans/2026-04-03-harness-engineering-auto-docs.md +1863 -0
- package/docs/superpowers/specs/2026-04-03-harness-engineering-auto-docs-design.md +169 -0
- package/examples/github-workflow.yml +32 -0
- package/markdown/harness-engineering-codex-agent-first-world.md +215 -0
- package/package.json +30 -0
- package/src/ai/anthropic.ts +23 -0
- package/src/ai/interface.ts +3 -0
- package/src/ai/minimax.ts +25 -0
- package/src/ai/openai.ts +20 -0
- package/src/cli.ts +122 -0
- package/src/core/diff.ts +77 -0
- package/src/core/generator.ts +294 -0
- package/src/core/relevance.ts +53 -0
- package/src/core/writer.ts +25 -0
- package/src/providers/github.ts +53 -0
- package/src/providers/gitlab.ts +16 -0
- package/src/providers/interface.ts +9 -0
- package/tests/core/anthropic.test.ts +33 -0
- package/tests/core/diff.test.ts +49 -0
- package/tests/core/generator.test.ts +93 -0
- package/tests/core/openai.test.ts +38 -0
- package/tests/core/relevance.test.ts +62 -0
- package/tests/core/writer.test.ts +56 -0
- package/tests/fixtures/diff-frontend.txt +11 -0
- package/tests/fixtures/diff-schema.txt +12 -0
- package/tests/fixtures/diff-small.txt +16 -0
- package/tests/integration/generate.test.ts +49 -0
- package/tests/providers/github.test.ts +69 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +7 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This is the entry point for AI coding agents working in this repository.
|
|
4
|
+
Read this file first, then follow the pointers below for deeper context.
|
|
5
|
+
|
|
6
|
+
## What this project does
|
|
7
|
+
|
|
8
|
+
`harness-engineering-auto-docs` is a TypeScript CLI that auto-generates Harness Engineering-style
|
|
9
|
+
documentation when a git tag is pushed. It diffs two adjacent tags, selects relevant
|
|
10
|
+
doc targets, calls an LLM (Claude or GPT), writes Markdown files, and opens a GitHub PR.
|
|
11
|
+
|
|
12
|
+
## Repository map
|
|
13
|
+
|
|
14
|
+
| Path | Purpose |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| `src/cli.ts` | CLI entry point — env wiring, orchestration, git/PR ops |
|
|
17
|
+
| `src/core/diff.ts` | Extract git diff between last two tags; group changed files |
|
|
18
|
+
| `src/core/relevance.ts` | Select which doc targets to generate from file groups |
|
|
19
|
+
| `src/core/generator.ts` | LLM prompts per target; generate + write file paths |
|
|
20
|
+
| `src/core/writer.ts` | `appendSection` / `createFile` helpers |
|
|
21
|
+
| `src/ai/interface.ts` | `AIProvider` interface |
|
|
22
|
+
| `src/ai/anthropic.ts` | Claude implementation |
|
|
23
|
+
| `src/ai/openai.ts` | OpenAI Chat implementation |
|
|
24
|
+
| `src/providers/interface.ts` | `PlatformProvider` interface |
|
|
25
|
+
| `src/providers/github.ts` | GitHub PR creation/update via Octokit |
|
|
26
|
+
| `src/providers/gitlab.ts` | GitLab stub — **not yet implemented** |
|
|
27
|
+
| `tests/` | Vitest unit + integration tests, diff fixtures |
|
|
28
|
+
| `examples/github-workflow.yml` | Copy into target repos to enable automation |
|
|
29
|
+
|
|
30
|
+
## Knowledge store
|
|
31
|
+
|
|
32
|
+
| Document | What it covers |
|
|
33
|
+
|----------|---------------|
|
|
34
|
+
| `ARCHITECTURE.md` | Domain layers, dependency rules, module map |
|
|
35
|
+
| `docs/DESIGN.md` | Design philosophy, key decisions, trade-offs |
|
|
36
|
+
| `docs/QUALITY_SCORE.md` | Per-domain quality grades and gap tracking |
|
|
37
|
+
| `docs/design-docs/index.md` | Index of all per-release design docs |
|
|
38
|
+
| `docs/design-docs/core-beliefs.md` | Agent-first operating principles |
|
|
39
|
+
| `docs/exec-plans/tech-debt-tracker.md` | Known tech debt, active work items |
|
|
40
|
+
| `docs/product-specs/index.md` | Feature spec index |
|
|
41
|
+
| `docs/references/` | Library-specific LLM reference files |
|
|
42
|
+
|
|
43
|
+
## Conventions
|
|
44
|
+
|
|
45
|
+
- **Language / runtime**: TypeScript, ESM (`"type": "module"`), Node ≥18, built with `tsc`
|
|
46
|
+
- **Package manager**: pnpm
|
|
47
|
+
- **Test runner**: Vitest (`npm test`)
|
|
48
|
+
- **Build**: `npm run build` → `dist/`
|
|
49
|
+
- **AI dispatch**: model prefix determines provider — `claude-*` → Anthropic, `gpt-*` → OpenAI, `MiniMax-*` → MiniMax (Anthropic-compatible, base URL `https://api.minimax.io/anthropic`)
|
|
50
|
+
- **File grouping heuristics** live in `src/core/diff.ts:groupFiles` — update there first when changing routing logic
|
|
51
|
+
- **Doc target set** is defined in `src/core/relevance.ts` — `CORE_TARGETS` always run; conditional targets gated on file groups
|
|
52
|
+
- **Prompts** are all colocated in `src/core/generator.ts:PROMPTS` — one entry per `DocTarget`
|
|
53
|
+
- **Output paths** are defined in `src/core/generator.ts:writeResult` switch — keep in sync with README table
|
|
54
|
+
|
|
55
|
+
## Required environment variables
|
|
56
|
+
|
|
57
|
+
| Variable | Description |
|
|
58
|
+
|----------|-------------|
|
|
59
|
+
| `AI_MODEL` | Model name: `claude-sonnet-4-6`, `gpt-4o`, etc. |
|
|
60
|
+
| `AI_API_KEY` | Anthropic or OpenAI API key |
|
|
61
|
+
| `GITHUB_TOKEN` | GitHub PAT or Actions token |
|
|
62
|
+
|
|
63
|
+
## How to extend
|
|
64
|
+
|
|
65
|
+
1. Add a new `DocTarget` literal to the union in `src/core/relevance.ts`
|
|
66
|
+
2. Add an entry to `CORE_TARGETS` or a conditional push in `selectTargets`
|
|
67
|
+
3. Add a prompt function in `PROMPTS` in `src/core/generator.ts`
|
|
68
|
+
4. Add an output path case in `writeResult`
|
|
69
|
+
5. Add tests in `tests/core/`
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# ARCHITECTURE.md
|
|
2
|
+
|
|
3
|
+
Top-level architecture of `harness-engineering-auto-docs`.
|
|
4
|
+
See `AGENTS.md` for navigation and `docs/design-docs/` for per-release decisions.
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
`harness-engineering-auto-docs` is a single-binary CLI (Node.js ESM) with no server or database.
|
|
9
|
+
It is invoked once per git tag event, runs to completion, and exits.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
CLI entry (src/cli.ts)
|
|
13
|
+
│
|
|
14
|
+
├─ Core pipeline
|
|
15
|
+
│ ├─ diff.ts — git diff extraction & file classification
|
|
16
|
+
│ ├─ relevance.ts — doc target selection
|
|
17
|
+
│ ├─ generator.ts — LLM prompt dispatch & file writing
|
|
18
|
+
│ └─ writer.ts — filesystem helpers
|
|
19
|
+
│
|
|
20
|
+
├─ AI layer (src/ai/)
|
|
21
|
+
│ ├─ interface.ts — AIProvider { generate(prompt): Promise<string> }
|
|
22
|
+
│ ├─ anthropic.ts — AnthropicProvider
|
|
23
|
+
│ └─ openai.ts — OpenAIProvider
|
|
24
|
+
│
|
|
25
|
+
└─ Platform layer (src/providers/)
|
|
26
|
+
├─ interface.ts — PlatformProvider { createOrUpdatePR(opts) }
|
|
27
|
+
├─ github.ts — GitHubProvider (Octokit)
|
|
28
|
+
└─ gitlab.ts — GitLabProvider (stub — NOT IMPLEMENTED)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Layers and dependency rules
|
|
32
|
+
|
|
33
|
+
Dependencies flow strictly **downward**. No layer may import from a layer above it.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
cli.ts (orchestration)
|
|
37
|
+
└── core/ (pure business logic — no AI or platform imports)
|
|
38
|
+
└── ai/ (AI provider abstraction)
|
|
39
|
+
└── providers/ (platform PR abstraction)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `core/` must not import from `ai/` or `providers/` — it accepts them as injected interfaces
|
|
43
|
+
- `ai/` and `providers/` must not import from each other
|
|
44
|
+
- All cross-cutting types (`AIProvider`, `PlatformProvider`) live in `interface.ts` files within their layer
|
|
45
|
+
|
|
46
|
+
## Data flow
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
git tag push
|
|
50
|
+
→ GitHub Actions triggers workflow
|
|
51
|
+
→ npx harness-engineering-auto-docs
|
|
52
|
+
→ extractDiff() reads git history, returns DiffResult
|
|
53
|
+
→ selectTargets() classifies files, returns DocTarget[]
|
|
54
|
+
→ generateDocs() calls AI provider in parallel, returns GenerationResult[]
|
|
55
|
+
→ writeResults() writes/appends Markdown files, returns written paths
|
|
56
|
+
→ git branch + commit + push
|
|
57
|
+
→ createOrUpdatePR() opens or updates GitHub PR
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Key interfaces
|
|
61
|
+
|
|
62
|
+
### `DiffResult` (`src/core/diff.ts`)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
interface DiffResult {
|
|
66
|
+
raw: string; // full git diff text (truncated to 8000 chars in prompts)
|
|
67
|
+
prevTag: string; // e.g. "v0.1.0" or "(initial)"
|
|
68
|
+
currentTag: string; // e.g. "v0.2.0"
|
|
69
|
+
changedFiles: string[];
|
|
70
|
+
fileGroups: FileGroups; // { frontend, schema, auth, infra, other }
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `AIProvider` (`src/ai/interface.ts`)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
interface AIProvider {
|
|
78
|
+
generate(prompt: string): Promise<string>;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `PlatformProvider` (`src/providers/interface.ts`)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
interface PlatformProvider {
|
|
86
|
+
createOrUpdatePR(opts: PROptions): Promise<string>; // returns PR URL
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Doc output layout (in target repositories)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
<repo-root>/
|
|
94
|
+
├── AGENTS.md
|
|
95
|
+
├── ARCHITECTURE.md
|
|
96
|
+
├── changelog/
|
|
97
|
+
│ └── vX.Y.Z.md
|
|
98
|
+
└── docs/
|
|
99
|
+
├── DESIGN.md
|
|
100
|
+
├── QUALITY_SCORE.md
|
|
101
|
+
├── FRONTEND.md (conditional)
|
|
102
|
+
├── SECURITY.md (conditional)
|
|
103
|
+
├── RELIABILITY.md (conditional)
|
|
104
|
+
├── design-docs/
|
|
105
|
+
│ ├── index.md
|
|
106
|
+
│ └── vX.Y.Z.md
|
|
107
|
+
├── exec-plans/
|
|
108
|
+
│ └── tech-debt-tracker.md
|
|
109
|
+
├── generated/
|
|
110
|
+
│ └── db-schema.md (conditional)
|
|
111
|
+
├── product-specs/
|
|
112
|
+
│ └── index.md (conditional)
|
|
113
|
+
└── references/
|
|
114
|
+
└── <lib>-llms.txt (conditional)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Constraints
|
|
118
|
+
|
|
119
|
+
- **No global state** — all state flows through function arguments
|
|
120
|
+
- **Parallel AI calls** — `generateDocs` uses `Promise.all`; each target is independent
|
|
121
|
+
- **Diff truncation** — prompts cap raw diff at 8 000 characters (see `PROMPTS` in `generator.ts`)
|
|
122
|
+
- **ESM only** — all imports use `.js` extensions per NodeNext module resolution
|
|
123
|
+
- **Strict TypeScript** — `strict: true` in `tsconfig.json`; no `any` types
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# harness-engineering-auto-docs
|
|
2
|
+
|
|
3
|
+
Auto-generate [Harness Engineering](https://openai.com/research/harness-engineering) style documentation when your project creates a git tag. Opens a PR with updated docs.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Add `.github/workflows/harness-docs.yml` to your project (see `examples/github-workflow.yml`), then set:
|
|
8
|
+
|
|
9
|
+
- `AI_API_KEY` — your Anthropic, OpenAI, or MiniMax API key (repository secret)
|
|
10
|
+
- `AI_MODEL` — model name, e.g. `claude-sonnet-4-6`, `gpt-4o`, or `MiniMax-Text-01`
|
|
11
|
+
- `GITHUB_TOKEN` — provided automatically by GitHub Actions
|
|
12
|
+
|
|
13
|
+
When you push a tag (`git tag v1.2.0 && git push --tags`), a PR is automatically opened with updated documentation.
|
|
14
|
+
|
|
15
|
+
## What gets updated
|
|
16
|
+
|
|
17
|
+
| File | Always | Conditional |
|
|
18
|
+
|------|--------|-------------|
|
|
19
|
+
| `AGENTS.md` | ✓ | |
|
|
20
|
+
| `ARCHITECTURE.md` | ✓ | |
|
|
21
|
+
| `docs/DESIGN.md` | ✓ | |
|
|
22
|
+
| `docs/QUALITY_SCORE.md` | ✓ | |
|
|
23
|
+
| `changelog/vX.Y.Z.md` | ✓ | |
|
|
24
|
+
| `docs/design-docs/vX.Y.Z.md` | ✓ | |
|
|
25
|
+
| `docs/design-docs/index.md` | ✓ | |
|
|
26
|
+
| `docs/exec-plans/tech-debt-tracker.md` | ✓ | |
|
|
27
|
+
| `docs/FRONTEND.md` | | frontend files changed |
|
|
28
|
+
| `docs/SECURITY.md` | | auth/security files changed |
|
|
29
|
+
| `docs/RELIABILITY.md` | | infra files changed |
|
|
30
|
+
| `docs/generated/db-schema.md` | | SQL/schema files changed |
|
|
31
|
+
| `docs/product-specs/index.md` | | new features detected |
|
|
32
|
+
| `docs/references/` | | new dependencies added |
|
|
33
|
+
|
|
34
|
+
## Local run
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
AI_MODEL=claude-sonnet-4-6 AI_API_KEY=sk-ant-... GITHUB_TOKEN=ghp_... npx harness-engineering-auto-docs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Supported models
|
|
41
|
+
|
|
42
|
+
Model routing is determined by the prefix of `AI_MODEL`:
|
|
43
|
+
|
|
44
|
+
| Prefix | Provider | Example models |
|
|
45
|
+
|--------|----------|----------------|
|
|
46
|
+
| `claude-*` | Anthropic | `claude-sonnet-4-6`, `claude-opus-4-6`, `claude-haiku-4-5-20251001` |
|
|
47
|
+
| `gpt-*` | OpenAI | `gpt-4o`, `gpt-4o-mini`, `o3` |
|
|
48
|
+
| `MiniMax-*` | MiniMax (Anthropic-compatible) | `MiniMax-Text-01` |
|
|
49
|
+
|
|
50
|
+
## Future
|
|
51
|
+
|
|
52
|
+
- GitLab support (MR creation)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
export class AnthropicProvider {
|
|
3
|
+
client;
|
|
4
|
+
model;
|
|
5
|
+
constructor(apiKey, model) {
|
|
6
|
+
this.client = new Anthropic({ apiKey });
|
|
7
|
+
this.model = model;
|
|
8
|
+
}
|
|
9
|
+
async generate(prompt) {
|
|
10
|
+
const message = await this.client.messages.create({
|
|
11
|
+
model: this.model,
|
|
12
|
+
max_tokens: 4096,
|
|
13
|
+
messages: [{ role: 'user', content: prompt }],
|
|
14
|
+
});
|
|
15
|
+
const content = message.content[0];
|
|
16
|
+
if (content.type !== 'text')
|
|
17
|
+
throw new Error('Unexpected response type');
|
|
18
|
+
return content.text;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
const MINIMAX_BASE_URL = 'https://api.minimax.io/anthropic';
|
|
3
|
+
export class MiniMaxProvider {
|
|
4
|
+
client;
|
|
5
|
+
model;
|
|
6
|
+
constructor(apiKey, model) {
|
|
7
|
+
this.client = new Anthropic({ apiKey, baseURL: MINIMAX_BASE_URL });
|
|
8
|
+
this.model = model;
|
|
9
|
+
}
|
|
10
|
+
async generate(prompt) {
|
|
11
|
+
const message = await this.client.messages.create({
|
|
12
|
+
model: this.model,
|
|
13
|
+
max_tokens: 4096,
|
|
14
|
+
messages: [{ role: 'user', content: prompt }],
|
|
15
|
+
});
|
|
16
|
+
const content = message.content[0];
|
|
17
|
+
if (content.type !== 'text')
|
|
18
|
+
throw new Error('Unexpected response type');
|
|
19
|
+
return content.text;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
export class OpenAIProvider {
|
|
3
|
+
client;
|
|
4
|
+
model;
|
|
5
|
+
constructor(apiKey, model) {
|
|
6
|
+
this.client = new OpenAI({ apiKey });
|
|
7
|
+
this.model = model;
|
|
8
|
+
}
|
|
9
|
+
async generate(prompt) {
|
|
10
|
+
const completion = await this.client.chat.completions.create({
|
|
11
|
+
model: this.model,
|
|
12
|
+
messages: [{ role: 'user', content: prompt }],
|
|
13
|
+
});
|
|
14
|
+
return completion.choices[0].message.content ?? '';
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { extractDiff } from './core/diff.js';
|
|
5
|
+
import { selectTargets } from './core/relevance.js';
|
|
6
|
+
import { generateDocs, writeResults } from './core/generator.js';
|
|
7
|
+
import { GitHubProvider } from './providers/github.js';
|
|
8
|
+
import { GitLabProvider } from './providers/gitlab.js';
|
|
9
|
+
import { AnthropicProvider } from './ai/anthropic.js';
|
|
10
|
+
import { OpenAIProvider } from './ai/openai.js';
|
|
11
|
+
import { MiniMaxProvider } from './ai/minimax.js';
|
|
12
|
+
function requireEnv(name) {
|
|
13
|
+
const value = process.env[name];
|
|
14
|
+
if (!value) {
|
|
15
|
+
console.error(`Error: ${name} environment variable is required`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function detectPlatform() {
|
|
21
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
22
|
+
return new GitHubProvider(requireEnv('GITHUB_TOKEN'));
|
|
23
|
+
}
|
|
24
|
+
if (process.env.GITLAB_CI) {
|
|
25
|
+
return new GitLabProvider();
|
|
26
|
+
}
|
|
27
|
+
const token = process.env.GITHUB_TOKEN;
|
|
28
|
+
if (!token) {
|
|
29
|
+
console.error('Error: GITHUB_TOKEN is required (or run in GITHUB_ACTIONS / GITLAB_CI)');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return new GitHubProvider(token);
|
|
33
|
+
}
|
|
34
|
+
function selectAI(model, apiKey) {
|
|
35
|
+
if (model.startsWith('claude-'))
|
|
36
|
+
return new AnthropicProvider(apiKey, model);
|
|
37
|
+
if (model.startsWith('gpt-'))
|
|
38
|
+
return new OpenAIProvider(apiKey, model);
|
|
39
|
+
if (model.startsWith('MiniMax-'))
|
|
40
|
+
return new MiniMaxProvider(apiKey, model);
|
|
41
|
+
console.error(`Error: Unknown model "${model}". Use a claude-*, gpt-*, or MiniMax-* model.`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
function getDefaultBranch() {
|
|
45
|
+
try {
|
|
46
|
+
return execSync('git symbolic-ref refs/remotes/origin/HEAD --short')
|
|
47
|
+
.toString().trim().replace('origin/', '');
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return 'main';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function main() {
|
|
54
|
+
const model = requireEnv('AI_MODEL');
|
|
55
|
+
const apiKey = requireEnv('AI_API_KEY');
|
|
56
|
+
const diff = extractDiff();
|
|
57
|
+
if (!diff.raw.trim()) {
|
|
58
|
+
console.log('No diff between tags. Nothing to document.');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
console.log(`Generating docs: ${diff.prevTag} → ${diff.currentTag}`);
|
|
62
|
+
const ai = selectAI(model, apiKey);
|
|
63
|
+
const targets = selectTargets(diff.fileGroups, diff.changedFiles);
|
|
64
|
+
console.log(`Updating ${targets.length} documents: ${targets.join(', ')}`);
|
|
65
|
+
const results = await generateDocs(ai, diff, targets);
|
|
66
|
+
const failures = results.filter(r => r.error);
|
|
67
|
+
if (failures.length > 0) {
|
|
68
|
+
console.warn(`\nWarning: ${failures.length} document(s) failed:`);
|
|
69
|
+
failures.forEach(f => console.warn(` - ${f.target}: ${f.error}`));
|
|
70
|
+
}
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const writtenFiles = writeResults(results.filter(r => !r.error), diff, cwd);
|
|
73
|
+
console.log(`\nWrote ${writtenFiles.length} files.`);
|
|
74
|
+
if (writtenFiles.length === 0) {
|
|
75
|
+
console.log('No files written. Skipping PR creation.');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
const branch = `harness-docs/${diff.currentTag}`;
|
|
79
|
+
const baseBranch = getDefaultBranch();
|
|
80
|
+
execSync(`git checkout -b ${branch}`);
|
|
81
|
+
execSync(`git add ${writtenFiles.map(f => `"${f}"`).join(' ')}`);
|
|
82
|
+
execSync(`git commit -m "docs: Harness Engineering docs update for ${diff.currentTag}"`, { env: { ...process.env, GIT_AUTHOR_NAME: 'harness-engineering-auto-docs', GIT_AUTHOR_EMAIL: 'bot@harness-engineering-auto-docs' } });
|
|
83
|
+
execSync(`git push -u origin ${branch}`);
|
|
84
|
+
const platform = detectPlatform();
|
|
85
|
+
const prUrl = await platform.createOrUpdatePR({
|
|
86
|
+
branch,
|
|
87
|
+
title: `docs: Harness Engineering docs for ${diff.currentTag}`,
|
|
88
|
+
body: [
|
|
89
|
+
`Auto-generated by [harness-engineering-auto-docs](https://github.com/your-org/harness-engineering-auto-docs).`,
|
|
90
|
+
``,
|
|
91
|
+
`Updates documentation based on changes from **${diff.prevTag}** to **${diff.currentTag}**.`,
|
|
92
|
+
``,
|
|
93
|
+
`**Updated files:**`,
|
|
94
|
+
writtenFiles.map(f => `- \`${f.replace(cwd + '/', '')}\``).join('\n'),
|
|
95
|
+
].join('\n'),
|
|
96
|
+
baseBranch,
|
|
97
|
+
});
|
|
98
|
+
console.log(`\nPR created: ${prUrl}`);
|
|
99
|
+
}
|
|
100
|
+
main().catch(err => {
|
|
101
|
+
console.error('Fatal:', err);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface FileGroups {
|
|
2
|
+
frontend: string[];
|
|
3
|
+
schema: string[];
|
|
4
|
+
auth: string[];
|
|
5
|
+
infra: string[];
|
|
6
|
+
other: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface DiffResult {
|
|
9
|
+
raw: string;
|
|
10
|
+
prevTag: string;
|
|
11
|
+
currentTag: string;
|
|
12
|
+
changedFiles: string[];
|
|
13
|
+
fileGroups: FileGroups;
|
|
14
|
+
}
|
|
15
|
+
export declare function extractDiff(): DiffResult;
|
|
16
|
+
export declare function parseChangedFiles(diff: string): string[];
|
|
17
|
+
export declare function groupFiles(files: string[]): FileGroups;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/core/diff.ts
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
export function extractDiff() {
|
|
4
|
+
const tags = execSync('git tag --sort=version:refname')
|
|
5
|
+
.toString()
|
|
6
|
+
.trim()
|
|
7
|
+
.split('\n')
|
|
8
|
+
.filter(Boolean);
|
|
9
|
+
if (tags.length === 0)
|
|
10
|
+
throw new Error('No tags found in repository');
|
|
11
|
+
const currentTag = tags[tags.length - 1];
|
|
12
|
+
const prevTag = tags.length >= 2 ? tags[tags.length - 2] : null;
|
|
13
|
+
const raw = prevTag
|
|
14
|
+
? execSync(`git diff ${prevTag} ${currentTag}`).toString()
|
|
15
|
+
: execSync(`git show ${currentTag} --format='' -p`).toString();
|
|
16
|
+
const changedFiles = parseChangedFiles(raw);
|
|
17
|
+
const fileGroups = groupFiles(changedFiles);
|
|
18
|
+
return {
|
|
19
|
+
raw,
|
|
20
|
+
prevTag: prevTag ?? '(initial)',
|
|
21
|
+
currentTag,
|
|
22
|
+
changedFiles,
|
|
23
|
+
fileGroups,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function parseChangedFiles(diff) {
|
|
27
|
+
const matches = diff.match(/^diff --git a\/.+ b\/(.+)$/gm) ?? [];
|
|
28
|
+
return matches.map(line => {
|
|
29
|
+
const match = line.match(/^diff --git a\/.+ b\/(.+)$/);
|
|
30
|
+
return match ? match[1] : '';
|
|
31
|
+
}).filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
export function groupFiles(files) {
|
|
34
|
+
const isFrontend = (f) => /\.(tsx?|jsx?|css|scss|html|vue|svelte)$/.test(f) ||
|
|
35
|
+
/\b(ui|frontend|components|pages|views)\b/.test(f);
|
|
36
|
+
const isSchema = (f) => /\.sql$/.test(f) || /\b(schema|migration|migrate)\b/i.test(f);
|
|
37
|
+
const isAuth = (f) => /\b(auth|permission|security|oauth|jwt|session|token)\b/i.test(f);
|
|
38
|
+
const isInfra = (f) => /\b(infra|deploy|k8s|docker|compose|helm|terraform|service|gateway)\b/i.test(f);
|
|
39
|
+
return {
|
|
40
|
+
frontend: files.filter(isFrontend),
|
|
41
|
+
schema: files.filter(isSchema),
|
|
42
|
+
auth: files.filter(isAuth),
|
|
43
|
+
infra: files.filter(isInfra),
|
|
44
|
+
other: files.filter(f => !isFrontend(f) && !isSchema(f) && !isAuth(f) && !isInfra(f)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AIProvider } from '../ai/interface.js';
|
|
2
|
+
import type { DocTarget } from './relevance.js';
|
|
3
|
+
import type { DiffResult } from './diff.js';
|
|
4
|
+
export interface GenerationResult {
|
|
5
|
+
target: DocTarget;
|
|
6
|
+
content: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function generateDocs(ai: AIProvider, diff: DiffResult, targets: DocTarget[]): Promise<GenerationResult[]>;
|
|
10
|
+
export declare function writeResults(results: GenerationResult[], diff: DiffResult, cwd: string): string[];
|