@sakanano-moe/agenthelm 6.5.3 → 6.6.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 (109) hide show
  1. package/bin/agenthelm.js +1 -1
  2. package/package.json +2 -2
  3. package/runtime/index.json +80 -0
  4. package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +19 -0
  5. package/runtime/plugins/agent-pack/.mcp.json +12 -0
  6. package/runtime/plugins/agent-pack/hooks/hooks.json +16 -0
  7. package/runtime/plugins/agent-pack/mcp.json +14 -0
  8. package/runtime/plugins/agent-pack/plugin.json +20 -0
  9. package/runtime/plugins/agent-pack/references/capability-config.md +124 -0
  10. package/runtime/{agenthelm.py → plugins/agent-pack/runtime/agenthelm.py} +42 -1
  11. package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +388 -0
  12. package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +46 -0
  13. package/runtime/plugins/agents-init/.codex-plugin/plugin.json +22 -0
  14. package/runtime/plugins/agents-init/content.json +31 -0
  15. package/runtime/plugins/agents-init/plugin.json +19 -0
  16. package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +376 -0
  17. package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +119 -0
  18. package/runtime/plugins/agents-init/skills/agents-init/index.html +357 -0
  19. package/runtime/plugins/agents-init/skills/agents-init/launcher.sh +93 -0
  20. package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +171 -0
  21. package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +1190 -0
  22. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +678 -0
  23. package/runtime/plugins/agents-init/skills/agents-init/skill.json +122 -0
  24. package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +26 -0
  25. package/runtime/plugins/opinion-manager/catalog/README.md +15 -0
  26. package/runtime/plugins/opinion-manager/catalog/rules/.gitkeep +0 -0
  27. package/runtime/plugins/opinion-manager/catalog/rules/om.align-requirements/versions/1.0.0.json +11 -0
  28. package/runtime/plugins/opinion-manager/catalog/rules/om.assumptions-authority/versions/1.0.0.json +11 -0
  29. package/runtime/plugins/opinion-manager/catalog/rules/om.batch-questions/versions/1.0.0.json +11 -0
  30. package/runtime/plugins/opinion-manager/catalog/rules/om.bilingual-terms/versions/1.0.0.json +11 -0
  31. package/runtime/plugins/opinion-manager/catalog/rules/om.calculations/versions/1.0.0.json +11 -0
  32. package/runtime/plugins/opinion-manager/catalog/rules/om.chinese-response/versions/1.0.0.json +11 -0
  33. package/runtime/plugins/opinion-manager/catalog/rules/om.clean-deliverable/versions/1.0.0.json +11 -0
  34. package/runtime/plugins/opinion-manager/catalog/rules/om.complete-options/versions/1.0.0.json +11 -0
  35. package/runtime/plugins/opinion-manager/catalog/rules/om.conclusion-first/versions/1.0.0.json +11 -0
  36. package/runtime/plugins/opinion-manager/catalog/rules/om.concrete-titles/versions/1.0.0.json +11 -0
  37. package/runtime/plugins/opinion-manager/catalog/rules/om.consistent-items/versions/1.0.0.json +11 -0
  38. package/runtime/plugins/opinion-manager/catalog/rules/om.device-readable/versions/1.0.0.json +11 -0
  39. package/runtime/plugins/opinion-manager/catalog/rules/om.editable-diagrams/versions/1.0.0.json +11 -0
  40. package/runtime/plugins/opinion-manager/catalog/rules/om.evidence-inference/versions/1.0.0.json +11 -0
  41. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-chinese/versions/1.0.0.json +11 -0
  42. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-project-language/versions/1.0.0.json +11 -0
  43. package/runtime/plugins/opinion-manager/catalog/rules/om.markdown-structure/versions/1.0.0.json +11 -0
  44. package/runtime/plugins/opinion-manager/catalog/rules/om.portable-files/versions/1.0.0.json +11 -0
  45. package/runtime/plugins/opinion-manager/catalog/rules/om.report-context/versions/1.0.0.json +11 -0
  46. package/runtime/plugins/opinion-manager/catalog/rules/om.report-formats/versions/1.0.0.json +11 -0
  47. package/runtime/plugins/opinion-manager/catalog/rules/om.report-review/versions/1.0.0.json +11 -0
  48. package/runtime/plugins/opinion-manager/catalog/rules/om.response-brief/versions/1.0.0.json +11 -0
  49. package/runtime/plugins/opinion-manager/catalog/rules/om.response-dense/versions/1.0.0.json +11 -0
  50. package/runtime/plugins/opinion-manager/catalog/rules/om.rules-tools/versions/1.0.0.json +11 -0
  51. package/runtime/plugins/opinion-manager/catalog/rules/om.semantic-wrap/versions/1.0.0.json +11 -0
  52. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-proportional/versions/1.0.0.json +11 -0
  53. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-three-level/versions/1.0.0.json +11 -0
  54. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-compact/versions/1.0.0.json +11 -0
  55. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-list-first/versions/1.0.0.json +11 -0
  56. package/runtime/plugins/opinion-manager/catalog/rules/om.test-evidence/versions/1.0.0.json +11 -0
  57. package/runtime/plugins/opinion-manager/catalog/rules/om.versioned-files/versions/1.0.0.json +11 -0
  58. package/runtime/plugins/opinion-manager/catalog/templates/.gitkeep +0 -0
  59. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/compact-tables/versions/1.0.0.json +21 -0
  60. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/list-first/versions/1.0.0.json +21 -0
  61. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/chinese-maintenance/versions/1.0.0.json +15 -0
  62. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/project-language/versions/1.0.0.json +15 -0
  63. package/runtime/plugins/opinion-manager/catalog/templates/response-style/brief-chinese/versions/1.0.0.json +19 -0
  64. package/runtime/plugins/opinion-manager/catalog/templates/response-style/dense-chinese/versions/1.0.0.json +19 -0
  65. package/runtime/plugins/opinion-manager/catalog/templates/scenario/concise-project/versions/1.0.0.json +39 -0
  66. package/runtime/plugins/opinion-manager/catalog/templates/scenario/reviewed-project/versions/1.0.0.json +39 -0
  67. package/runtime/plugins/opinion-manager/catalog/templates/work-method/proportional/versions/1.0.0.json +20 -0
  68. package/runtime/plugins/opinion-manager/catalog/templates/work-method/three-level/versions/1.0.0.json +20 -0
  69. package/runtime/plugins/opinion-manager/content.json +63 -0
  70. package/runtime/plugins/opinion-manager/plugin.json +18 -0
  71. package/runtime/plugins/opinion-manager/references/lock.schema.json +14 -0
  72. package/runtime/plugins/opinion-manager/references/onboarding.md +57 -0
  73. package/runtime/plugins/opinion-manager/references/profile.schema.json +56 -0
  74. package/runtime/plugins/opinion-manager/references/rule.schema.json +16 -0
  75. package/runtime/plugins/opinion-manager/references/template.schema.json +71 -0
  76. package/runtime/plugins/opinion-manager/references/versioned-template.schema.json +19 -0
  77. package/runtime/plugins/opinion-manager/references/versioning.md +263 -0
  78. package/runtime/plugins/opinion-manager/runtime/constants.py +7 -0
  79. package/runtime/plugins/opinion-manager/runtime/context.py +180 -0
  80. package/runtime/plugins/opinion-manager/runtime/lifecycle.py +395 -0
  81. package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +419 -0
  82. package/runtime/plugins/opinion-manager/runtime/versioned.py +705 -0
  83. package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +62 -0
  84. package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +4 -0
  85. package/runtime/plugins/opinion-manager/templates/.gitkeep +1 -0
  86. package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +18 -0
  87. package/runtime/plugins/project-orchestrator/content.json +37 -0
  88. package/runtime/plugins/project-orchestrator/plugin.json +19 -0
  89. package/runtime/plugins/project-orchestrator/references/asset-synonyms.example.json +8 -0
  90. package/runtime/plugins/project-orchestrator/references/case-workspaces.md +159 -0
  91. package/runtime/plugins/project-orchestrator/references/case.schema.json +31 -0
  92. package/runtime/plugins/project-orchestrator/runtime/assets.py +1288 -0
  93. package/runtime/plugins/project-orchestrator/runtime/case.py +557 -0
  94. package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +350 -0
  95. package/runtime/plugins/project-orchestrator/runtime/memory.py +363 -0
  96. package/runtime/plugins/project-orchestrator/runtime/project_base.py +107 -0
  97. package/runtime/plugins/project-orchestrator/runtime/project_cli.py +599 -0
  98. package/runtime/plugins/project-orchestrator/runtime/project_demo.py +504 -0
  99. package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +470 -0
  100. package/runtime/plugins/project-orchestrator/runtime/project_menu.py +394 -0
  101. package/runtime/plugins/project-orchestrator/runtime/project_real.py +522 -0
  102. package/runtime/plugins/project-orchestrator/runtime/project_write.py +329 -0
  103. package/runtime/plugins/project-orchestrator/runtime/wf.py +439 -0
  104. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +230 -0
  105. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +4 -0
  106. package/runtime/plugins/project-orchestrator/templates/CASE.md +39 -0
  107. /package/runtime/{agent_pack_config.py → plugins/agent-pack/runtime/agent_pack_config.py} +0 -0
  108. /package/runtime/{mcp_env_launcher.py → plugins/agent-pack/runtime/mcp_env_launcher.py} +0 -0
  109. /package/runtime/{mcp_server.py → plugins/agent-pack/runtime/mcp_server.py} +0 -0
