project-knowledge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/INDEX.md +53 -0
  3. package/README.md +79 -0
  4. package/_site/README.md +63 -0
  5. package/_site/_test/ai-profile-test.js +199 -0
  6. package/_site/_test/baseline-schema-test.js +132 -0
  7. package/_site/_test/commit-analysis-test.js +184 -0
  8. package/_site/_test/context-pack-test.js +199 -0
  9. package/_site/_test/draft-apply-test.js +363 -0
  10. package/_site/_test/git-validation-test.js +171 -0
  11. package/_site/_test/hook-trigger-test.js +257 -0
  12. package/_site/_test/initial-analysis-test.js +228 -0
  13. package/_site/_test/job-orchestrator-test.js +297 -0
  14. package/_site/_test/kb-v2-templates-test.js +189 -0
  15. package/_site/_test/pr-consumer-contract-test.js +236 -0
  16. package/_site/_test/run-all-tests.js +135 -0
  17. package/_site/_test/scanner-test.js +206 -0
  18. package/_site/_test/ui-smoke-test.js +237 -0
  19. package/_site/_test/ui-test.js +237 -0
  20. package/_site/index.html +1166 -0
  21. package/_site/lib/ai-adapter.js +287 -0
  22. package/_site/lib/analysis-orchestrator.js +433 -0
  23. package/_site/lib/context-pack-builder.js +290 -0
  24. package/_site/lib/draft-apply.js +219 -0
  25. package/_site/lib/git-runner.js +26 -0
  26. package/_site/lib/hook-manager.js +148 -0
  27. package/_site/lib/job-orchestrator.js +231 -0
  28. package/_site/lib/kb-validator.js +224 -0
  29. package/_site/lib/llm-client.js +126 -0
  30. package/_site/lib/scanner.js +94 -0
  31. package/_site/scripts/hook-trigger.js +133 -0
  32. package/_site/scripts/safe-runner.js +151 -0
  33. package/_site/server.js +1058 -0
  34. package/_site/start.bat +26 -0
  35. package/_site/stop.bat +11 -0
  36. package/ai-profiles.json +18 -0
  37. package/docs/ai-knowledge-base-system-design.md +395 -0
  38. package/docs/pr-consumer-contract.md +198 -0
  39. package/docs/project-goal.md +72 -0
  40. package/docs/project-registry-schema.md +46 -0
  41. package/docs/testing-strategy.md +169 -0
  42. package/iterations.json +23 -0
  43. package/package.json +47 -0
  44. package/scripts/gen-commit-doc.ps1 +178 -0
  45. package/scripts/gen-commit-doc.sh +197 -0
  46. package/scripts/list-features.ps1 +41 -0
  47. package/scripts/register-scheduled-task.bat +5 -0
  48. package/templates/change.md +59 -0
  49. package/templates/commit-feature.md +56 -0
  50. package/templates/feature.md +44 -0
  51. package/templates/framework.md +80 -0
  52. package/templates/index-header.md +3 -0
  53. package/templates/kb-manifest.json +38 -0
  54. package/templates/module.md +58 -0
  55. package/templates/project-analysis.md +48 -0
  56. package/templates/project-goal.md +55 -0
  57. package/templates/project-readme.md +60 -0
  58. package/templates/quality-review-rules.md +37 -0
  59. package/templates/update-entry.md +7 -0
