@givolt/i3-composer-agent 2.3.7 → 3.0.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 (81) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude/commands/i3-sop.md +19 -0
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.codex-plugin/plugin.json +2 -2
  6. package/.gemini/commands/i3-sop.toml +19 -0
  7. package/README.md +37 -3
  8. package/commands/i3-sop.toml +19 -0
  9. package/docs/codebuddy-setup.md +3 -3
  10. package/docs/codex-setup.md +2 -2
  11. package/docs/other-hosts.md +2 -2
  12. package/docs/workbuddy-setup.md +3 -3
  13. package/evals/cases/i3-enterprise-sop.json +64 -0
  14. package/evals/fixtures/i3-enterprise-sop/brief.md +11 -0
  15. package/evals/zh-CN/cases/i3-enterprise-sop.json +61 -0
  16. package/package.json +7 -1
  17. package/plugin.json +1 -1
  18. package/references/engineering-skeleton-resources/README.md +52 -0
  19. package/references/engineering-skeleton-resources/adoption.md +200 -0
  20. package/references/engineering-skeleton-resources/catalog.json +17 -0
  21. package/references/engineering-skeleton-resources/directory-standard.md +119 -0
  22. package/references/enterprise-sop-resources/README.md +165 -0
  23. package/references/enterprise-sop-resources/ad/README.md +70 -0
  24. package/references/enterprise-sop-resources/ad/assets-manifest.json +106 -0
  25. package/references/enterprise-sop-resources/ad/prompts/01-generate.md +58 -0
  26. package/references/enterprise-sop-resources/ad/prompts/02-review.md +54 -0
  27. package/references/enterprise-sop-resources/ad/prompts/03-revise.md +54 -0
  28. package/references/enterprise-sop-resources/ad/prompts/04-recheck.md +54 -0
  29. package/references/enterprise-sop-resources/ad/templates/AD.md +337 -0
  30. package/references/enterprise-sop-resources/assets-manifest.json +199 -0
  31. package/references/enterprise-sop-resources/brd/README.md +50 -0
  32. package/references/enterprise-sop-resources/brd/assets-manifest.json +94 -0
  33. package/references/enterprise-sop-resources/brd/prompts/01-generate.md +51 -0
  34. package/references/enterprise-sop-resources/brd/prompts/02-review.md +47 -0
  35. package/references/enterprise-sop-resources/brd/prompts/03-revise.md +47 -0
  36. package/references/enterprise-sop-resources/brd/prompts/04-recheck.md +47 -0
  37. package/references/enterprise-sop-resources/brd/templates/BRD.md +169 -0
  38. package/references/enterprise-sop-resources/codebuddy-project-guide.md +34 -0
  39. package/references/enterprise-sop-resources/controlled-handoff.md +41 -0
  40. package/references/enterprise-sop-resources/controlled_handoff.py +334 -0
  41. package/references/enterprise-sop-resources/handoff-contract.md +196 -0
  42. package/references/enterprise-sop-resources/handoff.py +277 -0
  43. package/references/enterprise-sop-resources/lifecycle-contract.md +91 -0
  44. package/references/enterprise-sop-resources/plan-handoff.md +73 -0
  45. package/references/enterprise-sop-resources/prd/README.md +137 -0
  46. package/references/enterprise-sop-resources/prd/assets-manifest.json +115 -0
  47. package/references/enterprise-sop-resources/prd/prompts/01-generate.md +64 -0
  48. package/references/enterprise-sop-resources/prd/prompts/02-review.md +49 -0
  49. package/references/enterprise-sop-resources/prd/prompts/03-revise.md +48 -0
  50. package/references/enterprise-sop-resources/prd/prompts/04-recheck.md +47 -0
  51. package/references/enterprise-sop-resources/prd/template-contracts.md +39 -0
  52. package/references/enterprise-sop-resources/prd/templates/AD.md +17 -0
  53. package/references/enterprise-sop-resources/prd/templates/PRD.md +257 -0
  54. package/references/enterprise-sop-resources/recovery-contract.md +65 -0
  55. package/references/enterprise-sop-resources/review-handoff.md +105 -0
  56. package/references/enterprise-sop-resources/selection-handoff.md +55 -0
  57. package/references/enterprise-sop-resources/selection_handoff.py +225 -0
  58. package/references/enterprise-sop-resources/stages.json +262 -0
  59. package/references/enterprise-sop-resources/technical-baseline/README.md +132 -0
  60. package/references/enterprise-sop-resources/technical-baseline/assets-manifest.json +101 -0
  61. package/references/enterprise-sop-resources/technical-baseline/baselines/PROVENANCE.md +40 -0
  62. package/references/enterprise-sop-resources/technical-baseline/baselines//344/274/201/344/270/232/345/272/224/347/224/250-/346/212/200/346/234/257/350/247/204/347/272/246_v0.0.2.md +295 -0
  63. package/references/enterprise-sop-resources/technical-baseline/references/engineering-directory-v0.2.0/adoption.md +112 -0
  64. package/references/enterprise-sop-resources/technical-baseline/references/engineering-directory-v0.2.0/directory-standard.md +119 -0
  65. package/references/enterprise-sop-resources/technical-baseline/templates/TECHNICAL-BASELINE.md +133 -0
  66. package/references/enterprise-sop-resources/template-contracts.md +142 -0
  67. package/references/enterprise-sop-resources/testcase/README.md +61 -0
  68. package/references/enterprise-sop-resources/testcase/assets-manifest.json +111 -0
  69. package/references/enterprise-sop-resources/testcase/prompts/01-generate.md +53 -0
  70. package/references/enterprise-sop-resources/testcase/prompts/02-review.md +51 -0
  71. package/references/enterprise-sop-resources/testcase/prompts/03-revise.md +49 -0
  72. package/references/enterprise-sop-resources/testcase/prompts/04-recheck.md +51 -0
  73. package/references/enterprise-sop-resources/testcase/templates/TestCase.md +269 -0
  74. package/references/enterprise-sop-resources/verification/README.md +149 -0
  75. package/references/enterprise-sop-resources/verification/check_execution.py +185 -0
  76. package/references/enterprise-sop-resources/verification/check_native_execution.py +282 -0
  77. package/references/enterprise-sop-resources/verification/check_reads.py +348 -0
  78. package/references/enterprise-sop-resources/verification/review-output.schema.json +155 -0
  79. package/references/enterprise-sop-resources/verification/review_handoff.py +329 -0
  80. package/skills/i3-enterprise-sop/SKILL.md +173 -0
  81. package/skills/i3-enterprise-sop/agents/openai.yaml +2 -0
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python3
2
+ """One CodeBuddy readonly preflight, receipt audit, then an explicitly selected reference.
3
+
4
+ Explicit adapter, not a Hook or enterprise auto. Python standard library only.
5
+ Request/evidence are caller records, never business approval or handoff JSON state.
6
+ """
7
+ import argparse
8
+ import importlib.util
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ import time
16
+ import uuid
17
+
18
+ sys.dont_write_bytecode = True
19
+ RESOURCES = Path(__file__).resolve().parent
20
+
21
+
22
+ def module(name, path):
23
+ spec = importlib.util.spec_from_file_location(name, path)
24
+ value = importlib.util.module_from_spec(spec)
25
+ spec.loader.exec_module(value)
26
+ return value
27
+
28
+
29
+ native = module('controlled_native', RESOURCES/'verification/check_native_execution.py')
30
+ handoff = module('controlled_json', RESOURCES/'handoff.py')
31
+ selection = module('controlled_selection', RESOURCES/'selection_handoff.py')
32
+
33
+
34
+ class Decision(ValueError):
35
+ def __init__(self, code, message):
36
+ super().__init__(message)
37
+ self.code = code
38
+
39
+
40
+ def save(path, value):
41
+ path.write_text(json.dumps(value, ensure_ascii=False, indent=2)+'\n')
42
+
43
+
44
+ def response(status, code, message, *, recommendation='核对所列原因,在新作业中重新只读预检。', evidence=None):
45
+ return {'schemaVersion': 1, 'status': status,
46
+ 'reason': {'code': code, 'message': message, 'evidence': evidence or []},
47
+ 'recommendation': {'id': 'readonly-preflight', 'description': recommendation},
48
+ 'options': [{'id': 'readonly-preflight', 'label': '重新只读预检'},
49
+ {'id': 'manual-handoff', 'label': '手动传递明确路径'},
50
+ {'id': 'stop', 'label': '暂停并保留现场'}],
51
+ 'registration': {'attempted': False},
52
+ 'businessApproved': False, 'implementationApproved': False, 'published': False}
53
+
54
+
55
+ def decision_response(error):
56
+ choices = {
57
+ 'topic_choice_required': ('choose-topic', '明确 PBC 与 PRD 主题', '请明确本次 PBC 与 PRD 主题,沿用真实选定路径,不按最高版本猜测。'),
58
+ 'work_conflict': ('choose-manifest', '确认本次工作与清单', '请确认本次工作的实际清单,保留其他工作与已有选择。'),
59
+ 'resource_identity': ('choose-source', '确认采用来源', '请确认项目实际采用的资料来源;不切换到其他副本。'),
60
+ 'skill_identity': ('choose-source', '确认采用来源', '请确认唯一实际安装的 SOP Skill,不自动借用其他安装。'),
61
+ 'manifest_required': ('choose-handoff', '选择清单或手动交接', '建议先使用准确路径手动交接;需要清单时另行明确创建范围。'),
62
+ 'selection_preview_required': ('create-preview', '先保存准确改选预览', '先只读核对准确组合与影响,再明确选择该预览;不直接替换。'),
63
+ }
64
+ choice = choices.get(getattr(error, 'code', ''))
65
+ out = response('needs_confirmation' if choice else 'blocked', getattr(error, 'code', 'execution_unconfirmed'), str(error))
66
+ if choice:
67
+ out['recommendation'] = {'id': choice[0], 'description': choice[2]}
68
+ out['options'].insert(0, {'id': choice[0], 'label': choice[1]})
69
+ return out
70
+
71
+
72
+ def local_file(root, raw):
73
+ if not isinstance(raw, str) or not raw.strip():
74
+ raise Decision('missing_path', '需要明确的实际文件路径,不能由模型猜测版本。')
75
+ p = (root/Path(raw)).resolve()
76
+ if not p.is_relative_to(root) or not p.is_file() or not p.read_text(encoding='utf-8').strip():
77
+ raise Decision('unavailable_input', '文件须在本项目内实际可读且非空:'+raw)
78
+ return str(p.relative_to(root))
79
+
80
+
81
+ def prepare(project_root, request, *, resource_root=RESOURCES):
82
+ root = Path(project_root).resolve(); resources = Path(resource_root).resolve()
83
+ if isinstance(request, dict) and request.get('operation') in ('selection-preview', 'selection-switch'):
84
+ proposal = selection.plan(root, request)
85
+ base = dict(proposal['preflightRequest'])
86
+ receipt = None
87
+ if request['operation'] == 'selection-switch':
88
+ preview = Path(request.get('preview', '')).resolve()
89
+ if preview.is_relative_to(root) or not preview.is_file():
90
+ raise Decision('selection_preview_required', '须先核对项目外保存的准确预览,再明确委托该次改选。')
91
+ receipt = native.loads(preview.read_text())
92
+ if (not isinstance(receipt, dict) or receipt.get('kind') != 'i3-sop-selection-preview'
93
+ or not isinstance(receipt.get('plan'), dict)
94
+ or not isinstance(receipt['plan'].get('preflightRequest'), dict)
95
+ or not isinstance(receipt['plan']['preflightRequest'].get('inputs'), list)):
96
+ raise Decision('selection_preview_required', '预览证据不符合契约,不能作为改选依据。')
97
+ base['inputs'] += receipt.get('plan', {}).get('preflightRequest', {}).get('inputs', [])
98
+ base['inputs'] += [str(resources/'selection-handoff.md')]
99
+ prepared = prepare(root, base, resource_root=resources)
100
+ prepared.update(request=request, selection=proposal, selectionReceipt=receipt)
101
+ return prepared
102
+ fields = {'schemaVersion', 'operation', 'work', 'manifest', 'reference', 'inputs', 'task'}
103
+ if not isinstance(request, dict) or set(request)-fields or request.get('schemaVersion') != 1:
104
+ raise Decision('request_contract', '调用方请求不符合单次交接契约。')
105
+ if request.get('operation') not in ('select', 'manual'):
106
+ raise Decision('explicit_enterprise_required', '本入口只接受明确选用登记或手动交接;旧候选登记及撤回操作已退出,不转换为选用授权。')
107
+ if not root.is_dir() or not resources.is_relative_to(root):
108
+ raise Decision('project_install_required', '本片仅支持项目内实际 CodeBuddy 安装;其他来源可继续手动交接。')
109
+ if not isinstance(request.get('work'), str) or not request['work'].strip() or not isinstance(request.get('task'), str) or not request['task'].strip():
110
+ raise Decision('missing_scope', '需要本次真实工作范围及任务说明。')
111
+ ref = request.get('reference')
112
+ if not isinstance(ref, dict) or set(ref)-{'role', 'path', 'capabilityId', 'prdId'} or ref.get('role') not in ('brd', 'prd', 'ad', 'testCase'):
113
+ raise Decision('reference_contract', '本片只登记明确的 BRD/PRD/AD/TestCase 现稿。')
114
+ if ref['role'] in handoff.TOPIC_ROLES and not all(isinstance(ref.get(k), str) and ref[k].strip() for k in ('capabilityId', 'prdId')):
115
+ raise Decision('topic_choice_required', '尚未明确 PBC 与 PRD 主题,不能按文件版本号自动选择。')
116
+ reference = handoff._reference(root, ref['role'], local_file(root, ref.get('path')), ref.get('capabilityId'), ref.get('prdId'))
117
+ inputs = request.get('inputs', [])
118
+ if not isinstance(inputs, list):
119
+ raise Decision('input_contract', '本次采用的直接上游、规约和骨架须以路径列表交接。')
120
+ paths = [local_file(root, p) for p in inputs]
121
+ locator = root/'.codebuddy/.i3-composer-agent.json'
122
+ metadata = native.loads(locator.read_text())
123
+ refs = metadata.get('resources', {}).get('references')
124
+ if metadata.get('name') != 'i3-composer-agent' or not all(metadata.get(k) for k in ('version', 'scope')) or metadata['scope'] != 'project' or not isinstance(refs, str) or (root/'.codebuddy'/refs/'enterprise-sop-resources').resolve() != resources:
125
+ raise Decision('resource_identity', '项目安装身份或实际资料根不一致,需先确认采用来源。')
126
+ locator_lines = [i for i, line in enumerate(locator.read_text().splitlines(), 1)
127
+ if any(line.lstrip().startswith('"'+key+'":') for key in ('name', 'version', 'scope', 'references'))]
128
+ selected = []
129
+ manifest = request.get('manifest')
130
+ if manifest:
131
+ mf = handoff._manifest_path(root, manifest)
132
+ local_file(root, str(mf))
133
+ value = handoff._load(mf.read_bytes())
134
+ if value['scope']['work'] != request['work']:
135
+ raise Decision('work_conflict', '交接清单属于其他工作范围,不能自动替换。')
136
+ context = value
137
+ if request['operation'] == 'select':
138
+ context = handoff._selected_only(value)
139
+ selection_check = json.loads(json.dumps(context['selected']))
140
+ handoff._assign(selection_check, reference)
141
+ # A first selection may name a new topic; only existing sources in
142
+ # that scope, plus global references, belong in the preflight.
143
+ context['selected']['topics'] = [
144
+ topic for topic in context['selected'].get('topics', [])
145
+ if reference['role'] in handoff.TOPIC_ROLES
146
+ and all(topic[key] == reference[key] for key in ('capabilityId', 'prdId'))]
147
+ resolved = handoff._resolve(root, context, ref.get('capabilityId'), ref.get('prdId'), ())
148
+ selected = [local_file(root, x['path']) for x in resolved['references']]
149
+ manifest = str(mf.relative_to(root))
150
+ elif request['operation'] == 'select':
151
+ raise Decision('manifest_required', '选用登记需要现有明确清单;确认采用后可授权初始化,或继续手动交接。')
152
+ skills = root/'.codebuddy/skills'
153
+ names = sorted({name for name in ('i3-enterprise-sop', 'enterprise-sop') if (skills/name/'SKILL.md').is_file()})
154
+ if len(names) != 1:
155
+ raise Decision('skill_identity', '需要唯一且可读取的项目 SOP Skill,不能选择另一安装副本。')
156
+ common = ['README.md', 'stages.json', 'template-contracts.md', 'handoff-contract.md',
157
+ 'lifecycle-contract.md', 'recovery-contract.md', 'controlled-handoff.md', 'verification/README.md']
158
+ required = [local_file(root, p) for p in ('CODEBUDDY.md', 'README.md') if (root/p).is_file()]
159
+ required += [str(locator.relative_to(root))]+[local_file(root, str(resources/p)) for p in common]
160
+ required += ([manifest] if manifest else [])+[reference['path']]+selected+paths
161
+ required = list(dict.fromkeys(required))
162
+ scope = {'profile': 'status', 'requiredReads': required, 'reportInputs': required, 'allowedWrites': [],
163
+ 'requiredSkills': names, 'files': native.inventory(root), 'gateExpectations': {},
164
+ 'tools': ['Read', 'Glob', 'Grep', 'Skill'], 'allowedShellCommands': [],
165
+ 'requiredReadLines': {str(locator.relative_to(root)): locator_lines},
166
+ 'forbiddenSkills': [p.name for p in skills.iterdir() if p.is_dir() and p.name not in names]}
167
+ return {'root': str(root), 'resourceRoot': str(resources), 'request': request, 'reference': reference,
168
+ 'manifest': manifest, 'scope': scope}
169
+
170
+
171
+ def prompt_for(prepared):
172
+ scope = prepared['scope']
173
+ report = {'schemaVersion': 1, 'failedAttempts': [],
174
+ 'inputChecks': [{'path': p, 'status': 'full'} for p in scope['reportInputs']], 'gates': []}
175
+ return ('本次显式企业 SOP 单次只读预检。原始任务:'+prepared['request']['task']+'\n'
176
+ '先实际调用 Skill '+scope['requiredSkills'][0]+'。只核现稿与真实来源,不生成或修订文档。\n'
177
+ '本会话仅开放 Read/Glob/Grep/Skill;不要提交 Bash、写工具或其他 Skill,外层负责后续获准登记。\n'
178
+ '逐项读完下列项目相对路径;允许在本次只读会话内补读漏项,不扩大来源、不选新版本。\n'
179
+ +json.dumps(scope['requiredReads'], ensure_ascii=False)+'\n'
180
+ '安装清单仅需下列行定位 name/version/scope/resources.references:'
181
+ +json.dumps(scope['requiredReadLines'], ensure_ascii=False)+'\n'
182
+ '如工具拒绝、失败、来源冲突,保留事实并说明原因、推荐、可选操作,不绕过或重复尝试。\n'
183
+ '结束时附且仅附一个原生执行报告块。inputChecks 路径全集固定为上面列表,状态枚举仅 full/partial/not_read;'
184
+ 'failedAttempts 按实际非成功尝试顺序列 tool/subject/status,status 仅 blocked/failed/unconfirmed;'
185
+ '工具成功加载了本次禁止的方法属于方法越界,应在正文如实说明,不能伪造为工具 failed 或 blocked;'
186
+ 'gates 固定为空数组,不创造业务或开工批准。全读成功无失败时的确切结构如下,否则按真实结果填写:\n'
187
+ +native.START+'\n```json\n'+json.dumps(report, ensure_ascii=False)+'\n```\n'+native.END+'\n')
188
+
189
+
190
+ def launch(prepared, evidence, *, timeout=240, max_turns=40):
191
+ executable = shutil.which('codebuddy')
192
+ if not executable:
193
+ raise Decision('host_unavailable', '本机没有 CodeBuddy CLI;可使用手动路径,不自动安装依赖。')
194
+ argv = [executable, '-p', '--verbose', '--output-format', 'stream-json', '--permission-mode', 'default',
195
+ '--tools', 'Read,Glob,Grep,Skill', '--allowedTools', 'Read', 'Glob', 'Grep', 'Skill',
196
+ '--disallowedTools', 'Bash,Write,Edit,PowerShell,NotebookEdit,Task,Agent',
197
+ '--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}',
198
+ '--max-turns', str(max_turns), '--session-id', str(uuid.uuid4())]
199
+ save(evidence/'invocation.json', {'argv': argv, 'cwd': prepared['root'], 'attempts': 1,
200
+ 'modelOverridden': False, 'systemPromptOverridden': False})
201
+ (evidence/'prompt.txt').write_text(prompt_for(prepared))
202
+ started = time.monotonic(); timed_out = False
203
+ with (evidence/'prompt.txt').open('rb') as inp, (evidence/'trace.jsonl').open('wb') as out, (evidence/'stderr.log').open('wb') as err:
204
+ proc = subprocess.Popen(argv, cwd=prepared['root'], stdin=inp, stdout=out, stderr=err,
205
+ env={**os.environ, 'CODEBUDDY_DISABLE_AUTO_MEMORY': '1', 'CODEBUDDY_CODE_DISABLE_AUTO_MEMORY': '1'})
206
+ try:
207
+ proc.wait(timeout=timeout)
208
+ except subprocess.TimeoutExpired:
209
+ timed_out = True; proc.terminate()
210
+ try:
211
+ proc.wait(timeout=10)
212
+ except subprocess.TimeoutExpired:
213
+ proc.kill(); proc.wait()
214
+ process = {'exitCode': proc.returncode, 'timedOut': timed_out, 'elapsedSeconds': round(time.monotonic()-started, 2)}
215
+ save(evidence/'process.json', process)
216
+ events = [native.loads(line) for line in (evidence/'trace.jsonl').read_text().splitlines() if line.strip()]
217
+ return events, process
218
+
219
+
220
+ def finish(prepared, events, process, evidence, *, select_reference=False, switch=False):
221
+ root = Path(prepared['root']); evidence = Path(evidence)
222
+ audit = native.assess(root, prepared['scope'], events, process)
223
+ save(evidence/'preflight-audit.json', audit)
224
+ if not audit['passed']:
225
+ out = response('blocked', 'preflight_failed', '实际读取、调用、报告或文件边界未通过;本次不启动登记。', evidence=['preflight-audit.json', 'trace.jsonl'])
226
+ out['reason']['details'] = {
227
+ 'incompleteInputs': [x['path'] for x in audit['inputs']['checks'] if x['status'] != 'full'],
228
+ 'failedAttempts': [x for x in audit['attempts'] if x['status'] != 'succeeded'],
229
+ 'methodIssues': audit['nativeMethods']['issues'],
230
+ 'unexpectedChanges': audit['boundaries']['changedFiles'],
231
+ 'reportIssues': audit['reportConsistency']['issues'],
232
+ 'executionIssues': audit['toolDiscipline']['issues']}
233
+ return out
234
+ if native.inventory(root) != prepared['scope']['files']:
235
+ return response('blocked', 'project_changed', '预检后项目发生变化,须回读当前内容,不能覆盖人工修改。')
236
+ if 'selection' in prepared:
237
+ return selection.finish(prepared, evidence, authorized=switch, response=response)
238
+ if prepared['request']['operation'] == 'manual':
239
+ out = response('manual_ready', 'readonly_handoff_ready', '手动交接路径已只读核对,未创建或更新 JSON。')
240
+ out['references'] = {'primary': prepared['reference'], 'inputs': prepared['request'].get('inputs', [])}
241
+ return out
242
+ if not select_reference:
243
+ out = response('needs_confirmation', 'selection_not_authorized', '只读预检通过,但尚无本次准确文档的明确选用委托。', recommendation='核对准确文档及配套后明确选用;已有明确委托可由调用方直接承接,不重复询问。')
244
+ out['options'].insert(0, {'id': 'authorize-selection', 'label': '明确采用这份文档并登记'})
245
+ out['recommendation']['id'] = 'authorize-selection'
246
+ return out
247
+ helper = Path(prepared['resourceRoot'])/'handoff.py'
248
+ ref = prepared['reference']; request = prepared['request']
249
+ mf = root/prepared['manifest']; before = mf.read_bytes(); previous = handoff._load(before)
250
+ expected = handoff._selected_only(previous)
251
+ handoff._assign(expected['selected'], ref)
252
+ args = [sys.executable, str(helper), 'select', '--project-root', str(root), '--manifest', prepared['manifest'],
253
+ '--work', request['work'], '--role', ref['role'], '--path', ref['path']]
254
+ for key, flag in [('capabilityId', '--capability-id'), ('prdId', '--prd-id')]:
255
+ if key in ref:
256
+ args += [flag, ref[key]]
257
+ record = {'attempted': False, 'helper': str(helper), 'argv': args, 'shell': False, 'attempts': 0}
258
+ help_run = subprocess.run([sys.executable, str(helper), '--help'], capture_output=True, text=True, timeout=20)
259
+ save(evidence/'helper-help.json', {'exitCode': help_run.returncode, 'stdout': help_run.stdout, 'stderr': help_run.stderr})
260
+ if help_run.returncode or native.inventory(root) != prepared['scope']['files']:
261
+ return response('blocked', 'helper_precheck_failed', '接口核对失败或项目已变化,不执行登记。')
262
+ record.update(attempted=True, attempts=1)
263
+ try:
264
+ proc = subprocess.run(args, capture_output=True, text=True, timeout=20)
265
+ record.update(exitCode=proc.returncode, stdout=proc.stdout, stderr=proc.stderr)
266
+ result = native.loads(proc.stdout)
267
+ record['result'] = result
268
+ except (OSError, subprocess.TimeoutExpired, ValueError) as exc:
269
+ record['error'] = str(exc); result = {}
270
+ save(evidence/'registration.json', record)
271
+ if record.get('exitCode') != 0 or result.get('ok') is not True:
272
+ out = response('blocked', 'registration_failed', '登记失败或结果未确认;保留现稿、清单和锁,不重试或旁路写入。', evidence=['registration.json'])
273
+ out['registration'] = record
274
+ return out
275
+ changed = native.inventory(root)
276
+ delta = sorted(k for k in set(changed)|set(prepared['scope']['files']) if changed.get(k) != prepared['scope']['files'].get(k))
277
+ allowed = [prepared['manifest']]
278
+ history = result.get('history')
279
+ historical = None
280
+ if history:
281
+ hp = handoff._manifest_path(root, history)
282
+ allowed.append(str(hp.relative_to(root))); historical = hp.read_bytes()
283
+ current = handoff._load(mf.read_bytes())
284
+ expected_change = expected != previous
285
+ passed = (current == expected and result.get('changed') is expected_change
286
+ and set(delta) == (set(allowed) if expected_change else set())
287
+ and (historical == before if expected_change else history is None))
288
+ save(evidence/'readback.json', {'passed': passed, 'changedFiles': delta, 'manifest': current,
289
+ 'historyMatchesPreviousBytes': historical == before if history else None,
290
+ 'businessDocumentsChanged': False if passed else None})
291
+ out = response('selected' if passed and expected_change else 'unchanged' if passed else 'blocked',
292
+ 'reference_selected' if passed else 'postcheck_failed',
293
+ '明确选用的引用已登记并回读;业务稿、其他主题和原Plan保持,计划批准另行处理。' if passed else '登记后差量不符合约定,保留实际现场交人工核对,不自动回滚。',
294
+ evidence=['preflight-audit.json', 'registration.json', 'readback.json'])
295
+ out['registration'] = record
296
+ return out
297
+
298
+
299
+ def run(project_root, request, evidence_dir, *, select_reference=False, switch=False, timeout=240, max_turns=40):
300
+ root = Path(project_root).resolve(); evidence = Path(evidence_dir).resolve()
301
+ if evidence.is_relative_to(root) or evidence.exists():
302
+ raise ValueError('evidence directory must be new and outside the project; never overwrite a prior run')
303
+ evidence.mkdir(parents=True)
304
+ save(evidence/'request.json', request)
305
+ try:
306
+ prepared = prepare(root, request)
307
+ save(evidence/'before.json', prepared)
308
+ events, process = launch(prepared, evidence, timeout=timeout, max_turns=max_turns)
309
+ out = finish(prepared, events, process, evidence, select_reference=select_reference, switch=switch)
310
+ except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as exc:
311
+ out = decision_response(exc)
312
+ out['reason']['evidence'].append('request.json')
313
+ save(evidence/'result.json', out)
314
+ return out
315
+
316
+
317
+ def main():
318
+ parser = argparse.ArgumentParser(description=__doc__)
319
+ parser.add_argument('--project-root', required=True)
320
+ parser.add_argument('--request', required=True, help='caller-prepared explicit task/paths, not approval state')
321
+ parser.add_argument('--evidence-dir', required=True)
322
+ parser.add_argument('--select-reference', action='store_true', help='only with actual user selection of this exact document; not candidate-registration permission')
323
+ parser.add_argument('--switch-selection', action='store_true', help='only with actual delegation for the exact saved selection preview')
324
+ parser.add_argument('--timeout', type=int, default=240)
325
+ parser.add_argument('--max-turns', type=int, default=40)
326
+ args = parser.parse_args()
327
+ out = run(args.project_root, native.loads(Path(args.request).read_text()), args.evidence_dir,
328
+ select_reference=args.select_reference, switch=args.switch_selection, timeout=args.timeout, max_turns=args.max_turns)
329
+ print(json.dumps(out, ensure_ascii=False, indent=2))
330
+ return 0 if out['status'] in ('selected', 'unchanged', 'manual_ready') else 2
331
+
332
+
333
+ if __name__ == '__main__':
334
+ sys.exit(main())
@@ -0,0 +1,196 @@
1
+ # JSON 交接与手动传递合同
2
+
3
+ <!-- i3:zh-guide:start -->
4
+ ## 中文使用说明
5
+
6
+ 按下文完整工程步骤、检查项与验证条件执行。根据任务选择适用步骤,保留证据、风险和退出条件;正文示例中的英文标签在面向用户输出时译为中文。
7
+
8
+ 默认使用简体中文解释、追问及生成报告;用户明确要求其他语言时可切换。原方法正文、引用、阈值和审批条件完整保留。代码标识、路径、命令、工具参数、JSON 键/枚举与原文引用保持原样。
9
+ <!-- i3:zh-guide:end -->
10
+
11
+ 合同版本:v0.8.2;资源组合:0.16.2。当前修订:只保留明确选用路径,新增Plan消费规则的源码/分发/真实验证分层记录,尚未发布。S06—S08旧候选相关证据保留原对象,不作为本修订验证。命令与 Skill 共用本合同,原专业方法、写权限和停轮不变。
12
+
13
+ ## 1. 两步交接及路径基准
14
+
15
+ 第一步按企业任务形成 BRD、同 PBC 多主题独立 PRD、各自 AD/TestCase 及必要辅助引用;第二步用本次实际选定集合承接 SPEC 六项,交原 Plan,再按获准任务进入原 Build/test/review/Ship。可以从已有 PRD、AD 或 Plan 接入,不补跑 BRD、不编造 EBC 或历史批准,一个主题就绪可独立交接。
16
+
17
+ 资源模板、提示词和共用合同属于资料根;真实规范、业务文档和工程资产属于项目选择,不能互相冒充。一份清单对应明确的业务项目根与工作范围。建议位置 .i3/sop/{实际工作范围}/handoff.json,已有位置优先;多份清单并存先确定本次对象,不按文件名、更新时间或最高版本猜选。
18
+
19
+ 清单内相对路径统一相对本次明确的业务项目根,不相对清单目录、资源根或临时 cwd。开始先从任务及已采用指引明确根并实际核对,迁移项目后重定根。明确提供的项目外资料可用绝对路径并在现有采用说明标来源/移植影响;不全盘查找或擅自复制。路径存在只是定位事实,还须读取正文核身份、版本、范围和直接上游。
20
+
21
+ ## 2. 最小字段
22
+
23
+ | 字段 | 含义 | 约束 |
24
+ | --- | --- | --- |
25
+ | schemaVersion | 清单格式,首期值 1 | 不等于软件、模板或业务文档版本;未知格式只读报告差异,不自动迁移 |
26
+ | scope | 实际工作范围,work 必填,已知 ebc 才写 | 结合明确的项目根定位,不推断能力登记或历史 |
27
+ | selected | 已明确选定且实际读过的真实输入引用 | 空对象可用;选择不代表批准 |
28
+
29
+ 3.0 清单顶层只保留 schemaVersion、scope、selected,不生成 candidates 或 delivery,不执行候选登记或撤回。生成文档后展示实际路径,明确选用后才写 selected。原 Plan 保存 tasks/plan.md 与默认 tasks/todo.md,或在计划中说明唯一任务目标;Build 按获准任务继续,产物不重复回写 JSON。
30
+
31
+ 首次准备格式如下,仅表达范围,无未来路径。此示例不是本包的真实业务清单,也不由安装自动创建。
32
+
33
+ ```json
34
+ {
35
+ "schemaVersion": 1,
36
+ "scope": {"work": "巡检管理任务编排"},
37
+ "selected": {}
38
+ }
39
+ ```
40
+
41
+ selected 可有 brd、technicalBaseline 以及 topics。每个主题按 capabilityId 和 prdId 共同区分;未知身份先核对,不补造。topics 内可有 prd、ad、testCase、engineeringSkeleton、prototype 和 contracts。单项文档引用用字符串路径,contracts用路径数组,按需存在,不预填。一个主题多个 PRD 版本为不同候选,主题身份不变;不同主题独立版本,不强制 PRD/AD/TestCase 同号。
42
+
43
+ prototype、engineeringSkeleton、contracts 等配套仍按实际选用情况记录在主题中。旧清单中的 candidates、delivery 仅兼容读取,不参与当前输入选择;只读操作不改旧稿,明确选用写入时保存原始历史并移除这两个旧字段。其他未知顶层扩展不静默删除,应先说明用途或手动迁移;scope/selected 内的人工内容保留。不复制正文、任务完成率、approved、passed、审批结论或会话摘要。
44
+
45
+ 文档版本从选定文件名与正文回读核对,不另维护一套容易失配的版本值。TestCase为Plan可选输入:没有独立文件则省略字段;AD/已采用测试规范需承接本次验证要求,Plan可安排补齐设计。工程骨架引用真实采用说明或模板/源码,AD目录图不等于可应用资产;Build应用验证后才有真实工程根和相关锁。
46
+
47
+ 基础交接不强制 SHA256,同路径人工修改允许;摘要、版本和旧审阅都不能替代当前正文。工程已有契约锁、模板生成基线或不可变证据的摘要机制仍保留,不由本合同删除。
48
+
49
+ ## 3. 生成、登记和选择时点
50
+
51
+ 以下为文档审阅到明确选用的交接规则;本次源码修订未测试。同主题改选仍用[受控预览](selection-handoff.md),手动传递保留。
52
+
53
+ | 时点 | 拟实现的登记动作 | 停点与边界 |
54
+ | --- | --- | --- |
55
+ | 首次明确采用文档 | 明确项目根/范围和清单位置;只有本次允许写清单时才初始化并记录选定引用 | 不在生成待审稿、安装或只读任务中创建清单;选用不代替原方法认可 |
56
+ | 从已有 PRD/AD/Plan 接入 | 回读实际材料及范围,明确采用后可登记所选输入;Plan沿用原产物约定 | 不要求先有 BRD 或补历史批准,不默认认领所有文件 |
57
+ | 成功生成或明确委托修订 | 最后编辑后回读最终稿,展示真实路径并交审 | 不登记候选、不自动改 selected;原方法要求停轮时立即停轮 |
58
+ | 用户明确选用某稿 | 回读对象与配套,按已明确范围更新 selected;已有选择可复用 | 留清单旧稿和业务旧稿,选择不代批准;持久选择不能由当次临时覆盖推定 |
59
+ | 原 Plan 或后续工程产物完成 | 按原方法保存计划、唯一任务目标及实际工程/报告,不回写 delivery | 原方法不天然读写清单;后续按原产物路径和任务来源交接,保留原停点 |
60
+ | guide/status/review/recheck 或其他只读任务 | 不创建目录、清单、报告或采用记录 | 只在会话报告,获准调用者另存,零项目写入 |
61
+
62
+ 文档生成/修订只写本次明确目标及必要已授权历史,交审停轮;选用登记是另项获准作业。显式plan只写原规划产物,不改业务稿或清单,不向原Skill/命令注入额外权限。AD准备优先读既有规范和实际技术栈/骨架选择;缺规范时可建议来源或最小候选,用户可手动调整,不能为填清单静默生成总规约或切换工程。
63
+
64
+ ## 4. 实际读取与冲突处理
65
+
66
+ 1. 先判断本次流程。明确标准任务不读取/使用企业清单、模板或提示词;资源存在不激活企业 SOP。
67
+ 2. 明确唯一清单、项目根和本次主题;只读本次选定材料及必要依赖。新稿不自动采用,未选版本或另一个主题不替换当前稿。读清单后必须实际读对应正文、采用依据和必要契约。
68
+ 3. 本轮明确手动路径可覆盖相同角色的当次输入,说明与现有选择的差异;不自动改 JSON/项目采用。来源间矛盾、未知格式或同主题重复选择不猜优先,报告具体范围等待明确。
69
+ 4. Plan前核本范围六项、依赖、已有未完计划及必要真实认可。Plan/任务保留实际来源、主题、需求/AC及验证依据,不只留下可变 JSON 链接。原 tasks/plan.md、唯一 todo/tracker及只规划停点保持。
70
+ 5. Build以获准 Plan任务为依据,回读关联实际设计。JSON或同路径内容已变时先核与计划差异;不能让旧任务静默按新设计施工,也不无故重置无关任务。
71
+ 6. 缺文件、不可读、主题/版本/上游矛盾或契约失效,报告准确路径、问题和依赖影响;无静默全局/历史/最新版兜底。仅限制依赖范围,可独立部分不全量阻断。实际测试、代码审查和Ship各保留原条件,GO不等于发布授权。
72
+
73
+ 原 /i3-spec 的默认根 SPEC.md 与原 /i3-build auto 的固定规格/Git条件仍保留;等效企业规格使用适用原方法交接,不写影子 SPEC、改原命令或使用 JSON 绕过限制。新项目的工程规格按 spec/ 组织,允许继续使用原Plan、逐任务Build及原Build auto;后者仍保留其完整规格、Git与计划认可条件。企业跨阶段auto留3.1.0,两者不混同。
74
+
75
+ ## 5. 修改保护及失败恢复
76
+
77
+ 本片可执行的最小恢复顺序、适用读集与复制提示见[恢复规则](recovery-contract.md)。复用本合同§8的init/select/resolve 三操作,不另建恢复状态库。只补登记与修订业务稿是不同任务;部分稿保留且不登记,工具失败先报告停止,后续获准作业回读后只补登记。status/恢复不调用Spec生成,但仍完成五项共用读取及实际用于定位的安装身份核对。
78
+
79
+ | 情况 | 处理 |
80
+ | --- | --- |
81
+ | 项目自改模板/提示词/规范/同路径正文 | 读当前内容、保留修改及来源,核影响;不按发行摘要还原,不继承未核对的旧批准 |
82
+ | 明确选用但登记失败 | 保留现有文档与旧清单,说明准确路径及失败;后续只补已明确选用的引用,不重新生成或覆盖文档 |
83
+ | 文档失败或只有部分稿 | 不选用部分稿,不切换选择;部分稿及失败保留作业记录 |
84
+ | 重复选用 | 相同主题与路径复用,不制造新业务版本/任务;需要清理旧字段时保留原始历史 |
85
+ | 清单人工扩展或其他会话修改 | 保留未知字段和不属于本次的条目;写前回读,发现冲突停写,不用模板空表替换现稿 |
86
+ | 无法保证原子并发更新 | 如实报告能力边界、保留差异与恢复依据;不能只凭提示词宣称消除并发覆盖 |
87
+
88
+ 清单登记与业务正文写入为可分别失败的动作,不宣称事务保证。更新前保存可回读旧稿,最后写后实际回读。未核实的外部动作不盲重试,不建设另一套审批/任务状态数据库。实际保护和恢复须在 S06/S08 验证,B04的只读定位证据不代替写入测试。
89
+
90
+ ## 6. 无 JSON 的手动方式
91
+
92
+ 没有清单也可使用原命令或适用 Skill 加明确企业任务和真实路径,不强制安装登记程序、建清单、补跑BRD或改项目指引。手动路径同样读取正文并核身份、配套与缺口;是否持久登记由实际意图决定,纯手动任务不写 JSON。
93
+
94
+ ```text
95
+ 本次使用企业SOP资料,按我给的实际路径手动交接,不创建JSON。
96
+ 请读取 <本次PRD>、<配套AD>、<已采用公共规范>、<骨架采用说明或真实工程模板> 及相关代码,核对本主题身份、直接上游、SPEC六项、依赖和未完计划。
97
+ 本轮只准备原Plan的输入摘要,保留实际来源及Q,交我审阅;已有TestCase就引用,没有则从AD/已采用测试规范核验证要求并安排补齐,不统一阻断规划。
98
+ ```
99
+
100
+ 此为准备任务;实际调用原 /i3-plan 或规划 Skill 时仍遵守其完整规则。命令/Skill共享引用含义、范围与停点,不假定所有宿主slash参数和工具一致。当前合同不证明新入口或原Plan/Build实际集成,相关验证留S10/S11。
101
+
102
+ ## 7. 新增、修改、撤回与退役的引用差量
103
+
104
+ 完整规则读[生命周期合同](lifecycle-contract.md)。schemaVersion仍1,不增加审批或任务状态。新主题/新版本保存后只展示实际路径,明确选用才更新对应selected及真实配套;未采用稿件没有JSON撤回操作。部分需求撤销保留完整主题;整个未实施主题的选择撤销另核任务与消费者,共用引用和其他主题保留。业务旧稿、选择前清单及依据留存,历史不默认采用。
105
+
106
+ 已实现功能的退役目标不代表生产已经切换;工程根、旧报告及已完成任务仍是历史事实。路径缺失是断引用,不推定撤销,不自动找高版本/history替换。只读影响预览不写任何项目对象。无JSON的手动交接含同样的变更意图、真实路径、旧新依据和原方法停点,不能因手动方式跳过必要正文读取或任务保护。
107
+
108
+ ## 8. S06最小辅助与边界
109
+
110
+ 工具为本资料根的[handoff.py](handoff.py),版本v0.2.0,Python 3.9+标准库即可运行;先以`python3 <资料根>/handoff.py --help`核对接口。原版方法不导入或自动调用该工具,业务文档及原Plan/任务文件不由工具改写。
111
+
112
+ | 操作 | 已实现结果 | 本片边界 |
113
+ | --- | --- | --- |
114
+ | init | 显式项目根、清单位置与work创建最小对象;同范围且已是三字段时重复调用不写;清理旧字段时保存原始历史 | 不自动扫描其他清单或推断EBC;scope不同拒绝覆盖 |
115
+ | select | 直接登记明确初次选用或复用相同选择;同PBC不同prdId独立 | 不需要候选预登记;不同既有路径仍拒绝替换,配套及改选沿用准确预览;选用不是批准 |
116
+ | resolve | 只读核选定路径;多主题要求明确身份 | 不读未选高版本或自动补TestCase;不改变Plan,语义和直接上游由调用方核 |
117
+ | resolve+manual | 没有清单也可按明确路径只读解析;已有清单可作当次覆盖 | 不创建JSON、目录、历史或采用关系;同角色重复手动路径报歧义 |
118
+
119
+ 引用角色:全局brd、technicalBaseline;主题prd、ad、testCase、prototype、engineeringSkeleton、contracts。主题引用需要capabilityId与prdId。相对路径按项目根,显式采用的项目外绝对路径只读引用,不复制;工程骨架可指真实采用文件或源码目录,仍须另读其实际说明。
120
+
121
+ 每次实际清单变化前保存同目录history旧稿,保留scope/selected内的人工内容与其他主题;未知顶层扩展先明确用途,不自动清理。写后脚本完整回读清单;调用方还须回读实际差量及必要旧稿。清单和history写入路径均拒绝符号链接越界。协作工具有基础排他锁、写前比对和原子替换;不保证与任意人工编辑器之间的事务,不自动清理旧锁或恢复失败。业务稿写入与JSON登记也不是跨文件事务。
122
+
123
+ 最小JSON不增加hash、批准或任务状态字段。同路径人工改稿后重新读取当前正文,不因旧路径或旧报告沿用认可。部分稿/失败的完成判定由调用方依据真实任务及回读证据负责;CLI成功只能说明路径登记,不能证明业务完成。
124
+
125
+ S06原生验证仅证明旧版当时用法,不证明本次三字段和选用登记修订;S07命令、S08完整改选与恢复、S09独立审查、S10完整双入口矩阵、S11原Plan/Build实际消费和I4/I5工程仍分别取证。
126
+
127
+ ## 9. 真实调用与执行报告
128
+
129
+ 工具调用一经提交就是真实尝试;description中的“占位”“不执行”等文字不取消调用。状态说明、计划和待办只用会话文字,不提交探测或占位命令。按本轮明确工具范围操作,所需输入先实际读完;使用辅助时先核准确的`--help`接口,再做获准登记。必要工具被拒或失败就报告并停止依赖动作,不扩大权限或改用旁路;误发无关调用被拒,应如实列出,不将其说成辅助不可用,也不重复尝试。
130
+
131
+ 报告以真实tool_use/tool_result为依据,分别写清尝试、被拒、执行失败、成功及未确认;被拒也计入尝试。功能、写入边界、执行纪律及报告一致性分别判定。工具路径登记成功不能抵销其他偏差,模型自述不能代替轨迹或人工认可。
132
+
133
+ 需要外层执行复核时,调用者事先固定唯一项目、辅助路径、清单、主题、允许的引用和必需动作;由外层按[校验说明](verification/README.md)检查原始轨迹、独立核心审计和最终报告。执行状态仅存在会话/外层证据报告,不写入业务handoff.json,不新增审批或任务状态,也不改变原方法。
134
+
135
+ 最终会话报告中附且仅附一个下列有界JSON块;每行对应本轮一条实际Bash尝试,ordinal从1连续编号,按真实顺序列全。operation只用help、init、select、resolve、other;status只用succeeded、blocked、failed、unconfirmed。禁止另加role等字段。长命令和来源由外层从轨迹保留,无须在此复制。下例为单次接口核对的格式示意,实际作业按真实次数展开:
136
+
137
+ <!-- i3-sop-execution-report -->
138
+ ```json
139
+ {"schemaVersion":1,"shellAttempts":[{"ordinal":1,"operation":"help","status":"succeeded"}]}
140
+ ```
141
+ <!-- /i3-sop-execution-report -->
142
+
143
+ 没有回执或回执不支持时使用unconfirmed,不猜成功;权限明确拒绝为blocked,工具/进程或辅助明确失败为failed,实际成功回执为succeeded。外层检查通过仍需人工审阅;普通非Bash作业不被本CodeBuddy专用校验器强制增加调用。历史自由文字报告保持原样,问题另存校订说明,不追溯要求旧轮满足新增格式。
144
+
145
+ 上述shellAttempts只适用于直接Python辅助操作的既有S06校验。外层验证原生Skill、状态、Plan或Build前置时,显式选择[原生执行证据配置](verification/README.md#原生方法与执行报告),不把原校验器放宽为任意Bash守卫。该配置从真实轨迹提取全部工具尝试,最终报告只列失败/被拒/未确认尝试、调用者指定的输入核查和门禁状态;不要求模型手抄成功调用计数或工具ID。
146
+
147
+ 成功标志与正文矛盾时保留矛盾:例如Skill回执虽is_error=false但明确返回Error,不能报成功。路径存在、AD列有引用、宿主可能自动注入规则,都不证明已显式全文读取。已核/未核与未尝试分别写清;“无写入”“无Bash”“无工具调用”含义不同。报告的机器可核部分通过,不代表自由文字、设计语义或业务批准通过。
148
+
149
+ ## 10. 从选定 AD 到原 Plan、Build 的输入合同
150
+
151
+ 新项目业务文件默认在 spec/{能力}/,已有已采用路径不自动搬迁。资源根不变;JSON仍schemaVersion 1,不新增Spec角色、业务摘要、hash或批准/任务状态。工程入口由主题selected.ad或本轮手动AD路径定位;实际读取该AD、其准确PRD、规范/契约条款、骨架来源及必要代码。没有AD或六项不足时报告依赖缺口,JSON解析成功不能代替工程规格检查。
152
+
153
+ 生成文档后完整回读最终稿、展示路径并遵守原 Spec 的保存后停轮,不自动创建清单。用户明确采用后,获准交接作业才准备三字段清单并记录 selected;已有明确委托可直接承接,不重复询问。若登记失败只补准确选用引用,文档成功与选用登记成功分别报告。
154
+
155
+ 显式采用的项目指引可记录工作范围、capabilityId+prdId和唯一清单,作为原Plan的查找线索;它不将标准任务切为SOP。若没有确定范围、多个主题可选或指引失效,原Plan应澄清/由用户手动给准确路径,不静默取最高版。原方法不会天然解析或写回JSON。
156
+
157
+ 可复制的最短手动交接:
158
+
159
+ ```text
160
+ /i3-plan 本次使用企业SOP的等效规格。只规划:spec/巡检管理/巡检管理-任务编排-AD_v0.0.3.md 及其明确引用的PRD、已采用规范/契约、骨架来源和相关代码。完整核本主题SPEC六项、FR/AC、依赖和验证;TestCase按需。保留真实来源到tasks/plan.md与唯一任务目标,交我审阅,不实施。
161
+ ```
162
+
163
+ 有明确选用清单时可改为:
164
+
165
+ ```text
166
+ /i3-plan 本次使用企业SOP。读取.i3/sop/巡检/handoff.json,仅选capabilityId=inspection、prdId=scheduling的AD及其必要配套;实际读正文并核直接上游/六项,不取候选或其他主题。按原方法规划并保留来源,交我审阅,不实施。
167
+ ```
168
+
169
+ 计划须保存真实输入路径/版本、主题、需求/AC及验证依据,不能仅保存一个可变清单链接。原默认产物、不同工作未完计划保护和人审停点保留;如使用原方法支持的外部tracker也只能有一个权威任务目标,并在原计划中说明。Plan 结果直接按原产物位置交接,不重复登记 JSON,不把缺少 delivery 列为交接缺口。
170
+
171
+ 原 /i3-build 只执行已获准的下一任务;原 /i3-build auto须满足其允许的规格位置与有效内容、Git条件和全计划明确认可。特别是.i3/sop、CODEBUDDY.md、docs/engineering及安装目录等未提交变化不在默认允许规划产物内,触发原停点时呈现冲突由用户决定,不能加ignore、隐藏或擅自提交业务稿绕过。已获准的独立测试仓库提交只证明该仓库的工程行为,不等于产品发布。
172
+
173
+ Build前回读任务及关联实际设计,同路径或selected变化时核影响,受影响旧任务先由原Plan修订并获准,不用新稿偷换计划;无关任务和已完成历史保留。新会话回读真实来源与认可范围,不将模型自述、JSON或用例fixture当真人批准。按主题分组加载,必要共享约束仍完整覆盖。
174
+
175
+ ## 11. 门禁对象、时点和允许范围
176
+
177
+ | 门禁 | 实际含义与时点 | 不应扩大或替代的事项 |
178
+ | --- | --- | --- |
179
+ | 原Spec保存后人审 | 起草/修订保存并完成最终回读后,按原方法停轮交审;后续采用与规划遵循真实审阅决定 | 不因外层检查通过继续自动调用Plan、Build或补登记 |
180
+ | 明确选用登记 | 用户明确采用准确文档与配套,并允许写清单时才更新selected;原方法要求停轮则后续获准作业再做 | 新稿存在不构成选用;旧候选登记委托不构成选用委托;选用不等于Plan批准或开工许可 |
181
+ | 局部Q/依赖缺口 | 写清影响的具体要求、契约、任务和最晚决定时点;未知保留待核 | 不把局部Q统一扩大成全部规划/登记禁令,也不跳过实际受影响任务前的决定 |
182
+ | SOP Plan身份/直接上游冲突 | AD与本次准确PRD、所选范围的能力/主题标识或直接上游路径/PRD版本矛盾时,先停,不加载原规划、不写plan/todo或候选计划 | 疑似头部笔误、正文相似、文件已选或待审状态不代替人工处理;非阻断Q条款不能越过此停点,PRD/AD不强制同号 |
183
+ | 原Plan审阅/批准 | 实际计划与唯一任务目标形成后,由真人明确批准具体版本、范围及检查点 | Spec认可、JSON选择、模型报告和测试fixture不能代替Plan批准 |
184
+ | 原Build工程前置 | 保留原命令方法加载要求;auto核有效规格、真实Git基线和全计划明确认可 | 安全停止正确不代表全部调用义务已满足;缺后续实现证据不写成已执行TDD/构建/提交 |
185
+ | 项目额外检查点 | 若计划要求任务完成后、提交前再次人审,应在计划审阅时明确是否采用、批准对象和时点 | 不静默新增检查点,同时承诺原auto无额外停轮;不改原Build来迁就候选Plan措辞 |
186
+ | 测试/审查/Ship | 沿用各原方法的真实输入、质量、风险及授权条件;就绪与实际执行分别取证 | 外层报告、候选稿或Ship就绪结论不构成发布/生产操作许可 |
187
+
188
+ 这些门禁说明不新增业务JSON审批状态或替代原版规则。[单次受控交接](controlled-handoff.md)提供显式 CodeBuddy 入口,只对该次预检到明确选用登记或[同主题准确改选](selection-handoff.md)实施程序约束,不自动挂载其他会话或原版命令。改选先预览配套原文证据和Plan影响,再绑定该次明确选择;不更新原Plan或迁移批准。有真实项目约束冲突时只标注,给原因、推荐与可选操作,由使用者决定;业务稿与已完成任务不自动改写。
189
+
190
+ ## 12. 显式SOP Plan消费
191
+
192
+ /i3-sop plan与显式Skill的plan按[同一规划合同](plan-handoff.md)确定唯一范围,复用resolve或明确手动路径,只读消费selected。计划输入不止AD,还包含准确PRD、实际规范/契约、工程采用和相关代码;已有适用TestCase/原型按需,SPEC六项仍核。实际调用原规划Skill并履行当前原Plan命令义务,保留原plan/todo、不同未完任务保护、回读与人审停轮。
193
+
194
+ 该动作不初始化或写回JSON,不登记Plan产物,不代批准或自动Build。无JSON不强制创建,坏JSON不静默换源;标准原/i3-plan保持原使用方式。工具只解析路径,身份/版本/直接上游及真实业务决定仍由实际原文与来源核对。
195
+
196
+ 手动和JSON Plan共用规划合同的首批必读及检查点;手动不豁免README、阶段索引、共用合同、交接合同与规划合同全文。身份或直接上游冲突优先于独立部分规划,先给证据和处理建议,不修改业务稿或JSON、不输出plan/todo;人工处理后重新核对再规划。此规则与§11局部Q的适用范围分开,不增加业务JSON状态或接管原版方法。