@@ -0,0 +1,263 @@
1
+ # Opinion 变体、版本与个人 Profile
2
+
3
+ ## 身份与选择
4
+
5
+ - 模板系列 `family` 标识用途,例如 `work-email`。
6
+ - 模板变体 `variant` 标识同一用途下的平行偏好,例如 `formal-report`。各个变体维护独立版本。
7
+ - 模板引用为 `family/variant@VERSION`,规则引用为 `ID@VERSION`。版本使用无前导零的 `major.minor.patch`。省略版本、使用 `latest` 和版本范围都会被拒绝。
8
+ - 同一个系列在一份 Profile 中只能选择一个变体和版本。不同系列可以组合。
9
+ - 原子规则包含 `id`、`version`、`slot`、`title`、`level`、`scopes`、`text`。`level` 为 `required` 或 `preferred`。
10
+ - `slot` 表示一项行为选择。同一个有效 slot 出现两条规则会终止生成;相同规则和版本会去重,相同规则的不同版本会报告冲突。用于不同场景的独立选择应分配不同 slot。
11
+ - 模板包含 `family`、`variant`、`version`、`name`、`description`、`scopes` 和精确规则引用数组 `rules`。模板名称与说明用于帮助用户选择。
12
+
13
+ 本文中的示例名称说明数据格式;实际可用规则、系列、变体与版本以 `catalog` 输出为准。随包候选目录说明见 `../catalog/README.md`,全部模板均为可选择的偏好。
14
+
15
+ ## 文件与版本
16
+
17
+ 每个规则和模板版本分别保存在规则目录:
18
+
19
+ - `rules/ID/versions/VERSION.json`
20
+ - `templates/FAMILY/VARIANT/versions/VERSION.json`
21
+
22
+ 每个项目的个人 Profile 保存在 `.opinion/profiles/ID/versions/VERSION.json`。Profile 包含精确模板引用、直接选择的规则、完整来源快照、个人 overrides、自定义正文及派生来源指纹。旧版本保留。项目根目录保存完整的 `OPINION.md` 和 `opinion.lock.json`。
23
+
24
+ 锁定文件包含完整 Profile 快照、Profile 指纹、正文指纹和渲染格式版本。运行 Agent 时读取完整正文;Profile 声明了分层加载时,可以按「分层加载」一节读取核心规则与规则束,两种方式得到的规则文字逐字一致。重建和核验不需要访问模板仓库或其他项目的本机路径。相同 Profile 在相同渲染格式下生成相同正文,正文没有变化的生成时间。
25
+
26
+ 指纹使用 SHA-256:JSON 使用 UTF-8、按字段名称排序、无额外空格的规范序列化;正文使用原始 UTF-8 内容。调整 JSON 排版不会改变内容指纹。
27
+
28
+ `publish` 和 `profile` 只创建新版本,重复发布完全相同内容返回 `unchanged`;相同身份与版本的不同内容被拒绝。工具不会修改已发布文件。使用中发现已锁定来源被人工修改时,升级检查和修订都会终止。
29
+
30
+ 版本含义:
31
+
32
+ - patch:拼写、名称或说明等不改变行为的修订。工具检查规则正文、等级、适用范围和 slot;模板检查有效规则与适用范围。
33
+ - minor:符合既有目标的行为完善或新增规则;发布者必须审查兼容性,用户确认后才能采用。
34
+ - major:改变既有目标、删除约束或引入不兼容行为;发布者必须说明适用条件并重新验证。
35
+
36
+ 任何版本更新都需要预览与确认。工具可以校验结构和依赖;行为兼容性需要内容审查。
37
+
38
+ ## 通用命令约定
39
+
40
+ 以下命令在完整分发仓库根目录执行,要求 Python 3.11 及以上。独立安装插件时,将示例中的 `plugins/opinion-manager/runtime/opinion_manager.py` 替换为该插件实际目录下的 `runtime/opinion_manager.py`,将 `plugins/opinion-manager/catalog` 替换为该插件的 `catalog` 目录。`/path/to/project` 必须已经存在。使用用户明确指定的私有目录保存个人内容,公开仓库中不得保存个人 Profile、来源文件或私有规则目录。
41
+
42
+ 所有写入默认预览。预览 JSON 包含 `confirmation_sha256`;向用户展示完整内容并取得确认后,在相同命令后追加 `--apply --confirm SHA256`。用户批准的是规则内容,Agent 负责传递指纹。任何相关内容在批准后改变,都必须重新预览。
43
+
44
+ 查看公开规则目录:
45
+
46
+ ```bash
47
+ python3 plugins/opinion-manager/runtime/opinion_manager.py catalog \
48
+ --catalog plugins/opinion-manager/catalog --output json
49
+ ```
50
+
51
+ 目录为空时,使用自定义生成或跳过配置。
52
+
53
+ ## 自定义生成
54
+
55
+ 读取用户已有说明,整理后展示完整规则。用户确认后使用文件编辑工具保存到用户指定的私有文件。已有规则文件可以直接使用,无需重复复制。
56
+
57
+ 预览个人 Profile:
58
+
59
+ ```bash
60
+ python3 plugins/opinion-manager/runtime/opinion_manager.py profile \
61
+ --project /path/to/project --id private.my-opinion --version 1.0.0 \
62
+ --custom-file /path/to/confirmed-rules.md
63
+ ```
64
+
65
+ 将 Profile 预览的 profile 对象保存为项目私有候选 JSON,正式 Profile 此时尚未保存。先以候选路径预览完整正文与锁,再集中确认内容:
66
+
67
+ ```bash
68
+ python3 plugins/opinion-manager/runtime/opinion_manager.py compose \
69
+ --project /path/to/project \
70
+ --profile /path/to/private-preview/profile.json \
71
+ --output json
72
+ ```
73
+
74
+ 两份预览内容确认后,写入前重新核对两项指纹。先用 Profile 指纹保存正式 Profile,再把 compose 路径换成正式 Profile,用 compose 指纹保存正文与锁;完整步骤见 [首次使用说明](onboarding.md)。原有正文包含实际规则时,先读取并纳入 Profile,使用 `--replace` 重新预览。初始化器原样生成的空白占位内容可以直接替换。
75
+
76
+ 最后核验:
77
+
78
+ ```bash
79
+ python3 plugins/opinion-manager/runtime/opinion_manager.py verify --project /path/to/project
80
+ ```
81
+
82
+ 后续自定义内容修订使用 `profile --from-profile <原文件> --id <相同编号> --version <新版本> --custom-file <完整的新自定义正文>`。`--custom-file` 替换自定义段落;新文件必须包含仍需保留的全部自定义要求。
83
+
84
+ ## 简单模式与复杂模式
85
+
86
+ 已有批准的版本化模板时,`profile` 使用 `--catalog <目录>`,并重复传入 `--template family/variant@VERSION`。预览每个系列的变体与说明,用户选择后展示最终合并内容。
87
+
88
+ 复杂模式可以重复传入 `--rule ID@VERSION` 逐条选择。也可以选择完整模板,然后使用 `--overrides-file <JSON文件>` 停用其中未确认的条目。问答由 Agent 交互完成,未确认的推荐不进入 Profile。
89
+
90
+ overrides 是规则编号到内容的对象:值为 `null` 表示停用;值为对象时必须完整包含 `title`、`level`、`scopes`、`text`。只能修改当前来源中的规则,不能修改其编号和 slot。新增个人原子规则应在私有目录独立发布后通过 `--rule` 引入;个人自定义段落也可以表达新的要求。
91
+
92
+ 多个模板发布版本都保留时,`catalog` 展示全部版本和变体;工具不会替用户选择最新版本。
93
+
94
+ ## 首次配置的选择与确认
95
+
96
+ 首次只提供直接用模板、选择部分条目、从空白开始三个主入口,另可基于已授权记忆提出候选。使用 [首次使用说明](onboarding.md) 的流程,避免提前展开偏好问卷。
97
+
98
+ Profile 和 compose 可在正式保存前各自预览。用户一次确认完整正文、来源、个人修改、差异与拟保存内容;Agent 分别使用两项实际返回指纹。写入前重新预览核对,内容变化重新审阅;未解决冲突和 blocked 状态不得写入。两次写入独立保护,不保证系统事务。
99
+
100
+ 默认模板只作为预览建议,确认前不生效。用户选择部分条目时保留精确模板引用并通过 overrides 停用;直接原子规则的 Profile 仅追踪原子规则更新。每个系列只选一个变体,slot 冲突须集中裁决。
101
+
102
+ Agent 为适用范围与候选含义做判断;scopes 只提供元数据。工具不替 Agent 判断任务属于哪个场景,不检索记忆,也不在后台自动晋升;候选与使用次数可以用「候选、灰度与使用记录」一节的命令记账,裁决始终来自用户;Profile 声明分层加载后,工具只按用户确认的声明切分正文,并核对 Agent 声明的交付信号。新观察只有用户确认后才能形成新规则版本。用户从空白开始时不建立空版本,已有规则保持原样。
103
+
104
+ ## 分层加载
105
+
106
+ 规则较多时,可以在 Profile 中声明哪些模板系列始终读取、哪些在任务命中时再读取。声明是 Profile 的一部分,与规则一样经过完整预览、用户确认和指纹锁定;没有声明的 Profile 保持完整读取,行为与此前版本相同。
107
+
108
+ 声明文件是一个 JSON 对象,通过 `profile --loading-file <文件>` 整体写入:
109
+
110
+ ```json
111
+ {
112
+ "core": ["baseline"],
113
+ "signals": {
114
+ "artifact": ["report", "email", "other"],
115
+ "activity": ["rule-maintenance", "none"]
116
+ },
117
+ "bundles": [
118
+ {
119
+ "family": "work-email",
120
+ "trigger": "撰写或审查对外邮件",
121
+ "exclude": "只是在回复中引用邮件内容",
122
+ "routes": [{"artifact": ["email"]}]
123
+ }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ - `core` 列出始终读取的模板系列。直接选择的原子规则、用户自定义段,以及没有出现在声明中的模板系列,同样始终读取;新增模板不会因为漏登记而被跳过。
129
+ - `bundles` 列出按需读取的模板系列。`trigger` 与 `exclude` 是给 Agent 的适用与不适用说明,会原样出现在索引中。
130
+ - `signals` 是交付信号词表,`routes` 把信号映射到规则束:一条路由内的所有键都命中时该路由成立,任一路由成立即要求读取该规则束。
131
+ - 一条规则同时属于常驻系列和按需系列时归入常驻。带声明的 Profile 使用 `schema_version` 2.1,旧版本工具会拒绝读取并给出明确错误;不带声明的 Profile 仍为 2.0,指纹不变。
132
+
133
+ 修订时未提供 `--loading-file` 则沿用原声明;`--remove-loading` 移除声明并恢复完整读取。移除了声明中引用的模板系列时,必须同时提供新的声明文件。
134
+
135
+ 读取与核对使用只读的 `context` 命令:
136
+
137
+ ```bash
138
+ python3 plugins/opinion-manager/runtime/opinion_manager.py context core --project /path/to/project
139
+ python3 plugins/opinion-manager/runtime/opinion_manager.py context bundle work-email --project /path/to/project
140
+ python3 plugins/opinion-manager/runtime/opinion_manager.py context index --project /path/to/project
141
+ python3 plugins/opinion-manager/runtime/opinion_manager.py context check --project /path/to/project \
142
+ --signal artifact=email --loaded work-email
143
+ ```
144
+
145
+ - `core` 输出常驻规则、自定义段和规则束索引;`bundle` 输出指定规则束;`index` 以 JSON 给出各部分的规则数与字节数。
146
+ - `check` 由 Agent 在交付前声明本次交付的信号和已读取的规则束,工具返回应读取与缺失的规则束。缺失时退出码为 1。信号必须取自词表。
147
+ - 每次调用先核对锁与正文;核对失败、Profile 没有声明、或 Project Profile 的 `opinion.loading_mode` 设为 `full` 时,命令返回 `fallback`,调用方改为完整读取 `OPINION.md`。前者退出码为 2,后两者为 3。
148
+ - 保存前可用 `context core --profile <候选 Profile>` 预览切分结果,此时不读取项目锁。
149
+
150
+ 判断任务属于哪个场景、声明哪些信号,始终由 Agent 负责;判断不清时一并读取。维护规则本身的任务读取完整正文。
151
+
152
+ ## 候选、灰度与使用记录
153
+
154
+ `lifecycle` 命令在项目内的私有目录保存候选、证据快照、人工裁决和灰度使用记录。它只记账,不写 `OPINION.md`、规则目录或 Profile;每个项目各自维护状态,互不引用。
155
+
156
+ ```bash
157
+ python3 plugins/opinion-manager/runtime/opinion_manager.py lifecycle queue --project /path/to/project --store .opinion/lifecycle
158
+ python3 plugins/opinion-manager/runtime/opinion_manager.py lifecycle status --project /path/to/project --store .opinion/lifecycle
159
+ python3 plugins/opinion-manager/runtime/opinion_manager.py lifecycle event --project /path/to/project --store .opinion/lifecycle --file 项目内事件.json
160
+ ```
161
+
162
+ `event` 默认只预览,返回完整状态变化与 `confirmation_sha256`;追加 `--apply --confirm <指纹>` 才写入。指纹只核对操作完整性,`human_confirmed` 必须来自用户原话。事件与证据文件必须位于项目内,证据在写入时保留不可变副本;原文件之后可以继续增长,副本被改动则阻断后续操作。
163
+
164
+ 事件的 `kind` 与字段:
165
+
166
+ 1. `capture`:`id`、`content`(完整的 `title`、`text`、`scopes`)。同一编号的内容不可修改,修订使用新编号。
167
+ 2. `observe`:`id`、`occurrence`、`validation`(`pending`、`passed`、`failed`)、`fixture`。
168
+ 3. `presented`:`id`、`content_sha256`,证据须证明完整内容已展示给用户。
169
+ 4. `decision`:`id`、`decision`(`accepted`、`rejected`、`held`)、`human_confirmed: true`、`content_sha256`。`accepted` 只允许据此准备 Profile。
170
+ 5. `adopt`:`human_confirmed: true`、当前 `profile_sha256`。先通过 `verify`,随后该 Profile 版本记为灰度。
171
+ 6. `use`:`occurrence`、`rules`(当前 Profile 的精确规则引用)、`passed`、`user_changed`、`fixture`。可选字段:
172
+ - `results`:为每条规则给出 `passed`、`not-applicable` 或 `violated`;
173
+ - `loaded_bundles`:本次读取的按需规则束;
174
+ - `routing_miss`:本应读取却没有读取的规则束;
175
+ - `date`:`YYYY-MM-DD`,用于显示规则最近一次使用。
176
+ 7. `formalize`:`human_confirmed: true`、当前 `profile_sha256`。只改变上线标签,不改规则正文。
177
+
178
+ 计数规则:
179
+
180
+ - `occurrence` 是项目内工作记录的相对路径,按前两级目录归为一个独立场景,同一任务的多个阶段只计一次。默认接受 `tasks`、`cases`、`reviews`、`projects`、`journal` 五个顶层目录,可用 `--occurrence-root` 改写。
181
+ - `fixture: true` 的记录不计入任何成熟度。
182
+ - 候选每新增一次独立真实发生次数 n,推荐信用增加 n/(n+1),达到 1 时标记待推荐,展示后扣 1。出现 `failed` 的候选显示为待处理。这是默认参数,没有经过大量使用验证。
183
+ - `use` 未通过、用户改动了结果、或存在 `routing_miss` 时记为阻断;有阻断的版本不能转正式,须在新版本中解决。
184
+ - 至少两个独立的真实成功使用且无阻断时,`status` 标记 `ready_for_review`。它只表示可以提交复盘,不会自动转正式。
185
+
186
+ `status` 在当前正文通过核验时附带覆盖视图:每条规则的独立成功使用次数、违反次数与最近使用日期,从未使用的规则;Profile 声明了分层加载时,还包括每个规则束被读取和实际用到的次数、漏读次数,以及从未用到的规则束。复盘时据此判断「正式」覆盖到了哪些规则。
187
+
188
+ 同一 Profile 编号发布新版本后,旧版本的使用记录在其全部规则指纹未变时沿用到新版本,并计入独立成功使用次数;规则被修改或个人修订后,相关记录不再沿用。沿用需要读取旧版本的 Profile 文件并核对指纹,默认位置为项目的 `.opinion/profiles`,可用 `--profiles-root` 指定;找不到或指纹不符时不沿用,并在 `not_carried` 中列出。旧版本的阻断不沿用,但违反记录仍可在覆盖视图中看到。
189
+
190
+ `queue --titles` 只输出尚未裁决候选的标题与适用范围,便于在普通任务中留意是否出现了对应情形;候选全文、证据与次数只在维护规则时展开。
191
+
192
+ ## 私有发布与公开发布
193
+
194
+ 用户维护自己的版本化规则时,先创建私有规则目录,再使用文件编辑工具准备完整 JSON 文件,参考 [规则格式](rule.schema.json) 与 [版本化模板格式](versioned-template.schema.json)。禁止把个人内容写入本仓库。
195
+
196
+ ```bash
197
+ python3 plugins/opinion-manager/runtime/opinion_manager.py publish \
198
+ --catalog /path/to/private-catalog --file /path/to/release.json
199
+ ```
200
+
201
+ 先发布模板依赖的精确规则版本,再发布模板。发布模板时检查所有依赖和内部 slot 冲突。
202
+
203
+ 公开目录发布还需要 `--public-approved`。使用此标志前必须执行 Skill 中的公开准入检查、展示待公开文件的全部内容并取得用户明确同意。标志本身不能代替用户的授权。公开目录只接收逐文件批准的规则、模板及说明;审批记录、私有来源映射、个人 Profile、锁和运行日志不能随目录复制到公开包。
204
+
205
+ ## 修订、派生与升级
206
+
207
+ 只读查看当前变体的可用新版本:
208
+
209
+ ```bash
210
+ python3 plugins/opinion-manager/runtime/opinion_manager.py updates \
211
+ --catalog /path/to/catalog --profile /path/to/current-profile.json
212
+ ```
213
+
214
+ 模板内部规则由模板版本精确选择。模板依赖出现新的规则版本时,需要模板发布者发布新的模板版本;`updates` 显示当前模板和直接选择的规则的升级候选。
215
+
216
+ 预览升级:
217
+
218
+ ```bash
219
+ python3 plugins/opinion-manager/runtime/opinion_manager.py profile \
220
+ --catalog /path/to/catalog --project /path/to/project \
221
+ --from-profile /path/to/current-profile.json \
222
+ --id private.my-opinion --version 1.1.0 \
223
+ --template FAMILY/VARIANT@VERSION
224
+ ```
225
+
226
+ 同系列的新选择替换原选择,其他系列、直接规则和自定义内容继续保留。不同变体之间也可以通过同一命令切换。移除整项选择使用 `--remove-template FAMILY` 或 `--remove-rule ID`;模板内部的单条规则通过 overrides 停用。
227
+
228
+ 私有派生使用独立 Profile 编号,传入 `--from-profile` 并指定新的 `--id` 与版本。派生记录保留原 Profile 编号、版本和指纹;个人 overrides 归属自己的 Profile,公开来源保持原样。
229
+
230
+ 升级对每条个人 overrides 比较三个状态:当前来源快照、个人有效内容、新来源内容。上游改变行为且个人内容存在不同选择时,预览包含完整 `base`、`local`、`upstream`;`unresolved` 非空时禁止保存。
231
+
232
+ 用户确认冲突处理后,通过 `--resolutions-file` 提供规则编号到 `keep-local` 或 `use-upstream` 的映射;或者通过 `--overrides-file` 明确指定完整的新个人内容。处理文件由文件编辑工具写入。上游删除规则时,明确保留的个人规则继续留在 Profile 内。
233
+
234
+ 查看两个已经保存的 Profile 差异:
235
+
236
+ ```bash
237
+ python3 plugins/opinion-manager/runtime/opinion_manager.py compare \
238
+ --from-profile /path/to/old-profile.json --to-profile /path/to/new-profile.json
239
+ ```
240
+
241
+ 修订可先预览新 Profile 与 compose,再集中确认完整结果;两份工具指纹分别用于保存。升级检查、Profile 预览和 Profile 发布都不会直接改变 `OPINION.md`。
242
+
243
+ ## 人工编辑与写入中断
244
+
245
+ 人工编辑的正文继续作为环境的有效内容。`verify` 会报告正文与锁定文件的差异,`compose` 会保护当前文件。
246
+
247
+ 处理步骤:完整读取当前正文;整理用户保留的规则并纳入新 Profile;展示新正文与当前正文的差异;取得用户确认;使用 `--accept-current <当前正文SHA256>` 重新预览并确认写入。指纹可以用 `sha256sum /path/to/project/OPINION.md` 读取。错误指纹不会解除保护。
248
+
249
+ 正文和锁定文件分别使用同目录临时文件原子替换。操作系统中断可能造成两个文件不同步;工具不会声称这种状态已通过核验。读取当前两份文件并确认目标 Profile 后,采用相同的显式当前正文确认流程修复。工具不使用 Git 恢复项目文件。
250
+
251
+ 正文文件缺失且锁定文件完整时,可以用原锁定 Profile 预览并重建完全相同的正文。先恢复原版本的核验结果,再进行升级。
252
+
253
+ ## 验收与实际使用
254
+
255
+ 1. 运行本插件测试:`python3 -m unittest discover -s plugins/opinion-manager/tests -v`。测试在被 Git 和安装器忽略的 `__pycache__/opinion-test-work/` 中创建独立真实文件,使用无个人含义的测试标签。
256
+ 2. 在独立私有项目使用自定义 Profile,检查预览没有写入;确认后检查三种文件以及 `verify` 结果。
257
+ 3. 在独立私有规则目录检查同系列两个变体、同变体两个版本的展示与精确选择;检查不同系列的组合和 slot 冲突。
258
+ 4. 检查 `updates` 只报告候选,旧 Profile 继续生成相同正文;离开规则目录仍可使用 Profile 重建。
259
+ 5. 检查个人 overrides 与上游新版本产生三方冲突,未确认时保存被拒绝;确认保留个人内容或采用上游内容后核验通过。
260
+ 6. 人工修改正文,确认 `verify` 报告差异且 `compose` 保留原文件;按导入流程生成新版本后再次核验。
261
+ 7. 用户确认整个流程后,分别整理生活项目与工作项目的私有规则;每个项目保留一份包含全部适用要求的完整正文。公开模板须单独通过内容审批。
262
+
263
+ 本版本管理已经确认的规则。Agent 可根据已授权记录提出私有候选,用户确认后才发布新版本;候选次数、灰度使用与覆盖情况由 `lifecycle` 命令在项目私有目录记账,工具不会自动晋升候选或把灰度版本转为正式。
@@ -0,0 +1,7 @@
1
+ KNOWN_PLACEHOLDERS = (
2
+ '# 项目 Opinion 覆盖层\n\n尚未配置 Opinion。\n\n使用 `opinion-manager` 选择直接用模板、选择部分条目或从空白开始;也可基于已授权记忆提出候选。完整预览经用户确认后生效,个人内容只写入当前项目。\n',
3
+ "# 项目 Opinion 覆盖层\n\n尚未配置 Opinion。\n\n"
4
+ "使用 `opinion-manager` 选择自定义生成、已批准模板组合、逐条引导或跳过。"
5
+ "公开模板目录可以为空;用户提供的内容只写入当前项目,禁止复制进公开模板。\n",
6
+ "# 项目 Opinion 覆盖层\n\n尚未配置 Opinion。\n",
7
+ )
@@ -0,0 +1,180 @@
1
+ """按 Profile 中用户确认的加载声明切分完整正文;只读,不写任何文件。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import versioned
11
+
12
+
13
+ FALLBACK = "完整读取项目根目录 OPINION.md"
14
+ PROJECT_PROFILE = Path(".agents") / "moe.sakanano.agent-pack" / "project.json"
15
+ DIRECT = "(direct)"
16
+ EXIT_OK, EXIT_MISSING, EXIT_ERROR, EXIT_FULL = 0, 1, 2, 3
17
+
18
+
19
+ class FullReading(RuntimeError):
20
+ """当前契约本身要求完整读取,调用方按 fallback 执行即可。"""
21
+
22
+
23
+ def project_mode(root: Path) -> str:
24
+ path = root / PROJECT_PROFILE
25
+ if not path.is_file():
26
+ return "tiered"
27
+ mode = versioned.read_object(path).get("opinion", {}).get("loading_mode", "tiered")
28
+ if mode not in ("tiered", "full"):
29
+ versioned.fail(f"Project Profile 的 opinion.loading_mode 必须为 tiered 或 full:{mode!r}")
30
+ return mode
31
+
32
+
33
+ def load(args: argparse.Namespace) -> tuple[dict[str, Any], dict[str, Any] | None]:
34
+ """返回 Profile 与锁;使用候选 Profile 预览时锁为 None。"""
35
+ if args.profile is not None:
36
+ profile, lock = versioned.load_profile(args.profile), None
37
+ else:
38
+ if args.project is None:
39
+ versioned.fail("context 需要 --project 或 --profile")
40
+ root = versioned.safe_path(args.project)
41
+ versioned.verify_project(root)
42
+ lock = versioned.read_object(root / "opinion.lock.json")
43
+ profile = lock["profile"]
44
+ if project_mode(root) == "full":
45
+ raise FullReading("Project Profile 设为完整读取")
46
+ if "loading" not in profile:
47
+ raise FullReading("当前 Profile 未声明分层加载")
48
+ return profile, lock
49
+
50
+
51
+ def partition(profile: dict[str, Any]) -> dict[str, Any]:
52
+ """把每条有效规则分到唯一的模板系列;常驻系列优先,未登记的系列按常驻处理。"""
53
+ loading = profile["loading"]
54
+ rules = versioned.effective_rules(profile)
55
+ routed = [bundle["family"] for bundle in loading["bundles"]]
56
+ templates = [(source["ref"], source["ref"].split("/")[0], source["value"]) for source in profile["sources"]["templates"]]
57
+ unlisted = [family for _, family, _ in templates if family not in loading["core"] and family not in routed]
58
+ always = set(loading["core"]) | set(unlisted)
59
+ assigned: dict[str, str] = {}
60
+ for wanted in (True, False):
61
+ for _, family, value in templates:
62
+ if (family in always) is not wanted:
63
+ continue
64
+ for ref in value["rules"]:
65
+ identity = versioned.parse_ref(ref, "rules")[0]
66
+ if identity in rules:
67
+ assigned.setdefault(identity, family)
68
+ groups: dict[str, list[str]] = {}
69
+ for identity in rules:
70
+ groups.setdefault(assigned.get(identity, DIRECT), []).append(identity)
71
+ return {
72
+ "rules": rules, "groups": groups, "unlisted": unlisted,
73
+ "refs": {family: ref for ref, family, _ in templates},
74
+ "names": {family: value["name"] for _, family, value in templates},
75
+ "always": [family for family in (*loading["core"], *unlisted, DIRECT) if family in groups],
76
+ "bundles": [bundle for bundle in loading["bundles"] if bundle["family"] in groups],
77
+ }
78
+
79
+
80
+ def group_text(profile: dict[str, Any], parts: dict[str, Any], family: str) -> str:
81
+ return "\n\n".join(versioned.render_rule(profile, parts["rules"][identity]) for identity in parts["groups"].get(family, []))
82
+
83
+
84
+ def core_text(profile: dict[str, Any], lock: dict[str, Any] | None) -> str:
85
+ parts = partition(profile)
86
+ origin = f"正文指纹:`{lock['opinion_sha256']}`" if lock else "候选 Profile 预览,尚未生效"
87
+ lines = ["# Agent Opinion 核心规则", "", f"Profile:`{profile['id']}@{profile['version']}`;{origin}。",
88
+ "本输出是完整正文的只读切片,始终适用。场景规则束见文末索引,命中时再读取。"]
89
+ for family in parts["always"]:
90
+ lines.extend(("", group_text(profile, parts, family)))
91
+ if profile["custom"]:
92
+ lines.extend(("", "## 用户自定义规则", "", profile["custom"]))
93
+ lines.extend(("", "## 规则束索引", "", "任务命中触发条件时用 `context bundle <系列>` 读取对应规则束;判断不清时一并读取。"))
94
+ for bundle in parts["bundles"]:
95
+ family = bundle["family"]
96
+ lines.append(f"- `{family}`({parts['names'][family]},{len(parts['groups'][family])} 条):{bundle['trigger']}。不适用:{bundle['exclude']}。")
97
+ return "\n".join(lines).rstrip() + "\n"
98
+
99
+
100
+ def resolve_bundle(parts: dict[str, Any], name: str) -> str:
101
+ family = {ref: family for family, ref in parts["refs"].items()}.get(name, name)
102
+ if family not in {bundle["family"] for bundle in parts["bundles"]}:
103
+ versioned.fail(f"未知的按需规则束:{name}")
104
+ return family
105
+
106
+
107
+ def bundle_text(profile: dict[str, Any], names: list[str]) -> str:
108
+ if not names:
109
+ versioned.fail("bundle 需要至少一个模板系列或精确模板引用")
110
+ parts = partition(profile)
111
+ lines: list[str] = []
112
+ for name in names:
113
+ family = resolve_bundle(parts, name)
114
+ lines.extend((f"# 规则束 `{parts['refs'][family]}`", "", group_text(profile, parts, family), ""))
115
+ return "\n".join(lines).rstrip() + "\n"
116
+
117
+
118
+ def index_view(profile: dict[str, Any], lock: dict[str, Any] | None) -> dict[str, Any]:
119
+ parts = partition(profile)
120
+
121
+ def entry(family: str) -> dict[str, Any]:
122
+ return {"family": family, "ref": parts["refs"].get(family), "name": parts["names"].get(family),
123
+ "rules": len(parts["groups"][family]), "bytes": len(group_text(profile, parts, family).encode("utf-8"))}
124
+
125
+ return {
126
+ "ok": True, "profile": f"{profile['id']}@{profile['version']}", "profile_sha256": versioned.digest(profile),
127
+ "opinion_sha256": lock["opinion_sha256"] if lock else None,
128
+ "core": [entry(family) for family in parts["always"]],
129
+ "custom_bytes": len((profile["custom"] or "").encode("utf-8")),
130
+ "bundles": [{**entry(bundle["family"]), "trigger": bundle["trigger"], "exclude": bundle["exclude"], "routes": bundle["routes"]} for bundle in parts["bundles"]],
131
+ "unlisted": parts["unlisted"], "signals": profile["loading"]["signals"],
132
+ "core_bytes": len(core_text(profile, lock).encode("utf-8")), "complete_bytes": len(versioned.render(profile).encode("utf-8")),
133
+ }
134
+
135
+
136
+ def check(profile: dict[str, Any], signals: list[str], loaded: list[str]) -> dict[str, Any]:
137
+ parts = partition(profile)
138
+ vocabulary = profile["loading"]["signals"]
139
+ declared: dict[str, set[str]] = {}
140
+ for item in signals:
141
+ key, _, value = item.partition("=")
142
+ if key not in vocabulary or value not in vocabulary[key]:
143
+ versioned.fail(f"未声明的交付信号:{item}")
144
+ declared.setdefault(key, set()).add(value)
145
+ if not declared:
146
+ versioned.fail("check 至少需要一个 --signal")
147
+ have = {resolve_bundle(parts, name) for name in loaded}
148
+ required = [bundle["family"] for bundle in parts["bundles"]
149
+ if any(all(declared.get(key, set()) & set(values) for key, values in route.items()) for route in bundle["routes"])]
150
+ missing = [family for family in required if family not in have]
151
+ return {"ok": not missing, "signals": {key: sorted(values) for key, values in declared.items()},
152
+ "required": required, "loaded": sorted(have), "missing": missing}
153
+
154
+
155
+ def add_command(subparsers: Any) -> None:
156
+ parser = subparsers.add_parser("context", help="按已确认的加载声明只读切分正文,并核对交付信号")
157
+ parser.add_argument("action", choices=("core", "bundle", "index", "check"))
158
+ parser.add_argument("names", nargs="*", help="bundle 的模板系列或精确模板引用")
159
+ parser.add_argument("--project", type=Path)
160
+ parser.add_argument("--profile", type=Path, help="用候选或已保存的 Profile 预览,不读取项目锁")
161
+ parser.add_argument("--signal", action="append", default=[], help="交付信号,格式为 键=值,可重复")
162
+ parser.add_argument("--loaded", action="append", default=[], help="本次已读取的规则束,可重复")
163
+ parser.add_argument("--output", choices=("json", "markdown"), default="markdown")
164
+
165
+
166
+ def execute(args: argparse.Namespace) -> tuple[int, str]:
167
+ try:
168
+ profile, lock = load(args)
169
+ if args.action == "core":
170
+ return EXIT_OK, core_text(profile, lock)
171
+ if args.action == "bundle":
172
+ return EXIT_OK, bundle_text(profile, args.names)
173
+ if args.action == "index":
174
+ return EXIT_OK, versioned.pretty(index_view(profile, lock))
175
+ result = check(profile, args.signal, args.loaded)
176
+ return (EXIT_OK if result["ok"] else EXIT_MISSING), versioned.pretty(result)
177
+ except FullReading as exc:
178
+ return EXIT_FULL, versioned.pretty({"ok": False, "error": str(exc), "fallback": FALLBACK})
179
+ except (versioned.OpinionVersionError, OSError, UnicodeDecodeError, json.JSONDecodeError, KeyError, TypeError) as exc:
180
+ return EXIT_ERROR, versioned.pretty({"ok": False, "error": str(exc), "fallback": FALLBACK})