@@ -0,0 +1,72 @@
1
+ # Project Goal
2
+
3
+ Status: accepted
4
+ Owner: SanQian.Xu
5
+ Last updated: 2026-06-12
6
+
7
+ ## One-Sentence Goal
8
+
9
+ 将 `project-knowledge-base` 建设成一个以项目目标为最高事实源、由 Git 提交触发、通过 AI 自动分析代码并生成可审核知识库草稿的本地项目监督系统,为后续 PR 自动审查与项目优化工具提供准确、可信、可追溯的知识基础。
10
+
11
+ ## Original Starting Goal
12
+
13
+ 项目一开始的目标是做一个本地 Git 项目知识库管理工具:
14
+
15
+ - 管理多个本地项目的知识库目录。
16
+ - 从 `projects.json` 读取项目列表。
17
+ - 通过本地 UI 导入或查看项目。
18
+ - 基于 Git 提交记录生成 Markdown 文档。
19
+ - 通过脚本和定时任务持续更新项目提交文档。
20
+
21
+ 这个早期目标偏向“本地知识库目录 + 管理 UI + Git commit 文档生成”。
22
+
23
+ ## Current Product Goal
24
+
25
+ 当前目标已经升级为一个 AI 项目知识库监督系统。它必须能够:
26
+
27
+ - 导入用户选中的本地项目。
28
+ - 校验项目路径和 Git 仓库状态。
29
+ - 初始化项目知识库目录结构。
30
+ - 记录项目状态,包括仓库状态、HEAD、待分析提交、上次已分析提交、知识库初始化状态和异常点。
31
+ - 配置 AI 模型、API 参数和每个项目的知识库输出语言。
32
+ - 自动扫描 Git 提交,发现尚未进入知识库的变更。
33
+ - 自动构建 context pack,收集 Git diff、相关源码、已有知识库、项目目标、配置文件和测试文件。
34
+ - 使用 AI 对项目做整体分析,生成 `project-goal.md` 和 `project-analysis.md` 草稿。
35
+ - 使用 AI 对增量提交做深度分析,判断变更是新功能、旧功能更新、bug fix、重构、基础设施、测试还是文档变更。
36
+ - 生成功能、模块、变更等知识库草稿。
37
+ - 将 AI 结果先写入 `_ai/drafts/`,由用户审核后再 apply 到正式知识库。
38
+ - 保证正式知识库内容是可审核、可回溯、可测试的,而不是 AI 静默写入的结果。
39
+ - 为未来的 PR 项目提供稳定读取契约,让 PR 项目可以基于项目目标和已接受知识库自动检查代码、优化代码、辅助测试。
40
+
41
+ ## Core Truth Hierarchy
42
+
43
+ 1. `project-goal.md`
44
+ 项目的真实目标,是最高优先级的人类确认事实。AI 可以提出修改建议,但不能自动覆盖它。
45
+
46
+ 2. `project-analysis.md`
47
+ 当前代码实现的分析,是“项目目标如何被实现”的现状说明。它是证据,不是目标本身,可以随着代码变化更新。
48
+
49
+ 3. `features/`, `modules/`, `changes/`
50
+ 记录功能、模块和提交变更。它们必须说明和项目目标的关系,并尽量引用证据。
51
+
52
+ 4. `_ai/drafts/`, `_ai/runs/`, `_ai/context-packs/`
53
+ AI 工作区。这里的内容默认不是可信知识,只有通过审核和 apply 后才能进入正式知识库。
54
+
55
+ ## Non-Goals
56
+
57
+ - 不把 AI 输出直接当作可信知识。
58
+ - 不让 AI 静默修改 `project-goal.md`。
59
+ - 不要求用户手动挑选相关文件再交给 AI;上下文收集必须尽量自动化。
60
+ - 不把每个 commit 都简单变成一个孤立文档,而忽略它对功能、模块和项目目标的影响。
61
+ - 不让未来 PR 项目读取 `_ai/drafts/` 作为权威事实。
62
+
63
+ ## Development Guardrails
64
+
65
+ - 项目目标优先于项目分析。
66
+ - 项目分析是实现现状,不是目标本身。
67
+ - AI 生成内容必须先进入草稿区。
68
+ - apply 必须是显式动作。
69
+ - `lastAnalyzedCommit` 只能在知识库正式应用成功后推进。
70
+ - 任何面向未来 PR 项目的契约都必须稳定、可测试。
71
+ - 没有测试通过的功能不能视为完成。
72
+
@@ -0,0 +1,46 @@
1
+ # Project Registry Schema
2
+
3
+ Status: baseline schema for TASK-001
4
+ Date: 2026-06-11
5
+
6
+ `projects.json` is the registry for projects supervised by the knowledge base.
7
+
8
+ The current schema version is `v1`. The server normalizes project entries on read and write so older entries can coexist with newer fields.
9
+
10
+ ## Required Identity Fields
11
+
12
+ ```json
13
+ {
14
+ "displayName": "Example Project",
15
+ "localPath": "D:\\SanQian.Xu\\Example",
16
+ "gitPath": "D:\\SanQian.Xu\\Example",
17
+ "kbPath": "D:\\SanQian.Xu\\project-knowledge-base\\projects\\example"
18
+ }
19
+ ```
20
+
21
+ ## Baseline Fields Added In TASK-001
22
+
23
+ | Field | Default | Purpose |
24
+ |------|---------|---------|
25
+ | `enabled` | `true` | Whether the project is included in future scan/analyze workflows. |
26
+ | `repoStatus` | `"unknown"` | Placeholder for Git validation state. TASK-002 will set concrete values. |
27
+ | `headCommit` | `null` | Latest known repository head commit. |
28
+ | `lastSeenCommit` | `null` | Latest commit observed by a scan. |
29
+ | `lastAnalyzedCommit` | `null` | Latest commit whose knowledge updates were applied. |
30
+ | `aiProfileId` | `"default-agent"` | Analyzer profile selected for this project. |
31
+ | `kbSchemaVersion` | `"v1"` | Knowledge-base layout/schema generation used by the project. |
32
+ | `goalStatus` | `"not-created"` | State of `project-goal.md` for future workflows. |
33
+
34
+ ## Normalization Rules
35
+
36
+ - Missing `gitPath` defaults to `localPath`.
37
+ - Missing `kbPath` defaults to `<KB_ROOT>\\projects\\<slug>`.
38
+ - Legacy `D:\\SanQian.Xu\\kb\\projects\\<slug>` paths are replaced with the current `project-knowledge-base` root.
39
+ - Missing `tags` becomes an empty array.
40
+ - String `tags` are split on commas.
41
+ - Existing unknown fields are preserved.
42
+
43
+ ## Compatibility
44
+
45
+ `kbInitialized`, `commitCount`, and `moduleCount` are runtime status fields returned by the site. Older project entries may still contain them; TASK-001 does not remove them to avoid unnecessary data churn.
46
+
@@ -0,0 +1,169 @@
1
+ # Testing Strategy for AI Knowledge Base Development
2
+
3
+ Status: draft
4
+ Date: 2026-06-11
5
+
6
+ ## 1. Testing Rule
7
+
8
+ No development task is complete until its tests pass.
9
+
10
+ If tests fail, the task returns to implementation. The developer must fix the feature or update an incorrect test with a clear reason, then run the test plan again.
11
+
12
+ ## 2. Test Levels
13
+
14
+ ### 2.1 Static and Syntax Tests
15
+
16
+ Purpose: catch broken files before runtime.
17
+
18
+ Required checks:
19
+
20
+ - JSON files parse successfully.
21
+ - Node server scripts pass syntax checks.
22
+ - PowerShell scripts parse and support `-WhatIf` or dry-run where possible.
23
+ - Markdown files that define machine contracts contain required headings and frontmatter.
24
+
25
+ Example commands:
26
+
27
+ ```powershell
28
+ node --check _site\server.js
29
+ powershell -NoProfile -Command "$null = Get-Content projects.json -Raw | ConvertFrom-Json"
30
+ ```
31
+
32
+ ### 2.2 API Integration Tests
33
+
34
+ Purpose: prove the local API performs real file and Git operations safely.
35
+
36
+ Required coverage:
37
+
38
+ - `GET /api/state`
39
+ - project upsert
40
+ - Git validation success
41
+ - Git validation failure: missing path
42
+ - Git validation failure: path exists but is not a Git repo
43
+ - Git validation edge case: repo exists but has no commits
44
+ - analysis job creation
45
+ - draft listing
46
+ - draft apply
47
+ - failure reporting
48
+
49
+ ### 2.3 Git Fixture Tests
50
+
51
+ Purpose: make Git-driven behavior deterministic.
52
+
53
+ The test harness should create temporary repositories with:
54
+
55
+ - no commits
56
+ - one initial commit
57
+ - multiple commits
58
+ - feature commit touching one module
59
+ - refactor commit touching multiple modules
60
+ - commit that changes tests only
61
+ - commit with binary or large files
62
+
63
+ The analyzer should never require real user repositories for automated tests.
64
+
65
+ ### 2.4 AI Adapter Tests
66
+
67
+ Purpose: test orchestration without relying on live AI calls.
68
+
69
+ Required adapters:
70
+
71
+ - `mock-agent`: deterministic output for tests
72
+ - `recorded-agent`: replay saved responses
73
+ - `real-agent`: manual or nightly verification only
74
+
75
+ Automated CI-style tests must use `mock-agent` or `recorded-agent`.
76
+
77
+ ### 2.5 Knowledge Write Snapshot Tests
78
+
79
+ Purpose: ensure generated knowledge files are stable and safe.
80
+
81
+ For a known Git fixture and mock AI response, assert:
82
+
83
+ - `project-goal.md` is created as draft first.
84
+ - `project-analysis.md` includes evidence.
85
+ - `features/<feature>.md` is created or updated correctly.
86
+ - `changes/<commit>.md` exists.
87
+ - `kb-manifest.json` points to valid files.
88
+ - accepted knowledge is not overwritten without an apply step.
89
+
90
+ ### 2.6 UI End-to-End Tests
91
+
92
+ Purpose: prove the user can complete workflows from the browser.
93
+
94
+ Required Playwright flows:
95
+
96
+ - import valid Git project
97
+ - reject invalid Git project
98
+ - show repository status
99
+ - configure AI profile
100
+ - run initial analysis with mock agent
101
+ - view generated drafts
102
+ - edit project goal
103
+ - apply draft
104
+ - run incremental commit analysis
105
+ - inspect failed analysis run
106
+
107
+ ### 2.7 Regression Tests for Existing Site
108
+
109
+ The current site behavior must remain working:
110
+
111
+ - projects render
112
+ - add project form works
113
+ - schedule controls work
114
+ - manual run starts a job
115
+ - log output renders
116
+ - tree view renders
117
+
118
+ ## 3. Task Completion Gate
119
+
120
+ Every task file must list:
121
+
122
+ - automated tests to add
123
+ - manual checks to run
124
+ - fixtures required
125
+ - failure behavior
126
+ - definition of done
127
+
128
+ Completion requires:
129
+
130
+ 1. All task-specific tests pass.
131
+ 2. Existing regression tests pass.
132
+ 3. The developer records the executed tests in the task file or test report.
133
+ 4. Known failures are either fixed or explicitly moved into a follow-up task.
134
+
135
+ ## 4. Failure Loop
136
+
137
+ ```mermaid
138
+ flowchart TD
139
+ A["Implement task"] --> B["Run required tests"]
140
+ B --> C{"All tests pass?"}
141
+ C -->|No| D["Fix implementation or justified test issue"]
142
+ D --> B
143
+ C -->|Yes| E["Mark task complete"]
144
+ ```
145
+
146
+ ## 5. Recommended Test File Layout
147
+
148
+ ```text
149
+ _site/_test/
150
+ ├─ ui-test.js
151
+ ├─ api-test.js
152
+ ├─ fixtures/
153
+ │ ├─ make-git-repos.js
154
+ │ └─ mock-agent-responses/
155
+ ├─ snapshots/
156
+ └─ TEST-REPORT.md
157
+ ```
158
+
159
+ Future non-UI scripts can use:
160
+
161
+ ```text
162
+ tests/
163
+ ├─ git-inspector.test.js
164
+ ├─ context-pack-builder.test.js
165
+ ├─ draft-writer.test.js
166
+ ├─ manifest.test.js
167
+ └─ fixtures/
168
+ ```
169
+
@@ -0,0 +1,23 @@
1
+ {
2
+ "_comment": "里程碑定义,供 gen-commit-doc.ps1 按里程碑聚合使用。since/until 格式 YYYY-MM-DD,null 表示开放。",
3
+ "claude-devsprite": [
4
+ { "id": "v1.0-skill-core", "since": "2024-01-01", "until": "2025-04-30", "title": "Skill 核心能力" },
5
+ { "id": "v2.0-knowledge-graph", "since": "2025-05-01", "until": "2026-04-30", "title": "知识图谱化" },
6
+ { "id": "v2.1-dashboard", "since": "2026-05-01", "until": null, "title": "Web Dashboard 与多 Agent" }
7
+ ],
8
+ "web-remote-control": [
9
+ { "id": "v0.1-webrtc-poc", "since": "2026-04-01", "until": "2026-05-15", "title": "WebRTC PoC" },
10
+ { "id": "v0.2-rrweb-and-deploy", "since": "2026-05-16", "until": null, "title": "rrweb 集成与部署" }
11
+ ],
12
+ "token-consumption-leaderboard": [
13
+ { "id": "v1.0-local-dashboard", "since": "2024-01-01", "until": "2024-04-30", "title": "本地 Dashboard" },
14
+ { "id": "v2.0-leaderboard", "since": "2024-05-01", "until": null, "title": "排行榜与认证" }
15
+ ],
16
+ "quant-platform": [
17
+ { "id": "v1.0-base", "since": null, "until": "2026-03-12", "title": "基础框架" },
18
+ { "id": "v3.0-multi-market", "since": "2026-03-13", "until": "2026-03-14", "title": "多市场 + 真实数据源" }
19
+ ],
20
+ "claude-code-ui": [
21
+ { "id": "v1.32.0", "since": "2026-01-01", "until": "2026-05-19", "title": "上游 v1.32.0 引用" }
22
+ ]
23
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "project-knowledge",
3
+ "version": "0.1.0",
4
+ "description": "Knowledge base manager with Git integration, AI-driven analysis, and bilingual (zh-CN/en-US) knowledge output",
5
+ "main": "_site/server.js",
6
+ "scripts": {
7
+ "start": "node _site/server.js",
8
+ "test": "node _site/_test/run-all-tests.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=18"
12
+ },
13
+ "files": [
14
+ "_site/server.js",
15
+ "_site/index.html",
16
+ "_site/lib/",
17
+ "_site/scripts/",
18
+ "_site/_test/*.js",
19
+ "_site/_test/fixtures/",
20
+ "_site/start.bat",
21
+ "_site/stop.bat",
22
+ "templates/",
23
+ "scripts/",
24
+ "ai-profiles.json",
25
+ "iterations.json",
26
+ "docs/",
27
+ "README.md",
28
+ "INDEX.md",
29
+ "CHANGELOG.md"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/SanQianX/project-knowledge-base.git"
34
+ },
35
+ "keywords": [
36
+ "knowledge-base",
37
+ "git",
38
+ "ai",
39
+ "claude",
40
+ "documentation",
41
+ "scanner",
42
+ "context-pack",
43
+ "post-commit-hook"
44
+ ],
45
+ "author": "SanQianX",
46
+ "license": "UNLICENSED"
47
+ }
@@ -0,0 +1,178 @@
1
+ # gen-commit-doc.ps1
2
+ # All Chinese strings are loaded from template files to avoid PowerShell
3
+ # parser issues with multi-byte UTF-8 characters in script source.
4
+ [CmdletBinding()]
5
+ param(
6
+ [Parameter(Mandatory = $true)]
7
+ [string] $ProjectSlug,
8
+ [string] $KbRoot = "D:\SanQian.Xu\project-knowledge-base",
9
+ [int] $MaxCommits = 0
10
+ )
11
+
12
+ $ErrorActionPreference = "Stop"
13
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
14
+ $OutputEncoding = [System.Text.Encoding]::UTF8
15
+ $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
16
+ $PSDefaultParameterValues['Set-Content:Encoding'] = 'utf8'
17
+ $PSDefaultParameterValues['Add-Content:Encoding'] = 'utf8'
18
+
19
+ $projectsJsonPath = Join-Path $KbRoot "projects.json"
20
+ if (-not (Test-Path $projectsJsonPath)) {
21
+ throw "Cannot find projects.json: $projectsJsonPath"
22
+ }
23
+ $projects = Get-Content $projectsJsonPath -Raw -Encoding UTF8 | ConvertFrom-Json
24
+
25
+ $templatePath = Join-Path $KbRoot "templates\commit-feature.md"
26
+ $updateEntryPath = Join-Path $KbRoot "templates\update-entry.md"
27
+ $indexHeaderPath = Join-Path $KbRoot "templates\index-header.md"
28
+ if (-not (Test-Path $templatePath)) { throw "Cannot find template: $templatePath" }
29
+ if (-not (Test-Path $updateEntryPath)) { throw "Cannot find template: $updateEntryPath" }
30
+ if (-not (Test-Path $indexHeaderPath)) { throw "Cannot find template: $indexHeaderPath" }
31
+
32
+ $FEATURE_TEMPLATE = Get-Content $templatePath -Raw -Encoding UTF8
33
+ $UPDATE_ENTRY_TEMPLATE = Get-Content $updateEntryPath -Raw -Encoding UTF8
34
+ $INDEX_HEADER_TEMPLATE = Get-Content $indexHeaderPath -Raw -Encoding UTF8
35
+
36
+ $slugs = @()
37
+ if ($ProjectSlug -eq "ALL") {
38
+ $slugs = @($projects.PSObject.Properties.Name)
39
+ } else {
40
+ if (-not $projects.$ProjectSlug) {
41
+ throw "Slug $ProjectSlug not in projects.json"
42
+ }
43
+ $slugs = @($ProjectSlug)
44
+ }
45
+
46
+ function Get-FeatureSlug([string]$Subject) {
47
+ $s = $Subject -replace '^(feat|fix|refactor|perf|docs|chore|test|style|build|ci)(\([^)]+\))?:\s*', ''
48
+ # Try ASCII slug first
49
+ $slug = $s.ToLower() -replace '[^a-z0-9]+', '-' -replace '-+', '-' -replace '^-|-$', ''
50
+ if ([string]::IsNullOrWhiteSpace($slug)) {
51
+ # Non-ASCII subject (e.g. Chinese): use a sanitized slice of the original
52
+ $slug = $s.Substring(0, [Math]::Min(20, $s.Length)) -replace '[\s\\/:*?"<>|]+', '-' -replace '-+', '-' -replace '^-|-$', ''
53
+ }
54
+ if ([string]::IsNullOrWhiteSpace($slug)) { $slug = "untitled" }
55
+ if ($slug.Length -gt 50) { $slug = $slug.Substring(0, 50).TrimEnd('-') }
56
+ return $slug
57
+ }
58
+
59
+ function Get-CommitType([string]$Subject) {
60
+ if ($Subject -match '^(feat|fix|refactor|perf|docs|chore|test|style|build|ci)(\([^)]+\))?:\s*') {
61
+ return $Matches[1]
62
+ }
63
+ # No conventional prefix: treat as feat (the most common case for older projects)
64
+ return "feat"
65
+ }
66
+
67
+ function Test-CommitWriteable([string]$Type) {
68
+ return (@('feat','fix','refactor','perf') -contains $Type)
69
+ }
70
+
71
+ function New-FeatureDocFromTemplate([string]$Project, [string]$FeatureSlug, [string]$Hash, [string]$Date, [string]$Author, [string]$Subject, [string]$Type) {
72
+ $today = Get-Date -Format "yyyy-MM-dd"
73
+ $subjectLine = "# ${Subject}"
74
+ $headerLine = "---`nfeature: ${FeatureSlug}`nproject: ${Project}`nfirstCommit: ${Hash} (${Date}, ${Author})`nlastUpdate: ${Hash} (${Date}, ${Author})`nstatus: active`ncommitType: ${Type}`nrelatedModule: null`ngeneratedAt: ${today}`n---"
75
+ $noteLine = "> Auto-generated by gen-commit-doc.ps1 on $today."
76
+ $initialLine = "## Initial Implementation`n`n- **Commit**: ${Hash} (${Date}, ${Author}): ${Subject}`n- **Type**: ${Type}`n- **Business Impact**: TODO"
77
+ $relatedLine = "## Related Commits`n`n- ${Hash}: ${Subject}"
78
+ $linkedModules = "## Related Modules`n`n- [Framework](../framework.md)"
79
+ $body = "$headerLine`n`n$subjectLine`n`n$noteLine`n`n## Overview`n`nTODO - human to fill in the description.`n`n$initialLine`n`n## Update History (reverse chronological)`n`n$relatedLine`n`n$linkedModules"
80
+ return $body
81
+ }
82
+
83
+ function New-UpdateEntry([string]$Date, [string]$Hash, [string]$Subject, [string]$Type, [string]$Author) {
84
+ $body = $UPDATE_ENTRY_TEMPLATE
85
+ $body = $body.Replace('__DATE__', $Date)
86
+ $body = $body.Replace('__HASH__', $Hash)
87
+ $body = $body.Replace('__SUBJECT__', $Subject)
88
+ $body = $body.Replace('__TYPE__', $Type)
89
+ $body = $body.Replace('__AUTHOR__', $Author)
90
+ return $body
91
+ }
92
+
93
+ function Rebuild-Index([string]$Slug, [string]$CommitsDir) {
94
+ $files = Get-ChildItem -Path $CommitsDir -Filter "*.md" |
95
+ Where-Object { $_.Name -ne "00-index.md" } |
96
+ Sort-Object Name -Descending
97
+ $header = $INDEX_HEADER_TEMPLATE.Replace('__SLUG__', $Slug)
98
+ $sb = New-Object System.Text.StringBuilder
99
+ [void]$sb.AppendLine($header)
100
+ [void]$sb.AppendLine("| File | Size | Modified |")
101
+ [void]$sb.AppendLine("|------|------|----------|")
102
+ foreach ($f in $files) {
103
+ $size = $f.Length
104
+ $mtime = $f.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
105
+ [void]$sb.AppendLine("| [$($f.Name)](./$($f.Name)) | $size | $mtime |")
106
+ }
107
+ [void]$sb.AppendLine("")
108
+ [void]$sb.AppendLine("---")
109
+ [void]$sb.AppendLine("")
110
+ [void]$sb.AppendLine("## Auto Update")
111
+ [void]$sb.AppendLine("")
112
+ [void]$sb.AppendLine("Run daily at 08:00: ``project-knowledge-base\scripts\gen-commit-doc.ps1 -ProjectSlug $Slug``")
113
+ [void]$sb.AppendLine("")
114
+ $indexPath = Join-Path $CommitsDir "00-index.md"
115
+ Set-Content -Path $indexPath -Value $sb.ToString() -Encoding UTF8
116
+ }
117
+
118
+ function Process-Project([string]$Slug, [object]$Project) {
119
+ $gitPath = $Project.gitPath
120
+ $commitsDir = Join-Path $KbRoot "projects\$Slug\commits"
121
+ if (-not (Test-Path $commitsDir)) {
122
+ New-Item -ItemType Directory -Force -Path $commitsDir | Out-Null
123
+ }
124
+
125
+ Write-Host ""
126
+ Write-Host "=== $Slug ==="
127
+ Write-Host " git: $gitPath"
128
+
129
+ $logArgs = @("log", "--no-merges", "--pretty=format:%h|%ad|%an|%s%n", "--date=short")
130
+ if ($MaxCommits -gt 0) { $logArgs += @("-n", "$MaxCommits") }
131
+ $rawOutput = & git -C $gitPath @logArgs 2>&1
132
+ if ($LASTEXITCODE -ne 0) { Write-Warning "git log failed: $rawOutput"; return }
133
+ if ($rawOutput -is [array]) { $rawLog = ($rawOutput -join "`n") } else { $rawLog = [string]$rawOutput }
134
+ if ([string]::IsNullOrWhiteSpace($rawLog)) { Write-Host " (no commits)"; return }
135
+
136
+ $commits = @($rawLog -split "`n" | Where-Object { $_ -match '\|' })
137
+ $newCount = 0; $updatedCount = 0; $skippedCount = 0
138
+
139
+ $existingMap = @{}
140
+ Get-ChildItem -Path $commitsDir -Filter "*.md" | ForEach-Object {
141
+ $name = $_.BaseName
142
+ if ($name -match '_(.+)$') { $slugKey = $Matches[1] } else { $slugKey = $name }
143
+ $existingMap[$slugKey] = $_.FullName
144
+ }
145
+
146
+ foreach ($line in $commits) {
147
+ $parts = $line -split '\|', 4
148
+ if ($parts.Count -lt 4) { continue }
149
+ $hash, $date, $author, $subject = $parts
150
+ $type = Get-CommitType $subject
151
+ if (-not (Test-CommitWriteable $type)) { $skippedCount++; continue }
152
+ $slug = Get-FeatureSlug $subject
153
+ $shortHash = $hash.Substring(0, [Math]::Min(7, $hash.Length))
154
+ $filename = "${date}_${shortHash}_${slug}.md"
155
+ $filepath = Join-Path $commitsDir $filename
156
+
157
+ if (Test-Path $filepath) { $existingMap[$slug] = $filepath; continue }
158
+ if ($existingMap.ContainsKey($slug)) {
159
+ $entry = New-UpdateEntry $date $shortHash $subject $type $author
160
+ Add-Content -Path $existingMap[$slug] -Value $entry -Encoding UTF8
161
+ $updatedCount++
162
+ } else {
163
+ $body = New-FeatureDocFromTemplate $Slug $slug $shortHash $date $author $subject $type
164
+ Set-Content -Path $filepath -Value $body -Encoding UTF8
165
+ $newCount++
166
+ $existingMap[$slug] = $filepath
167
+ }
168
+ }
169
+
170
+ Write-Host " new: $newCount, updated: $updatedCount, skipped: $skippedCount"
171
+ Rebuild-Index $Slug $commitsDir
172
+ }
173
+
174
+ foreach ($slug in $slugs) {
175
+ Process-Project $slug $projects.$slug
176
+ }
177
+ Write-Host ""
178
+ Write-Host "Done."