Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
| 类别 | 路径 | 说明 |
|------|------|------|
| Docs | [docs/](./docs/) | 学习与说明文档 |
| └ Submodules | [docs/git/submodules.md](./docs/git/submodules.md) | 多目录 / 可配置 submodule 助手用法 |
| Skillsets | [skillsets/](./skillsets/) | 独立 Skill 项目集合 |
| └ devops-skill | [qdriven/devops-skill](https://github.com/qdriven/devops-skill) | DevOps / Git 工作流 skills([文档站](https://qdriven.github.io/devops-skill/)) |
| └ docmd skill | [skillsets/devops-skill/docmd/](./skillsets/devops-skill/docmd/) | docmd + Diátaxis 文档站模版与构建说明 |
Expand Down
88 changes: 88 additions & 0 deletions apps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Apps

Applications and related projects are registered in `apps/registry.yaml`.
Their on-disk `path` may live under any **configured root** (not only `apps/`).

This repo does **not** enforce `web/desktop/cli` folders. Each entry is a project directory with its own stack and docs.

## Multi-folder roots

Full how-to (config fields, CLI examples, env overrides, common errors): [docs/git/submodules.md](../docs/git/submodules.md).

Allowed parent folders for submodules are configured in `scripts/submodule.config.json`:

| Key | Default | Meaning |
|-----|---------|---------|
| `roots` | `apps`, `tutorials`, `skillsets`, `ai-infra` | Allowed top-level folders |
| `defaultRoot` | `apps` | Used when only a project name is given |
| `org` | `variableway` | GitHub org for inferred clone URLs |
| `host` | `github.com` | Git host for inferred URLs |

Override without editing the file:

- Env: `SUBMODULE_ROOTS`, `SUBMODULE_DEFAULT_ROOT`, `SUBMODULE_ORG`, `SUBMODULE_HOST`
- CLI flags on the helper (below)

## Local vs submodule

| `source` in registry | Meaning |
|----------------------|---------|
| `local` | Code lives directly in this repo |
| `submodule` | Linked git submodule (separate GitHub repo) |

Add a submodule manually:

```bash
git submodule add https://github.com/<org>/<repo>.git apps/<project-name>
# or another configured root:
git submodule add https://github.com/<org>/<repo>.git tutorials/<project-name>
```

Then register it in `apps/registry.yaml` (set `path` to the actual folder) and keep `.gitmodules` in sync.

Or use the helper script:

```bash
# default root (apps/)
node scripts/add-submodule.mts <project-name>

# other configured root
node scripts/add-submodule.mts <project-name> --folder tutorials
node scripts/add-submodule.mts --path skillsets/devops-skill --repo https://github.com/qdriven/devops-skill.git

# explicit URL / org
node scripts/add-submodule.mts my-app --repo https://github.com/acme/my-app.git
node scripts/add-submodule.mts my-app --folder apps --org acme
```

Behavior:

- Default repo URL: `https://<host>/<org>/<project-name>.git` (from config/CLI/env)
- Path must be under an allowed `roots` entry
- If the target folder already exists and is a git clone, and `--repo` was not passed, the script uses that folder's `origin` URL and runs `git submodule add --force`

## Showcase site (planned)

`apps/site` can be the monorepo portal (apps list, registry metadata, GitHub Pages). It is not added yet.

## Registry

`apps/registry.yaml` is the source of truth for app metadata. Indexed by `scripts/generate-manifest.mjs` → `data/manifest.json`.

`path` may be under any configured root, e.g. `apps/innate-feeds` or `skillsets/devops-skill`.

Recommended metadata fields per app entry:

| Field | Required | Example | Notes |
|------|----------|---------|------|
| `name` | yes | `my-tool` | unique id |
| `title` | yes | `My Tool` | display name |
| `path` | yes | `apps/my-tool` | project path (any configured root) |
| `source` | yes | `local` / `submodule` | code location mode |
| `kind` | optional | `web`, `cli`, `desktop`, `service` | app type label (free-form) |
| `tags` | optional | `[internal, mvp, ai-agent]` | multiple labels for search/filter |
| `visibility` | optional | `public` / `private` | release intent |
| `status` | optional | `planned` / `active` | lifecycle |
| `repo` | submodule only | `https://github.com/org/repo` | source repository |

Node apps under a registered path with a `package.json` can be pnpm workspace members when a root `pnpm-workspace.yaml` is present.
69 changes: 69 additions & 0 deletions apps/registry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Application registry — local apps and git submodules.
# Consumed by scripts/generate-manifest.mjs (and future apps/site).
#
# Submodule helper config (multi-folder): scripts/submodule.config.json
# - roots: allowed parent folders for `scripts/add-submodule.mts`
# - defaultRoot / org / host: defaults for inferred paths and URLs
# Override via CLI (--folder/--path/--repo/--org/--host) or env
# (SUBMODULE_ROOTS, SUBMODULE_DEFAULT_ROOT, SUBMODULE_ORG, SUBMODULE_HOST).
#
# App `path` may live under any configured root (apps/, tutorials/, skillsets/, …),
# not only apps/.

apps:
- name: innate-feeds
title: Innate Feeds
kind: web
tags: [submodule, feed, ai-agent]
path: apps/innate-feeds
source: submodule
repo: https://github.com/variableway/innate-feeds.git
visibility: public
status: active
description: Innate Feeds app linked as a git submodule.

# Example local app:
# - name: my-tool
# title: My Tool
# kind: web # optional: web/desktop/cli/service/etc.
# tags: [internal, mvp, ai-agent] # optional labels for filtering/search
# path: apps/my-tool
# source: local
# visibility: public
# status: active
# description: ...

# Example submodule under a non-apps root:
# - name: devops-skill
# title: DevOps Skill
# kind: skillset
# tags: [submodule, skills]
# path: skillsets/devops-skill
# source: submodule
# repo: https://github.com/qdriven/devops-skill.git
# visibility: public
# status: active
# description: DevOps skillset linked as a git submodule.

# Example submodule app:
# - name: example-project
# title: Example Project
# kind: cli
# tags: [submodule, public]
# path: apps/example-project
# source: submodule
# repo: https://github.com/variableway/example-project
# visibility: public
# status: planned
# description: Sample project linked as a git submodule.

# Planned showcase site (add when ready):
# - name: site
# title: Agentway Site
# kind: web
# tags: [portal, github-pages]
# path: apps/site
# source: local
# visibility: public
# status: planned
# description: Showcase for skills and apps. GitHub Pages.
10 changes: 10 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Docs

学习与操作说明(按主题归类)。

| 文档 | 说明 |
|------|------|
| [如何添加可配置多目录 Submodule](./git/submodules.md) | `submodule.config.json`、多根目录、CLI / env、registry |
| [Git Worktree 如何工作](./git/worktree.md) | 并行分支隔离检出 |

应用元数据与 registry 字段见 [apps/README.md](../apps/README.md)。
200 changes: 200 additions & 0 deletions docs/git/submodules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# 如何添加可配置多目录 Submodule

## 一句话理解

用 `scripts/add-submodule.mts` 把外部 Git 仓库挂进本仓的**允许根目录**(默认 `apps` / `tutorials` / `skillsets` / `ai-infra`),路径与远程 URL 可由配置文件、CLI 或环境变量覆盖,不必写死在 `apps/` 下。

## 为什么需要它

手工 `git submodule add` 容易:

- 路径写错(越界到未约定的顶层目录)
- 远程 URL 与本仓约定 org/host 不一致
- 忘了在 `apps/registry.yaml` 登记,`generate-manifest` 扫不到

助手脚本统一做三件事:校验路径落在允许根下、推断或采用显式 remote、提示下一步登记 registry。

相关文件:

| 文件 | 作用 |
|------|------|
| `scripts/submodule.config.json` | 默认 org / host / roots / defaultRoot |
| `scripts/add-submodule.mts` | 添加 submodule 的 CLI |
| `scripts/generate-manifest.mjs` | 扫 registry + 技能目录 → `data/manifest.json` |
| `apps/registry.yaml` | 应用/项目元数据(`path` 可在任意配置根下) |

更偏 registry 字段说明见 [apps/README.md](../../apps/README.md)。

## 配置文件

路径:`scripts/submodule.config.json`

```json
{
"org": "variableway",
"host": "github.com",
"defaultRoot": "apps",
"roots": ["apps", "tutorials", "skillsets", "ai-infra"]
}
```

| 字段 | 含义 |
|------|------|
| `roots` | 允许的**顶层**父目录;`--folder` / `--path` 的第一段必须在此列表中 |
| `defaultRoot` | 只传项目名、未指定 `--folder` 时使用的根(默认 `apps`) |
| `org` | 未传 `--repo` 时,推断 URL 的 GitHub org |
| `host` | 未传 `--repo` 时,推断 URL 的 host(如 `github.com`) |

优先级(从高到低):**CLI 标志 / 对应环境变量** → **配置文件** → **脚本内置默认值**。

## 基本用法

在仓库根目录执行:

```bash
# 默认根 apps/ → apps/<project-name>
# 推断 URL:https://github.com/variableway/<project-name>.git
node scripts/add-submodule.mts <project-name>

# 查看帮助(含当前允许的 roots)
node scripts/add-submodule.mts --help
```

### 挂到不同根目录

```bash
# apps(显式)
node scripts/add-submodule.mts my-app --folder apps

# tutorials
node scripts/add-submodule.mts my-tutorial --folder tutorials

# skillsets
node scripts/add-submodule.mts devops-skill --folder skillsets

# ai-infra
node scripts/add-submodule.mts my-proxy --folder ai-infra
```

### 用完整相对路径

```bash
node scripts/add-submodule.mts --path skillsets/devops-skill \
--repo https://github.com/qdriven/devops-skill.git
```

`--path` 必须是 `<allowed-root>/<name>`(至少两段),且不可含 `..` 路径穿越。

### 指定远程 / org / host

```bash
# 显式 URL(不再用 org/host 推断)
node scripts/add-submodule.mts my-app --repo https://github.com/acme/my-app.git

# 仍推断路径,但换 org / host
node scripts/add-submodule.mts my-app --folder apps --org acme
node scripts/add-submodule.mts my-app --host github.com --org variableway
```

### 已有本地 clone

若目标目录已存在且是 git 仓库,且**没有**传 `--repo`,脚本会读取该目录 `remote.origin.url`,并以 `git submodule add --force` 挂入。

## CLI 标志一览

| 标志 | 说明 |
|------|------|
| `<project-name>` | 位置参数;不可含 `/`;与 `--folder` / `defaultRoot` 组成路径 |
| `--folder <root>` | 父目录,须在 `roots` 中 |
| `--path <root/name>` | 完整相对路径;与单独传 name 二选一 |
| `--repo <url>` | 显式 git remote |
| `--org <org>` | 覆盖推断用 org |
| `--host <host>` | 覆盖推断用 host |
| `-h` / `--help` | 打印用法与当前允许 roots |

## 环境变量覆盖

### add-submodule(`SUBMODULE_*`)

| 变量 | 对应配置 |
|------|----------|
| `SUBMODULE_ORG` | `org` |
| `SUBMODULE_HOST` | `host` |
| `SUBMODULE_DEFAULT_ROOT` | `defaultRoot` |
| `SUBMODULE_ROOTS` | `roots`(逗号分隔,如 `apps,tutorials,skillsets`) |

示例:

```bash
SUBMODULE_ORG=acme SUBMODULE_DEFAULT_ROOT=tutorials \
node scripts/add-submodule.mts demo-course
```

### generate-manifest(`MANIFEST_*`)

| 变量 | 默认 | 说明 |
|------|------|------|
| `MANIFEST_REGISTRY` | `apps/registry.yaml` | 相对仓库根的 registry 路径 |
| `MANIFEST_OUT` | `data/manifest.json` | 输出路径 |
| `MANIFEST_SKILLS_DIRS` | (自动) | 逗号分隔的技能扫描目录;未设时扫描 `skills/` 与配置里**已存在**的 `roots` |

配置文件的 `roots` 也会写入 manifest 的 `config.submoduleRoots`,便于 showcase 站点读取。

```bash
node scripts/generate-manifest.mjs
MANIFEST_OUT=tmp/manifest.json node scripts/generate-manifest.mjs
```

## Registry 与 `path`

添加 submodule 后,若希望出现在 manifest / 门户列表里,在 `apps/registry.yaml` 增加条目,**`path` 写成真实落盘路径**(不必仍以 `apps/` 开头):

```yaml
apps:
- name: devops-skill
title: DevOps Skill
path: skillsets/devops-skill # 任意配置根下均可
source: submodule
repo: https://github.com/qdriven/devops-skill.git
visibility: public
status: active
```

`source: local` 表示代码直接在本仓;`submodule` 表示独立远程仓库。字段说明见 [apps/README.md](../../apps/README.md)。

## 常见错误

| 现象 | 原因 / 处理 |
|------|-------------|
| `folder "…" is not allowed` | `--folder` 不在 `roots` / `SUBMODULE_ROOTS` 中;改配置或换合法根 |
| `--path must be under an allowed root` | 路径顶层段不在允许列表,或含 `..` / 绝对路径 |
| `--path must include <root>/<project-name>` | `--path` 只有一段(如 `apps`),需 `apps/foo` |
| `project name must not contain path separators` | 位置参数里写了 `tutorials/foo`;改用 `--folder` 或 `--path` |
| `git submodule add` 失败 | 远程不存在、权限不足、或路径已被占用;检查 URL 与 `.gitmodules` |
| 加了 submodule 但 manifest 没有 | 未在 `apps/registry.yaml` 登记,或未跑 `generate-manifest.mjs` |

## 推荐流程

```bash
# 1. 按需改 scripts/submodule.config.json(或用 env)
# 2. 添加
node scripts/add-submodule.mts my-tool --folder tutorials

# 3. 在 apps/registry.yaml 登记 path / source / repo
# 4. 更新 manifest
node scripts/generate-manifest.mjs

# 5. 提交:.gitmodules、子模块指针、registry(及 manifest,若纳入版本控制)
```

## 最小心智模型

> 允许根由 `submodule.config.json` 决定;助手只往这些根下挂 submodule。
> URL 默认同 org/host 推断,可用 `--repo` 或已有 clone 的 origin 覆盖。
> 挂完还要在 `apps/registry.yaml` 写对 `path`,manifest 才会看到。

## 延伸阅读

- [apps/README.md](../../apps/README.md) — registry 字段与 local / submodule 约定
- [Git Worktree 如何工作](./worktree.md) — 并行分支隔离检出
- `git submodule --help` / [Git Tools - Submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
1 change: 1 addition & 0 deletions docs/git/worktree.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,6 @@ git worktree remove ../proj-hotfix

- `man git-worktree` / `git worktree --help`
- Git 文档:[Git Tools - Multiple Working Trees](https://git-scm.com/docs/git-worktree)
- [如何添加可配置多目录 Submodule](./submodules.md) — 多根目录挂外部仓库
- Agent Skill(devops-skill):[`skillsets/devops-skill/git-worktree/`](../../skillsets/devops-skill/git-worktree/SKILL.md) — 与 git-workflow / local-workflow 组合的隔离开发流程

Loading