@chengzhao-dev/dsh-cpp-workflow 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.en.md +107 -0
- package/README.md +106 -2
- package/cordis.patch.yml +14 -0
- package/icon.svg +7 -0
- package/lib/index.js +203 -0
- package/lib/workspace.js +168 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +81 -4
- package/skills/building-cpp/SKILL.md +44 -0
- package/skills/building-cpp/assets/cmake/applying-warnings.cmake +10 -0
- package/skills/building-cpp/assets/cmake/fetching-googletest.cmake +15 -0
- package/skills/building-cpp/assets/cmake/setting-rpath.cmake +8 -0
- package/skills/building-cpp/assets/config/.clang-format +40 -0
- package/skills/building-cpp/assets/config/.clang-tidy +92 -0
- package/skills/building-cpp/assets/config/.clangd +12 -0
- package/skills/building-cpp/assets/config/.vscode/extensions.json +6 -0
- package/skills/building-cpp/assets/config/.vscode/settings.json +34 -0
- package/skills/building-cpp/assets/templates/projects/common/running-build.sh +152 -0
- package/skills/building-cpp/assets/templates/projects/multi/CMakeLists.txt +18 -0
- package/skills/building-cpp/assets/templates/projects/multi/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/multi/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/multi/src/main.cpp +8 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/CMakeLists.txt +24 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/CMakeLists.txt +5 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/greeting/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/shared-library/src/main.cpp +8 -0
- package/skills/building-cpp/assets/templates/projects/single/CMakeLists.txt +14 -0
- package/skills/building-cpp/assets/templates/projects/single/src/main.cpp +9 -0
- package/skills/building-cpp/assets/templates/projects/static-library/CMakeLists.txt +18 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/CMakeLists.txt +5 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/include/greeting.h +10 -0
- package/skills/building-cpp/assets/templates/projects/static-library/greeting/src/greeting.cpp +13 -0
- package/skills/building-cpp/assets/templates/projects/static-library/src/main.cpp +8 -0
- package/skills/building-cpp/examples/cmake.md +150 -0
- package/skills/building-cpp/examples/environment.md +125 -0
- package/skills/building-cpp/examples/tooling.md +155 -0
- package/skills/building-cpp/references/build-script.md +63 -0
- package/skills/building-cpp/references/cmake.md +168 -0
- package/skills/building-cpp/references/environment.md +104 -0
- package/skills/building-cpp/references/linking.md +101 -0
- package/skills/building-cpp/references/static-analysis.md +136 -0
- package/skills/building-cpp/scripts/checking-environment.mjs +498 -0
- package/skills/building-cpp/steps/01-build.md +76 -0
- package/skills/building-cpp/steps/02-tooling.md +41 -0
- package/skills/building-cpp/steps/03-check.md +60 -0
- package/skills/developing-cpp/SKILL.md +42 -0
- package/skills/developing-cpp/examples/comments.md +231 -0
- package/skills/developing-cpp/examples/exceptions.md +134 -0
- package/skills/developing-cpp/examples/naming.md +157 -0
- package/skills/developing-cpp/examples/structure.md +79 -0
- package/skills/developing-cpp/references/comments.md +123 -0
- package/skills/developing-cpp/references/declaration-order.md +97 -0
- package/skills/developing-cpp/references/exceptions.md +79 -0
- package/skills/developing-cpp/references/glossary.md +88 -0
- package/skills/developing-cpp/references/google-cpp-style.md +109 -0
- package/skills/developing-cpp/references/language.md +119 -0
- package/skills/developing-cpp/references/layout.md +73 -0
- package/skills/developing-cpp/references/naming.md +62 -0
- package/skills/developing-cpp/references/performance.md +82 -0
- package/skills/developing-cpp/references/project-design.md +79 -0
- package/skills/developing-cpp/references/standard-library.md +100 -0
- package/skills/developing-cpp/references/types.md +147 -0
- package/skills/developing-cpp/steps/01-behavior.md +36 -0
- package/skills/developing-cpp/steps/02-boundary.md +40 -0
- package/skills/developing-cpp/steps/03-declaration.md +32 -0
- package/skills/developing-cpp/steps/04-implementation.md +33 -0
- package/templates/cpp-workflow-project/SKILL.md +49 -0
- package/templates/cpp-workflow-project/environment.json +5 -0
- package/templates/cpp-workflow-project/stages/stage-table.md +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 chengzhao-dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# dsh-cpp-workflow
|
|
2
|
+
|
|
3
|
+
[中文](README.md)
|
|
4
|
+
|
|
5
|
+
Two skills for C++20 source work and build work. The plugin registers a skill provider only; it does not register tools, compile, download a toolchain, or write project files.
|
|
6
|
+
|
|
7
|
+
## What it provides
|
|
8
|
+
|
|
9
|
+
| Skill | Covers | Open it when |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `developing-cpp` | Behavior, boundaries, declarations, implementation; naming, exceptions, and source comments in `.h` / `.cpp` | The user names source code, or the work is a new project or whole-stage refactor |
|
|
12
|
+
| `building-cpp` | Build setup, tool configuration, checks; CMake, Ninja, Clang config, build scripts, and environment combinations | The change touches build files, tool config, or the environment declaration, or the project is first connected to a build |
|
|
13
|
+
|
|
14
|
+
Both skills read the project skill `cpp-workflow-project` first. Without it, the workspace gate never exposes these names.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Pinned npm install:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.1.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The matching Git tag:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.1.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
|
|
31
|
+
|
|
32
|
+
## Workspace gate
|
|
33
|
+
|
|
34
|
+
Both conditions must hold before either skill appears:
|
|
35
|
+
|
|
36
|
+
1. Walking up from the current directory to the Git root, the path contains `.agents/skills/cpp-workflow-project/SKILL.md`.
|
|
37
|
+
2. Inside the same Git repository that owns the marker, outside the skipped directories, there is a first-party `CMakeLists.txt`, or a `.c`, `.cc`, `.cpp`, `.cxx`, `.h`, `.hh`, `.hpp`, or `.hxx` source file.
|
|
38
|
+
|
|
39
|
+
If either condition fails, the list is empty: without the project skill, CMake or `.cpp` files alone do not match; with the project skill but no first-party CMake or C++ source file in the repository, the result is also empty. A docs-only repository or one that only copied the template does not match. Scan limits, skipped directories, and the eight boundary cases are in [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-boundaries.md).
|
|
40
|
+
|
|
41
|
+
The packaged skills use rank 550. A project `.agents/skills` skill uses rank 200 and overrides a plugin skill with the same name; do not copy `developing-cpp` or `building-cpp` into a project.
|
|
42
|
+
|
|
43
|
+
## Project conventions
|
|
44
|
+
|
|
45
|
+
Copy [templates/cpp-workflow-project](templates/cpp-workflow-project) into the target repository's `.agents/skills/` directory, keeping the `cpp-workflow-project` directory name. Add only files that do not exist; do not overwrite existing source, `CMakeLists.txt`, or `.clang-*` files. The project skill records only facts that cannot be inferred from the source:
|
|
46
|
+
|
|
47
|
+
- project and component names, source root;
|
|
48
|
+
- library/program boundary and public header location;
|
|
49
|
+
- an entry command that runs from the repository root;
|
|
50
|
+
- stage-table path and terminology additions.
|
|
51
|
+
|
|
52
|
+
Keep one `environment.json` per repository. Its three axes are intentionally empty in the template and must be filled with the project's real combination after copying; while empty, the four source steps still work, but build setup stops. The stage table records observable behavior, not internal refactors.
|
|
53
|
+
|
|
54
|
+
## Environment combinations
|
|
55
|
+
|
|
56
|
+
| Axis | Values | Meaning |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `host` | `windows`, `linux`, `macos` | Where the editor and agent shell run |
|
|
59
|
+
| `build_host` | `native`, `wsl` | `wsl` is valid only with `host=windows` |
|
|
60
|
+
| `compiler` | `clang`, `gcc` | One compiler per configuration |
|
|
61
|
+
|
|
62
|
+
The generator is always Ninja. Build directories are `build/clang/` and `build/gcc/`; they are never shared. Analysis tools are always `clang-format`, `clang-tidy`, and `clangd`; GCC mode does not switch to another analyzer.
|
|
63
|
+
|
|
64
|
+
Run the declaration check first, then the live check on the build host. `<skill-dir>` is the installed `building-cpp` directory, not a directory in the target project:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node "<skill-dir>/scripts/checking-environment.mjs" --project .
|
|
68
|
+
node "<skill-dir>/scripts/checking-environment.mjs" --project . --live
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`windows + native` is a valid combination, but this version ships no PowerShell build script; `--live` reports the missing POSIX shell, not a missing compiler. The checker never installs, downloads, or edits PATH.
|
|
72
|
+
|
|
73
|
+
## Package layout
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
dsh-cpp-workflow/
|
|
77
|
+
├── lib/ # build output, shipped in the npm package
|
|
78
|
+
├── skills/ # developing-cpp and building-cpp
|
|
79
|
+
├── templates/ # empty project template to copy
|
|
80
|
+
├── package.json
|
|
81
|
+
├── package-lock.json # repository only, used by npm ci
|
|
82
|
+
├── LICENSE
|
|
83
|
+
├── README.md
|
|
84
|
+
├── README.en.md
|
|
85
|
+
├── cordis.patch.yml # one insert row
|
|
86
|
+
├── locale/ # Chinese and English title/description
|
|
87
|
+
└── icon.svg
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Maintenance notes: flow and gate in [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-flow.md), directory and npm whitelist in [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-layout.md), version rules in [docs/versioning.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/versioning.md).
|
|
91
|
+
|
|
92
|
+
## Local development
|
|
93
|
+
|
|
94
|
+
Requires Node.js 22 or newer.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm ci
|
|
98
|
+
npm test # build, structural check, gate/environment/schema tests
|
|
99
|
+
npm run check # same as npm test
|
|
100
|
+
npm run pack:check # inspect the npm package contents
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Source lives in `src/`; `npm run build` writes `lib/`. The structural check is `scripts/checking-skill-links.mjs`. Tests do not invoke a local compiler; the environment checks use fixed version strings.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT, see [LICENSE](LICENSE).
|
package/README.md
CHANGED
|
@@ -1,3 +1,107 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-cpp-workflow
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](README.en.md)
|
|
4
|
+
|
|
5
|
+
把 C++20 的源码工作和构建工作收成两个技能。插件只注册技能 provider,不注册工具、不编译、不下载工具链、不写项目文件。
|
|
6
|
+
|
|
7
|
+
## 提供什么
|
|
8
|
+
|
|
9
|
+
| 技能 | 管什么 | 什么时候打开 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `developing-cpp` | 定行为、定边界、定声明、定实现;`.h` / `.cpp` 的命名、异常和源码注释 | 用户点名源码,或需要新建/整段重构源码 |
|
|
12
|
+
| `building-cpp` | 定构建、定工具配置、通过检查;CMake、Ninja、Clang 配置、构建脚本和环境组合 | 改动落在构建文件、工具配置或环境声明上,或第一次接构建 |
|
|
13
|
+
|
|
14
|
+
两个技能都先读项目技能 `cpp-workflow-project`。没有项目技能时,门禁不会放出这两个名字。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
npm 安装钉死版本:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.1.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
同版本的 Git 标签:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.1.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
|
|
31
|
+
|
|
32
|
+
## 工作区门禁
|
|
33
|
+
|
|
34
|
+
一个会话里要同时满足两件事,技能才会出现:
|
|
35
|
+
|
|
36
|
+
1. 从当前目录向上、停在 git 根,沿途有 `.agents/skills/cpp-workflow-project/SKILL.md`。
|
|
37
|
+
2. 这份标记所在的 git 仓库里,跳过目录之外,有第一方 `CMakeLists.txt`,或有 `.c`、`.cc`、`.cpp`、`.cxx`、`.h`、`.hh`、`.hpp`、`.hxx` 源文件。
|
|
38
|
+
|
|
39
|
+
缺一件就返回空列表:没有项目技能时,即使仓库里有 CMake 或 `.cpp` 也不命中;有项目技能、但仓库里没有第一方 CMake 或 C++ 源文件,同样不命中。纯文档仓库、只复制了模板的仓库也不命中。扫描上限、跳过目录和八类边界情形见 [docs/plugin-boundaries.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-boundaries.md)。
|
|
40
|
+
|
|
41
|
+
插件技能 rank 550。项目自己的 `.agents/skills` 技能 rank 200,会以同名覆盖插件;不要在项目里复制 `developing-cpp` 或 `building-cpp`。
|
|
42
|
+
|
|
43
|
+
## 项目级约定
|
|
44
|
+
|
|
45
|
+
把 [templates/cpp-workflow-project](templates/cpp-workflow-project) 整个复制到目标仓库的 `.agents/skills/` 下,目录名保持 `cpp-workflow-project`;只补还不存在的文件,不覆盖已有的源码、`CMakeLists.txt` 或 `.clang-*`。项目技能只写本工程特有的事实:
|
|
46
|
+
|
|
47
|
+
- 工程名、组件名和源码根;
|
|
48
|
+
- 库与程序的边界、公共头位置;
|
|
49
|
+
- 从仓库根能直接运行的入口命令;
|
|
50
|
+
- 阶段表路径和术语增补。
|
|
51
|
+
|
|
52
|
+
`environment.json` 一个仓库只有一份。模板里的三个轴故意留空,复制后必须填成工程的真实组合;留空时源码四步仍能做,但定构建会停。阶段表只写可观察行为,不写内部重构。
|
|
53
|
+
|
|
54
|
+
## 环境组合
|
|
55
|
+
|
|
56
|
+
| 轴 | 允许值 | 说明 |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `host` | `windows`、`linux`、`macos` | 编辑器和代理壳在哪 |
|
|
59
|
+
| `build_host` | `native`、`wsl` | `wsl` 只在 `host=windows` 时合法 |
|
|
60
|
+
| `compiler` | `clang`、`gcc` | 一次配置一个编译器 |
|
|
61
|
+
|
|
62
|
+
生成器固定 Ninja,构建目录是 `build/clang/` 与 `build/gcc/`,不共用 `build/`。诊断工具固定 `clang-format`、`clang-tidy`、`clangd`;GCC 模式也不换成别的分析器。
|
|
63
|
+
|
|
64
|
+
构建前先跑声明校验,再在构建宿主上跑活动校验。`<skill-dir>` 是插件安装后的 `building-cpp` 目录,不是目标工程里的目录:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node "<skill-dir>/scripts/checking-environment.mjs" --project .
|
|
68
|
+
node "<skill-dir>/scripts/checking-environment.mjs" --project . --live
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`windows + native` 是合法组合,但本版没有 PowerShell 构建脚本;`--live` 会明确报告缺 POSIX 壳,而不是缺编译器。环境脚本不安装、不下载、不改 PATH。
|
|
72
|
+
|
|
73
|
+
## 包结构
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
dsh-cpp-workflow/
|
|
77
|
+
├── lib/ # 构建产物,随 npm 包发布
|
|
78
|
+
├── skills/ # developing-cpp 与 building-cpp
|
|
79
|
+
├── templates/ # 复制到项目的空模板
|
|
80
|
+
├── package.json
|
|
81
|
+
├── package-lock.json # 只在仓库,供 npm ci
|
|
82
|
+
├── LICENSE
|
|
83
|
+
├── README.md
|
|
84
|
+
├── README.en.md
|
|
85
|
+
├── cordis.patch.yml # 只 insert 一行
|
|
86
|
+
├── locale/ # 中英文标题与描述
|
|
87
|
+
└── icon.svg
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
边界与门禁的维护说明在 [docs/plugin-flow.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-flow.md),目录和进包白名单在 [docs/plugin-layout.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/plugin-layout.md),版本规则在 [docs/versioning.md](https://github.com/chengzhao-dev/dsh-cpp-workflow/blob/main/docs/versioning.md)。
|
|
91
|
+
|
|
92
|
+
## 本地开发
|
|
93
|
+
|
|
94
|
+
需要 Node.js 22 或更新版本。
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm ci
|
|
98
|
+
npm test # 构建、结构检查、门禁/环境/schema 测试
|
|
99
|
+
npm run check # 同 npm test
|
|
100
|
+
npm run pack:check # 核对 npm 包内容
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
源码在 `src/`,`npm run build` 生成 `lib/`。结构检查在 `scripts/checking-skill-links.mjs`;测试不调用本机编译器,环境检查用固定版本字符串。
|
|
104
|
+
|
|
105
|
+
## 许可
|
|
106
|
+
|
|
107
|
+
MIT,见 [LICENSE](LICENSE)。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# dsh-cpp-workflow bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# Additive only: insert one host row. No official row is edited, disabled or
|
|
4
|
+
# replaced. The row loads this package's `main` entry, which registers the
|
|
5
|
+
# packaged developing-cpp and building-cpp skills through `ctx.skills`.
|
|
6
|
+
#
|
|
7
|
+
# Install from npm or from the matching Git tag, for example:
|
|
8
|
+
#
|
|
9
|
+
# dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.1.0
|
|
10
|
+
# dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.1.0
|
|
11
|
+
#
|
|
12
|
+
- insert:
|
|
13
|
+
- id: cpp-workflow
|
|
14
|
+
name: "@chengzhao-dev/dsh-cpp-workflow"
|
package/icon.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="C++ workflow">
|
|
2
|
+
<rect width="64" height="64" rx="14" fill="#0d1117"/>
|
|
3
|
+
<path d="M24 14 10 32l14 18" fill="none" stroke="#58a6ff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M40 14l14 18-14 18" fill="none" stroke="#58a6ff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<path d="M27 24l5 8-5 8M37 24l-5 8 5 8" fill="none" stroke="#f0f6fc" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
<path d="M14 56h36" stroke="#3fb950" stroke-width="4" stroke-linecap="round"/>
|
|
7
|
+
</svg>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// dsh-cpp-workflow bundle entry.
|
|
2
|
+
//
|
|
3
|
+
// Registers the two packaged skills as one bundle-scoped skill provider. The
|
|
4
|
+
// provider is read-only and scans only `skills/<name>/SKILL.md`; each skill
|
|
5
|
+
// directory becomes the resourceBase so relative steps, references, examples
|
|
6
|
+
// and assets resolve from the installed package.
|
|
7
|
+
//
|
|
8
|
+
// The provider scopes itself to real C++ workflow workspaces: `list()` returns
|
|
9
|
+
// nothing unless the session cwd sits inside a git checkout that carries the
|
|
10
|
+
// explicit `.agents/skills/cpp-workflow-project/SKILL.md` marker on the path to
|
|
11
|
+
// the git root AND that checkout has first-party C++ or CMake evidence. A
|
|
12
|
+
// Markdown-only repository, a docs folder, a project template, or a C++ sample
|
|
13
|
+
// outside the checkout never sees these names. Source evidence is repository-
|
|
14
|
+
// wide once the marker opts the checkout in; only the marker lookup ignores
|
|
15
|
+
// sibling directories.
|
|
16
|
+
//
|
|
17
|
+
// Rank 550 is lower priority than project/user skills (project `.agents/skills`
|
|
18
|
+
// 200, user `.agents/skills` 500) and higher priority than the bundled root
|
|
19
|
+
// (600), so a project can override either skill by name without editing the
|
|
20
|
+
// package. The project skill itself (cpp-workflow-project, rank 200) is a
|
|
21
|
+
// separate name and does not collide.
|
|
22
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { isCppWorkflowWorkspace } from './workspace.js';
|
|
26
|
+
export const name = 'dsh-cpp-workflow';
|
|
27
|
+
export const inject = ['skills'];
|
|
28
|
+
const PROVIDER = 'dsh-cpp-workflow';
|
|
29
|
+
const RANK = 550;
|
|
30
|
+
const KNOWN_SKILLS = new Set(['building-cpp', 'developing-cpp']);
|
|
31
|
+
const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
32
|
+
const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
33
|
+
const SKILLS_ROOT = path.join(PACKAGE_ROOT, 'skills');
|
|
34
|
+
/** Minimal YAML frontmatter reader for the keys SKILL.md actually uses. */
|
|
35
|
+
function parseFrontmatter(source) {
|
|
36
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
|
|
37
|
+
if (!match)
|
|
38
|
+
return { data: {}, body: source };
|
|
39
|
+
const data = {};
|
|
40
|
+
const stack = [
|
|
41
|
+
{ indent: -1, target: data },
|
|
42
|
+
];
|
|
43
|
+
for (const rawLine of match[1].split(/\r?\n/)) {
|
|
44
|
+
const line = rawLine.replace(/\t/g, ' ');
|
|
45
|
+
if (!line.trim() || line.trimStart().startsWith('#'))
|
|
46
|
+
continue;
|
|
47
|
+
const indent = line.length - line.trimStart().length;
|
|
48
|
+
const pair = /^([A-Za-z0-9_.-]+):(?:\s*(.*))?$/.exec(line.trim());
|
|
49
|
+
if (!pair)
|
|
50
|
+
continue;
|
|
51
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent)
|
|
52
|
+
stack.pop();
|
|
53
|
+
const parent = stack[stack.length - 1].target;
|
|
54
|
+
const key = pair[1];
|
|
55
|
+
const value = (pair[2] ?? '').trim();
|
|
56
|
+
if (value === '') {
|
|
57
|
+
const child = {};
|
|
58
|
+
parent[key] = child;
|
|
59
|
+
stack.push({ indent, target: child });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
parent[key] = parseScalar(value);
|
|
63
|
+
}
|
|
64
|
+
return { data, body: source.slice(match[0].length) };
|
|
65
|
+
}
|
|
66
|
+
function parseScalar(value) {
|
|
67
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
68
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
69
|
+
const quote = value[0];
|
|
70
|
+
const inner = value.slice(1, -1);
|
|
71
|
+
return quote === '"'
|
|
72
|
+
? inner.replace(/\\"/g, '"').replace(/\\n/g, '\n')
|
|
73
|
+
: inner.replace(/''/g, "'");
|
|
74
|
+
}
|
|
75
|
+
if (value === 'true')
|
|
76
|
+
return true;
|
|
77
|
+
if (value === 'false')
|
|
78
|
+
return false;
|
|
79
|
+
if (value === 'null' || value === '~')
|
|
80
|
+
return null;
|
|
81
|
+
if (/^\[.*\]$/.test(value)) {
|
|
82
|
+
return value
|
|
83
|
+
.slice(1, -1)
|
|
84
|
+
.split(',')
|
|
85
|
+
.map((item) => parseScalar(item.trim()))
|
|
86
|
+
.filter((item) => item !== '');
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
async function readCandidate(skillDir) {
|
|
91
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
92
|
+
let source;
|
|
93
|
+
try {
|
|
94
|
+
source = await readFile(skillFile, 'utf8');
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
const { data } = parseFrontmatter(source);
|
|
100
|
+
if (typeof data.name !== 'string' || !SKILL_NAME_RE.test(data.name))
|
|
101
|
+
return undefined;
|
|
102
|
+
if (!KNOWN_SKILLS.has(data.name))
|
|
103
|
+
return undefined;
|
|
104
|
+
if (typeof data.description !== 'string' || data.description.trim() === '')
|
|
105
|
+
return undefined;
|
|
106
|
+
if (data.name !== path.basename(skillDir))
|
|
107
|
+
return undefined;
|
|
108
|
+
const candidate = {
|
|
109
|
+
name: data.name,
|
|
110
|
+
description: data.description.trim(),
|
|
111
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
112
|
+
source: 'bundled',
|
|
113
|
+
provider: PROVIDER,
|
|
114
|
+
rank: RANK,
|
|
115
|
+
locator: skillDir,
|
|
116
|
+
resourceBase: { kind: 'directory', path: skillDir },
|
|
117
|
+
path: skillFile,
|
|
118
|
+
};
|
|
119
|
+
if (typeof data.whenToUse === 'string' && data.whenToUse.trim()) {
|
|
120
|
+
candidate.whenToUse = data.whenToUse.trim();
|
|
121
|
+
}
|
|
122
|
+
if (data.metadata && typeof data.metadata === 'object' && !Array.isArray(data.metadata)) {
|
|
123
|
+
candidate.metadata = data.metadata;
|
|
124
|
+
}
|
|
125
|
+
return candidate;
|
|
126
|
+
}
|
|
127
|
+
async function listSkills(options = {}) {
|
|
128
|
+
if (options.signal?.aborted)
|
|
129
|
+
return [];
|
|
130
|
+
if (!(await isCppWorkflowWorkspace(options.cwd)))
|
|
131
|
+
return [];
|
|
132
|
+
if (options.signal?.aborted)
|
|
133
|
+
return [];
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = await readdir(SKILLS_ROOT, { withFileTypes: true });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const candidates = [];
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.isDirectory() || !KNOWN_SKILLS.has(entry.name))
|
|
144
|
+
continue;
|
|
145
|
+
if (options.signal?.aborted)
|
|
146
|
+
return [];
|
|
147
|
+
const candidate = await readCandidate(path.join(SKILLS_ROOT, entry.name));
|
|
148
|
+
if (candidate)
|
|
149
|
+
candidates.push(candidate);
|
|
150
|
+
}
|
|
151
|
+
candidates.sort((a, b) => a.name.localeCompare(b.name));
|
|
152
|
+
return candidates;
|
|
153
|
+
}
|
|
154
|
+
async function getSkill(candidate, options = {}) {
|
|
155
|
+
if (options.signal?.aborted)
|
|
156
|
+
return undefined;
|
|
157
|
+
if (!candidate || candidate.provider !== PROVIDER)
|
|
158
|
+
return undefined;
|
|
159
|
+
// A candidate can outlive a directory change in the same session. Re-check
|
|
160
|
+
// only when the caller supplies a cwd; a plain `get()` keeps working for
|
|
161
|
+
// candidates that were already discovered in this workspace.
|
|
162
|
+
if (options.cwd !== undefined && !(await isCppWorkflowWorkspace(options.cwd)))
|
|
163
|
+
return undefined;
|
|
164
|
+
if (!KNOWN_SKILLS.has(candidate.name))
|
|
165
|
+
return undefined;
|
|
166
|
+
const skillDir = candidate.locator;
|
|
167
|
+
if (typeof skillDir !== 'string')
|
|
168
|
+
return undefined;
|
|
169
|
+
const resolved = path.resolve(skillDir);
|
|
170
|
+
if (resolved !== SKILLS_ROOT && !resolved.startsWith(SKILLS_ROOT + path.sep))
|
|
171
|
+
return undefined;
|
|
172
|
+
if (path.basename(resolved) !== candidate.name)
|
|
173
|
+
return undefined;
|
|
174
|
+
let source;
|
|
175
|
+
try {
|
|
176
|
+
source = await readFile(path.join(resolved, 'SKILL.md'), 'utf8');
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
const { data, body } = parseFrontmatter(source);
|
|
182
|
+
if (data.name !== candidate.name)
|
|
183
|
+
return undefined;
|
|
184
|
+
return {
|
|
185
|
+
name: candidate.name,
|
|
186
|
+
description: candidate.description,
|
|
187
|
+
...(candidate.whenToUse ? { whenToUse: candidate.whenToUse } : {}),
|
|
188
|
+
invocation: candidate.invocation,
|
|
189
|
+
source: candidate.source,
|
|
190
|
+
provider: PROVIDER,
|
|
191
|
+
resourceBase: { kind: 'directory', path: resolved },
|
|
192
|
+
path: path.join(resolved, 'SKILL.md'),
|
|
193
|
+
...(candidate.metadata ? { metadata: candidate.metadata } : {}),
|
|
194
|
+
content: body.trim(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
export function apply(ctx) {
|
|
198
|
+
ctx.skills.registerProvider(() => ({
|
|
199
|
+
name: PROVIDER,
|
|
200
|
+
list: listSkills,
|
|
201
|
+
get: getSkill,
|
|
202
|
+
}));
|
|
203
|
+
}
|
package/lib/workspace.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Workspace evidence for dsh-cpp-workflow.
|
|
2
|
+
//
|
|
3
|
+
// A repository opts in only when it carries the project skill on the path from
|
|
4
|
+
// the session cwd up to the git root AND that repository carries first-party C++
|
|
5
|
+
// or CMake evidence. A stray `CMakeLists.txt`, a sample under `templates/`, or a
|
|
6
|
+
// CMake file above the git root is never enough. The git root is the
|
|
7
|
+
// repository boundary: once the marker opts the repository in, evidence is
|
|
8
|
+
// searched across the repository, not outside it or in a sibling checkout.
|
|
9
|
+
import { access, readdir } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
export const PROJECT_SKILL = path.join('.agents', 'skills', 'cpp-workflow-project', 'SKILL.md');
|
|
12
|
+
export const MAX_ASCENT = 64;
|
|
13
|
+
export const MAX_SCAN_DEPTH = 12;
|
|
14
|
+
export const MAX_SCAN_ENTRIES = 4000;
|
|
15
|
+
const SKIPPED_DIRS = new Set([
|
|
16
|
+
'.git',
|
|
17
|
+
'node_modules',
|
|
18
|
+
'vendor',
|
|
19
|
+
'third_party',
|
|
20
|
+
'templates',
|
|
21
|
+
'build',
|
|
22
|
+
'temp',
|
|
23
|
+
'.cache',
|
|
24
|
+
]);
|
|
25
|
+
export const CPP_SOURCE_EXTENSIONS = new Set([
|
|
26
|
+
'.c',
|
|
27
|
+
'.cc',
|
|
28
|
+
'.cpp',
|
|
29
|
+
'.cxx',
|
|
30
|
+
'.h',
|
|
31
|
+
'.hh',
|
|
32
|
+
'.hpp',
|
|
33
|
+
'.hxx',
|
|
34
|
+
]);
|
|
35
|
+
export const CPP_BUILD_FILES = new Set(['CMakeLists.txt']);
|
|
36
|
+
async function exists(target) {
|
|
37
|
+
try {
|
|
38
|
+
await access(target);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Nearest enclosing git checkout, including a worktree's `.git` file. */
|
|
46
|
+
export async function findGitRoot(cwd) {
|
|
47
|
+
if (typeof cwd !== 'string' || cwd.trim() === '')
|
|
48
|
+
return undefined;
|
|
49
|
+
let cursor;
|
|
50
|
+
try {
|
|
51
|
+
cursor = path.resolve(cwd);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
for (let depth = 0; depth < MAX_ASCENT; depth += 1) {
|
|
57
|
+
if (await exists(path.join(cursor, '.git')))
|
|
58
|
+
return cursor;
|
|
59
|
+
const parent = path.dirname(cursor);
|
|
60
|
+
if (parent === cursor)
|
|
61
|
+
return undefined;
|
|
62
|
+
cursor = parent;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Find the nearest project-skill marker between `cwd` and the git root,
|
|
68
|
+
* inclusive. A marker above the git root never counts. The marker's directory
|
|
69
|
+
* is also returned for diagnostics, but evidence is repository-wide.
|
|
70
|
+
*/
|
|
71
|
+
export async function findProjectSkill(cwd, gitRoot) {
|
|
72
|
+
if (typeof cwd !== 'string' || cwd.trim() === '')
|
|
73
|
+
return undefined;
|
|
74
|
+
let cursor;
|
|
75
|
+
try {
|
|
76
|
+
cursor = path.resolve(cwd);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (!gitRoot) {
|
|
82
|
+
gitRoot = await findGitRoot(cursor);
|
|
83
|
+
if (!gitRoot)
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
gitRoot = path.resolve(gitRoot);
|
|
88
|
+
const relative = path.relative(gitRoot, cursor);
|
|
89
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (let depth = 0; depth < MAX_ASCENT; depth += 1) {
|
|
94
|
+
const file = path.join(cursor, PROJECT_SKILL);
|
|
95
|
+
if (await exists(file))
|
|
96
|
+
return { file, root: cursor };
|
|
97
|
+
if (cursor === gitRoot)
|
|
98
|
+
return undefined;
|
|
99
|
+
const parent = path.dirname(cursor);
|
|
100
|
+
if (parent === cursor)
|
|
101
|
+
return undefined;
|
|
102
|
+
cursor = parent;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
/** Boolean form of {@link findProjectSkill}. */
|
|
107
|
+
export async function hasProjectSkill(cwd, gitRoot) {
|
|
108
|
+
return (await findProjectSkill(cwd, gitRoot)) !== undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* True when `root` contains at least one first-party `CMakeLists.txt` or C++
|
|
112
|
+
* source/header. Generated, dependency, template and build trees are skipped.
|
|
113
|
+
* The scan is bounded to 12 levels and 4000 directory entries.
|
|
114
|
+
*/
|
|
115
|
+
export async function hasCppProject(root) {
|
|
116
|
+
let seen = 0;
|
|
117
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
118
|
+
while (queue.length > 0) {
|
|
119
|
+
const current = queue.shift();
|
|
120
|
+
if (!current)
|
|
121
|
+
break;
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = await readdir(current.dir, { withFileTypes: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
seen += 1;
|
|
131
|
+
if (seen > MAX_SCAN_ENTRIES)
|
|
132
|
+
return false;
|
|
133
|
+
if (entry.isSymbolicLink())
|
|
134
|
+
continue;
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
if (current.depth >= MAX_SCAN_DEPTH)
|
|
137
|
+
continue;
|
|
138
|
+
if (SKIPPED_DIRS.has(entry.name))
|
|
139
|
+
continue;
|
|
140
|
+
queue.push({ dir: path.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!entry.isFile())
|
|
144
|
+
continue;
|
|
145
|
+
if (CPP_BUILD_FILES.has(entry.name))
|
|
146
|
+
return true;
|
|
147
|
+
if (CPP_SOURCE_EXTENSIONS.has(path.extname(entry.name).toLowerCase()))
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Session gate. Both halves are required:
|
|
155
|
+
* 1. the project skill sits between `cwd` and the git root (inclusive), and
|
|
156
|
+
* 2. that git repository contains first-party C++ or CMake evidence.
|
|
157
|
+
* No cwd, no git checkout, a marker above the git root, a marker only in a
|
|
158
|
+
* sibling directory, or a documentation-only repository returns false.
|
|
159
|
+
*/
|
|
160
|
+
export async function isCppWorkflowWorkspace(cwd) {
|
|
161
|
+
const gitRoot = await findGitRoot(cwd);
|
|
162
|
+
if (!gitRoot)
|
|
163
|
+
return false;
|
|
164
|
+
const project = await findProjectSkill(cwd, gitRoot);
|
|
165
|
+
if (!project)
|
|
166
|
+
return false;
|
|
167
|
+
return hasCppProject(gitRoot);
|
|
168
|
+
}
|
package/locale/en.json
ADDED