project-knowledge 0.1.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +201 -58
- package/_site/_test/ai-profile-test.js +59 -1
- package/_site/_test/baseline-schema-test.js +4 -3
- package/_site/_test/claude-workbench-test.js +72 -0
- package/_site/_test/draft-apply-test.js +12 -6
- package/_site/_test/kb-v2-templates-test.js +31 -43
- package/_site/_test/knowledge-store-logs-supervision-test.js +143 -0
- package/_site/_test/package-startup-test.js +108 -0
- package/_site/_test/project-control-panel-task14-test.js +151 -0
- package/_site/_test/task15-20-integration-test.js +194 -0
- package/_site/_test/task15-20-ui-flow-test.js +144 -0
- package/_site/_test/ui-smoke-test.js +2 -2
- package/_site/index.html +1640 -90
- package/_site/lib/ai-adapter.js +3 -3
- package/_site/lib/ai-workspace.js +120 -0
- package/_site/lib/analysis-orchestrator.js +117 -32
- package/_site/lib/claude-cli-runner.js +862 -0
- package/_site/lib/context-pack-builder.js +19 -11
- package/_site/lib/draft-apply.js +80 -31
- package/_site/lib/index-builder.js +100 -0
- package/_site/lib/job-orchestrator.js +15 -11
- package/_site/lib/kb-v3.js +188 -0
- package/_site/lib/kb-validator.js +84 -0
- package/_site/lib/knowledge-store.js +141 -0
- package/_site/lib/llm-client.js +103 -56
- package/_site/lib/prompt-registry.js +102 -0
- package/_site/lib/structured-logger.js +120 -0
- package/_site/lib/supervision.js +103 -0
- package/_site/server.js +887 -30
- package/_site/vendor/tailwind-browser.js +947 -0
- package/_site/vendor/vue.global.prod.js +9 -0
- package/ai-profiles.json +13 -3
- package/bin/project-knowledge.js +51 -0
- package/docs/development-progress.md +141 -0
- package/package.json +11 -2
- package/scripts/gen-commit-doc.ps1 +1 -1
- package/scripts/list-features.ps1 +1 -1
- package/scripts/register-scheduled-task.bat +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,47 @@
|
|
|
9
9
|
- **Removed**:删除文件 / 目录
|
|
10
10
|
- **Fixed**:修复错误
|
|
11
11
|
|
|
12
|
+
## [1.0.0] — 2026-06-15
|
|
13
|
+
|
|
14
|
+
First public npm release of `project-knowledge`. The tool ships as a single
|
|
15
|
+
zero-dependency Node server plus a vendored Vue 3 + Tailwind dashboard.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Server & API** — `_site/server.js` provides the dashboard, REST API, and
|
|
19
|
+
background job runner using only Node built-ins.
|
|
20
|
+
- **Dashboard** — Vue 3 + Tailwind single-page UI in `_site/index.html`:
|
|
21
|
+
project control panel, collapsible summary groups, issues panel with
|
|
22
|
+
deduplication, supervision view.
|
|
23
|
+
- **AI pipeline**
|
|
24
|
+
- `ai-adapter` — pluggable adapter dispatch (mock / Claude CLI / custom LLM)
|
|
25
|
+
- `analysis-orchestrator` — initial scan + commit-batch analysis
|
|
26
|
+
- `context-pack-builder` — packs context per LLM call
|
|
27
|
+
- `claude-cli-runner` + `claude-workbench` integration
|
|
28
|
+
- `ai-workspace`, `prompt-registry`, `llm-client`
|
|
29
|
+
- `draft-apply` — reviewable drafts before writeback
|
|
30
|
+
- **Knowledge store v3** — `kb-v3` simplified layout, plus `knowledge-store`
|
|
31
|
+
external store abstraction and `kb-validator` for schema checks.
|
|
32
|
+
- **Supervision & logging** — `supervision` + `structured-logger` for audit
|
|
33
|
+
of every AI job (mode, adapter, tokens, status).
|
|
34
|
+
- **Dashboard features (TASK-014 … TASK-020)**
|
|
35
|
+
- TASK-014: project control panel simplification
|
|
36
|
+
- TASK-015: project delete functionality
|
|
37
|
+
- TASK-016: source branch tagging
|
|
38
|
+
- TASK-017: collapsible project groups in summary panel
|
|
39
|
+
- TASK-018: issues panel duplication
|
|
40
|
+
- TASK-019: KB storage framework + summary panel persistence across project switches
|
|
41
|
+
- TASK-020: kb-v3 simplified layout
|
|
42
|
+
- **Vendored browser libs** — `_site/vendor/{vue.global.prod.js, tailwind-browser.js}`
|
|
43
|
+
- **Tag-driven publish** — `.github/workflows/publish.yml` publishes to npm on
|
|
44
|
+
`v*` tags with provenance.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- `package.json` — version 1.0.0; `files` updated to include `_site/vendor/`.
|
|
48
|
+
- Expanded test suite under `_site/_test/` (AI profile, baseline schema,
|
|
49
|
+
draft-apply, kb-v2/v3 templates, knowledge-store + logs + supervision,
|
|
50
|
+
claude-workbench, project-control-panel task14, task15-20 integration +
|
|
51
|
+
UI flow, ui-smoke).
|
|
52
|
+
|
|
12
53
|
## 2026-06-08
|
|
13
54
|
|
|
14
55
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,79 +1,222 @@
|
|
|
1
|
-
#
|
|
1
|
+
# project-knowledge
|
|
2
|
+
|
|
3
|
+
> Knowledge-base manager with Git integration, AI-driven analysis, and bilingual (zh-CN / en-US) knowledge output.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/project-knowledge)
|
|
6
|
+
[](https://www.npmjs.com/package/project-knowledge)
|
|
7
|
+
|
|
8
|
+
`project-knowledge` runs a local web dashboard that scans Git repositories,
|
|
9
|
+
hands batches of commits to an AI agent (Claude CLI / mock adapter / custom
|
|
10
|
+
LLM), and writes the resulting knowledge entries back to a structured
|
|
11
|
+
knowledge base per project — modules, framework docs, commit changelogs,
|
|
12
|
+
quality reviews, and operation notes.
|
|
13
|
+
|
|
14
|
+
It is built to be a single-developer tool: zero runtime dependencies (Node
|
|
15
|
+
built-ins only for the server), a vendored Vue 3 + Tailwind dashboard, and a
|
|
16
|
+
file-based store you can read, grep, and version-control.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Project registry** — declare projects in `projects.json`; the dashboard
|
|
23
|
+
drives scan / analyze / commit-draft / publish flows per project.
|
|
24
|
+
- **Git scanner** — walks the working tree of each registered project,
|
|
25
|
+
extracts features and recent commits, and feeds the AI pipeline.
|
|
26
|
+
- **AI pipeline**
|
|
27
|
+
- `analysis-orchestrator` — initial scan + commit-batch analysis modes.
|
|
28
|
+
- `ai-adapter` — pluggable profiles (mock / Claude CLI / custom LLM via
|
|
29
|
+
`llm-client`); `ai-profiles.json` controls which adapter runs.
|
|
30
|
+
- `context-pack-builder` — packs relevant context for each LLM call.
|
|
31
|
+
- `claude-cli-runner` + `claude-workbench` integration — runs Claude Code
|
|
32
|
+
in a sandboxed workspace and surfaces its session log.
|
|
33
|
+
- `prompt-registry` — versioned prompt templates (`claude-prompts.json`).
|
|
34
|
+
- `draft-apply` — turns LLM output into reviewable draft files; the user
|
|
35
|
+
approves, edits, or rejects each draft before it lands.
|
|
36
|
+
- **Knowledge store (kb-v3)** — simplified layout: `framework.md`,
|
|
37
|
+
`modules/`, `commits/`, `features/`, `references/`, `operations/`,
|
|
38
|
+
`quality/`, plus a generated `kb-manifest.json` per project.
|
|
39
|
+
- **Dashboard** — Vue 3 + Tailwind single-page UI: project control panel,
|
|
40
|
+
collapsible summary groups, issues panel with dedup, supervision view
|
|
41
|
+
showing structured logs and AI job runs.
|
|
42
|
+
- **Supervision & structured logging** — every AI job is logged with
|
|
43
|
+
structured fields (run id, mode, adapter, token usage, status) so you can
|
|
44
|
+
audit what the agent did and replay.
|
|
45
|
+
- **Hook integration** — installs a Windows scheduled task / post-commit
|
|
46
|
+
hook so each Git commit can trigger an analysis job automatically.
|
|
47
|
+
- **Validator & PR context packs** — `kb-validator` checks the knowledge
|
|
48
|
+
base against the baseline schema; `buildPrContextPack` exports a focused
|
|
49
|
+
pack for a PR so reviewers (or an LLM) get just the relevant context.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g project-knowledge
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Requirements:
|
|
58
|
+
|
|
59
|
+
- Node.js ≥ 18
|
|
60
|
+
- Git on `PATH`
|
|
61
|
+
- (Optional) [Claude Code CLI](https://docs.claude.com/en/docs/claude-code)
|
|
62
|
+
if you want the Claude adapter (the `mock-agent` adapter works without it)
|
|
63
|
+
- Windows has the most complete hook/scheduled-task flow. The dashboard,
|
|
64
|
+
registry, AI profiles, and knowledge-store APIs use install-relative paths.
|
|
2
65
|
|
|
3
|
-
|
|
66
|
+
## Quick start
|
|
4
67
|
|
|
5
|
-
|
|
68
|
+
Global install:
|
|
6
69
|
|
|
70
|
+
```bash
|
|
71
|
+
npm install -g project-knowledge
|
|
72
|
+
project-knowledge # starts server on http://localhost:7777
|
|
73
|
+
project-knowledge start --port 9000
|
|
7
74
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
75
|
+
|
|
76
|
+
Or run from source:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/SanQianX/project-knowledge-base.git
|
|
80
|
+
cd project-knowledge-base
|
|
81
|
+
npm install
|
|
82
|
+
npm start # node _site/server.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then open <http://localhost:7777>. Override the port with
|
|
86
|
+
`KB_SITE_PORT=9000 npm start`.
|
|
87
|
+
|
|
88
|
+
### First-time setup
|
|
89
|
+
|
|
90
|
+
1. Open the dashboard → **Projects** → register a project (path + slug).
|
|
91
|
+
On first run, v1.0.1 creates missing runtime JSON files automatically. If
|
|
92
|
+
`projects.json` or `ai-profiles.json` is empty or invalid, the server recovers
|
|
93
|
+
with a safe default; invalid JSON is backed up as `*.invalid-<timestamp>.bak`.
|
|
94
|
+
|
|
95
|
+
2. Open **Settings** / **AI profiles** to add MiniMax / GLM / GPT /
|
|
96
|
+
Anthropic-compatible models. The npm package ships with a safe mock profile
|
|
97
|
+
and no API keys.
|
|
98
|
+
3. Run **Scan** → **Initial analysis** → review drafts → **Apply drafts**.
|
|
99
|
+
4. (Optional) **Install hook** so future commits trigger an analysis job.
|
|
100
|
+
|
|
101
|
+
### Upgrading from v1.0.0
|
|
102
|
+
|
|
103
|
+
v1.0.0 did not publish a `bin` command, so a global install could complete
|
|
104
|
+
without creating the `project-knowledge` executable. Reinstall v1.0.1:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm uninstall -g project-knowledge
|
|
108
|
+
npm install -g project-knowledge@latest
|
|
109
|
+
project-knowledge --version
|
|
110
|
+
project-knowledge
|
|
25
111
|
```
|
|
26
112
|
|
|
27
|
-
##
|
|
113
|
+
## Architecture
|
|
28
114
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
115
|
+
```
|
|
116
|
+
project-knowledge-base/
|
|
117
|
+
├── _site/ # The dashboard + server (ships to npm)
|
|
118
|
+
│ ├── server.js # HTTP server, REST API, zero deps
|
|
119
|
+
│ ├── index.html # Vue 3 + Tailwind single-page UI
|
|
120
|
+
│ ├── lib/ # All server-side modules
|
|
121
|
+
│ │ ├── ai-adapter.js # Profile dispatch (mock / Claude / LLM)
|
|
122
|
+
│ │ ├── analysis-orchestrator.js
|
|
123
|
+
│ │ ├── context-pack-builder.js
|
|
124
|
+
│ │ ├── claude-cli-runner.js
|
|
125
|
+
│ │ ├── claude-workbench (workspace manager)
|
|
126
|
+
│ │ ├── draft-apply.js # LLM output → reviewable draft files
|
|
127
|
+
│ │ ├── ai-workspace.js
|
|
128
|
+
│ │ ├── job-orchestrator.js
|
|
129
|
+
│ │ ├── kb-v3.js # Knowledge base layout v3
|
|
130
|
+
│ │ ├── kb-validator.js
|
|
131
|
+
│ │ ├── knowledge-store.js # External store abstraction
|
|
132
|
+
│ │ ├── supervision.js # Audit / monitoring layer
|
|
133
|
+
│ │ ├── structured-logger.js
|
|
134
|
+
│ │ ├── prompt-registry.js
|
|
135
|
+
│ │ ├── llm-client.js
|
|
136
|
+
│ │ ├── index-builder.js
|
|
137
|
+
│ │ ├── scanner.js
|
|
138
|
+
│ │ ├── git-runner.js
|
|
139
|
+
│ │ └── hook-manager.js
|
|
140
|
+
│ ├── scripts/ # hook-trigger.js, safe-runner.js
|
|
141
|
+
│ ├── vendor/ # vue.global.prod.js, tailwind-browser.js
|
|
142
|
+
│ ├── _test/ # smoke + unit tests (run via `npm test`)
|
|
143
|
+
│ ├── start.bat / stop.bat # Windows convenience launchers
|
|
144
|
+
│ └── server.js
|
|
145
|
+
├── templates/ # Markdown templates for generated docs
|
|
146
|
+
├── scripts/ # PowerShell + bash commit-doc generators
|
|
147
|
+
├── docs/ # Design docs and contracts
|
|
148
|
+
├── tasks/ # Task tracking (TASK-001 …)
|
|
149
|
+
├── projects/ # The actual knowledge base (one dir per project)
|
|
150
|
+
├── ai-profiles.json # AI adapter profiles
|
|
151
|
+
├── claude-prompts.json # Prompt templates
|
|
152
|
+
├── projects.json # Project registry
|
|
153
|
+
└── iterations.json # Milestone definitions
|
|
154
|
+
```
|
|
34
155
|
|
|
35
|
-
|
|
156
|
+
The server is intentionally dependency-free — every line in `_site/server.js`
|
|
157
|
+
and `_site/lib/*.js` uses only Node built-ins, so the install footprint is
|
|
158
|
+
the dashboard assets and your data.
|
|
36
159
|
|
|
37
|
-
|
|
38
|
-
|------|----------|--------|
|
|
39
|
-
| `claude-code-ui` | `Claude-Code-UI\Reference Project\claudecodeui` | Claude Code UI |
|
|
40
|
-
| `claude-devsprite` | `Claude-DevSprite` | Claude-DevSprite |
|
|
41
|
-
| `token-consumption-leaderboard` | `Token Consumption Leaderboard` | Token Consumption Leaderboard |
|
|
42
|
-
| `quant-platform` | `quant-platform` | quant-platform |
|
|
43
|
-
| `web-remote-control` | `web-remote-control` | web-remote-control |
|
|
44
|
-
| `tokenrank-cloud` | `TokenRank Cloud` | TokenRank Cloud |
|
|
160
|
+
## Configuration
|
|
45
161
|
|
|
46
|
-
|
|
162
|
+
| File | Purpose |
|
|
163
|
+
| --------------------- | -------------------------------------------------------------------- |
|
|
164
|
+
| `projects.json` | Registry of projects: slug, name, path, source control info. |
|
|
165
|
+
| `iterations.json` | Milestone definitions referenced by summary panels. |
|
|
166
|
+
| `ai-profiles.json` | One entry per AI adapter (id, type, model, prompt-id, options). |
|
|
167
|
+
| `claude-prompts.json` | Versioned prompt templates keyed by id; referenced by profiles. |
|
|
168
|
+
| `knowledge-store.json` | Runtime state of the external knowledge store (gitignored). |
|
|
169
|
+
| `logging.json` | Runtime logging config (gitignored). |
|
|
47
170
|
|
|
48
|
-
|
|
171
|
+
Environment variables:
|
|
49
172
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- 改 `INDEX.md`、`projects.json`、`iterations.json`:知识库 owner
|
|
173
|
+
- `KB_SITE_PORT` — server port (default `7777`)
|
|
174
|
+
- `KB_SITE_HOST` — bind host (default `127.0.0.1`)
|
|
53
175
|
|
|
54
|
-
|
|
176
|
+
## Testing
|
|
55
177
|
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
.\gen-commit-doc.ps1 -ProjectSlug claude-devsprite # 单项目
|
|
59
|
-
.\gen-commit-doc.ps1 -ProjectSlug ALL # 全部项目
|
|
178
|
+
```bash
|
|
179
|
+
npm test
|
|
60
180
|
```
|
|
61
181
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
182
|
+
Runs `_site/_test/run-all-tests.js`, which executes the smoke + unit tests
|
|
183
|
+
across AI adapters, draft-apply, kb-v2/v3 templates, knowledge store,
|
|
184
|
+
supervision, claude workbench, the project control panel (TASK-014),
|
|
185
|
+
TASK-015–020 integration, and the UI flow.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
67
188
|
|
|
68
|
-
|
|
189
|
+
The dashboard is a static `index.html` that ships Vue 3 + Tailwind from
|
|
190
|
+
`_site/vendor/`. Edit `_site/index.html` directly and refresh the browser;
|
|
191
|
+
no build step. Restart the server after editing anything under `_site/lib/`.
|
|
192
|
+
|
|
193
|
+
Useful entry points:
|
|
194
|
+
|
|
195
|
+
- Add a new AI adapter → `_site/lib/ai-adapter.js` + a profile in
|
|
196
|
+
`ai-profiles.json`.
|
|
197
|
+
- Add a new prompt → `claude-prompts.json` + reference it from a profile.
|
|
198
|
+
- Add a new dashboard page → `_site/index.html` (Vue components) + a route
|
|
199
|
+
handler in `_site/server.js`.
|
|
200
|
+
|
|
201
|
+
## Publishing
|
|
202
|
+
|
|
203
|
+
Releases are tag-driven. The `.github/workflows/publish.yml` workflow runs
|
|
204
|
+
`npm publish --provenance --access public` on every `v*` tag push, using the
|
|
205
|
+
`NPM_TOKEN` secret. To cut a release:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# bump version in package.json
|
|
209
|
+
git commit -am "Release vX.Y.Z"
|
|
210
|
+
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
211
|
+
git push origin main
|
|
212
|
+
git push origin vX.Y.Z # triggers publish
|
|
213
|
+
```
|
|
69
214
|
|
|
70
|
-
##
|
|
215
|
+
## License
|
|
71
216
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- **Frontmatter**:每个 `.md` 顶部带 YAML frontmatter(用 `---` 包围)
|
|
75
|
-
- **每文件一职责**:一个模块一文件、一次功能交付一文件
|
|
217
|
+
Currently `UNLICENSED` (see `package.json`). Revisit before public
|
|
218
|
+
consumption — MIT or Apache-2.0 are reasonable defaults for a tool like this.
|
|
76
219
|
|
|
77
|
-
##
|
|
220
|
+
## Changelog
|
|
78
221
|
|
|
79
|
-
|
|
222
|
+
See [CHANGELOG.md](./CHANGELOG.md).
|
|
@@ -7,6 +7,8 @@ const {
|
|
|
7
7
|
ADAPTERS, getAdapter, listAdapters,
|
|
8
8
|
validateInitialOutput, validateCommitBatchOutput,
|
|
9
9
|
} = require('../lib/ai-adapter');
|
|
10
|
+
const { readConfig: readLlmConfig } = require('../lib/llm-client');
|
|
11
|
+
const { buildClaudeEnvFromProfile } = require('../lib/claude-cli-runner');
|
|
10
12
|
|
|
11
13
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
12
14
|
const SERVER = path.join(ROOT, '_site', 'server.js');
|
|
@@ -15,6 +17,7 @@ const AI_PROFILES_JSON = path.join(ROOT, 'ai-profiles.json');
|
|
|
15
17
|
const PORT = process.env.KB_AI_TEST_PORT || '7795';
|
|
16
18
|
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
17
19
|
const TEMP_SLUG = 'task-005-temp';
|
|
20
|
+
const ORIGINAL_AI_PROFILES_TEXT = fs.readFileSync(AI_PROFILES_JSON, 'utf-8');
|
|
18
21
|
|
|
19
22
|
function assert(cond, msg) { if (!cond) throw new Error(msg); }
|
|
20
23
|
|
|
@@ -52,6 +55,7 @@ async function cleanup() {
|
|
|
52
55
|
delete cur[TEMP_SLUG];
|
|
53
56
|
fs.writeFileSync(PROJECTS_JSON, JSON.stringify(cur, null, 2) + '\n', 'utf-8');
|
|
54
57
|
}
|
|
58
|
+
fs.writeFileSync(AI_PROFILES_JSON, ORIGINAL_AI_PROFILES_TEXT, 'utf-8');
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
(async () => {
|
|
@@ -146,17 +150,68 @@ async function cleanup() {
|
|
|
146
150
|
assert(!r.res.ok && r.res.status === 400, 'invalid profile id should 400');
|
|
147
151
|
assert(Array.isArray(r.data.errors), 'should return errors list');
|
|
148
152
|
|
|
153
|
+
r = await json('PUT', '/api/ai-profiles', {
|
|
154
|
+
schema: 'ai-profiles/v1',
|
|
155
|
+
defaultProfileId: 'minimax-m3',
|
|
156
|
+
profiles: [
|
|
157
|
+
{ id: 'mock-agent', name: 'Mock', enabled: true, implementation: 'mock-agent' },
|
|
158
|
+
{ id: 'minimax-m3', name: 'Disabled LLM', enabled: false, implementation: 'claude-code-agent' },
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
assert(!r.res.ok && r.res.status === 400, 'disabled default profile should 400');
|
|
162
|
+
|
|
149
163
|
// 9. PUT /api/ai-profiles with valid config
|
|
150
164
|
r = await json('PUT', '/api/ai-profiles', {
|
|
151
165
|
schema: 'ai-profiles/v1',
|
|
152
166
|
defaultProfileId: 'mock-agent',
|
|
153
167
|
profiles: [
|
|
154
168
|
{ id: 'mock-agent', name: 'Mock', enabled: true, implementation: 'mock-agent' },
|
|
155
|
-
{
|
|
169
|
+
{
|
|
170
|
+
id: 'minimax-m3',
|
|
171
|
+
name: 'MiniMax M3',
|
|
172
|
+
enabled: false,
|
|
173
|
+
implementation: 'claude-code-agent',
|
|
174
|
+
baseUrl: 'https://example.test/anthropic',
|
|
175
|
+
apiKey: 'test-key',
|
|
176
|
+
model: 'test-model',
|
|
177
|
+
version: '2023-06-01',
|
|
178
|
+
temperature: 0.1,
|
|
179
|
+
maxTokens: 1234,
|
|
180
|
+
timeoutMs: 7654,
|
|
181
|
+
},
|
|
156
182
|
],
|
|
157
183
|
});
|
|
158
184
|
assert(r.res.ok, 'valid profile config should be accepted');
|
|
159
185
|
|
|
186
|
+
const llmCfg = readLlmConfig({ profileId: 'minimax-m3' });
|
|
187
|
+
assert(llmCfg.baseUrl === 'https://example.test/anthropic', 'llm client should read baseUrl from profile');
|
|
188
|
+
assert(llmCfg.apiKey === 'test-key', 'llm client should read apiKey from profile');
|
|
189
|
+
assert(llmCfg.model === 'test-model', 'llm client should read model from profile');
|
|
190
|
+
assert(llmCfg.timeoutMs === 7654, 'llm client should read timeoutMs from profile');
|
|
191
|
+
|
|
192
|
+
const claudeEnv = buildClaudeEnvFromProfile({
|
|
193
|
+
apiKey: 'test-key',
|
|
194
|
+
baseUrl: 'https://example.test/anthropic',
|
|
195
|
+
model: 'test-model',
|
|
196
|
+
timeoutMs: 7654,
|
|
197
|
+
});
|
|
198
|
+
assert(claudeEnv.ANTHROPIC_AUTH_TOKEN === 'test-key', 'claude env should set auth token');
|
|
199
|
+
assert(claudeEnv.ANTHROPIC_BASE_URL === 'https://example.test/anthropic', 'claude env should set base URL');
|
|
200
|
+
assert(claudeEnv.ANTHROPIC_MODEL === 'test-model', 'claude env should set model');
|
|
201
|
+
assert(claudeEnv.ANTHROPIC_DEFAULT_SONNET_MODEL === 'test-model', 'claude env should map sonnet alias');
|
|
202
|
+
assert(claudeEnv.ANTHROPIC_DEFAULT_OPUS_MODEL === 'test-model', 'claude env should map opus alias');
|
|
203
|
+
assert(claudeEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL === 'test-model', 'claude env should map haiku alias');
|
|
204
|
+
assert(claudeEnv.API_TIMEOUT_MS === '7654', 'claude env should set timeout');
|
|
205
|
+
assert(claudeEnv.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === '1', 'claude env should disable nonessential traffic');
|
|
206
|
+
|
|
207
|
+
r = await json('POST', '/api/ai-profiles/test', {
|
|
208
|
+
profileId: 'mock-agent',
|
|
209
|
+
prompt: 'what model are you?',
|
|
210
|
+
});
|
|
211
|
+
assert(r.res.ok, 'mock profile test should return 200');
|
|
212
|
+
assert(r.data.ok && r.data.model === 'mock-agent', 'mock profile test should identify mock-agent');
|
|
213
|
+
assert(/what model are you/.test(r.data.text), 'mock profile test should echo prompt');
|
|
214
|
+
|
|
160
215
|
// 10. Per-project ai profile selection
|
|
161
216
|
r = await json('PUT', '/api/projects', {
|
|
162
217
|
slug: TEMP_SLUG,
|
|
@@ -165,6 +220,9 @@ async function cleanup() {
|
|
|
165
220
|
assert(r.res.ok, 'upsert should succeed');
|
|
166
221
|
assert(r.data.repoStatus !== undefined, 'repoStatus should be returned');
|
|
167
222
|
|
|
223
|
+
r = await json('PUT', `/api/projects/${TEMP_SLUG}/ai-profile`, { aiProfileId: 'minimax-m3' });
|
|
224
|
+
assert(!r.res.ok && r.res.status === 400, 'disabled ai profile should not be assignable');
|
|
225
|
+
|
|
168
226
|
r = await json('PUT', `/api/projects/${TEMP_SLUG}/ai-profile`, { aiProfileId: 'mock-agent' });
|
|
169
227
|
assert(r.res.ok, 'set ai profile should succeed');
|
|
170
228
|
assert(r.data.aiProfileId === 'mock-agent', 'aiProfileId should be set');
|
|
@@ -73,7 +73,7 @@ async function removeTempProject() {
|
|
|
73
73
|
|
|
74
74
|
const stateResult = await json('GET', '/api/state');
|
|
75
75
|
assert(stateResult.res.ok, '/api/state should return 200');
|
|
76
|
-
assert(stateResult.data.projectSchemaVersion === '
|
|
76
|
+
assert(stateResult.data.projectSchemaVersion === 'v3', 'projectSchemaVersion should be v3');
|
|
77
77
|
assert(stateResult.data.kbRoot.endsWith('project-knowledge-base'), 'kbRoot should point to project-knowledge-base');
|
|
78
78
|
|
|
79
79
|
const projects = stateResult.data.projects;
|
|
@@ -85,7 +85,7 @@ async function removeTempProject() {
|
|
|
85
85
|
assert(Object.prototype.hasOwnProperty.call(cfg, 'lastSeenCommit'), `${slug} missing lastSeenCommit`);
|
|
86
86
|
assert(Object.prototype.hasOwnProperty.call(cfg, 'lastAnalyzedCommit'), `${slug} missing lastAnalyzedCommit`);
|
|
87
87
|
assert(cfg.aiProfileId, `${slug} missing aiProfileId`);
|
|
88
|
-
assert(
|
|
88
|
+
assert(['v1', 'v2', 'v3'].includes(cfg.kbSchemaVersion), `${slug} missing supported kbSchemaVersion`);
|
|
89
89
|
assert(cfg.goalStatus, `${slug} missing goalStatus`);
|
|
90
90
|
assert(!String(cfg.kbPath || '').includes('\\SanQian.Xu\\kb\\'), `${slug} uses legacy kbPath`);
|
|
91
91
|
}
|
|
@@ -106,7 +106,8 @@ async function removeTempProject() {
|
|
|
106
106
|
assert(projectsResult.res.ok, '/api/projects should return 200');
|
|
107
107
|
const temp = projectsResult.data[TEMP_SLUG];
|
|
108
108
|
assert(temp, 'temp project should exist');
|
|
109
|
-
assert(temp.kbPath.
|
|
109
|
+
assert(!String(temp.kbPath || '').includes('\\SanQian.Xu\\kb\\'), 'legacy kbPath should be normalized');
|
|
110
|
+
assert(String(temp.kbPath || '').endsWith(`\\${TEMP_SLUG}`), 'normalized kbPath should end with the slug');
|
|
110
111
|
assert(temp.enabled === true, 'temp project should default enabled=true');
|
|
111
112
|
// repoStatus is auto-validated on upsert; ROOT may or may not be a git repo.
|
|
112
113
|
// Accept any of the well-defined statuses that prove normalization ran.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Claude workbench regression test.
|
|
2
|
+
// Verifies permission-gated turns and persisted session metadata without spawning Claude.
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const runner = require('../lib/claude-cli-runner');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
9
|
+
const TEMP_KB = path.join(ROOT, 'projects', 'claude-workbench-test-temp');
|
|
10
|
+
const TEMP_AI = path.join(ROOT, '_site', '_ai', 'claude-workbench-test-temp');
|
|
11
|
+
|
|
12
|
+
function assert(cond, msg) {
|
|
13
|
+
if (!cond) throw new Error(msg);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
(async () => {
|
|
17
|
+
fs.rmSync(TEMP_KB, { recursive: true, force: true });
|
|
18
|
+
fs.rmSync(TEMP_AI, { recursive: true, force: true });
|
|
19
|
+
fs.mkdirSync(TEMP_KB, { recursive: true });
|
|
20
|
+
|
|
21
|
+
const started = runner.startSession({
|
|
22
|
+
slug: 'claude-workbench-test-temp',
|
|
23
|
+
projectPath: ROOT,
|
|
24
|
+
kbPath: TEMP_KB,
|
|
25
|
+
promptKey: 'initial-analysis',
|
|
26
|
+
aiProfile: {
|
|
27
|
+
id: 'test-profile',
|
|
28
|
+
implementation: 'claude-code-agent',
|
|
29
|
+
model: 'test-model',
|
|
30
|
+
},
|
|
31
|
+
vars: {
|
|
32
|
+
SLUG: 'claude-workbench-test-temp',
|
|
33
|
+
PROJECT_PATH: ROOT,
|
|
34
|
+
PRIMARY_LANGUAGE: 'JavaScript',
|
|
35
|
+
KNOWLEDGE_LANGUAGE: 'zh-CN',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert(started.sessionId, 'startSession should return sessionId');
|
|
40
|
+
assert(started.pendingPermission, 'initial turn should require permission');
|
|
41
|
+
|
|
42
|
+
const state = runner.getState(started.sessionId);
|
|
43
|
+
assert(state.state === 'pending-permission', `expected pending-permission, got ${state.state}`);
|
|
44
|
+
assert(state.pendingPermission.requestId === started.pendingPermission.requestId, 'state should expose pending request');
|
|
45
|
+
|
|
46
|
+
const replay = [];
|
|
47
|
+
const unsubscribe = runner.subscribe(started.sessionId, event => replay.push(event.type));
|
|
48
|
+
unsubscribe();
|
|
49
|
+
assert(replay.includes('claude/permission-request'), 'subscribe should replay permission request');
|
|
50
|
+
|
|
51
|
+
const recordPath = path.join(TEMP_AI, 'claude-workbench', `${started.sessionId}.json`);
|
|
52
|
+
assert(fs.existsSync(recordPath), 'session record should be persisted');
|
|
53
|
+
const record = JSON.parse(fs.readFileSync(recordPath, 'utf-8'));
|
|
54
|
+
assert(record.claudeSessionId === null, 'no Claude session id before approval/spawn');
|
|
55
|
+
assert(record.events.some(e => e.type === 'claude/permission-request'), 'record should include permission event');
|
|
56
|
+
|
|
57
|
+
const resolved = runner.resolvePermission(started.sessionId, started.pendingPermission.requestId, { allow: false });
|
|
58
|
+
assert(resolved.ok && resolved.started === false, 'denying permission should not start Claude');
|
|
59
|
+
const after = runner.getState(started.sessionId);
|
|
60
|
+
assert(after.state === 'idle', `denied permission should return to idle, got ${after.state}`);
|
|
61
|
+
assert(!after.pendingPermission, 'pending permission should be cleared after denial');
|
|
62
|
+
|
|
63
|
+
runner.deleteSession(started.sessionId);
|
|
64
|
+
fs.rmSync(TEMP_KB, { recursive: true, force: true });
|
|
65
|
+
fs.rmSync(TEMP_AI, { recursive: true, force: true });
|
|
66
|
+
console.log('Claude workbench test passed');
|
|
67
|
+
})().catch(err => {
|
|
68
|
+
try { fs.rmSync(TEMP_KB, { recursive: true, force: true }); } catch {}
|
|
69
|
+
try { fs.rmSync(TEMP_AI, { recursive: true, force: true }); } catch {}
|
|
70
|
+
console.error(err);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
@@ -103,7 +103,9 @@ async function cleanup() {
|
|
|
103
103
|
|
|
104
104
|
// 3. List drafts
|
|
105
105
|
const drafts = listDrafts(kbBase, runId);
|
|
106
|
-
assert(drafts.length >=
|
|
106
|
+
assert(drafts.length >= 2, `expected at least change + feature drafts, got ${drafts.length}`);
|
|
107
|
+
assert(drafts.some(d => d.path.startsWith('changes/')), 'expected a changes draft');
|
|
108
|
+
assert(drafts.some(d => d.path.startsWith('features/')), 'expected a features draft for feature commit');
|
|
107
109
|
// The orchestrator does NOT produce a goal draft for commit runs. It produces changes/* and a feature/*.
|
|
108
110
|
|
|
109
111
|
// 4. Build draft payloads from the on-disk drafts
|
|
@@ -291,7 +293,8 @@ async function cleanup() {
|
|
|
291
293
|
// 14. List drafts
|
|
292
294
|
r = await json('GET', `/api/projects/${slug}/drafts/${srvRunId}`);
|
|
293
295
|
assert(r.res.ok, 'list drafts should succeed');
|
|
294
|
-
assert(r.data.drafts.length >=
|
|
296
|
+
assert(r.data.drafts.length >= 2, `expected at least change + feature drafts, got ${r.data.drafts.length}`);
|
|
297
|
+
assert(r.data.drafts.some(d => d.path.startsWith('changes/')), 'server should list a changes draft');
|
|
295
298
|
const serverDrafts = r.data.drafts;
|
|
296
299
|
|
|
297
300
|
// 15. Read raw draft text
|
|
@@ -301,10 +304,12 @@ async function cleanup() {
|
|
|
301
304
|
assert(typeof r.data.content === 'string' && r.data.content.length > 0, 'raw content should be non-empty');
|
|
302
305
|
|
|
303
306
|
// 16. Apply without allowGoalEdit (no goal in these drafts, so this should succeed)
|
|
304
|
-
const payloads2 =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
const payloads2 = [];
|
|
308
|
+
for (const d of serverDrafts) {
|
|
309
|
+
const raw = await json('GET', `/api/projects/${slug}/drafts/${srvRunId}/raw?path=${encodeURIComponent(d.path)}`);
|
|
310
|
+
assert(raw.res.ok, `raw draft should load for ${d.path}`);
|
|
311
|
+
payloads2.push({ path: d.path, content: raw.data.content, sourceBranch: d.sourceBranch, sourceHeadCommit: d.sourceHeadCommit });
|
|
312
|
+
}
|
|
308
313
|
r = await json('POST', `/api/projects/${slug}/drafts/${srvRunId}/apply`, {
|
|
309
314
|
drafts: payloads2,
|
|
310
315
|
allowGoalEdit: false,
|
|
@@ -323,6 +328,7 @@ async function cleanup() {
|
|
|
323
328
|
|
|
324
329
|
// 19. Refuse: try to apply a goal draft without allowGoalEdit
|
|
325
330
|
fs.mkdirSync(path.join(kbPath, '_ai', 'drafts', 'goal-test'), { recursive: true });
|
|
331
|
+
fs.mkdirSync(path.join(kbPath, '_ai', 'runs'), { recursive: true });
|
|
326
332
|
fs.writeFileSync(path.join(kbPath, '_ai', 'drafts', 'goal-test', 'project-goal.md'), '# Goal — bad');
|
|
327
333
|
fs.writeFileSync(path.join(kbPath, '_ai', 'runs', 'goal-test.json'), JSON.stringify({ schema: 'ai-run/v1', runId: 'goal-test' }));
|
|
328
334
|
r = await json('POST', `/api/projects/${slug}/drafts/goal-test/apply`, {
|