@sakanano-moe/agenthelm 6.6.0 → 6.8.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.
Files changed (35) hide show
  1. package/README.md +68 -0
  2. package/README.zh-CN.md +28 -0
  3. package/package.json +2 -2
  4. package/runtime/index.json +15 -15
  5. package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +11 -4
  6. package/runtime/plugins/agent-pack/plugin.json +1 -1
  7. package/runtime/plugins/agent-pack/references/capability-config.md +29 -5
  8. package/runtime/plugins/agent-pack/runtime/agent_pack_claude.py +448 -0
  9. package/runtime/plugins/agent-pack/runtime/agent_pack_config.py +178 -22
  10. package/runtime/plugins/agent-pack/runtime/agenthelm.py +237 -6
  11. package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +19 -0
  12. package/runtime/plugins/agent-pack/runtime/mcp_server.py +30 -8
  13. package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +2 -2
  14. package/runtime/plugins/agents-init/.codex-plugin/plugin.json +5 -5
  15. package/runtime/plugins/agents-init/content.json +15 -0
  16. package/runtime/plugins/agents-init/plugin.json +2 -2
  17. package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +14 -1
  18. package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +18 -3
  19. package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +43 -4
  20. package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +933 -0
  21. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +551 -0
  22. package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +64 -0
  23. package/runtime/plugins/agents-init/skills/agents-init/skill.json +3 -3
  24. package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +5 -5
  25. package/runtime/plugins/opinion-manager/plugin.json +1 -1
  26. package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +1 -1
  27. package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +2 -2
  28. package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +10 -4
  29. package/runtime/plugins/project-orchestrator/plugin.json +1 -1
  30. package/runtime/plugins/project-orchestrator/runtime/project_cli.py +69 -9
  31. package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +380 -27
  32. package/runtime/plugins/project-orchestrator/runtime/project_menu.py +18 -0
  33. package/runtime/plugins/project-orchestrator/runtime/project_real.py +7 -1
  34. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +1 -1
  35. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +2 -2
@@ -34,7 +34,12 @@
34
34
  "distribution": {"enum": ["marketplace", "bundled", "external"]},
35
35
  "required": {"type": "boolean"},
36
36
  "commit_policy": {"enum": ["explicit", "project-policy"]},
37
- "push_policy": {"enum": ["explicit", "project-policy"]}
37
+ "push_policy": {"enum": ["explicit", "project-policy"]},
38
+ "source_repository": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:采用的来源仓库。运行时代码不读取,check 不比较,也不进脚手架指纹。"},
39
+ "source_ref": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:采用的发布 tag。同上,仅作记录。"},
40
+ "source_commit": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:采用的提交。同上,仅作记录。"},
41
+ "plugin_version": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:采用时的 project-orchestrator 版本。同上,仅作记录。"},
42
+ "capability_manager_version": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:采用时的 agent-pack 版本。同上,仅作记录。"}
38
43
  }
39
44
  },
40
45
  "opinion": {
@@ -45,7 +50,8 @@
45
50
  "provider": {"type": ["string", "null"]},
46
51
  "project_overlay": {"type": ["string", "null"]},
47
52
  "strict_mode": {"enum": ["smart", "strict", "disabled"]},
48
- "loading_mode": {"enum": ["tiered", "full"]}
53
+ "loading_mode": {"enum": ["tiered", "full"]},
54
+ "tool_version": {"type": "string", "x-agenthelm-scope": "audit", "description": "审计记录:生成该 Opinion 时的工具版本;不参与比较。"}
49
55
  }
50
56
  },
