@hunter-harness/workflow-harness 0.2.46 → 0.2.47
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 +1 -1
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +9 -10
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -7
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/claude-code/scripts/harness_events.py +30 -1
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +216 -64
- package/harness/bundles/general/claude-code/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +9 -11
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +30 -1
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +216 -64
- package/harness/bundles/general/codebuddy/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +9 -11
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/codex/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/codex/scripts/harness_events.py +30 -1
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/codex/scripts/harness_gate.py +216 -64
- package/harness/bundles/general/codex/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +9 -11
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/cursor/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/cursor/scripts/harness_events.py +30 -1
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/cursor/scripts/harness_gate.py +216 -64
- package/harness/bundles/general/cursor/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +9 -10
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +3 -2
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -7
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/claude-code/scripts/harness_events.py +30 -1
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +216 -64
- package/harness/bundles/java/claude-code/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +9 -11
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +30 -1
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +216 -64
- package/harness/bundles/java/codebuddy/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +9 -11
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/codex/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/codex/scripts/harness_events.py +30 -1
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/codex/scripts/harness_gate.py +216 -64
- package/harness/bundles/java/codex/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +9 -11
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/cursor/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/cursor/scripts/harness_events.py +30 -1
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/cursor/scripts/harness_gate.py +216 -64
- package/harness/bundles/java/cursor/scripts/harness_headless.py +37 -0
- package/harness/manifests/general/claude-code.json +25 -17
- package/harness/manifests/general/codebuddy.json +25 -17
- package/harness/manifests/general/codex.json +25 -17
- package/harness/manifests/general/cursor.json +25 -17
- package/harness/manifests/java/claude-code.json +27 -19
- package/harness/manifests/java/codebuddy.json +27 -19
- package/harness/manifests/java/codex.json +27 -19
- package/harness/manifests/java/cursor.json +27 -19
- package/hunter-workflow-family.json +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Harness 工作流族的**纯数据 npm 包**(bundles + manifests + migrations
|
|
|
7
7
|
- npm 包 `version` 与 Registry 中 **Workflow Family 发布版本** 1:1 对齐(例如 Family `1.0.0` → 本包 `1.0.0`)。
|
|
8
8
|
- `hunter-workflow-family.json` 记录族 slug、所需 profile 与 bundle 元数据,以及 `content_sha256`
|
|
9
9
|
——对 `harness/` 下所有文件按路径排序后 `{path, content}` 规范化 JSON(`canonicalJson`)取 SHA-256,
|
|
10
|
-
与 `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
|
|
10
|
+
与 hunter-platform `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
|
|
11
11
|
供离线/本地校验本包内容完整性。
|
|
12
12
|
|
|
13
13
|
## 布局
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-archive
|
|
3
|
-
description: "归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive
|
|
3
|
+
description: "归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用 /harness-archive 时使用;不得被其他阶段 skill 自动接续触发。"
|
|
4
|
+
disable-model-invocation: true
|
|
4
5
|
argument-hint: "变更名或留空自动检测"
|
|
5
6
|
effort: medium
|
|
6
7
|
allowed-tools: [Bash(powershell.exe:*), Read, Write, Edit, Glob, Grep]
|
|
@@ -20,7 +21,7 @@ disallowed-tools:
|
|
|
20
21
|
- Bash(awk *)
|
|
21
22
|
- Bash(curl *)
|
|
22
23
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
25
|
# harness-archive — 变更归档
|
|
25
26
|
|
|
26
27
|
## Purpose
|
|
@@ -29,11 +30,9 @@ disallowed-tools:
|
|
|
29
30
|
|
|
30
31
|
## When to Use
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
仅当用户显式调用 `/harness-archive` 时执行。submit/merge 完成后**不自动**进入本阶段;用户口头说"归档/收尾"而未调用本 skill 时,先确认。
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
- submit 已推送、test/review 报告已就绪(或已明确标记跳过/未运行),需要封存变更产出。
|
|
36
|
-
- 变更闭环最后一站,归档后从工作区移除 `.harness/changes/<change-name>/`。
|
|
35
|
+
前置:submit 已推送、test/review 报告已就绪(或已明确标记跳过/未运行)。变更闭环最后一站,归档后从工作区移除 `.harness/changes/<change-name>/`。
|
|
37
36
|
|
|
38
37
|
跳过场景:
|
|
39
38
|
|
|
@@ -42,8 +41,8 @@ disallowed-tools:
|
|
|
42
41
|
|
|
43
42
|
自动调用边界:
|
|
44
43
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
44
|
+
- 归档涉及移动/删除原变更目录,**禁止被其他 skill 自动调用**;只接受用户显式 `/harness-archive` 调用。
|
|
45
|
+
- `harness_archive.py auto-gate` 只影响**本次显式调用内**是否需要归档确认对话:返回 `ARCHIVE_AUTO_GATE_SATISFIED`(Submit 或 Merge 已终态,archive-boundary snapshot 存在且全部归档门禁满足)时可跳过 AskQuestion 直接归档;其他情况仍须确认。auto-gate 不构成跨阶段自动触发的授权。
|
|
47
46
|
|
|
48
47
|
## 前置条件
|
|
49
48
|
|
|
@@ -177,7 +176,7 @@ disallowed-tools:
|
|
|
177
176
|
|
|
178
177
|
无测试报告 → 显示"未运行测试 / 静态验证",不得 100% 通过率;无 review → "📝ADVISORY:未运行 review"。状态用 ✅OK / 🟡WARN / 🔁REUSED / 🔁RETESTED / 📝ADVISORY / 🧹NON_BEHAVIORAL_CLEANUP,复用前一阶段结果显示 🔁REUSED,**不得伪装成重新执行,不得无脑全绿**。
|
|
179
178
|
|
|
180
|
-
**final-summary
|
|
179
|
+
**final-summary 是可再生展示投影,非硬产物**:`summary-data.json` 是最终报告的唯一权威数据源,必须产出并通过 validate/adequacy。HTML 渲染默认执行(Node 渲染器失败时自动 Python fallback),但渲染失败只记 warning,**不再回滚归档**;`finalize --no-html` 可完全跳过本地 HTML(配置远端平台时由平台按 summary-data.json 渲染)。
|
|
181
180
|
|
|
182
181
|
### 九、CONDITIONAL_OK 最终状态
|
|
183
182
|
|
|
@@ -208,7 +207,7 @@ git 命令通过 `powershell.exe -Command "..."` 执行;archive-meta.md 和 fi
|
|
|
208
207
|
- `.harness/archive/YYYY-MM-DD-<change-name>/meta/archive-meta.md` — 归档元数据
|
|
209
208
|
- `.harness/archive/YYYY-MM-DD-<change-name>/events.ndjson` — 结构化事件层(新流程推荐;旧 archive 可缺失)
|
|
210
209
|
- `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/summary-data.json` — 最终报告数据源
|
|
211
|
-
- `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/final-summary.html` —
|
|
210
|
+
- `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/final-summary.html` — 可选展示投影,由 `render-summary.mjs` 渲染(Node 失败时 Python fallback);渲染失败降级 warning,`--no-html` 可跳过
|
|
212
211
|
- `.harness/archive/YYYY-MM-DD-<change-name>/evidence/archive-manifest-before.json` / `archive-manifest-after.json` — 归档前后 manifest/checksum
|
|
213
212
|
|
|
214
213
|
## 渐进披露
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-codebase-map — 代码库地图
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-knowledge-ingest
|
|
25
25
|
|
|
26
26
|
从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。
|
package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py
CHANGED
|
@@ -22,6 +22,8 @@ import subprocess
|
|
|
22
22
|
import sys
|
|
23
23
|
import time
|
|
24
24
|
import unicodedata
|
|
25
|
+
import urllib.error
|
|
26
|
+
import urllib.request
|
|
25
27
|
from difflib import SequenceMatcher
|
|
26
28
|
from pathlib import Path
|
|
27
29
|
from typing import Any
|
|
@@ -394,6 +396,187 @@ def project_id(project: Path) -> str:
|
|
|
394
396
|
return slugify(project.name, 80)
|
|
395
397
|
|
|
396
398
|
|
|
399
|
+
def _yaml_scalar(text: str, key: str) -> str | None:
|
|
400
|
+
"""Read a top-level or nested YAML scalar without depending on PyYAML."""
|
|
401
|
+
pattern = rf"(?m)^[ \t]*{re.escape(key)}:\s*['\"]?([^'\"\n#]+)"
|
|
402
|
+
match = re.search(pattern, text)
|
|
403
|
+
if match is None:
|
|
404
|
+
return None
|
|
405
|
+
value = match.group(1).strip()
|
|
406
|
+
return value or None
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def platform_project_id(project: Path) -> str | None:
|
|
410
|
+
"""Platform-bound project id from project.yaml / credentials (not the local slug)."""
|
|
411
|
+
for relative in (
|
|
412
|
+
".harness/credentials.local.yaml",
|
|
413
|
+
".harness/project.yaml",
|
|
414
|
+
):
|
|
415
|
+
path = project / relative
|
|
416
|
+
if not path.is_file():
|
|
417
|
+
continue
|
|
418
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
419
|
+
value = _yaml_scalar(text, "project_id")
|
|
420
|
+
if value and value.lower() not in {"null", "~", "none"}:
|
|
421
|
+
return value
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def resolve_remote_endpoint(project: Path) -> dict[str, str] | None:
|
|
426
|
+
"""P3 remote mode: credentials.local.yaml with server_url+token enables platform ingest.
|
|
427
|
+
|
|
428
|
+
When configured, local index.json / index.sqlite / judge / maintenance-outbox are
|
|
429
|
+
not maintained; entries still land under entries/ for offline grep fallback.
|
|
430
|
+
"""
|
|
431
|
+
creds = project / ".harness" / "credentials.local.yaml"
|
|
432
|
+
if not creds.is_file():
|
|
433
|
+
return None
|
|
434
|
+
text = creds.read_text(encoding="utf-8", errors="ignore")
|
|
435
|
+
server_url = _yaml_scalar(text, "server_url")
|
|
436
|
+
token = _yaml_scalar(text, "token")
|
|
437
|
+
if not server_url or not token:
|
|
438
|
+
return None
|
|
439
|
+
project_bound = platform_project_id(project)
|
|
440
|
+
if not project_bound:
|
|
441
|
+
return None
|
|
442
|
+
return {
|
|
443
|
+
"server_url": server_url.rstrip("/"),
|
|
444
|
+
"token": token,
|
|
445
|
+
"project_id": project_bound,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def post_knowledge_ingest(
|
|
450
|
+
endpoint: dict[str, str],
|
|
451
|
+
entries: list[dict[str, Any]],
|
|
452
|
+
*,
|
|
453
|
+
timeout: float = 30.0,
|
|
454
|
+
) -> dict[str, Any]:
|
|
455
|
+
"""POST entries to the platform knowledge ingest API (idempotent)."""
|
|
456
|
+
if not entries:
|
|
457
|
+
return {"accepted": 0, "created": 0, "updated": 0, "duplicates": 0, "skipped": True}
|
|
458
|
+
url = (
|
|
459
|
+
f"{endpoint['server_url']}/api/v1/projects/"
|
|
460
|
+
f"{endpoint['project_id']}/knowledge/ingest"
|
|
461
|
+
)
|
|
462
|
+
body = json.dumps(
|
|
463
|
+
{"schema_version": 1, "entries": entries},
|
|
464
|
+
ensure_ascii=False,
|
|
465
|
+
separators=(",", ":"),
|
|
466
|
+
).encode("utf-8")
|
|
467
|
+
request = urllib.request.Request(
|
|
468
|
+
url,
|
|
469
|
+
data=body,
|
|
470
|
+
method="POST",
|
|
471
|
+
headers={
|
|
472
|
+
"Accept": "application/json",
|
|
473
|
+
"Authorization": f"Bearer {endpoint['token']}",
|
|
474
|
+
"Content-Type": "application/json",
|
|
475
|
+
},
|
|
476
|
+
)
|
|
477
|
+
try:
|
|
478
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
479
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
480
|
+
except urllib.error.HTTPError as exc:
|
|
481
|
+
detail = exc.read().decode("utf-8", errors="replace")
|
|
482
|
+
raise RuntimeError(f"knowledge ingest HTTP {exc.code}: {detail}") from exc
|
|
483
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
|
|
484
|
+
raise RuntimeError(f"knowledge ingest failed: {exc}") from exc
|
|
485
|
+
if not isinstance(payload, dict):
|
|
486
|
+
raise RuntimeError("knowledge ingest returned a non-object payload")
|
|
487
|
+
return payload
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def query_remote_semantic(
|
|
491
|
+
endpoint: dict[str, str],
|
|
492
|
+
query: str,
|
|
493
|
+
*,
|
|
494
|
+
limit: int = 10,
|
|
495
|
+
timeout: float = 20.0,
|
|
496
|
+
) -> list[dict[str, Any]]:
|
|
497
|
+
"""Query the platform semantic search API; empty list when unreachable."""
|
|
498
|
+
from urllib.parse import urlencode
|
|
499
|
+
|
|
500
|
+
params = urlencode({"q": query, "project_id": endpoint["project_id"]})
|
|
501
|
+
url = f"{endpoint['server_url']}/api/v1/semantic/search?{params}"
|
|
502
|
+
request = urllib.request.Request(
|
|
503
|
+
url,
|
|
504
|
+
method="GET",
|
|
505
|
+
headers={
|
|
506
|
+
"Accept": "application/json",
|
|
507
|
+
"Authorization": f"Bearer {endpoint['token']}",
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
try:
|
|
511
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
512
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
513
|
+
except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError):
|
|
514
|
+
return []
|
|
515
|
+
items = payload.get("items") if isinstance(payload, dict) else None
|
|
516
|
+
if not isinstance(items, list):
|
|
517
|
+
return []
|
|
518
|
+
matches: list[dict[str, Any]] = []
|
|
519
|
+
for item in items[:limit]:
|
|
520
|
+
if not isinstance(item, dict):
|
|
521
|
+
continue
|
|
522
|
+
document = item.get("document") if isinstance(item.get("document"), dict) else item
|
|
523
|
+
metadata = document.get("metadata") if isinstance(document.get("metadata"), dict) else {}
|
|
524
|
+
matches.append(
|
|
525
|
+
{
|
|
526
|
+
"id": str(metadata.get("entry_id") or document.get("document_id") or ""),
|
|
527
|
+
"type": str(metadata.get("entry_type") or "decision"),
|
|
528
|
+
"status": str(metadata.get("status") or "active"),
|
|
529
|
+
"title": str(document.get("title") or ""),
|
|
530
|
+
"source": {
|
|
531
|
+
"archive": str(metadata.get("source_archive") or "remote"),
|
|
532
|
+
},
|
|
533
|
+
"scope": {
|
|
534
|
+
"sourceFiles": list(metadata.get("source_files") or []),
|
|
535
|
+
},
|
|
536
|
+
"body": str(document.get("body") or ""),
|
|
537
|
+
"summary": str(document.get("title") or ""),
|
|
538
|
+
}
|
|
539
|
+
)
|
|
540
|
+
return [match for match in matches if match["id"]]
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def grep_entry_files(
|
|
544
|
+
knowledge: Path,
|
|
545
|
+
query: str,
|
|
546
|
+
limit: int = 10,
|
|
547
|
+
statuses: list[str] | None = None,
|
|
548
|
+
) -> list[dict[str, Any]]:
|
|
549
|
+
"""Offline fallback: substring scan over entries/*.json when remote is down."""
|
|
550
|
+
needle = query.strip().lower()
|
|
551
|
+
if not needle:
|
|
552
|
+
return []
|
|
553
|
+
selected = set(statuses or ["active", "candidate"])
|
|
554
|
+
matches: list[dict[str, Any]] = []
|
|
555
|
+
for status in sorted(selected):
|
|
556
|
+
folder = knowledge / "entries" / status
|
|
557
|
+
if not folder.is_dir():
|
|
558
|
+
continue
|
|
559
|
+
for path in sorted(folder.glob("*.json")):
|
|
560
|
+
try:
|
|
561
|
+
entry = read_json(path)
|
|
562
|
+
except (OSError, json.JSONDecodeError):
|
|
563
|
+
continue
|
|
564
|
+
if not isinstance(entry, dict):
|
|
565
|
+
continue
|
|
566
|
+
haystack = " ".join(
|
|
567
|
+
str(entry.get(key) or "")
|
|
568
|
+
for key in ("id", "title", "summary", "body", "type", "status")
|
|
569
|
+
).lower()
|
|
570
|
+
keywords = entry.get("keywords") if isinstance(entry.get("keywords"), list) else []
|
|
571
|
+
haystack += " " + " ".join(str(item) for item in keywords).lower()
|
|
572
|
+
if needle not in haystack:
|
|
573
|
+
continue
|
|
574
|
+
matches.append(entry)
|
|
575
|
+
if len(matches) >= limit:
|
|
576
|
+
return matches
|
|
577
|
+
return matches
|
|
578
|
+
|
|
579
|
+
|
|
397
580
|
def git_head(project: Path) -> str | None:
|
|
398
581
|
result = run_git(project, ["rev-parse", "HEAD"])
|
|
399
582
|
return result.stdout.strip() if result.returncode == 0 else None
|
|
@@ -3110,11 +3293,16 @@ def build_index(
|
|
|
3110
3293
|
# so a repeated ingest is a true no-op (design §3.5, cluster 6, UT-025).
|
|
3111
3294
|
# Also require index.sqlite to exist so a query never sees a stale index.json
|
|
3112
3295
|
# pointing at a missing sqlite (API-009 single ensure-current).
|
|
3296
|
+
# P3 remote mode does not maintain sqlite; the remote pointer index is enough.
|
|
3297
|
+
remote_endpoint = resolve_remote_endpoint(project)
|
|
3113
3298
|
if incremental:
|
|
3114
3299
|
if (
|
|
3115
3300
|
isinstance(old_index, dict)
|
|
3116
3301
|
and old_index.get("inputsHash") == inputs_hash
|
|
3117
|
-
and (
|
|
3302
|
+
and (
|
|
3303
|
+
remote_endpoint is not None
|
|
3304
|
+
or (knowledge / "index.sqlite").exists()
|
|
3305
|
+
)
|
|
3118
3306
|
):
|
|
3119
3307
|
stale_mode = dict(old_index.get("ingestMode", {}))
|
|
3120
3308
|
stale_mode.update(
|
|
@@ -3134,6 +3322,7 @@ def build_index(
|
|
|
3134
3322
|
"archivesChanged": 0,
|
|
3135
3323
|
"archivesRemoved": 0,
|
|
3136
3324
|
"archivesDelta": 0,
|
|
3325
|
+
**({"remote": True} if remote_endpoint is not None else {}),
|
|
3137
3326
|
}
|
|
3138
3327
|
)
|
|
3139
3328
|
result = dict(old_index)
|
|
@@ -3363,6 +3552,40 @@ def build_index(
|
|
|
3363
3552
|
)
|
|
3364
3553
|
|
|
3365
3554
|
reporter.emit("sqlite", 0, len(indexed_entries), force=True)
|
|
3555
|
+
remote = resolve_remote_endpoint(project)
|
|
3556
|
+
if remote is not None:
|
|
3557
|
+
# P3 remote mode: keep entry JSON files for offline grep; skip local
|
|
3558
|
+
# index.json / index.sqlite / views / outbox projections and push to platform.
|
|
3559
|
+
ingest_mode["mode"] = "remote"
|
|
3560
|
+
ingest_mode["remote"] = True
|
|
3561
|
+
ingest_mode["remoteProjectId"] = remote["project_id"]
|
|
3562
|
+
ingest_mode["sqliteRebuild"] = 0
|
|
3563
|
+
ingest_mode["sqliteUpsert"] = 0
|
|
3564
|
+
ingest_mode["sqliteDelete"] = 0
|
|
3565
|
+
ingest_mode["sqliteRowsTouched"] = 0
|
|
3566
|
+
try:
|
|
3567
|
+
remote_result = post_knowledge_ingest(remote, indexed_entries)
|
|
3568
|
+
ingest_mode["remoteIngest"] = remote_result
|
|
3569
|
+
ingest_mode["remoteOk"] = True
|
|
3570
|
+
except RuntimeError as exc:
|
|
3571
|
+
ingest_mode["remoteOk"] = False
|
|
3572
|
+
ingest_mode["remoteError"] = str(exc)
|
|
3573
|
+
index = make_manifest(
|
|
3574
|
+
project,
|
|
3575
|
+
pname,
|
|
3576
|
+
summary_paths,
|
|
3577
|
+
archive_records,
|
|
3578
|
+
indexed_entries,
|
|
3579
|
+
failures,
|
|
3580
|
+
duplicates,
|
|
3581
|
+
ingest_mode,
|
|
3582
|
+
)
|
|
3583
|
+
# Lightweight pointer only — not a local search index.
|
|
3584
|
+
write_json_if_changed(knowledge / "index.json", index)
|
|
3585
|
+
write_ingest_report(knowledge, index, failures, duplicates)
|
|
3586
|
+
reporter.emit("complete", len(indexed_entries), len(indexed_entries), force=True)
|
|
3587
|
+
return index
|
|
3588
|
+
|
|
3366
3589
|
sqlite_dirty_entries = [
|
|
3367
3590
|
entry
|
|
3368
3591
|
for entry in indexed_entries
|
|
@@ -4417,6 +4640,27 @@ def auto_knowledge(
|
|
|
4417
4640
|
audit_limit: int = 10,
|
|
4418
4641
|
) -> dict[str, Any]:
|
|
4419
4642
|
project = project.resolve()
|
|
4643
|
+
remote = resolve_remote_endpoint(project)
|
|
4644
|
+
if remote is not None:
|
|
4645
|
+
sync = sync_status(project, update=True, incremental=incremental)
|
|
4646
|
+
return {
|
|
4647
|
+
"command": "auto",
|
|
4648
|
+
"project": str(project),
|
|
4649
|
+
"generatedAt": now_iso(),
|
|
4650
|
+
"mode": "remote",
|
|
4651
|
+
"remote": {
|
|
4652
|
+
"serverUrl": remote["server_url"],
|
|
4653
|
+
"projectId": remote["project_id"],
|
|
4654
|
+
},
|
|
4655
|
+
"sync": sync,
|
|
4656
|
+
"skipped": {
|
|
4657
|
+
"suggestValidators": True,
|
|
4658
|
+
"verify": True,
|
|
4659
|
+
"audit": True,
|
|
4660
|
+
"judge": True,
|
|
4661
|
+
"reason": "remote mode: candidate review happens on the platform",
|
|
4662
|
+
},
|
|
4663
|
+
}
|
|
4420
4664
|
knowledge = project / ".harness" / "knowledge"
|
|
4421
4665
|
config_summary = ensure_auto_knowledge_config(knowledge)
|
|
4422
4666
|
sync = sync_status(project, update=True, incremental=incremental)
|
|
@@ -4530,6 +4774,46 @@ def query_index(
|
|
|
4530
4774
|
) -> dict[str, Any]:
|
|
4531
4775
|
project = project.resolve()
|
|
4532
4776
|
knowledge = project / ".harness" / "knowledge"
|
|
4777
|
+
remote = resolve_remote_endpoint(project)
|
|
4778
|
+
if remote is not None:
|
|
4779
|
+
# P3: prefer platform semantic search; degrade to local entry grep when offline.
|
|
4780
|
+
entries = query_remote_semantic(remote, query, limit=limit)
|
|
4781
|
+
source = "remote"
|
|
4782
|
+
if not entries:
|
|
4783
|
+
entries = grep_entry_files(knowledge, query, limit=limit, statuses=statuses)
|
|
4784
|
+
source = "offline-grep"
|
|
4785
|
+
context_path = write_context_pack(project, knowledge, query, entries)
|
|
4786
|
+
filters = {
|
|
4787
|
+
"files": file_filters or [],
|
|
4788
|
+
"statuses": statuses or [],
|
|
4789
|
+
"types": types or [],
|
|
4790
|
+
}
|
|
4791
|
+
return {
|
|
4792
|
+
"query": query,
|
|
4793
|
+
"matchCount": len(entries),
|
|
4794
|
+
"contextPack": str(context_path),
|
|
4795
|
+
"filters": filters,
|
|
4796
|
+
"mode": "remote",
|
|
4797
|
+
"source": source,
|
|
4798
|
+
"planInput": {
|
|
4799
|
+
"kind": "harness-knowledge-context-pack",
|
|
4800
|
+
"path": str(context_path),
|
|
4801
|
+
"requiredBefore": "harness-plan",
|
|
4802
|
+
"usage": "Read this context pack before design, planning, code exploration, or implementation.",
|
|
4803
|
+
},
|
|
4804
|
+
"matches": [
|
|
4805
|
+
{
|
|
4806
|
+
"id": entry.get("id"),
|
|
4807
|
+
"type": entry.get("type"),
|
|
4808
|
+
"status": entry.get("status"),
|
|
4809
|
+
"title": entry.get("title"),
|
|
4810
|
+
"sourceArchive": (entry.get("source") or {}).get("archive"),
|
|
4811
|
+
"sourceFiles": (entry.get("scope") or {}).get("sourceFiles") or [],
|
|
4812
|
+
}
|
|
4813
|
+
for entry in entries
|
|
4814
|
+
],
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4533
4817
|
sqlite_path = knowledge / "index.sqlite"
|
|
4534
4818
|
# API-009: one ensure-current. Build the shared snapshot once (inputs_hash
|
|
4535
4819
|
# computed exactly once) and a single build_index call whose no-op fast path
|
|
@@ -4621,6 +4905,45 @@ def sync_status(
|
|
|
4621
4905
|
check: bool = False,
|
|
4622
4906
|
) -> dict[str, Any]:
|
|
4623
4907
|
project = project.resolve()
|
|
4908
|
+
remote = resolve_remote_endpoint(project)
|
|
4909
|
+
if remote is not None:
|
|
4910
|
+
# P3: remote mode — local sqlite/outbox are intentionally not maintained.
|
|
4911
|
+
refreshed: dict[str, Any] | None = None
|
|
4912
|
+
action = "none"
|
|
4913
|
+
if update and not check:
|
|
4914
|
+
refreshed = build_index(
|
|
4915
|
+
project,
|
|
4916
|
+
incremental=incremental,
|
|
4917
|
+
progress=progress,
|
|
4918
|
+
)
|
|
4919
|
+
action = "ingested-remote"
|
|
4920
|
+
remote_ok = True
|
|
4921
|
+
remote_error = None
|
|
4922
|
+
if refreshed is not None:
|
|
4923
|
+
mode = refreshed.get("ingestMode") or {}
|
|
4924
|
+
remote_ok = bool(mode.get("remoteOk", True))
|
|
4925
|
+
remote_error = mode.get("remoteError")
|
|
4926
|
+
return {
|
|
4927
|
+
"ok": remote_ok,
|
|
4928
|
+
"current": remote_ok,
|
|
4929
|
+
"action": action,
|
|
4930
|
+
"reasons": [] if remote_ok else [str(remote_error or "remote ingest failed")],
|
|
4931
|
+
"mode": "remote",
|
|
4932
|
+
"remote": {
|
|
4933
|
+
"serverUrl": remote["server_url"],
|
|
4934
|
+
"projectId": remote["project_id"],
|
|
4935
|
+
"ok": remote_ok,
|
|
4936
|
+
**({} if remote_error is None else {"error": remote_error}),
|
|
4937
|
+
},
|
|
4938
|
+
"maintenance": {
|
|
4939
|
+
"attempted": False,
|
|
4940
|
+
"skipped": True,
|
|
4941
|
+
"skippedReason": "remote mode: judge/outbox run on the platform",
|
|
4942
|
+
"ok": True,
|
|
4943
|
+
},
|
|
4944
|
+
"index": refreshed,
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4624
4947
|
snapshot = build_snapshot(project)
|
|
4625
4948
|
knowledge = snapshot.knowledge
|
|
4626
4949
|
index_path = knowledge / "index.json"
|
|
@@ -6295,6 +6618,16 @@ def maintain_knowledge(project: Path, archive_id: str) -> dict[str, Any]:
|
|
|
6295
6618
|
judge checklist, running->completed (or completed_rules_pending_judge).
|
|
6296
6619
|
Idempotent for completed items. Failure -> failed, attempts+1, retryable."""
|
|
6297
6620
|
project = project.resolve()
|
|
6621
|
+
remote = resolve_remote_endpoint(project)
|
|
6622
|
+
if remote is not None:
|
|
6623
|
+
return {
|
|
6624
|
+
"ok": True,
|
|
6625
|
+
"archiveId": archive_id,
|
|
6626
|
+
"status": "skipped-remote",
|
|
6627
|
+
"skipped": True,
|
|
6628
|
+
"skippedReason": "remote mode: maintenance runs on the platform",
|
|
6629
|
+
"remoteProjectId": remote["project_id"],
|
|
6630
|
+
}
|
|
6298
6631
|
outbox_root = _outbox_root(project)
|
|
6299
6632
|
outbox_root.mkdir(parents=True, exist_ok=True)
|
|
6300
6633
|
|
|
@@ -6379,6 +6712,19 @@ def drain_maintenance_outbox(
|
|
|
6379
6712
|
simply including the stale id in the drain batch is sufficient recovery.
|
|
6380
6713
|
"""
|
|
6381
6714
|
project = project.resolve()
|
|
6715
|
+
remote = resolve_remote_endpoint(project)
|
|
6716
|
+
if remote is not None:
|
|
6717
|
+
return {
|
|
6718
|
+
"ok": True,
|
|
6719
|
+
"project": str(project),
|
|
6720
|
+
"processed": 0,
|
|
6721
|
+
"remaining": 0,
|
|
6722
|
+
"staleRunningRecovered": 0,
|
|
6723
|
+
"results": [],
|
|
6724
|
+
"skipped": True,
|
|
6725
|
+
"skippedReason": "remote mode: maintenance outbox is not used",
|
|
6726
|
+
"mode": "remote",
|
|
6727
|
+
}
|
|
6382
6728
|
root = _outbox_root(project)
|
|
6383
6729
|
now = time.time()
|
|
6384
6730
|
retryable_ids = {
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-knowledge-query
|
|
25
25
|
|
|
26
26
|
并行 change 中查询必须传 `--change <change-id>`;成功后以该 change 的 `meta/knowledge-context.json` 为稳定指针。全局 `context-packs/latest.json` 仅向后兼容,不得用于判断多个 active change 的上下文归属。
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-plan
|
|
3
|
-
description: "
|
|
3
|
+
description: "将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入 Harness 规划阶段时使用;不得因用户描述需求就自动触发。"
|
|
4
|
+
disable-model-invocation: true
|
|
4
5
|
argument-hint: "需求描述 | --adversarial"
|
|
5
6
|
effort: medium
|
|
6
7
|
allowed-tools: [Read, Glob, Grep, Edit, Write, Agent, Bash(powershell.exe:*)]
|
|
@@ -20,7 +21,7 @@ disallowed-tools:
|
|
|
20
21
|
- Bash(awk *)
|
|
21
22
|
- Bash(curl *)
|
|
22
23
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
25
|
# harness-plan — 需求规划
|
|
25
26
|
|
|
26
27
|
## Purpose
|
|
@@ -31,7 +32,9 @@ disallowed-tools:
|
|
|
31
32
|
|
|
32
33
|
## When to Use
|
|
33
34
|
|
|
34
|
-
`/harness-plan
|
|
35
|
+
仅当用户显式调用 `/harness-plan`(或明确说"用 harness 规划这个需求")时执行。用户只是描述需求、提问或讨论方案时,**不得**自动进入本 skill。
|
|
36
|
+
|
|
37
|
+
**单阶段原则**:本 skill 只负责 plan 阶段。plan finalize + verify 完成后必须停止并交还用户,仅提示下一步可执行 `/harness-run`;禁止自动开始编码或调用其他 harness 阶段 skill。
|
|
35
38
|
|
|
36
39
|
## 统一读取协议
|
|
37
40
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-review
|
|
3
|
-
description: "6维度代码审查(架构/安全/规范/兼容/测试/性能),对照项目规则(见 .harness/context-index.json
|
|
3
|
+
description: "6维度代码审查(架构/安全/规范/兼容/测试/性能),对照项目规则(见 .harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在 test 结束后自动接续执行。"
|
|
4
|
+
disable-model-invocation: true
|
|
4
5
|
argument-hint: "变更名或留空自动检测"
|
|
5
6
|
effort: high
|
|
6
7
|
allowed-tools: [Read, Write, Edit, Glob, Grep, Agent, Bash(powershell.exe:*)]
|
|
@@ -21,7 +22,7 @@ disallowed-tools:
|
|
|
21
22
|
- Bash(curl *)
|
|
22
23
|
- Bash(codegraph *)
|
|
23
24
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
25
26
|
# harness-review — 代码审查
|
|
26
27
|
|
|
27
28
|
## Purpose
|
|
@@ -30,11 +31,9 @@ disallowed-tools:
|
|
|
30
31
|
|
|
31
32
|
## When to Use
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
- 合并评审时需要系统性检查
|
|
37
|
-
- 用户说"审查代码"、"review"、"检查代码质量"
|
|
34
|
+
仅当用户显式调用 `/harness-review` 时执行。test 阶段结束后**不自动**进入本阶段;用户口头提到"审查/review"而未调用本 skill 时,先确认是否走 Harness 审查阶段。
|
|
35
|
+
|
|
36
|
+
**单阶段原则**:review 关门(报告 + fixback 产出)后必须停止并交还用户,仅提示下一步可执行 `/harness-run --fixback`(有待修项)或 `/harness-submit`;禁止自动接续执行。
|
|
38
37
|
|
|
39
38
|
## Inputs
|
|
40
39
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-run
|
|
3
|
-
description: "按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR
|
|
3
|
+
description: "按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用 /harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。"
|
|
4
|
+
disable-model-invocation: true
|
|
4
5
|
argument-hint: "变更名 | --subagent | --inline | --fixback | 留空自动检测"
|
|
5
6
|
effort: medium
|
|
6
7
|
allowed-tools: [Read, Edit, Write, Glob, Grep, Bash(powershell.exe:*)]
|
|
@@ -21,7 +22,7 @@ disallowed-tools:
|
|
|
21
22
|
- Bash(curl *)
|
|
22
23
|
- Bash(codegraph *)
|
|
23
24
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
25
26
|
# harness-run — 需求编码
|
|
26
27
|
|
|
27
28
|
## Purpose
|
|
@@ -30,7 +31,9 @@ disallowed-tools:
|
|
|
30
31
|
|
|
31
32
|
## When to Use
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
仅当用户显式调用 `/harness-run` 时执行;plan 完成后**不自动**进入本阶段。参数:`--subagent` 强制 Subagent-Driven;`--inline` 等同默认;`--fixback` 读最新 review fixback。**默认 Inline,不询问执行模式**。
|
|
35
|
+
|
|
36
|
+
**单阶段原则**:run 关门(步骤 4 的 gate close + context close)完成后必须停止并交还用户,仅提示下一步可执行 `/harness-test`;禁止自动开始执行测试、review 或 submit。
|
|
34
37
|
|
|
35
38
|
## 前置条件
|
|
36
39
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-submit
|
|
3
|
-
description: "最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff
|
|
3
|
+
description: "最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用 /harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。"
|
|
4
|
+
disable-model-invocation: true
|
|
4
5
|
argument-hint: "变更名或留空自动检测"
|
|
5
6
|
effort: medium
|
|
6
7
|
allowed-tools: [Bash(powershell.exe:*), Read, Write, Edit, Glob, Grep]
|
|
@@ -20,7 +21,7 @@ disallowed-tools:
|
|
|
20
21
|
- Bash(awk *)
|
|
21
22
|
- Bash(curl *)
|
|
22
23
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
24
25
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
25
26
|
|
|
26
27
|
## Purpose
|
|
@@ -29,12 +30,9 @@ disallowed-tools:
|
|
|
29
30
|
|
|
30
31
|
## When to Use
|
|
31
32
|
|
|
32
|
-
-
|
|
33
|
-
- 用户说「提交代码」「commit」「push」「完成开发」
|
|
34
|
-
- worktree 模式:用户说「合并分支」「merge to main」「合入主分支」「/harness-merge」(别名,从合并阶段重入亦可)
|
|
35
|
-
- review 和 test 已通过或已明确跳过
|
|
33
|
+
仅当用户显式调用 `/harness-submit`(或 `/harness-merge` 从合并段重入)时执行。用户口头说「提交代码」「commit」「push」「完成开发」而未调用本 skill 时,先确认是否走 Harness 提交阶段,**不得自动触发**。前置:review 和 test 已通过或已明确跳过。
|
|
36
34
|
|
|
37
|
-
> **主目录模式**(`worktree.json` requested=false):commit+push
|
|
35
|
+
> **主目录模式**(`worktree.json` requested=false):commit+push 主分支后**停止**,仅提示可执行 `/harness-archive`,不自动归档。**worktree 模式**(requested=true):worktree 内仅本地 commit,随后本 skill 在同一次调用内接续合并流程(这是同一次提交动作的一部分,不属于跨阶段自动推进);**push 只在主分支发生一次**;合并完成后同样**停止**,仅提示 `/harness-archive`。
|
|
38
36
|
|
|
39
37
|
## 前置条件
|
|
40
38
|
|
|
@@ -19,7 +19,7 @@ disallowed-tools:
|
|
|
19
19
|
- Bash(awk *)
|
|
20
20
|
- Bash(curl *)
|
|
21
21
|
---
|
|
22
|
-
<!-- generated by harness_deploy.py; core=
|
|
22
|
+
<!-- generated by harness_deploy.py; core=d5cc283de8c743a2; overlay=none; agent=claude-code; do not edit -->
|
|
23
23
|
# harness-sync
|
|
24
24
|
|
|
25
25
|
## Purpose
|