svharness 0.14.13 → 0.14.17
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 +148 -9
- package/assets/codechat-agent.ico +0 -0
- package/dist/adapters/codex.js +19 -0
- package/dist/adapters/index.js +2 -0
- package/dist/commands/doctor/utils.js +2 -0
- package/dist/commands/init.js +37 -129
- package/dist/commands/shell-integration.js +40 -3
- package/dist/commands/wiki.js +127 -0
- package/dist/config/index.js +2 -1
- package/dist/config/merge-options.js +19 -0
- package/dist/config/normalize.js +18 -0
- package/dist/core/state.js +39 -0
- package/dist/index.js +58 -10
- package/dist/lib/agent-launcher.js +14 -16
- package/dist/lib/codechat-runner-resolver.js +208 -0
- package/dist/lib/wiki/run-wiki-generation.js +161 -0
- package/dist/lib/win-registry.js +10 -0
- package/dist/utils/validate-args.js +1 -0
- package/dist/wiki/repowikiIndexer.js +19 -150
- package/dist/wiki/repowikiPrompts.js +296 -0
- package/dist/wiki/repowikiStructureNormalize.js +31 -1
- package/dist/wiki/wikiTasksWriter.js +59 -22
- package/docs/agent-launcher-design.md +127 -26
- package/docs/standalone-codechat-ps1.md +123 -0
- package/package.json +2 -1
- package/scripts/preuninstall.js +45 -5
- package/templates/_shared/build-rules/harness-build-rule-agent-agnostic.md +2 -2
- package/templates/_shared/build-rules/harness-build-rule-specs-schema.md +1 -1
- package/templates/_shared/build-skills/harness-build-skill-orchestrator.md +1 -1
- package/templates/_shared/build-skills/harness-build-skill-wiki-writer.md +15 -6
- package/templates/codechat/Start-CodeChat.ps1 +359 -0
- package/templates/svharness.config.example.yaml +7 -0
package/README.md
CHANGED
|
@@ -133,7 +133,7 @@ Harness 是一个 **项目本地的知识层**,由两大部分组成:
|
|
|
133
133
|
└── tasks/ # 可复用的开发者工作流
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
同时,CLI 会在**项目根**写入 `CLAUDE.md`(`--agent codechat` 或 `claude-code`)或 `AGENTS.md`(`qoder` / `cursor` / `opencode` / `generic`),并向 Agent IDE 的 skill 目录注入 **7 个构建辅助 skill**:
|
|
136
|
+
同时,CLI 会在**项目根**写入 `CLAUDE.md`(`--agent codechat` 或 `claude-code`)或 `AGENTS.md`(`qoder` / `cursor` / `opencode` / `codex` / `generic`),并向 Agent IDE 的 skill 目录注入 **7 个构建辅助 skill**:
|
|
137
137
|
|
|
138
138
|
| Skill | 职责 |
|
|
139
139
|
|-------|------|
|
|
@@ -228,6 +228,7 @@ svharness wizard
|
|
|
228
228
|
| `apply` | 对应 `svharness apply` |
|
|
229
229
|
| `convert` | 对应 `svharness convert` |
|
|
230
230
|
| `doctor` | 对应 `svharness doctor` |
|
|
231
|
+
| `wiki` | 对应 `svharness wiki` |
|
|
231
232
|
|
|
232
233
|
`build` 节除路径外,可写人类可读说明(仅写入配置文件,供团队阅读):
|
|
233
234
|
|
|
@@ -270,7 +271,7 @@ svharness build \
|
|
|
270
271
|
| `--harness-name <name>` | ✅ 必填* | harness 名称;可在 `build.harnessName` 中提供 | — |
|
|
271
272
|
| `--name <name>` | ⚠️ 已废弃别名 | 等同 `--harness-name`,下个 minor 版本移除 | — |
|
|
272
273
|
| `--arch <arch>` | 可选 | 架构模板:`android-compose` / `android-xml` / `cpp` / `web-react` / `python` | `android-compose` |
|
|
273
|
-
| `--agent <agent>` | 可选 | 目标 Agent IDE:`codechat` / `qoder` / `cursor` / `claude-code` / `opencode` / `generic` | `codechat` |
|
|
274
|
+
| `--agent <agent>` | 可选 | 目标 Agent IDE:`codechat` / `qoder` / `cursor` / `claude-code` / `opencode` / `codex` / `generic` | `codechat` |
|
|
274
275
|
| `--baseline <path\|url>` | 可选 | 基线来源:本地目录 **或** git 仓库 URL | — |
|
|
275
276
|
| `--requirements <path>` | 可选 | 需求输入路径(文件或目录);build 阶段**扁平**拷贝到 `requirements/raw/`(不保留源目录里的 `assets/raw/converted_md` 等嵌套),随后自动 convert 到 `requirements/md/`(失败仅告警,不中断 build) | — |
|
|
276
277
|
| `--references <path>` | 可选 | 参考资料输入路径(文件或目录);build 阶段**扁平**拷贝到 `references/raw/`,随后自动 convert 到 `references/md/`(失败仅告警,不中断 build) | — |
|
|
@@ -312,6 +313,8 @@ svharness build \
|
|
|
312
313
|
| `--baseline` + `--generate-wiki` | 逐页调用 LLM 生成完整 wiki | 初始 `DONE`;失败降级为 `PENDING` |
|
|
313
314
|
|
|
314
315
|
> Wiki 生成失败不会回滚 harness 骨架,仅输出警告。
|
|
316
|
+
>
|
|
317
|
+
> build 内置 wiki 的 LLM prompt **面向编码 Agent**(知识路由卡片);独立 `svharness wiki` 默认面向人类开发者,见 [`wiki` 命令](#wiki-standalone) 一节。
|
|
315
318
|
|
|
316
319
|
#### Repomix(`--repomix`){#repomix-repomix}
|
|
317
320
|
|
|
@@ -351,7 +354,9 @@ svharness build --harness-name my-app --baseline ./src --repomix
|
|
|
351
354
|
| `qoder` | `.qoder/skills/` | `.md` | — |
|
|
352
355
|
| `cursor` | `.cursor/rules/` | `.mdc` | 自动注入 frontmatter |
|
|
353
356
|
| `claude-code` | `.claude/skills/` | `.md` | — |
|
|
354
|
-
| `
|
|
357
|
+
| `opencode` | `.opencode/skills/` | `.md` | — |
|
|
358
|
+
| `codex` | `.agents/skills/` | `.md` | — |
|
|
359
|
+
| `generic` | `.harness/skills/` | `.md` | 中立回退 |
|
|
355
360
|
|
|
356
361
|
### `apply` —— 把已构建好的 harness 绑定到目标项目
|
|
357
362
|
|
|
@@ -367,7 +372,7 @@ svharness apply --harness ../my-app-harness --target ./to-apply-project --clone
|
|
|
367
372
|
|
|
368
373
|
**在 Agent IDE 中触发**:对话框输入 `应用 harness-apply-skills-main 完成 xxx 功能开发`,薄入口会提示并优先使用已注入的 `harness-apply-skills-*` 子 skill。
|
|
369
374
|
|
|
370
|
-
同时,CLI 会在**目标项目根**写入 `CLAUDE.md`(`--agent codechat` 或 `claude-code`)或 `AGENTS.md`(`qoder` / `cursor` / `opencode` / `generic`)。该入口由 `<harness>/AGENTS_APPLY.md` 拷贝并重命名生成(与 `build` 写入项目根入口的规则一致,不再单独落地 `AGENTS_APPLY.md`)。
|
|
375
|
+
同时,CLI 会在**目标项目根**写入 `CLAUDE.md`(`--agent codechat` 或 `claude-code`)或 `AGENTS.md`(`qoder` / `cursor` / `opencode` / `codex` / `generic`)。该入口由 `<harness>/AGENTS_APPLY.md` 拷贝并重命名生成(与 `build` 写入项目根入口的规则一致,不再单独落地 `AGENTS_APPLY.md`)。
|
|
371
376
|
|
|
372
377
|
此外,`apply` 会同步注入运行期资产:
|
|
373
378
|
|
|
@@ -399,7 +404,7 @@ S60(`harness-build-skill-references-intake`)属于 **build 阶段**。若某
|
|
|
399
404
|
|------|----------|------|--------|
|
|
400
405
|
| `--harness <path>` | ✅ 必填 | 已构建好的 harness 目录(形如 `./my-app-harness`) | — |
|
|
401
406
|
| `--target <path>` | 可选 | 目标项目根目录 | **当前工作目录(cwd)** |
|
|
402
|
-
| `--agent <agent>` | 可选 | 目标 Agent:`codechat` / `qoder` / `cursor` / `claude-code` / `opencode` / `generic` | `codechat` |
|
|
407
|
+
| `--agent <agent>` | 可选 | 目标 Agent:`codechat` / `qoder` / `cursor` / `claude-code` / `opencode` / `codex` / `generic` | `codechat` |
|
|
403
408
|
| `--clone` | 可选 flag | 兼容参数;当前实现下行为与默认一致(都会拷贝 harness) | `false` |
|
|
404
409
|
| `--include-build-assets` | 可选 flag | 显式拷贝构建期 skills/rules 到 `build-agent-env/`(用于二次改造) | `false` |
|
|
405
410
|
| `--inject-build-main-bridge` | 可选 flag | 向运行期 skills 注入 `harness-build-skills-bridge`(需 `--include-build-assets`) | `false` |
|
|
@@ -574,6 +579,92 @@ convert-failures.json
|
|
|
574
579
|
|
|
575
580
|
完整契约、fallback chain、Docker 部署见 [markitdown_serve/README.md](./markitdown_serve/README.md);模块设计见 [tmp/svharnessbuild-detail-design.md §4.10](../tmp/svharnessbuild-detail-design.md#410-文档转换convert-命令--markitdown_serve-远端服务)。
|
|
576
581
|
|
|
582
|
+
### `wiki` —— 对任意代码工程生成 baseline wiki(v0.15+)
|
|
583
|
+
|
|
584
|
+
独立子命令,**不依赖 harness 生命周期**,直接对任意代码工程目录就地扫描生成 wiki。与 `build` 内置 wiki 共用同一 repowiki 核心,但不拷贝 code、不创建 harness 骨架。
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
# 对当前工程生成 wiki 骨架(默认 ./wiki/)
|
|
588
|
+
svharness wiki
|
|
589
|
+
|
|
590
|
+
# 完整生成(LLM 逐页)
|
|
591
|
+
svharness wiki --generate-wiki
|
|
592
|
+
|
|
593
|
+
# 指定源码目录 + 完整生成
|
|
594
|
+
svharness wiki --wiki-source ./src --generate-wiki --output ./docs/wiki
|
|
595
|
+
|
|
596
|
+
# 对外部仓库目录生成(不拷贝,就地扫描)
|
|
597
|
+
svharness wiki --wiki-source /path/to/other-project --output ./other-wiki
|
|
598
|
+
|
|
599
|
+
# 可选:同步到已有 harness 的 S10_wiki
|
|
600
|
+
svharness wiki --wiki-source ./src \
|
|
601
|
+
--output ./my-app-harness/baseline/wiki \
|
|
602
|
+
--harness ./my-app-harness --update-state --generate-wiki
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
#### 全部参数
|
|
606
|
+
|
|
607
|
+
| 参数 | 必填 | 说明 | 默认值 |
|
|
608
|
+
|------|------|------|--------|
|
|
609
|
+
| `--wiki-source <path>` | 否 | 要扫描的代码工程根目录 | `.`(cwd) |
|
|
610
|
+
| `--output <path>` | 否 | wiki 产物输出目录 | `./wiki` |
|
|
611
|
+
| `--generate-wiki` | 否 | 完整生成(LLM 逐页;与 `--wiki-tasks-only` 互斥) | `false` |
|
|
612
|
+
| `--wiki-tasks-only` | 否 | 仅生成 outline + TASKS.md(与默认等价) | `true`(隐式默认) |
|
|
613
|
+
| `--wiki-lang <lang>` | 否 | 输出语言:`zh` \| `en` | `zh` |
|
|
614
|
+
| `--wiki-model <model>` | 否 | LLM 模型名称 | 内置默认 |
|
|
615
|
+
| `--wiki-base-url <url>` | 否 | OpenAI 兼容 API 地址 | 内置默认 |
|
|
616
|
+
| `--wiki-api-key <key>` | 否 | API 密钥 | 环境变量 / `.env` |
|
|
617
|
+
| `--project-name <name>` | 否 | TASKS.md 标题(默认取 `wiki-source` 的目录名) | — |
|
|
618
|
+
| `--force-full` | 否 | full 模式忽略 checkpoint 强制重新生成 | `false` |
|
|
619
|
+
| `--harness <path>` | 否 | 已有 harness 根目录;**提供时 wiki prompt 面向编码 Agent**(配合 `--update-state` 同步 state) | — |
|
|
620
|
+
| `--update-state` | 否 | 同步 S10_wiki 到 harness 的 `.harness-build-state.yaml`(须同时提供 `--harness`) | `false` |
|
|
621
|
+
| `--config <path>` | 否 | 从配置文件读取 wiki 节 | `svharness.config.yaml` |
|
|
622
|
+
| `--verbose` | 否 | 显示详细日志 | `false` |
|
|
623
|
+
|
|
624
|
+
#### 与 `build` 内置 wiki 的区别
|
|
625
|
+
|
|
626
|
+
| 维度 | `svharness build`(内置) | `svharness wiki`(独立) |
|
|
627
|
+
|------|--------------------------|-------------------------|
|
|
628
|
+
| 源码 | 拷贝 `--baseline` → `<harness>/baseline/code/` | **就地扫描**,不拷贝 |
|
|
629
|
+
| 默认源码根 | `baseline/code/` | **cwd(`.`)** |
|
|
630
|
+
| 输出目录 | `<harness>/baseline/wiki/` | **`./wiki/`** |
|
|
631
|
+
| 依赖 harness | 是(创建 harness 骨架) | **否** |
|
|
632
|
+
|
|
633
|
+
**Wiki 受众(prompt 自动选择,无需 CLI 开关)**:
|
|
634
|
+
|
|
635
|
+
| 入口 | 受众 | 产出风格 |
|
|
636
|
+
|------|------|----------|
|
|
637
|
+
| `svharness build --baseline` | 编码 AI Agent | 知识地图:路由式大纲、固定章节正文 |
|
|
638
|
+
| `svharness wiki`(无 `--harness`) | 人类开发者 | 教程式大纲、简介 + TOC + 叙述性文档 |
|
|
639
|
+
| `svharness wiki --harness <path>` | 编码 AI Agent | 与 build 相同(供 harness baseline/wiki 消费) |
|
|
640
|
+
| state 更新 | init 时自动写 S10_wiki | 默认不写;可选 `--update-state --harness` |
|
|
641
|
+
|
|
642
|
+
#### 产出布局
|
|
643
|
+
|
|
644
|
+
```
|
|
645
|
+
./wiki/ # 或 --output 指定的目录
|
|
646
|
+
├── structure.xml # outline XML
|
|
647
|
+
├── TASKS.md # tasks 模式产出(可交给 agent 补页)
|
|
648
|
+
├── metadata.json # full 模式产出
|
|
649
|
+
├── checkpoint.json # full 模式进度(可续跑)
|
|
650
|
+
└── zh/<section>/<page>.md # full 模式各页面
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
#### 配置文件支持
|
|
654
|
+
|
|
655
|
+
在 `svharness.config.yaml` 中可配置 wiki 节:
|
|
656
|
+
|
|
657
|
+
```yaml
|
|
658
|
+
wiki:
|
|
659
|
+
wikiSource: .
|
|
660
|
+
output: ./wiki
|
|
661
|
+
generateWiki: false
|
|
662
|
+
wikiLang: zh
|
|
663
|
+
wikiModel: Qwen3.6-27B
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
优先级:CLI 参数 > `wiki:` 节 > `defaults:` > 内置默认。
|
|
667
|
+
|
|
577
668
|
### `start-agent` —— 启动 Agent 填充 harness(v0.16+)
|
|
578
669
|
|
|
579
670
|
`build` 生成骨架后,用本命令在**项目根(workdir)**启动 CodeChat CLI,驱动 S10–S90 构建阶段;`apply` 之后亦可在目标项目根启动 Agent 做业务开发。
|
|
@@ -608,12 +699,58 @@ svharness start-agent --work-dir . --no-sync-env --no-skip-permissions
|
|
|
608
699
|
| `--no-sync-env` | 不读取项目 `.claude/.env` 同步到用户级(仍可用已有 `~/.claude/.env`;若无则仅写入用户级内置模板) | false |
|
|
609
700
|
| `--no-skip-permissions` | 不传 `--dangerously-skip-permissions` | 默认传 |
|
|
610
701
|
|
|
702
|
+
#### CodeChat runner 解析
|
|
703
|
+
|
|
704
|
+
`start-agent` / `launch_codechat_cli` 按以下优先级查找 `run.bat`(Windows)或 `run.sh`(Linux/macOS):
|
|
705
|
+
|
|
706
|
+
| 优先级 | 来源 | 路径示例 |
|
|
707
|
+
|--------|------|----------|
|
|
708
|
+
| 1 | 环境变量 | `CODECHAT_CLI_RUNNER` 指向绝对路径 |
|
|
709
|
+
| 2 | 独立安装 | `~/.codechat/cli_app/run.bat`(Win)/ `run.sh`(Linux) |
|
|
710
|
+
| 3 | IDE 插件 | `%APPDATA%\Google\AndroidStudio*\plugins\CodeChat\cli\run.bat` 等 |
|
|
711
|
+
|
|
712
|
+
若 CodeChat 仅通过 **Android Studio / IntelliJ IDEA 插件**安装(无 `~/.codechat/cli_app`),会自动扫描 IDE 插件目录,**无需指定 IDE 版本号**。
|
|
713
|
+
|
|
714
|
+
启动成功时日志示例:
|
|
715
|
+
|
|
716
|
+
```text
|
|
717
|
+
CodeChat CLI: C:\Users\...\AndroidStudio2024.2\plugins\CodeChat\cli\run.bat (ide-plugin: Android Studio 2024.2)
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**手动指定 runner**(PowerShell):
|
|
721
|
+
|
|
722
|
+
```powershell
|
|
723
|
+
$env:CODECHAT_CLI_RUNNER = "$env:APPDATA\Google\AndroidStudio2024.2\plugins\CodeChat\cli\run.bat"
|
|
724
|
+
svharness start-agent --work-dir D:\projects\my-app
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**bash**:
|
|
728
|
+
|
|
729
|
+
```bash
|
|
730
|
+
export CODECHAT_CLI_RUNNER="$HOME/.local/share/JetBrains/IntelliJIdea2024.2/plugins/CodeChat/cli/run.sh"
|
|
731
|
+
svharness start-agent --work-dir ~/projects/my-app
|
|
732
|
+
```
|
|
733
|
+
|
|
611
734
|
#### 平台说明
|
|
612
735
|
|
|
613
|
-
| 平台 |
|
|
614
|
-
|
|
615
|
-
| Windows |
|
|
616
|
-
| Linux |
|
|
736
|
+
| 平台 | spawn 模式 | 右键菜单 |
|
|
737
|
+
|------|------------|----------|
|
|
738
|
+
| Windows | `shell: true` | 支持(`svharness shell`) |
|
|
739
|
+
| Linux / macOS | `shell: false` | 不支持 |
|
|
740
|
+
|
|
741
|
+
### 无需 svharness:独立 PowerShell 启动器
|
|
742
|
+
|
|
743
|
+
未安装 npm / svharness 时,可拷贝 [`templates/codechat/Start-CodeChat.ps1`](templates/codechat/Start-CodeChat.ps1) 到项目 `.claude\`,实现相同的 env 同步与 `run.bat` 解析(含 Android Studio / IDEA 插件路径):
|
|
744
|
+
|
|
745
|
+
```powershell
|
|
746
|
+
Copy-Item .\templates\codechat\Start-CodeChat.ps1 .\.claude\
|
|
747
|
+
.\.claude\Start-CodeChat.ps1 # 启动
|
|
748
|
+
.\.claude\Start-CodeChat.ps1 -Action InstallMenu # 可选:独立右键菜单
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
右键菜单使用注册表键 `CodeChatStandaloneAgent`,**不与** `svharness shell install` 冲突;可与 svharness 菜单并存。
|
|
752
|
+
|
|
753
|
+
详见 [`docs/standalone-codechat-ps1.md`](docs/standalone-codechat-ps1.md)。
|
|
617
754
|
|
|
618
755
|
### `launch_codechat_cli` —— 全局快捷命令(v0.16+)
|
|
619
756
|
|
|
@@ -633,6 +770,8 @@ launch_codechat_cli ~/projects/my-app # Linux
|
|
|
633
770
|
svharness shell install # 注册(幂等)
|
|
634
771
|
svharness shell uninstall # 移除
|
|
635
772
|
svharness shell status # 查看 stub / 注册表状态
|
|
773
|
+
svharness shell uninstall
|
|
774
|
+
npm uninstall -g svharness
|
|
636
775
|
```
|
|
637
776
|
|
|
638
777
|
`npm install -g svharness` 在 Windows 上 **默认自动** `shell install`。跳过:
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.codexAdapter = void 0;
|
|
4
|
+
const _frontmatter_1 = require("./_frontmatter");
|
|
5
|
+
/**
|
|
6
|
+
* OpenAI Codex: project skills under `.agents/skills/<name>/SKILL.md`,
|
|
7
|
+
* project guidance via `AGENTS.md`.
|
|
8
|
+
* @see https://developers.openai.com/codex/skills
|
|
9
|
+
* @see https://developers.openai.com/codex/guides/agents-md
|
|
10
|
+
*/
|
|
11
|
+
exports.codexAdapter = {
|
|
12
|
+
name: 'codex',
|
|
13
|
+
projectEntryFile: 'AGENTS.md',
|
|
14
|
+
skillsDir: '.agents/skills',
|
|
15
|
+
skillExt: '.md',
|
|
16
|
+
rulesDir: '.agents/rules',
|
|
17
|
+
ruleExt: '.mdc',
|
|
18
|
+
ruleTransform: (0, _frontmatter_1.ensureMdcFrontmatter)('Harness build-time rule'),
|
|
19
|
+
};
|
package/dist/adapters/index.js
CHANGED
|
@@ -8,12 +8,14 @@ const cursor_1 = require("./cursor");
|
|
|
8
8
|
const claude_code_1 = require("./claude-code");
|
|
9
9
|
const generic_1 = require("./generic");
|
|
10
10
|
const opencode_1 = require("./opencode");
|
|
11
|
+
const codex_1 = require("./codex");
|
|
11
12
|
const REGISTRY = {
|
|
12
13
|
codechat: codechat_1.codechatAdapter,
|
|
13
14
|
qoder: qoder_1.qoderAdapter,
|
|
14
15
|
cursor: cursor_1.cursorAdapter,
|
|
15
16
|
'claude-code': claude_code_1.claudeCodeAdapter,
|
|
16
17
|
opencode: opencode_1.opencodeAdapter,
|
|
18
|
+
codex: codex_1.codexAdapter,
|
|
17
19
|
generic: generic_1.genericAdapter,
|
|
18
20
|
};
|
|
19
21
|
function getAdapter(name) {
|
|
@@ -164,6 +164,7 @@ exports.AGENT_BLACKLIST = [
|
|
|
164
164
|
'cursor',
|
|
165
165
|
'claude-code',
|
|
166
166
|
'opencode',
|
|
167
|
+
'codex',
|
|
167
168
|
'claude',
|
|
168
169
|
'chatgpt',
|
|
169
170
|
'gpt-4',
|
|
@@ -172,6 +173,7 @@ exports.AGENT_BLACKLIST = [
|
|
|
172
173
|
'.cursor/',
|
|
173
174
|
'.claude/',
|
|
174
175
|
'.opencode/',
|
|
176
|
+
'.agents/',
|
|
175
177
|
];
|
|
176
178
|
exports.TASK_CATEGORY_HINTS = [
|
|
177
179
|
['新增', '信号', '接口', '数据', 'increment', 'add'],
|
package/dist/commands/init.js
CHANGED
|
@@ -61,6 +61,7 @@ const extra_assets_intake_1 = require("../core/extra-assets-intake");
|
|
|
61
61
|
const baseline_copy_1 = require("../utils/baseline-copy");
|
|
62
62
|
const doc_intake_paths_1 = require("../core/doc-intake-paths");
|
|
63
63
|
const convert_1 = require("./convert");
|
|
64
|
+
const run_wiki_generation_1 = require("../lib/wiki/run-wiki-generation");
|
|
64
65
|
const wiki_1 = require("../wiki");
|
|
65
66
|
/**
|
|
66
67
|
* Resolve the absolute path to the bundled templates/ directory.
|
|
@@ -387,11 +388,17 @@ async function runInit(opts) {
|
|
|
387
388
|
agent: validated.agent,
|
|
388
389
|
force: !!opts.force,
|
|
389
390
|
});
|
|
391
|
+
// Collect adapter-specific root directories for gitignore.
|
|
392
|
+
const ignoreRoots = new Set();
|
|
393
|
+
ignoreRoots.add(node_path_1.default.posix.join('/', node_path_1.default.posix.dirname(adapterEntry.skillsDir), '/'));
|
|
394
|
+
if (adapterEntry.rulesDir) {
|
|
395
|
+
ignoreRoots.add(node_path_1.default.posix.join('/', node_path_1.default.posix.dirname(adapterEntry.rulesDir), '/'));
|
|
396
|
+
}
|
|
390
397
|
const ignoreEntries = [
|
|
391
398
|
`/${dirName}/`,
|
|
392
399
|
'/AGENTS.md',
|
|
393
400
|
'/CLAUDE.md',
|
|
394
|
-
|
|
401
|
+
...ignoreRoots,
|
|
395
402
|
];
|
|
396
403
|
const ignoreAdded = await (0, project_ignore_1.appendProjectIgnoreEntries)({
|
|
397
404
|
projectRoot: cwd,
|
|
@@ -454,141 +461,42 @@ async function runInit(opts) {
|
|
|
454
461
|
// the wiki result.
|
|
455
462
|
let resolvedWikiPhase = wikiMode === 'off' ? 'none' : wikiMode === 'tasks' ? 'pending' : 'done';
|
|
456
463
|
let resolvedWikiSource;
|
|
457
|
-
if (wikiMode === 'full') {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
logger_1.logger.info(` 源码根:${repoRootFs}`);
|
|
484
|
-
logger_1.logger.info(` 输出到:${node_path_1.default.join(wikiOutputRootFs, wikiRelPath)}`);
|
|
485
|
-
logger_1.logger.info(` 语言:${langFolder} · 模型:${cfg.model}`);
|
|
486
|
-
await (0, wiki_1.runRepowikiIndex)({
|
|
487
|
-
repoRootFs,
|
|
488
|
-
wikiOutputRootFs,
|
|
489
|
-
wikiRelPath,
|
|
490
|
-
baseUrl: cfg.baseUrl,
|
|
491
|
-
apiKey: cfg.apiKey,
|
|
492
|
-
model: cfg.model,
|
|
493
|
-
languages: [langRun],
|
|
494
|
-
comprehensiveSections: true,
|
|
495
|
-
onLog: (m) => logger_1.logger.info(m),
|
|
496
|
-
onProgress: (p) => {
|
|
497
|
-
if (p.total > 0) {
|
|
498
|
-
logger_1.logger.info(` [${p.done}/${p.total}] ${p.detail}`);
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
logger_1.logger.info(` ${p.detail}`);
|
|
502
|
-
}
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
logger_1.logger.success('baseline wiki 已生成');
|
|
506
|
-
wikiFullOk = true;
|
|
507
|
-
}
|
|
508
|
-
catch (err) {
|
|
509
|
-
if ((0, wiki_1.isAbortError)(err)) {
|
|
510
|
-
logger_1.logger.warn('wiki 生成被中断(已写出部分页可从 checkpoint 恢复)');
|
|
464
|
+
if (wikiMode === 'full' || wikiMode === 'tasks') {
|
|
465
|
+
const stepLabel = wikiMode === 'full'
|
|
466
|
+
? `步骤 ${stepWiki}/${totalSteps} - 生成 baseline wiki`
|
|
467
|
+
: `步骤 ${stepWiki}/${totalSteps} - 生成 baseline wiki 任务清单`;
|
|
468
|
+
logger_1.logger.section(stepLabel);
|
|
469
|
+
const wikiResult = await (0, run_wiki_generation_1.runWikiGeneration)({
|
|
470
|
+
repoRootFs: wikiSourceRoot,
|
|
471
|
+
wikiOutputRootFs: targetRoot,
|
|
472
|
+
wikiRelPath: node_path_1.default.join('baseline', 'wiki'),
|
|
473
|
+
wikiLang: opts.wikiLang,
|
|
474
|
+
wikiModel: opts.wikiModel,
|
|
475
|
+
wikiBaseUrl: opts.wikiBaseUrl,
|
|
476
|
+
wikiApiKey: opts.wikiApiKey,
|
|
477
|
+
projectName: validated.name,
|
|
478
|
+
cliVersion,
|
|
479
|
+
mode: wikiMode === 'full' ? 'full' : 'tasks',
|
|
480
|
+
cwd,
|
|
481
|
+
wikiAudience: 'agent',
|
|
482
|
+
sourceRootRel: node_path_1.default.relative(targetRoot, wikiSourceRoot).replace(/\\/g, '/') || '.',
|
|
483
|
+
onLog: (m) => logger_1.logger.info(m),
|
|
484
|
+
});
|
|
485
|
+
resolvedWikiPhase = wikiResult.wikiPhase;
|
|
486
|
+
resolvedWikiSource = wikiResult.wikiSource;
|
|
487
|
+
if (wikiResult.ok) {
|
|
488
|
+
if (wikiMode === 'full') {
|
|
489
|
+
logger_1.logger.success('baseline wiki 已生成');
|
|
511
490
|
}
|
|
512
491
|
else {
|
|
513
|
-
logger_1.logger.
|
|
492
|
+
logger_1.logger.success(`baseline wiki 任务清单已生成:${node_path_1.default.relative(targetRoot, wikiResult.tasksPath)} (共 ${wikiResult.pageCount} 页任务)`);
|
|
514
493
|
}
|
|
515
494
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (!wikiFullOk) {
|
|
519
|
-
resolvedWikiPhase = 'pending';
|
|
520
|
-
resolvedWikiSource = 'cli-full-degraded';
|
|
495
|
+
else if (wikiMode === 'full') {
|
|
496
|
+
// Full mode failure downgrades to PENDING so the agent can pick it up.
|
|
521
497
|
logger_1.logger.warn('S10_wiki 将标记为 PENDING,等待 agent 接力补齐');
|
|
522
498
|
}
|
|
523
499
|
}
|
|
524
|
-
else if (wikiMode === 'tasks') {
|
|
525
|
-
logger_1.logger.section(`步骤 ${stepWiki}/${totalSteps} - 生成 baseline wiki 任务清单`);
|
|
526
|
-
try {
|
|
527
|
-
const repoRootFs = wikiSourceRoot;
|
|
528
|
-
const wikiOutputRootFs = targetRoot;
|
|
529
|
-
const wikiRelPath = node_path_1.default.join('baseline', 'wiki');
|
|
530
|
-
if (hasBaseline) {
|
|
531
|
-
const exists = await fs_extra_1.default.pathExists(repoRootFs);
|
|
532
|
-
const entries = exists ? await fs_extra_1.default.readdir(repoRootFs) : [];
|
|
533
|
-
if (!exists || entries.length === 0) {
|
|
534
|
-
throw new Error(`Wiki 源码目录为空或不存在:${repoRootFs}。` +
|
|
535
|
-
'请检查基线拷贝结果(黑名单/大小上限可能过滤掉了所有文件)。');
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
const cfg = await (0, wiki_1.resolveWikiRunConfig)({
|
|
539
|
-
cwd,
|
|
540
|
-
apiKey: opts.wikiApiKey,
|
|
541
|
-
baseUrl: opts.wikiBaseUrl,
|
|
542
|
-
model: opts.wikiModel,
|
|
543
|
-
});
|
|
544
|
-
const langFolder = opts.wikiLang === 'en' ? 'en' : 'zh';
|
|
545
|
-
const langRun = (0, wiki_1.langRunForRepowikiTarget)(langFolder);
|
|
546
|
-
logger_1.logger.info(` 源码根:${repoRootFs}`);
|
|
547
|
-
logger_1.logger.info(` 输出到:${node_path_1.default.join(wikiOutputRootFs, wikiRelPath)}`);
|
|
548
|
-
logger_1.logger.info(` 语言:${langFolder} · 模型:${cfg.model}`);
|
|
549
|
-
logger_1.logger.info(' 模式:仅任务清单(tasks-only,默认)');
|
|
550
|
-
const outline = await (0, wiki_1.runRepowikiOutlineOnly)({
|
|
551
|
-
repoRootFs,
|
|
552
|
-
wikiOutputRootFs,
|
|
553
|
-
wikiRelPath,
|
|
554
|
-
baseUrl: cfg.baseUrl,
|
|
555
|
-
apiKey: cfg.apiKey,
|
|
556
|
-
model: cfg.model,
|
|
557
|
-
languages: [langRun],
|
|
558
|
-
comprehensiveSections: true,
|
|
559
|
-
onLog: (m) => logger_1.logger.info(m),
|
|
560
|
-
onProgress: (p) => {
|
|
561
|
-
if (p.total > 0) {
|
|
562
|
-
logger_1.logger.info(` [${p.done}/${p.total}] ${p.detail}`);
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
logger_1.logger.info(` ${p.detail}`);
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
});
|
|
569
|
-
const tasksPath = await (0, wiki_1.writeWikiTasks)({
|
|
570
|
-
outline,
|
|
571
|
-
targetRoot,
|
|
572
|
-
wikiRelPath,
|
|
573
|
-
projectName: validated.name,
|
|
574
|
-
cliVersion,
|
|
575
|
-
model: cfg.model,
|
|
576
|
-
generatedAtIso: new Date().toISOString(),
|
|
577
|
-
sourceRootRel: node_path_1.default
|
|
578
|
-
.relative(targetRoot, wikiSourceRoot)
|
|
579
|
-
.replace(/\\/g, '/') || '.',
|
|
580
|
-
});
|
|
581
|
-
logger_1.logger.success(`baseline wiki 任务清单已生成:${node_path_1.default.relative(targetRoot, tasksPath)} (共 ${outline.pages.length} 页任务)`);
|
|
582
|
-
}
|
|
583
|
-
catch (err) {
|
|
584
|
-
if ((0, wiki_1.isAbortError)(err)) {
|
|
585
|
-
logger_1.logger.warn('wiki 任务清单生成被中断');
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
logger_1.logger.warn(`wiki 任务清单生成失败(不回滚 harness 骨架):${err.message}`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
500
|
// 7. Write build state file (after wiki, so wiki result is accurately reflected).
|
|
593
501
|
let declarationSummary;
|
|
594
502
|
try {
|
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveShellMenuIconSourcePath = resolveShellMenuIconSourcePath;
|
|
7
|
+
exports.installShellMenuIcon = installShellMenuIcon;
|
|
6
8
|
exports.resolveLaunchScriptPath = resolveLaunchScriptPath;
|
|
7
9
|
exports.resolveWindowsTerminalPath = resolveWindowsTerminalPath;
|
|
8
10
|
exports.runShellInstall = runShellInstall;
|
|
@@ -19,6 +21,10 @@ const win_registry_1 = require("../lib/win-registry");
|
|
|
19
21
|
const STUB_DIR = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'svharness', 'bin');
|
|
20
22
|
const STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.ps1');
|
|
21
23
|
const LEGACY_STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.cmd');
|
|
24
|
+
const ICON_FILENAME = 'codechat-agent.ico';
|
|
25
|
+
const ICON_STUB_PATH = path_1.default.join(STUB_DIR, ICON_FILENAME);
|
|
26
|
+
const STANDALONE_MENU_KEY_BG = 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\CodeChatStandaloneAgent';
|
|
27
|
+
const STANDALONE_MENU_KEY_DIR = 'HKCU\\Software\\Classes\\Directory\\shell\\CodeChatStandaloneAgent';
|
|
22
28
|
function logInfo(msg, silent) {
|
|
23
29
|
if (!silent)
|
|
24
30
|
logger_1.logger.info(msg);
|
|
@@ -36,6 +42,28 @@ function assertWin32() {
|
|
|
36
42
|
'Linux/macOS 请使用:launch_codechat_cli 或 svharness start-agent --work-dir <path>');
|
|
37
43
|
}
|
|
38
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve bundled context-menu icon from the installed package.
|
|
47
|
+
*/
|
|
48
|
+
function resolveShellMenuIconSourcePath() {
|
|
49
|
+
const fromDist = path_1.default.resolve(__dirname, '..', '..', 'assets', ICON_FILENAME);
|
|
50
|
+
if (fs_extra_1.default.existsSync(fromDist)) {
|
|
51
|
+
return fromDist;
|
|
52
|
+
}
|
|
53
|
+
const fromCwd = path_1.default.resolve(__dirname, '..', '..', '..', 'assets', ICON_FILENAME);
|
|
54
|
+
if (fs_extra_1.default.existsSync(fromCwd)) {
|
|
55
|
+
return fromCwd;
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`未找到 ${ICON_FILENAME}(预期:${fromDist})`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Copy icon to LOCALAPPDATA stub dir; return registry Icon value (`path,0`).
|
|
61
|
+
*/
|
|
62
|
+
async function installShellMenuIcon() {
|
|
63
|
+
const source = resolveShellMenuIconSourcePath();
|
|
64
|
+
await fs_extra_1.default.copy(source, ICON_STUB_PATH, { overwrite: true });
|
|
65
|
+
return `${ICON_STUB_PATH},0`;
|
|
66
|
+
}
|
|
39
67
|
/**
|
|
40
68
|
* Resolve absolute path to bin/launch-codechat-cli.js in the installed package.
|
|
41
69
|
*/
|
|
@@ -121,8 +149,9 @@ function buildMenuCommand(ps1Path, dirPlaceholder, wtPath) {
|
|
|
121
149
|
}
|
|
122
150
|
return `powershell.exe ${psArgs}`;
|
|
123
151
|
}
|
|
124
|
-
function registerMenuKey(menuKey, command) {
|
|
152
|
+
function registerMenuKey(menuKey, command, iconValue) {
|
|
125
153
|
(0, win_registry_1.regSetDefault)(menuKey, win_registry_1.SHELL_MENU_LABEL);
|
|
154
|
+
(0, win_registry_1.regSetString)(menuKey, 'Icon', iconValue);
|
|
126
155
|
(0, win_registry_1.regSetDefault)(`${menuKey}\\command`, command);
|
|
127
156
|
}
|
|
128
157
|
/**
|
|
@@ -130,20 +159,25 @@ function registerMenuKey(menuKey, command) {
|
|
|
130
159
|
*/
|
|
131
160
|
async function runShellInstall(opts = {}) {
|
|
132
161
|
assertWin32();
|
|
162
|
+
// Avoid duplicate menu entries: overwrite standalone menu if present.
|
|
163
|
+
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
|
|
164
|
+
(0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
|
|
133
165
|
const launchScriptPath = resolveLaunchScriptPath();
|
|
134
166
|
await fs_extra_1.default.ensureDir(STUB_DIR);
|
|
135
167
|
await writeStubFile(launchScriptPath);
|
|
136
168
|
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
137
169
|
await fs_extra_1.default.remove(LEGACY_STUB_PATH);
|
|
138
170
|
}
|
|
171
|
+
const iconValue = await installShellMenuIcon();
|
|
139
172
|
const wtPath = resolveWindowsTerminalPath();
|
|
140
173
|
const bgCommand = buildMenuCommand(STUB_PATH, '"%V"', wtPath);
|
|
141
174
|
const dirCommand = buildMenuCommand(STUB_PATH, '"%1"', wtPath);
|
|
142
|
-
registerMenuKey(win_registry_1.SHELL_MENU_KEY_BG, bgCommand);
|
|
143
|
-
registerMenuKey(win_registry_1.SHELL_MENU_KEY_DIR, dirCommand);
|
|
175
|
+
registerMenuKey(win_registry_1.SHELL_MENU_KEY_BG, bgCommand, iconValue);
|
|
176
|
+
registerMenuKey(win_registry_1.SHELL_MENU_KEY_DIR, dirCommand, iconValue);
|
|
144
177
|
const via = wtPath ? 'Windows Terminal' : 'PowerShell(UTF-8)';
|
|
145
178
|
logSuccess(`Windows 右键菜单已注册:「在此启动 CodeChat Agent」(${via})`, opts.silent);
|
|
146
179
|
logInfo(` stub: ${STUB_PATH}`, opts.silent);
|
|
180
|
+
logInfo(` icon: ${ICON_STUB_PATH}`, opts.silent);
|
|
147
181
|
logInfo(` 脚本: ${launchScriptPath}`, opts.silent);
|
|
148
182
|
if (wtPath) {
|
|
149
183
|
logInfo(` wt: ${wtPath}`, opts.silent);
|
|
@@ -159,6 +193,9 @@ async function runShellUninstall(opts = {}) {
|
|
|
159
193
|
if (await fs_extra_1.default.pathExists(STUB_PATH)) {
|
|
160
194
|
await fs_extra_1.default.remove(STUB_PATH);
|
|
161
195
|
}
|
|
196
|
+
if (await fs_extra_1.default.pathExists(ICON_STUB_PATH)) {
|
|
197
|
+
await fs_extra_1.default.remove(ICON_STUB_PATH);
|
|
198
|
+
}
|
|
162
199
|
if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
|
|
163
200
|
await fs_extra_1.default.remove(LEGACY_STUB_PATH);
|
|
164
201
|
}
|