51
57
  "memory": {
@@ -99,7 +105,16 @@
99
105
  "type": ["object", "null"], "additionalProperties": true,
100
106
  "properties": {
101
107
  "root": {"$ref": "#/$defs/relativePath"},
102
- "artifact_mode": {"enum": ["case-local", "external-references"]}
108
+ "artifact_mode": {"enum": ["case-local", "external-references"]},
109
+ "command": {"type": ["array", "string"], "description": "项目自己的 Case 工具命令;声明后项目入口不绕过它写 Case。"},
110
+ "directory_naming": {
111
+ "type": "object", "additionalProperties": true,
112
+ "description": "Case 目录命名规则,check 据此检查目录名;缺省按 CASE-日期-序号-主题。fix case-naming 只给改名计划,不移动目录。",
113
+ "properties": {
114
+ "style": {"enum": ["case-id", "date-topic", "custom"]},
115
+ "pattern": {"type": "string", "description": "匹配整个目录名的正则;style 为 custom 时必填。"}
116
+ }
117
+ }
103
118
  }
104
119
  },
105
120
  "assets": {
@@ -715,6 +715,17 @@ def _profile_launcher(profile: dict) -> str | None:
715
715
  return validate_entry_name(launcher) if isinstance(launcher, str) else None
716
716
 
717
717
 
718
+ def _scaffold():
719
+ """scaffold.py 与本文件同目录;绑定到正在运行的本模块,保证异常类一致。"""
720
+ here = str(Path(__file__).resolve().parent)
721
+ if here not in sys.path:
722
+ sys.path.insert(0, here)
723
+ import scaffold # noqa: PLC0415
724
+
725
+ scaffold.bind(sys.modules[__name__])
726
+ return scaffold
727
+
728
+
718
729
  def _unified_diff(relative: str, current: str, new: str, limit: int = 200) -> str:
719
730
  lines = list(difflib.unified_diff(
720
731
  current.splitlines(keepends=True), new.splitlines(keepends=True),
@@ -756,6 +767,7 @@ def refresh_project(args: argparse.Namespace, root: Path) -> tuple[int, dict]:
756
767
  if accept & keep:
757
768
  raise InitError("同一文件不能同时 --accept 与 --keep:" + ", ".join(sorted(map(str, accept & keep))))
758
769
  plan: list[dict] = []
770
+ kept_before = _scaffold().keep_fingerprints(root)
759
771
  for relative in tracked:
760
772
  new = rendered[relative]
761
773
  new_digest = hashlib.sha256(new.encode("utf-8")).hexdigest()
@@ -776,7 +788,10 @@ def refresh_project(args: argparse.Namespace, root: Path) -> tuple[int, dict]:
776
788
  state, action = "clean", "keep" if relative in keep else "replace"
777
789
  else:
778
790
  state = "modified"
779
- action = "replace" if relative in accept else "keep" if relative in keep else "blocked"
791
+ decided = kept_before.get(relative.as_posix()) == new_digest # 之前选过保留,且期望内容没变
792
+ action = "replace" if relative in accept else "keep" if relative in keep or decided else "blocked"
793
+ if decided and relative not in accept:
794
+ state = "kept-by-decision"
780
795
  item = {"path": relative.as_posix(), "state": state, "action": action, "new_sha256": new_digest}
781
796
  if action in {"replace", "blocked", "create"} or (state == "clean" and action == "keep"):
782
797
  item["diff"] = _unified_diff(relative.as_posix(), current or "", new)
@@ -831,6 +846,12 @@ def refresh_project(args: argparse.Namespace, root: Path) -> tuple[int, dict]:
831
846
  temporary = lock_path.with_name(f".{lock_path.name}.{os.getpid()}.tmp")
832
847
  temporary.write_text(json_text(lock), encoding="utf-8")
833
848
  os.replace(temporary, lock_path)
849
+ # 显式 --keep 的选择记入决定文件:同样的模板内容以后不再追问,模板实质变化时才再问。
850
+ for item in plan:
851
+ if Path(item["path"]) in keep and item["state"] in {"modified", "local-only", "kept-by-decision"}:
852
+ _scaffold().decide(root, f"content:{item['path']}", "keep", reason="refresh --keep",
853
+ fingerprint=item["new_sha256"], scope="project", apply=True)
854
+ result.setdefault("decisions", []).append(f"content:{item['path']}")
834
855
  result["applied"] = True
835
856
  return 0, result
836
857
 
@@ -943,8 +964,10 @@ def add_entry(args: argparse.Namespace, root: Path) -> tuple[int, dict]:
943
964
  lock_path = root / CONTENT_LOCK_PATH
944
965
  lock = _read_project_json(root, CONTENT_LOCK_PATH, "content lock") if lock_path.exists() \
945
966
  else {"schema_version": 1, "packs": {}}
967
+ # 新条目只记录刚写入的启动器,它与当前模板一致,所以版本就是当前包版本;
968
+ # 不再写占位的 0.0.0(那会让 check 永远提示“可更新”)。
946
969
  pack_entry = lock.setdefault("packs", {}).get(pack["id"]) or \
947
- {"plugin": "agents-init", "version": UNKNOWN_PACK_VERSION, "files": {}}
970
+ {"plugin": "agents-init", "version": pack["version"], "files": {}}
948
971
  pack_entry["files"] = dict(sorted({**pack_entry.get("files", {}),
949
972
  name: hashlib.sha256(template.encode("utf-8")).hexdigest()}.items()))
950
973
  lock["packs"][pack["id"]] = pack_entry
@@ -964,9 +987,20 @@ def parser() -> argparse.ArgumentParser:
964
987
  result.add_argument("--stack", type=split_csv)
965
988
  result.add_argument("--runtime")
966
989
  result.add_argument("--agent-cli", type=split_csv)
967
- result.add_argument("--mode", choices=("init", "migrate", "refresh", "entry"), default="init",
990
+ result.add_argument("--mode", choices=("init", "migrate", "refresh", "entry", "check", "add-skeleton", "profile",
991
+ "repair-lock", "decide", "case-naming"), default="init",
968
992
  help="refresh:内容包更新后按 Profile 重新渲染已跟踪的模板文件;"
969
- "entry:为已初始化的项目补上根目录启动器与 Profile 的 entry 段")
993
+ "entry:为已初始化的项目补上根目录启动器与 Profile 的 entry 段;"
994
+ "check:只读,对比期望骨架清单;add-skeleton:按模板补上缺失的骨架文件;"
995
+ "profile:把缺失或不符的受检字段改为模板值;repair-lock:修复内容锁的 0.0.0 版本;"
996
+ "decide:记录或撤销对某个发现的决定;case-naming:只输出 Case 目录改名计划与受影响的引用,不移动任何目录")
997
+ result.add_argument("--target", action="append", default=[],
998
+ help="add-skeleton:骨架路径;profile:字段;decide:发现 ID(可重复;省略则处理全部可处理项)")
999
+ result.add_argument("--decision", choices=("skip", "keep", "later", "unskip"), help="decide:决定;unskip 撤销")
1000
+ result.add_argument("--reason", help="decide:理由(建议写,随项目同步)")
1001
+ result.add_argument("--fingerprint", help="decide:期望指纹(缺省取该发现当前的指纹)")
1002
+ result.add_argument("--reprompt", action="store_true", help="check:忽略已记录的决定,重新评估全部发现")
1003
+ result.add_argument("--state-dir", type=Path, help="机器本地决定的状态目录(缺省 $XDG_STATE_HOME 或 ~/.local/state)")
970
1004
  result.add_argument("--entry-name", help=f"根目录启动器文件名(init 缺省 {DEFAULT_ENTRY};entry 模式必填)")
971
1005
  result.add_argument("--no-entry", action="store_true", help="init:不生成根目录启动器")
972
1006
  result.add_argument("--readme-entry", action="store_true",
@@ -996,6 +1030,11 @@ def main(argv: list[str] | None = None) -> int:
996
1030
  args = parser().parse_args(argv)
997
1031
  try:
998
1032
  root = validate_root(args.project)
1033
+ if args.mode in ("check", "add-skeleton", "profile", "repair-lock", "decide", "case-naming"):
1034
+ scaffold = _scaffold()
1035
+ code, result = scaffold.run_mode(args, root)
1036
+ print(json_text(result), end="") if args.output == "json" else print(scaffold.render_text(result), end="")
1037
+ return code
999
1038
  if args.mode == "entry":
1000
1039
  code, result = add_entry(args, root)
1001
1040
  if args.output == "json":