svharness 0.14.16 → 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 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
- | `generic` | `.agent/skills/` | `.md` | 中立回退 |
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
- | 平台 | CodeChat runner |
614
- |------|-----------------|
615
- | Windows | `~/.codechat/cli_app/run.bat` |
616
- | Linux | `~/.codechat/cli_app/run.sh` |
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`。跳过:
@@ -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
+ };
@@ -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'],
@@ -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
- '/.codechat/',
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
- logger_1.logger.section(`步骤 ${stepWiki}/${totalSteps} - 生成 baseline wiki`);
459
- let wikiFullOk = false;
460
- try {
461
- const repoRootFs = wikiSourceRoot;
462
- const wikiOutputRootFs = targetRoot;
463
- const wikiRelPath = node_path_1.default.join('baseline', 'wiki');
464
- // Guard: baseline wiki must be based on real code. If the user provided
465
- // --baseline but the copy step produced an empty directory (e.g. every
466
- // file was filtered out), refuse to silently index cwd or an empty dir.
467
- if (hasBaseline) {
468
- const exists = await fs_extra_1.default.pathExists(repoRootFs);
469
- const entries = exists ? await fs_extra_1.default.readdir(repoRootFs) : [];
470
- if (!exists || entries.length === 0) {
471
- throw new Error(`Wiki 源码目录为空或不存在:${repoRootFs}。` +
472
- '请检查基线拷贝结果(黑名单/大小上限可能过滤掉了所有文件)。');
473
- }
474
- }
475
- const cfg = await (0, wiki_1.resolveWikiRunConfig)({
476
- cwd,
477
- apiKey: opts.wikiApiKey,
478
- baseUrl: opts.wikiBaseUrl,
479
- model: opts.wikiModel,
480
- });
481
- const langFolder = opts.wikiLang === 'en' ? 'en' : 'zh';
482
- const langRun = (0, wiki_1.langRunForRepowikiTarget)(langFolder);
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.warn(`wiki 生成失败(不回滚 harness 骨架):${err.message}`);
492
+ logger_1.logger.success(`baseline wiki 任务清单已生成:${node_path_1.default.relative(targetRoot, wikiResult.tasksPath)} (共 ${wikiResult.pageCount} 页任务)`);
514
493
  }
515
494
  }
516
- // If the full run failed, downgrade S10_wiki from DONE to PENDING
517
- // so the agent can pick it up via harness-build-skill-wiki-writer.
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 {
@@ -23,6 +23,8 @@ const STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.ps1');
23
23
  const LEGACY_STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.cmd');
24
24
  const ICON_FILENAME = 'codechat-agent.ico';
25
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';
26
28
  function logInfo(msg, silent) {
27
29
  if (!silent)
28
30
  logger_1.logger.info(msg);
@@ -157,6 +159,9 @@ function registerMenuKey(menuKey, command, iconValue) {
157
159
  */
158
160
  async function runShellInstall(opts = {}) {
159
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);
160
165
  const launchScriptPath = resolveLaunchScriptPath();
161
166
  await fs_extra_1.default.ensureDir(STUB_DIR);
162
167
  await writeStubFile(launchScriptPath);
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ /**
3
+ * `svharness wiki` — standalone wiki generation for any code project.
4
+ *
5
+ * Unlike the wiki embedded in `svharness build`, this command:
6
+ * - Scans source code in-place (no baseline copy).
7
+ * - Defaults output to `./wiki/` (relative to cwd).
8
+ * - Does NOT require a harness; optionally syncs S10_wiki via --update-state.
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.runWiki = runWiki;
15
+ const fs_extra_1 = __importDefault(require("fs-extra"));
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const picocolors_1 = __importDefault(require("picocolors"));
18
+ const version_1 = require("../utils/version");
19
+ const logger_1 = require("../utils/logger");
20
+ const run_wiki_generation_1 = require("../lib/wiki/run-wiki-generation");
21
+ const state_1 = require("../core/state");
22
+ async function runWiki(opts) {
23
+ (0, logger_1.setVerbose)(!!opts.verbose);
24
+ const cwd = opts.cwd ?? process.cwd();
25
+ // 1. Mutual exclusion check
26
+ if (opts.generateWiki && opts.wikiTasksOnly) {
27
+ logger_1.logger.error('--generate-wiki 与 --wiki-tasks-only 不可同时启用');
28
+ return 1;
29
+ }
30
+ // 2. --update-state requires --harness
31
+ if (opts.updateState && !opts.harness) {
32
+ logger_1.logger.error('--update-state 须同时提供 --harness <path>');
33
+ return 1;
34
+ }
35
+ // 3. Resolve paths
36
+ const repoRootFs = node_path_1.default.resolve(cwd, opts.wikiSource ?? '.');
37
+ const outputRoot = node_path_1.default.resolve(cwd, opts.output ?? './wiki');
38
+ const wikiRelPath = node_path_1.default.relative(outputRoot, outputRoot); // always '.'
39
+ const projectName = opts.projectName ?? node_path_1.default.basename(repoRootFs);
40
+ const mode = opts.generateWiki ? 'full' : 'tasks';
41
+ // 4. Source guard
42
+ if (!(await fs_extra_1.default.pathExists(repoRootFs))) {
43
+ logger_1.logger.error(`源码目录不存在:${repoRootFs}`);
44
+ return 1;
45
+ }
46
+ // 5. Display config
47
+ logger_1.logger.info(`源码根:${repoRootFs}`);
48
+ logger_1.logger.info(`输出到:${outputRoot}`);
49
+ logger_1.logger.info(`模式:${mode === 'full' ? '完整生成(generate-wiki)' : '仅任务清单(tasks-only)'}`);
50
+ logger_1.logger.info(`语言:${opts.wikiLang ?? 'zh'}`);
51
+ // 6. Run wiki generation
52
+ const result = await (0, run_wiki_generation_1.runWikiGeneration)({
53
+ repoRootFs,
54
+ wikiOutputRootFs: outputRoot,
55
+ wikiRelPath: '.', // wiki output is directly in outputRoot
56
+ wikiLang: opts.wikiLang,
57
+ wikiModel: opts.wikiModel,
58
+ wikiBaseUrl: opts.wikiBaseUrl,
59
+ wikiApiKey: opts.wikiApiKey,
60
+ projectName,
61
+ cliVersion: (0, version_1.getCliVersion)(),
62
+ mode,
63
+ cwd,
64
+ forceFull: opts.forceFull,
65
+ wikiAudience: opts.harness ? 'agent' : 'human',
66
+ sourceRootRel: '.',
67
+ onLog: (m) => logger_1.logger.info(m),
68
+ });
69
+ // 7. Handle result
70
+ if (result.ok) {
71
+ if (mode === 'full') {
72
+ logger_1.logger.success(`wiki 已完整生成到:${outputRoot}`);
73
+ }
74
+ else {
75
+ logger_1.logger.success(`wiki 任务清单已生成:${result.tasksPath} (共 ${result.pageCount} 页任务)`);
76
+ }
77
+ }
78
+ else {
79
+ if (mode === 'full') {
80
+ logger_1.logger.error('wiki 完整生成失败(已产出部分文件可从 checkpoint 恢复)');
81
+ }
82
+ else {
83
+ logger_1.logger.error('wiki 任务清单生成失败');
84
+ }
85
+ }
86
+ // 8. Optional: sync S10_wiki to harness state
87
+ if (opts.updateState && opts.harness) {
88
+ const harnessRoot = node_path_1.default.resolve(cwd, opts.harness);
89
+ if (!(await fs_extra_1.default.pathExists(node_path_1.default.join(harnessRoot, 'harness.yaml')))) {
90
+ logger_1.logger.warn(`未找到 harness.yaml:${harnessRoot},跳过 state 更新`);
91
+ }
92
+ else {
93
+ // Warn if output doesn't match harness/baseline/wiki
94
+ const expectedOutput = node_path_1.default.resolve(harnessRoot, 'baseline', 'wiki');
95
+ if (node_path_1.default.resolve(outputRoot) !== expectedOutput) {
96
+ logger_1.logger.warn(`--output (${outputRoot}) 不等于 <harness>/baseline/wiki。` +
97
+ 'state 中 S10_wiki 的 tasks_file 路径可能不匹配实际产出位置。');
98
+ }
99
+ await (0, state_1.patchS10WikiPhase)(harnessRoot, {
100
+ wikiPhase: result.wikiPhase,
101
+ wikiSource: result.wikiSource,
102
+ });
103
+ }
104
+ }
105
+ // 9. Next-steps hint
106
+ if (result.ok) {
107
+ logger_1.logger.plain('');
108
+ if (opts.updateState && opts.harness) {
109
+ logger_1.logger.plain(picocolors_1.default.bold('下一步(harness 联动):'));
110
+ logger_1.logger.plain(' - 可执行 svharness doctor --harness <path> 校验 wiki 状态');
111
+ if (mode === 'tasks') {
112
+ logger_1.logger.plain(' - 可用 harness-build-skill-wiki-writer 按 TASKS.md 逐页补写');
113
+ }
114
+ }
115
+ else {
116
+ logger_1.logger.plain(picocolors_1.default.bold('下一步:'));
117
+ if (mode === 'tasks') {
118
+ logger_1.logger.plain(` - 编辑 ${node_path_1.default.join(outputRoot, 'TASKS.md')} 中的 wiki 页面任务`);
119
+ logger_1.logger.plain(' - 或使用 --generate-wiki 直接完整生成');
120
+ }
121
+ else {
122
+ logger_1.logger.plain(` - wiki 文件位于 ${outputRoot}`);
123
+ }
124
+ }
125
+ }
126
+ return result.ok ? 0 : 1;
127
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveConfigSection = exports.mergeDoctorOptions = exports.mergeConvertOptions = exports.mergeApplyOptions = exports.mergeBuildOptions = exports.resolveConfigHarnessName = exports.normalizeConfig = exports.resolveConfigPath = exports.loadConfig = exports.CONFIG_SCHEMA_VERSION = exports.DEFAULT_CONFIG_FILENAME = void 0;
3
+ exports.saveConfigSection = exports.mergeWikiOptions = exports.mergeDoctorOptions = exports.mergeConvertOptions = exports.mergeApplyOptions = exports.mergeBuildOptions = exports.resolveConfigHarnessName = exports.normalizeConfig = exports.resolveConfigPath = exports.loadConfig = exports.CONFIG_SCHEMA_VERSION = exports.DEFAULT_CONFIG_FILENAME = void 0;
4
4
  var constants_1 = require("./constants");
5
5
  Object.defineProperty(exports, "DEFAULT_CONFIG_FILENAME", { enumerable: true, get: function () { return constants_1.DEFAULT_CONFIG_FILENAME; } });
6
6
  Object.defineProperty(exports, "CONFIG_SCHEMA_VERSION", { enumerable: true, get: function () { return constants_1.CONFIG_SCHEMA_VERSION; } });
@@ -15,5 +15,6 @@ Object.defineProperty(exports, "mergeBuildOptions", { enumerable: true, get: fun
15
15
  Object.defineProperty(exports, "mergeApplyOptions", { enumerable: true, get: function () { return merge_options_1.mergeApplyOptions; } });
16
16
  Object.defineProperty(exports, "mergeConvertOptions", { enumerable: true, get: function () { return merge_options_1.mergeConvertOptions; } });
17
17
  Object.defineProperty(exports, "mergeDoctorOptions", { enumerable: true, get: function () { return merge_options_1.mergeDoctorOptions; } });
18
+ Object.defineProperty(exports, "mergeWikiOptions", { enumerable: true, get: function () { return merge_options_1.mergeWikiOptions; } });
18
19
  var save_config_1 = require("./save-config");
19
20
  Object.defineProperty(exports, "saveConfigSection", { enumerable: true, get: function () { return save_config_1.saveConfigSection; } });