@givolt/i3-composer-agent 3.1.0 → 3.2.1
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/.agents/plugins/marketplace.json +1 -1
- package/.claude/commands/i3-sop.md +3 -3
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/i3-sop.toml +3 -3
- package/README.md +22 -27
- package/commands/i3-sop.toml +3 -3
- package/docs/codex-setup.md +1 -1
- package/docs/other-hosts.md +1 -1
- package/docs/workbuddy-setup.md +1 -1
- package/evals/cases/i3-enterprise-sop.json +70 -32
- package/evals/zh-CN/cases/i3-enterprise-sop.json +77 -31
- package/package.json +1 -23
- package/plugin.json +1 -1
- package/skills/i3-enterprise-sop/SKILL.md +151 -91
- package/skills/i3-enterprise-sop/references/engineering-skeleton/directory-standard.md +29 -15
- package/skills/i3-enterprise-sop/references/handoff.py +171 -100
- package/skills/i3-enterprise-sop/references/read_text.py +60 -0
- package/skills/i3-enterprise-sop/references/technical-baseline/baselines/enterprise-application-technical-baseline-v0-0-4.md +28 -28
- package/skills/i3-enterprise-sop/references/technical-baseline/readme.md +9 -118
- package/skills/i3-enterprise-sop/references/{ad/templates → templates}/ad.md +34 -27
- package/skills/i3-enterprise-sop/references/{brd/templates → templates}/brd.md +21 -10
- package/skills/i3-enterprise-sop/references/{prd/templates → templates}/prd.md +57 -24
- package/skills/i3-enterprise-sop/references/{testcase/templates → templates}/testcase.md +32 -22
- package/docs/upstream-repository/AGENTS.md +0 -92
- package/docs/upstream-repository/CLAUDE.md +0 -60
- package/docs/upstream-repository/CONTRIBUTING.md +0 -137
- package/docs/upstream-repository/README.md +0 -8
- package/docs/upstream-repository/gitattributes.txt +0 -1
- package/docs/upstream-repository/gitignore.txt +0 -10
- package/docs/upstream-repository/manifest.json +0 -71
- package/docs/upstream-repository/opencode-skills.link +0 -1
- package/docs/upstream-repository/skill-gap.yml +0 -80
- package/docs/upstream-repository/skills-contributing.md +0 -15
- package/docs/upstream-repository/test-plugin-install.yml +0 -167
- package/skills/i3-enterprise-sop/references/ad/assets-manifest.json +0 -86
- package/skills/i3-enterprise-sop/references/ad/prompts/01-generate.md +0 -58
- package/skills/i3-enterprise-sop/references/ad/prompts/02-review.md +0 -54
- package/skills/i3-enterprise-sop/references/ad/prompts/03-revise.md +0 -54
- package/skills/i3-enterprise-sop/references/ad/prompts/04-recheck.md +0 -54
- package/skills/i3-enterprise-sop/references/ad/readme.md +0 -70
- package/skills/i3-enterprise-sop/references/assets-manifest.json +0 -230
- package/skills/i3-enterprise-sop/references/brd/assets-manifest.json +0 -83
- package/skills/i3-enterprise-sop/references/brd/prompts/01-generate.md +0 -51
- package/skills/i3-enterprise-sop/references/brd/prompts/02-review.md +0 -47
- package/skills/i3-enterprise-sop/references/brd/prompts/03-revise.md +0 -47
- package/skills/i3-enterprise-sop/references/brd/prompts/04-recheck.md +0 -47
- package/skills/i3-enterprise-sop/references/brd/readme.md +0 -50
- package/skills/i3-enterprise-sop/references/codebuddy-project-guide.md +0 -34
- package/skills/i3-enterprise-sop/references/controlled-handoff.md +0 -41
- package/skills/i3-enterprise-sop/references/controlled_handoff.py +0 -379
- package/skills/i3-enterprise-sop/references/engineering-skeleton/adoption.md +0 -200
- package/skills/i3-enterprise-sop/references/engineering-skeleton/catalog.json +0 -17
- package/skills/i3-enterprise-sop/references/engineering-skeleton/readme.md +0 -52
- package/skills/i3-enterprise-sop/references/handoff-contract.md +0 -196
- package/skills/i3-enterprise-sop/references/lifecycle-contract.md +0 -91
- package/skills/i3-enterprise-sop/references/plan-handoff.md +0 -73
- package/skills/i3-enterprise-sop/references/prd/assets-manifest.json +0 -109
- package/skills/i3-enterprise-sop/references/prd/prompts/01-generate.md +0 -64
- package/skills/i3-enterprise-sop/references/prd/prompts/02-review.md +0 -49
- package/skills/i3-enterprise-sop/references/prd/prompts/03-revise.md +0 -48
- package/skills/i3-enterprise-sop/references/prd/prompts/04-recheck.md +0 -47
- package/skills/i3-enterprise-sop/references/prd/readme.md +0 -137
- package/skills/i3-enterprise-sop/references/prd/template-contracts.md +0 -39
- package/skills/i3-enterprise-sop/references/prd/templates/ad.md +0 -17
- package/skills/i3-enterprise-sop/references/readme.md +0 -167
- package/skills/i3-enterprise-sop/references/recovery-contract.md +0 -65
- package/skills/i3-enterprise-sop/references/review-handoff.md +0 -105
- package/skills/i3-enterprise-sop/references/selection-handoff.md +0 -55
- package/skills/i3-enterprise-sop/references/selection_handoff.py +0 -225
- package/skills/i3-enterprise-sop/references/stages.json +0 -271
- package/skills/i3-enterprise-sop/references/technical-baseline/assets-manifest.json +0 -84
- package/skills/i3-enterprise-sop/references/template-contracts.md +0 -142
- package/skills/i3-enterprise-sop/references/testcase/assets-manifest.json +0 -91
- package/skills/i3-enterprise-sop/references/testcase/prompts/01-generate.md +0 -53
- package/skills/i3-enterprise-sop/references/testcase/prompts/02-review.md +0 -51
- package/skills/i3-enterprise-sop/references/testcase/prompts/03-revise.md +0 -49
- package/skills/i3-enterprise-sop/references/testcase/prompts/04-recheck.md +0 -51
- package/skills/i3-enterprise-sop/references/testcase/readme.md +0 -61
- package/skills/i3-enterprise-sop/references/verification/check_execution.py +0 -185
- package/skills/i3-enterprise-sop/references/verification/check_native_execution.py +0 -282
- package/skills/i3-enterprise-sop/references/verification/check_reads.py +0 -348
- package/skills/i3-enterprise-sop/references/verification/readme.md +0 -149
- package/skills/i3-enterprise-sop/references/verification/review-output.schema.json +0 -155
- package/skills/i3-enterprise-sop/references/verification/review_handoff.py +0 -329
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:i3:i3-enterprise-sop:review-output:v1",
|
|
4
|
-
"title": "企业 SOP 只读文档审查会话输出 v1",
|
|
5
|
-
"description": "只约束审查报告结构;不是业务 handoff.json、批准或测试结果。",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"required": [
|
|
8
|
-
"verdict",
|
|
9
|
-
"findings",
|
|
10
|
-
"checkedFacts",
|
|
11
|
-
"unread"
|
|
12
|
-
],
|
|
13
|
-
"additionalProperties": false,
|
|
14
|
-
"properties": {
|
|
15
|
-
"verdict": {
|
|
16
|
-
"type": "string",
|
|
17
|
-
"enum": [
|
|
18
|
-
"交人工审阅",
|
|
19
|
-
"可交人工审阅",
|
|
20
|
-
"需修订",
|
|
21
|
-
"无法判断"
|
|
22
|
-
]
|
|
23
|
-
},
|
|
24
|
-
"findings": {
|
|
25
|
-
"type": "array",
|
|
26
|
-
"items": {
|
|
27
|
-
"$ref": "#/$defs/finding"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"checkedFacts": {
|
|
31
|
-
"type": "array",
|
|
32
|
-
"minItems": 1,
|
|
33
|
-
"items": {
|
|
34
|
-
"$ref": "#/$defs/fact"
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"unread": {
|
|
38
|
-
"type": "array",
|
|
39
|
-
"items": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"minLength": 1
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
"$defs": {
|
|
46
|
-
"reference": {
|
|
47
|
-
"type": "object",
|
|
48
|
-
"required": [
|
|
49
|
-
"path",
|
|
50
|
-
"startLine",
|
|
51
|
-
"quote"
|
|
52
|
-
],
|
|
53
|
-
"additionalProperties": false,
|
|
54
|
-
"properties": {
|
|
55
|
-
"path": {
|
|
56
|
-
"type": "string",
|
|
57
|
-
"minLength": 1
|
|
58
|
-
},
|
|
59
|
-
"startLine": {
|
|
60
|
-
"type": "integer",
|
|
61
|
-
"minimum": 1
|
|
62
|
-
},
|
|
63
|
-
"endLine": {
|
|
64
|
-
"type": "integer",
|
|
65
|
-
"minimum": 1
|
|
66
|
-
},
|
|
67
|
-
"quote": {
|
|
68
|
-
"type": "string",
|
|
69
|
-
"minLength": 1
|
|
70
|
-
},
|
|
71
|
-
"role": {
|
|
72
|
-
"type": "string",
|
|
73
|
-
"minLength": 1,
|
|
74
|
-
"description": "可选的机器来源标识。实际作业必须由build_review_schema(scope)注入sourceRoles键的枚举;普通资料引用省略role,资料性质写入reason或conclusion。"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
"finding": {
|
|
79
|
-
"type": "object",
|
|
80
|
-
"required": [
|
|
81
|
-
"id",
|
|
82
|
-
"severity",
|
|
83
|
-
"target",
|
|
84
|
-
"source",
|
|
85
|
-
"reason",
|
|
86
|
-
"suggestion"
|
|
87
|
-
],
|
|
88
|
-
"additionalProperties": false,
|
|
89
|
-
"properties": {
|
|
90
|
-
"id": {
|
|
91
|
-
"type": "string",
|
|
92
|
-
"minLength": 1
|
|
93
|
-
},
|
|
94
|
-
"severity": {
|
|
95
|
-
"type": "string",
|
|
96
|
-
"enum": [
|
|
97
|
-
"low",
|
|
98
|
-
"medium",
|
|
99
|
-
"high",
|
|
100
|
-
"critical"
|
|
101
|
-
]
|
|
102
|
-
},
|
|
103
|
-
"kind": {
|
|
104
|
-
"type": "string",
|
|
105
|
-
"enum": [
|
|
106
|
-
"contradiction",
|
|
107
|
-
"evidence-gap",
|
|
108
|
-
"clarification",
|
|
109
|
-
"optional"
|
|
110
|
-
]
|
|
111
|
-
},
|
|
112
|
-
"target": {
|
|
113
|
-
"$ref": "#/$defs/reference"
|
|
114
|
-
},
|
|
115
|
-
"source": {
|
|
116
|
-
"$ref": "#/$defs/reference"
|
|
117
|
-
},
|
|
118
|
-
"reason": {
|
|
119
|
-
"type": "string",
|
|
120
|
-
"minLength": 1
|
|
121
|
-
},
|
|
122
|
-
"suggestion": {
|
|
123
|
-
"type": "string",
|
|
124
|
-
"minLength": 1
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
"fact": {
|
|
129
|
-
"type": "object",
|
|
130
|
-
"required": [
|
|
131
|
-
"id",
|
|
132
|
-
"target",
|
|
133
|
-
"source",
|
|
134
|
-
"conclusion"
|
|
135
|
-
],
|
|
136
|
-
"additionalProperties": false,
|
|
137
|
-
"properties": {
|
|
138
|
-
"id": {
|
|
139
|
-
"type": "string",
|
|
140
|
-
"minLength": 1
|
|
141
|
-
},
|
|
142
|
-
"target": {
|
|
143
|
-
"$ref": "#/$defs/reference"
|
|
144
|
-
},
|
|
145
|
-
"source": {
|
|
146
|
-
"$ref": "#/$defs/reference"
|
|
147
|
-
},
|
|
148
|
-
"conclusion": {
|
|
149
|
-
"type": "string",
|
|
150
|
-
"minLength": 1
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""One explicit CodeBuddy review/recheck in a fresh read-only tool context.
|
|
3
|
-
|
|
4
|
-
Evidence goes outside the project. This is not a workflow scheduler, a business
|
|
5
|
-
approval mechanism, or an operating-system filesystem sandbox.
|
|
6
|
-
"""
|
|
7
|
-
import argparse
|
|
8
|
-
import copy
|
|
9
|
-
import hashlib
|
|
10
|
-
import importlib.util
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
|
-
from pathlib import Path
|
|
14
|
-
import shutil
|
|
15
|
-
import subprocess
|
|
16
|
-
import sys
|
|
17
|
-
import time
|
|
18
|
-
import uuid
|
|
19
|
-
|
|
20
|
-
sys.dont_write_bytecode = True
|
|
21
|
-
HERE = Path(__file__).resolve().parent
|
|
22
|
-
RESOURCES = HERE.parent
|
|
23
|
-
VERSION = '0.2.0'
|
|
24
|
-
# Filled from the reviewed source and its existing branding transform. These are
|
|
25
|
-
# compatibility fingerprints, not a lock on business documents or project edits.
|
|
26
|
-
CONTEXT_BASELINES = {"readme.md":["c4a7a2cdd5f81612ec58f15a24c682ceb329820b1b36b9becdaec3dcba2c46dc","79da8b70f3975046a35cd464cb690a4f2f87290dce41b980c74208926d97a200"],"ad/readme.md":["70b8e0b39b3e8c2ee6f1123fd0f57a10d9848c697ab43240970414b7f79a0246","0052fe43876055749d6988f9ad246c08bdfa3929985bcb53cc9d89e063ba0056"],"ad/assets-manifest.json":["952a14ca0e675d6faca73ba751cda6695b3244bbff37e5e371bae8574fcaa431","7b6e771687f4a50da500cca262620235d2c6171e71c41a227e7413619a3f5703"],"ad/prompts/02-review.md":["35178174982907ee623fa89a8f69cb7fc20bfbcef98b0c33a14c861e7dffd5ad","11a5b03a62ff7e78cff62881e3e22c2b14b5f65024264cb97c8893f8b47f228d"],"ad/prompts/04-recheck.md":["702e3c221a4b5e688eb13dccd9edc4bf8ccbe2f7b8b7aa2ca8c1af46434806e8","239031036b1877a88deb221355c39dd0dd25894b464487be73a718ebe227cdd8"],"ad/templates/ad.md":["0e48c7149792aede3b5c7be70b65b7e4f9b312889760f2fb4105d184162abeae","37d3f77efe23b85ebaf50cfad09d92344227d4f778ea7e965c4fea70d89817bb"],"brd/readme.md":["ea267d7072a0b257b82c79fe3524d3d13dd9360c7485e77edcde7dba3631d3b8","b0ac06d121b4fa34a3cdbaa4a43fd453fa08e20ead81679c2d90d1afaf865717"],"brd/assets-manifest.json":["5925d5f7e402602d81208a82311e505ea79e6a32edd6e1afa78ab20db40c9f5c","f4178f99b5210645dd2b03f5af2f0bfd839c8a8ae415f97be36347c217dfda1e"],"brd/prompts/02-review.md":["d93e6123e1149fad579dabf82de32bd4172a5fb1da19fb8ca4e6f3c791d0543e","45971e0896c80253f92de03de1246cd2ee0edafbe411144e72be8665916b35d0"],"brd/prompts/04-recheck.md":["573a81502f554a05c78c25e55385b67116c79d67914e4b235d766d4fe483881d","422442b1491457e8e05d1b1ec3df240d835fdce817ac2fb8eb324d813f52d7bb"],"brd/templates/brd.md":["2c9c4061e720877e984c6094232f797ebdf0380b8ff27693ac8d930fe648c47c","777f408fb596ec5f90ea8507d3b59974df11dde647df7edef070b30ee6f380af"],"handoff-contract.md":["0a38d6858e298f14e1a8cb07b3f67649f450cfb47e894d19835a014185c3df53","47337cadc2060a8ef24f2d00ea88ee712e47ac0ac7390dcaa7953263a666d110"],"lifecycle-contract.md":["0fd7446bf53b43795dcbde03832c7d8a1e7680ee53b8c1898185da8aa8a444c0","479a6ea40077594229fbddc8971ad92a73aad6a0f83b65ae6c0bf7d06b566a8b"],"prd/readme.md":["60603eaa38487fa2eb6ead60378ba4f832830c2dafc4fee612dac3a0679a05e3","c9667e22973d6c7b2a5e14d8fa6311fd99192a46404339e9cab5d044fc6038da"],"prd/assets-manifest.json":["efba4a5c287f89cde8a6f621b17faddf443577fe9afb60f9e7d3ce314c815381","b0d0e3e33b799ab26409a6c97c9230e485039df230001f185734209d3ec2db95"],"prd/prompts/02-review.md":["83b03378d7dec4a18217571e777b715a8aba02443ac453f3a5e118f114882f09","03278975fb19b69d9023ec834c450322582d92672422f4b04e555cac8472b07e"],"prd/prompts/04-recheck.md":["33f6d6f20b2c08daaf6ec833fd775c3ad73fa70803c7e386f8f27395e46521ae","bb457ebaadf55edd0aa913c4228b66786d2727c1b91d81abd4e8c3a2e5e22638"],"prd/templates/prd.md":["1c728d4865ae1ad3936a735d3543c21f4a06fd195f7f94c5ac721d560ca10aee","22f7b09d1b9914975d602e24f39ce7e63d945493f55a4654b6484a8569b73a8a"],"review-handoff.md":["ebe62b4a7d50bfd7a54dcff6897686a2fd3736302242a9144b6281ddff6085cb","a04238ea42eda2c9ed198a20917f589a53ee3a301472b312ffa60a68614115e3"],"stages.json":["e25f2ae57486912df3f4505d4af4b813e49dcb06998ae4480c5b7267a5e06ae0","ccf1cea6e4f2efc5a487f41915387c520af3792dcf20b5194ff97f8aec934942"],"template-contracts.md":["43c9fccc2319f476514df2c14a4520941b1176f77ef27a72022458db61aaee2b","b9b6ee9d936b7745a88616e5ffd77992fead16d89092b34147ab2c33237397ad"],"testcase/readme.md":["0919de2fa32c3c66383b357b1dc3e0142da2c7d07efbe943e9c34dfd6db9827c","4c24ab0f434e414afd94437cc5caf8bd27e454eb832335eb9d97c1ea702bcb0c"],"testcase/assets-manifest.json":["c9e017fd85b7e2382748cc4bd4017a0c9c62c6c05fc4b94a211def1b264dd95d","c3495698bff33906f4e458e9d09dba7ea984cf9964fc38d48d8954f312b370c6"],"testcase/prompts/02-review.md":["eb78b1c515e720be6910ae7d08d0f3833959e9684159dfb978046ae266c93fd6","7b8d6bc672bba40f191bc154efe48971024f988e990c4a5f9b0f3e2a9a8b3a26"],"testcase/prompts/04-recheck.md":["18555bad45f167ee121310b4e627549317d5bdeddf48596c16e05f8a98121b50","3d81f78a3a18a76abed8e18e2771c64063b830c6c1aec10c2a454257201da758"],"testcase/templates/testcase.md":["9b3d04762a37ca05f99567b2f1beb371f0d10a65e1c7ad1bdebbe6c9af71b914","ffb317bb73397a350fbd0b98fa4bcb6c03773c223dd06fcf6c94420cba19cf6a"],"verification/readme.md":["2ce5c4984bd2da9b1c3b451eeecf79fc50b189bea32844bb337592a86032b04c","fa39cdc3639911676397e891ed85d11558abf36ff8f0397384dd9b76d655c4a3"],"verification/review-output.schema.json":["366dcfe39d5dca6a99b6eff5af400f288ed164cca04e28de316b36cadcfc42b1","70e2e5c66c72fc52193310a5b52f43faaabda192619baa365751c398b4a4d944"]} # s10-fixed-context-baselines
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def module(name, path):
|
|
30
|
-
spec = importlib.util.spec_from_file_location(name, path)
|
|
31
|
-
value = importlib.util.module_from_spec(spec)
|
|
32
|
-
spec.loader.exec_module(value)
|
|
33
|
-
return value
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
reads = module('s09_read_evidence', HERE / 'check_reads.py')
|
|
37
|
-
native = module('s09_native_evidence', HERE / 'check_native_execution.py')
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def save(path, value):
|
|
41
|
-
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + '\n')
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def file_at(base, raw, boundary=None):
|
|
45
|
-
if not isinstance(raw, str) or not raw.strip():
|
|
46
|
-
raise ValueError('input_path_missing')
|
|
47
|
-
path = (base / raw).resolve()
|
|
48
|
-
if not path.is_relative_to(boundary or base):
|
|
49
|
-
raise ValueError('outside_project_or_selected_resource:' + raw)
|
|
50
|
-
if not path.is_file():
|
|
51
|
-
raise ValueError('input_unreadable:' + raw)
|
|
52
|
-
path.read_text() # Fail before launch on unreadable or non-text input.
|
|
53
|
-
return path
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def fingerprint(path):
|
|
57
|
-
return {'sha256': hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
58
|
-
'mode': path.stat().st_mode & 0o777}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def prepare(project_root, request, *, resource_root=RESOURCES, session_id=None):
|
|
62
|
-
root, resources = Path(project_root).resolve(), Path(resource_root).resolve()
|
|
63
|
-
if not root.is_dir():
|
|
64
|
-
raise ValueError('project_root_unavailable')
|
|
65
|
-
if request.get('action') not in ('review', 'recheck'):
|
|
66
|
-
raise ValueError('review_action_required')
|
|
67
|
-
action, stage = request['action'], request.get('stage')
|
|
68
|
-
if stage not in ('brd', 'prd', 'ad', 'testcase'):
|
|
69
|
-
raise ValueError('explicit_document_stage_required')
|
|
70
|
-
target = file_at(root, request.get('target'))
|
|
71
|
-
sources = request.get('sources')
|
|
72
|
-
if not isinstance(sources, list) or not sources:
|
|
73
|
-
raise ValueError('explicit_business_sources_required')
|
|
74
|
-
sources = [file_at(root, raw) for raw in sources]
|
|
75
|
-
if target in sources:
|
|
76
|
-
raise ValueError('target_cannot_be_its_own_business_source')
|
|
77
|
-
authors = request.get('authorSessionIds', [])
|
|
78
|
-
if not isinstance(authors, list) or any(not isinstance(x, str) or not x for x in authors):
|
|
79
|
-
raise ValueError('author_sessions_invalid')
|
|
80
|
-
session = session_id or str(uuid.uuid4())
|
|
81
|
-
if session in authors:
|
|
82
|
-
raise ValueError('author_session_reused')
|
|
83
|
-
index_file = file_at(resources, 'stages.json')
|
|
84
|
-
index = native.loads(index_file.read_text())
|
|
85
|
-
shared = [file_at(resources, 'readme.md'), index_file]
|
|
86
|
-
shared += [file_at(resources, index[key]) for key in ('sharedContract', 'handoffContract', 'lifecycleContract')]
|
|
87
|
-
contract = file_at(resources, index['independentReview']['contract'])
|
|
88
|
-
guide = file_at(resources, index['readEvidenceChecker']['entry'])
|
|
89
|
-
schema_file = file_at(resources, index['readEvidenceChecker']['reviewOutputContract'])
|
|
90
|
-
shared += [contract, guide, schema_file]
|
|
91
|
-
selected = index['stages'][stage]
|
|
92
|
-
manifest = file_at(resources, selected['manifest'])
|
|
93
|
-
data = native.loads(manifest.read_text())
|
|
94
|
-
bundle_file = file_at(resources, 'assets-manifest.json')
|
|
95
|
-
bundle = native.loads(bundle_file.read_text())
|
|
96
|
-
if bundle.get('resourceVersion') != index.get('resourceVersion'):
|
|
97
|
-
raise ValueError('resource_version_mismatch:bundle')
|
|
98
|
-
if (selected.get('resourceVersion') != data.get('resourceVersion')
|
|
99
|
-
or selected.get('templateVersion') != data['source'].get('templateVersion')):
|
|
100
|
-
raise ValueError('resource_version_mismatch:' + stage)
|
|
101
|
-
stage_readme = file_at(manifest.parent, 'readme.md', resources)
|
|
102
|
-
template = file_at(manifest.parent, data['source']['resourcePath'], resources)
|
|
103
|
-
action_file = file_at(manifest.parent, data['prompts'][action], resources)
|
|
104
|
-
dependencies = [file_at(manifest.parent, item['path'], resources) for item in data.get('dependencies', [])]
|
|
105
|
-
stage_files = [manifest, stage_readme, template, action_file]
|
|
106
|
-
covered = {str(p.relative_to(resources)): p for p in shared + stage_files + dependencies}
|
|
107
|
-
compact = (index['independentReview'].get('contextProfile') == 'fixed-review-context-v1'
|
|
108
|
-
and all(fingerprint(p)['sha256'] in CONTEXT_BASELINES.get(name, [])
|
|
109
|
-
for name, p in covered.items()))
|
|
110
|
-
reason = 'fixed_resources_covered; project_rules_remain_authoritative' if compact else 'selected_resources_not_covered_by_fixed_profile'
|
|
111
|
-
project_rules = [file_at(root, name) for name in ('CODEBUDDY.md', 'README.md') if (root / name).is_file()]
|
|
112
|
-
if compact and any('三份共用合同' in p.read_text() and 'review/recheck按' not in p.read_text()
|
|
113
|
-
for p in project_rules):
|
|
114
|
-
compact, reason = False, 'project_instruction_requires_full_context'
|
|
115
|
-
files = [contract, stage_readme, template, action_file] if compact else shared + stage_files + dependencies
|
|
116
|
-
protected = shared + stage_files + dependencies
|
|
117
|
-
# Never drop metadata from drift protection merely because the model no
|
|
118
|
-
# longer reads it. Schema/checker/helper bytes also bind this one execution.
|
|
119
|
-
protected += [bundle_file,
|
|
120
|
-
file_at(resources, 'verification/review_handoff.py'),
|
|
121
|
-
file_at(resources, 'verification/check_reads.py'),
|
|
122
|
-
file_at(resources, 'verification/check_native_execution.py')]
|
|
123
|
-
files += [target, *sources]
|
|
124
|
-
prior = None
|
|
125
|
-
if action == 'recheck':
|
|
126
|
-
for key in ('priorReport', 'responses', 'previousTarget'):
|
|
127
|
-
if not request.get(key):
|
|
128
|
-
raise ValueError('recheck_input_missing:' + key)
|
|
129
|
-
files.append(file_at(root, request[key]))
|
|
130
|
-
prior = native.loads(file_at(root, request['priorReport']).read_text())
|
|
131
|
-
if not isinstance(prior, dict) or not isinstance(prior.get('findings'), list):
|
|
132
|
-
raise ValueError('prior_review_report_invalid')
|
|
133
|
-
ids = [item.get('id') for item in prior['findings']]
|
|
134
|
-
if any(not isinstance(x, str) or not x for x in ids) or len(ids) != len(set(ids)):
|
|
135
|
-
raise ValueError('prior_review_ids_invalid')
|
|
136
|
-
files += project_rules
|
|
137
|
-
for raw in request.get('inputs', []):
|
|
138
|
-
files.append(file_at(root, raw))
|
|
139
|
-
files = list(dict.fromkeys(files))
|
|
140
|
-
|
|
141
|
-
def relative(path):
|
|
142
|
-
return str(path.relative_to(root)) if path.is_relative_to(root) else str(path)
|
|
143
|
-
|
|
144
|
-
scope = {'requiredReads': [relative(path) for path in files], 'allowedWrites': [],
|
|
145
|
-
'forbiddenAccessPrefixes': request.get('forbiddenAccessPrefixes', []),
|
|
146
|
-
'reviewCarrier': 'structured_output'}
|
|
147
|
-
if request.get('sourceRoles') or request.get('requiredSourceRoles'):
|
|
148
|
-
scope['sourceRoles'] = copy.deepcopy(request.get('sourceRoles', {}))
|
|
149
|
-
scope['requiredSourceRoles'] = copy.deepcopy(request.get('requiredSourceRoles', []))
|
|
150
|
-
for raw in scope['sourceRoles'].values():
|
|
151
|
-
if file_at(root, raw) not in files:
|
|
152
|
-
raise ValueError('role_not_in_model_inputs:' + raw)
|
|
153
|
-
protected = list(dict.fromkeys(protected + files))
|
|
154
|
-
return {'root': str(root), 'request': copy.deepcopy(request), 'sessionId': session,
|
|
155
|
-
'target': relative(target), 'sources': [relative(path) for path in sources],
|
|
156
|
-
'scope': scope, 'schema': reads.build_review_schema(scope),
|
|
157
|
-
'files': native.inventory(root),
|
|
158
|
-
'inputSnapshots': {str(path): fingerprint(path) for path in protected},
|
|
159
|
-
'context': {'mode': 'compact' if compact else 'full-selected', 'reason': reason,
|
|
160
|
-
'profile': 'fixed-review-context-v1',
|
|
161
|
-
'modelRequiredFileCount': len(files),
|
|
162
|
-
'modelRequiredBytes': sum(p.stat().st_size for p in files),
|
|
163
|
-
'outerOnlyPaths': [relative(p) for p in protected if p not in files],
|
|
164
|
-
'outerParsingIsModelReadEvidence': False},
|
|
165
|
-
'priorFindingIds': [item['id'] for item in prior['findings']] if prior else []}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def prompt_for(prepared):
|
|
169
|
-
request = prepared['request']
|
|
170
|
-
return '\n'.join([
|
|
171
|
-
'本次是已明确委托的企业SOP独立只读' + request['action'] + ',不是文档生成或工程实施。',
|
|
172
|
-
'这是全新会话,不继承作者自查。只用Read/Glob/Grep读取准确材料,StructuredOutput返回报告;不要调用Skill、Shell、写入或其他Agent。',
|
|
173
|
-
'先完整读列出的统一审查约定、本阶段README、完整模板及唯一动作,再核目标和真实业务来源。只读标注问题,保留人工修改空间。',
|
|
174
|
-
'外层已经解析并保护索引、清单和校验说明,不冒充你已读;只按下列模型必读清单完整读正文,不重复加载纯元数据。',
|
|
175
|
-
'若实际项目规则另要求完整读选定原件,遵守项目规则并说明精简未适用;不要回退低优先级发行模板。',
|
|
176
|
-
'本次资源加载判定:' + prepared['context']['mode'] + ';' + prepared['context']['reason'],
|
|
177
|
-
'任务范围:' + request.get('task', '按本阶段审查规则核对指定目标和必要来源。'),
|
|
178
|
-
'精确目标:' + prepared['target'],
|
|
179
|
-
'真实业务来源:' + json.dumps(prepared['sources'], ensure_ascii=False),
|
|
180
|
-
'必读文件(长文允许分段覆盖,不用摘要冒充全文):',
|
|
181
|
-
*prepared['scope']['requiredReads'],
|
|
182
|
-
'不得读取未选版本、历史、其他主题或低优先级模板;必要来源未列入或不可读时如实报告缺口,不猜补。',
|
|
183
|
-
'复查时读旧稿、旧报告和逐项回应;checkedFacts中按旧finding.id逐项给结论,conclusion以“已关闭”“部分关闭”“未关闭”或“无法核实”开头。',
|
|
184
|
-
'原问题未核清不得关闭,新问题使用新ID。报告审查语义,不声称业务认可、工程通过或发布许可。',
|
|
185
|
-
'最终使用所给Schema的StructuredOutput,只提交verdict/findings/checkedFacts/unread。',
|
|
186
|
-
'每条引文用真实路径、实际行号和行内逐字短摘录;普通引用不加role,有绑定才用Schema枚举。无真实问题则findings为空。',
|
|
187
|
-
'报告由外层调用者另存;不要自行保存、修订目标、更新JSON或进入下一阶段。'])
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def command(prepared, executable, max_turns=60):
|
|
191
|
-
if prepared['schema'] != reads.build_review_schema(prepared['scope']):
|
|
192
|
-
raise ValueError('schema_scope_mismatch')
|
|
193
|
-
return [executable, '-p', '--verbose', '--output-format', 'stream-json',
|
|
194
|
-
'--permission-mode', 'default', '--tools', 'Read,Glob,Grep,StructuredOutput',
|
|
195
|
-
'--allowedTools', 'Read', 'Glob', 'Grep', 'StructuredOutput',
|
|
196
|
-
'--disallowedTools', 'Bash', 'Write', 'Edit', 'PowerShell', 'NotebookEdit', 'Skill', 'Task', 'Agent',
|
|
197
|
-
'--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}',
|
|
198
|
-
'--no-session-persistence', '--session-id', prepared['sessionId'],
|
|
199
|
-
'--max-turns', str(max_turns), '--json-schema',
|
|
200
|
-
json.dumps(prepared['schema'], ensure_ascii=False, separators=(',', ':'))]
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def evaluate(prepared, events, process):
|
|
204
|
-
root, issues = Path(prepared['root']), []
|
|
205
|
-
finals = [event for event in events if event.get('type') == 'result']
|
|
206
|
-
starts = [event for event in events if event.get('type') == 'system' and event.get('subtype') == 'init']
|
|
207
|
-
if not starts or not finals or any(event.get('session_id') != prepared['sessionId'] for event in starts + finals):
|
|
208
|
-
issues.append('session_identity_mismatch')
|
|
209
|
-
if process.get('exitCode') != 0 or process.get('timedOut') or not finals or finals[-1].get('is_error'):
|
|
210
|
-
issues.append('native_completion_not_proven')
|
|
211
|
-
# CodeBuddy init lists the agent catalog before options.tools filtering.
|
|
212
|
-
# Bind the actual launch argv and inspect every real tool attempt instead.
|
|
213
|
-
calls, receipts, trace_issues = native.trace_calls(events)
|
|
214
|
-
issues += trace_issues
|
|
215
|
-
for call in calls:
|
|
216
|
-
name = call['name']
|
|
217
|
-
if name not in ('Read', 'Glob', 'Grep', 'StructuredOutput'):
|
|
218
|
-
issues.append('tool_outside_readonly_scope:' + name)
|
|
219
|
-
result = receipts.get(call['id'])
|
|
220
|
-
status, _ = native.outcome(result[1] if result else None, name)
|
|
221
|
-
if status != 'succeeded':
|
|
222
|
-
issues.append('tool_attempt_' + status + ':' + name)
|
|
223
|
-
if name == 'Read':
|
|
224
|
-
file = (root / (call.get('input') or {}).get('file_path', '')).resolve()
|
|
225
|
-
if str(file) not in prepared['inputSnapshots']:
|
|
226
|
-
issues.append('read_outside_selected_inputs:' + str(file))
|
|
227
|
-
current = native.inventory(root)
|
|
228
|
-
changed = sorted(name for name in set(current) | set(prepared['files'])
|
|
229
|
-
if current.get(name) != prepared['files'].get(name))
|
|
230
|
-
if changed:
|
|
231
|
-
issues.append('project_changed_during_review')
|
|
232
|
-
if any(not Path(raw).is_file() or fingerprint(Path(raw)) != frozen
|
|
233
|
-
for raw, frozen in prepared['inputSnapshots'].items()):
|
|
234
|
-
issues.append('inputs_changed')
|
|
235
|
-
reading = reads.assess(root, prepared['scope'], events)
|
|
236
|
-
quoting = reads.check_review(root, events, reading, prepared['scope'])
|
|
237
|
-
if not reading['passed']:
|
|
238
|
-
issues += reading['failures']
|
|
239
|
-
if not quoting['passed']:
|
|
240
|
-
issues += quoting['errorCodes']
|
|
241
|
-
report = finals[-1].get('structured_output') if finals else None
|
|
242
|
-
if quoting['passed']:
|
|
243
|
-
references = report['findings'] + report['checkedFacts']
|
|
244
|
-
targets = str((root / prepared['target']).resolve())
|
|
245
|
-
sources = {str((root / raw).resolve()) for raw in prepared['sources']}
|
|
246
|
-
if not any(str((root / item['target']['path']).resolve()) == targets
|
|
247
|
-
and str((root / item['source']['path']).resolve()) in sources for item in references):
|
|
248
|
-
issues.append('review_target_not_bound')
|
|
249
|
-
facts = {item['id']: item for item in report['checkedFacts']}
|
|
250
|
-
for old_id in prepared['priorFindingIds']:
|
|
251
|
-
if old_id not in facts or not facts[old_id]['conclusion'].startswith(('已关闭', '部分关闭', '未关闭', '无法核实')):
|
|
252
|
-
issues.append('prior_finding_not_rechecked:' + old_id)
|
|
253
|
-
return {'helperVersion': VERSION, 'status': 'needs_human_review' if issues else 'evidence_accepted',
|
|
254
|
-
'issues': list(dict.fromkeys(issues)), 'sessionId': prepared['sessionId'],
|
|
255
|
-
'target': prepared['target'], 'changedFiles': changed,
|
|
256
|
-
'hostToolCatalogIsEffectiveAllowlist': False,
|
|
257
|
-
'reads': reading, 'quotes': quoting, 'report': report,
|
|
258
|
-
'context': copy.deepcopy(prepared['context']),
|
|
259
|
-
'businessApproved': False, 'implementationApproved': False, 'published': False,
|
|
260
|
-
'proofScope': 'one fresh CLI session, readonly tool/trace evidence, input snapshots and report references; not semantic approval or OS sandbox'}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def run(project_root, request, evidence_dir, *, resource_root=RESOURCES, timeout=420, max_turns=60):
|
|
264
|
-
root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve()
|
|
265
|
-
if evidence.is_relative_to(root):
|
|
266
|
-
raise ValueError('evidence_must_be_outside_project')
|
|
267
|
-
prepared = prepare(root, request, resource_root=resource_root)
|
|
268
|
-
evidence.mkdir(parents=True, exist_ok=False)
|
|
269
|
-
save(evidence / 'request.json', request)
|
|
270
|
-
save(evidence / 'prepared.json', prepared)
|
|
271
|
-
(evidence / 'task.md').write_text(prompt_for(prepared) + '\n')
|
|
272
|
-
executable = shutil.which('codebuddy')
|
|
273
|
-
if not executable:
|
|
274
|
-
result = {'status': 'new_session_required', 'reason': 'codebuddy_unavailable',
|
|
275
|
-
'recommendation': '在明确的新会话中使用task.md;没有宿主权限证明时只声明提示约束。',
|
|
276
|
-
'businessApproved': False, 'implementationApproved': False, 'published': False}
|
|
277
|
-
save(evidence / 'result.json', result)
|
|
278
|
-
return result
|
|
279
|
-
argv = command(prepared, executable, max_turns)
|
|
280
|
-
save(evidence / 'invocation.json', {'argv': argv, 'cwd': str(root), 'attempts': 1,
|
|
281
|
-
'newSession': True, 'resumed': False,
|
|
282
|
-
'modelOverridden': False, 'systemPromptOverridden': False})
|
|
283
|
-
started, timed_out = time.monotonic(), False
|
|
284
|
-
with (evidence / 'task.md').open('rb') as inp, (evidence / 'trace.jsonl').open('wb') as out, (evidence / 'stderr.log').open('wb') as err:
|
|
285
|
-
proc = subprocess.Popen(argv, cwd=root, stdin=inp, stdout=out, stderr=err,
|
|
286
|
-
env={**os.environ, 'PYTHONDONTWRITEBYTECODE': '1',
|
|
287
|
-
'CODEBUDDY_DISABLE_AUTO_MEMORY': '1', 'CODEBUDDY_CODE_DISABLE_AUTO_MEMORY': '1'})
|
|
288
|
-
try:
|
|
289
|
-
proc.wait(timeout=timeout)
|
|
290
|
-
except subprocess.TimeoutExpired:
|
|
291
|
-
timed_out = True
|
|
292
|
-
proc.terminate()
|
|
293
|
-
try:
|
|
294
|
-
proc.wait(timeout=10)
|
|
295
|
-
except subprocess.TimeoutExpired:
|
|
296
|
-
proc.kill()
|
|
297
|
-
proc.wait()
|
|
298
|
-
process = {'exitCode': proc.returncode, 'timedOut': timed_out,
|
|
299
|
-
'elapsedSeconds': round(time.monotonic() - started, 2)}
|
|
300
|
-
save(evidence / 'process.json', process)
|
|
301
|
-
events = [native.loads(line) for line in (evidence / 'trace.jsonl').read_text().splitlines() if line.strip()]
|
|
302
|
-
result = evaluate(prepared, events, process)
|
|
303
|
-
save(evidence / 'result.json', result)
|
|
304
|
-
if result.get('report') is not None:
|
|
305
|
-
save(evidence / 'report.json', result['report'])
|
|
306
|
-
return result
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def main():
|
|
310
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
311
|
-
parser.add_argument('--project-root', required=True)
|
|
312
|
-
parser.add_argument('--request', required=True)
|
|
313
|
-
parser.add_argument('--evidence-dir', required=True)
|
|
314
|
-
parser.add_argument('--timeout', type=int, default=420)
|
|
315
|
-
parser.add_argument('--max-turns', type=int, default=60)
|
|
316
|
-
args = parser.parse_args()
|
|
317
|
-
try:
|
|
318
|
-
result = run(args.project_root, native.loads(Path(args.request).read_text()), args.evidence_dir,
|
|
319
|
-
timeout=args.timeout, max_turns=args.max_turns)
|
|
320
|
-
except (ValueError, OSError, KeyError, TypeError) as error:
|
|
321
|
-
result = {'status': 'blocked', 'reason': str(error),
|
|
322
|
-
'recommendation': '核对准确目标、来源及所选资料;缺隔离时使用新会话任务,不修改业务稿。',
|
|
323
|
-
'businessApproved': False, 'implementationApproved': False, 'published': False}
|
|
324
|
-
print(json.dumps(result, ensure_ascii=False))
|
|
325
|
-
return 0 if result['status'] == 'evidence_accepted' else 1
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if __name__ == '__main__':
|
|
329
|
-
raise SystemExit(main())
|