@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.
Files changed (86) hide show
  1. package/.agents/plugins/marketplace.json +1 -1
  2. package/.claude/commands/i3-sop.md +3 -3
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/.claude-plugin/plugin.json +1 -1
  5. package/.codex-plugin/plugin.json +1 -1
  6. package/.gemini/commands/i3-sop.toml +3 -3
  7. package/README.md +22 -27
  8. package/commands/i3-sop.toml +3 -3
  9. package/docs/codex-setup.md +1 -1
  10. package/docs/other-hosts.md +1 -1
  11. package/docs/workbuddy-setup.md +1 -1
  12. package/evals/cases/i3-enterprise-sop.json +70 -32
  13. package/evals/zh-CN/cases/i3-enterprise-sop.json +77 -31
  14. package/package.json +1 -23
  15. package/plugin.json +1 -1
  16. package/skills/i3-enterprise-sop/SKILL.md +151 -91
  17. package/skills/i3-enterprise-sop/references/engineering-skeleton/directory-standard.md +29 -15
  18. package/skills/i3-enterprise-sop/references/handoff.py +171 -100
  19. package/skills/i3-enterprise-sop/references/read_text.py +60 -0
  20. package/skills/i3-enterprise-sop/references/technical-baseline/baselines/enterprise-application-technical-baseline-v0-0-4.md +28 -28
  21. package/skills/i3-enterprise-sop/references/technical-baseline/readme.md +9 -118
  22. package/skills/i3-enterprise-sop/references/{ad/templates → templates}/ad.md +34 -27
  23. package/skills/i3-enterprise-sop/references/{brd/templates → templates}/brd.md +21 -10
  24. package/skills/i3-enterprise-sop/references/{prd/templates → templates}/prd.md +57 -24
  25. package/skills/i3-enterprise-sop/references/{testcase/templates → templates}/testcase.md +32 -22
  26. package/docs/upstream-repository/AGENTS.md +0 -92
  27. package/docs/upstream-repository/CLAUDE.md +0 -60
  28. package/docs/upstream-repository/CONTRIBUTING.md +0 -137
  29. package/docs/upstream-repository/README.md +0 -8
  30. package/docs/upstream-repository/gitattributes.txt +0 -1
  31. package/docs/upstream-repository/gitignore.txt +0 -10
  32. package/docs/upstream-repository/manifest.json +0 -71
  33. package/docs/upstream-repository/opencode-skills.link +0 -1
  34. package/docs/upstream-repository/skill-gap.yml +0 -80
  35. package/docs/upstream-repository/skills-contributing.md +0 -15
  36. package/docs/upstream-repository/test-plugin-install.yml +0 -167
  37. package/skills/i3-enterprise-sop/references/ad/assets-manifest.json +0 -86
  38. package/skills/i3-enterprise-sop/references/ad/prompts/01-generate.md +0 -58
  39. package/skills/i3-enterprise-sop/references/ad/prompts/02-review.md +0 -54
  40. package/skills/i3-enterprise-sop/references/ad/prompts/03-revise.md +0 -54
  41. package/skills/i3-enterprise-sop/references/ad/prompts/04-recheck.md +0 -54
  42. package/skills/i3-enterprise-sop/references/ad/readme.md +0 -70
  43. package/skills/i3-enterprise-sop/references/assets-manifest.json +0 -230
  44. package/skills/i3-enterprise-sop/references/brd/assets-manifest.json +0 -83
  45. package/skills/i3-enterprise-sop/references/brd/prompts/01-generate.md +0 -51
  46. package/skills/i3-enterprise-sop/references/brd/prompts/02-review.md +0 -47
  47. package/skills/i3-enterprise-sop/references/brd/prompts/03-revise.md +0 -47
  48. package/skills/i3-enterprise-sop/references/brd/prompts/04-recheck.md +0 -47
  49. package/skills/i3-enterprise-sop/references/brd/readme.md +0 -50
  50. package/skills/i3-enterprise-sop/references/codebuddy-project-guide.md +0 -34
  51. package/skills/i3-enterprise-sop/references/controlled-handoff.md +0 -41
  52. package/skills/i3-enterprise-sop/references/controlled_handoff.py +0 -379
  53. package/skills/i3-enterprise-sop/references/engineering-skeleton/adoption.md +0 -200
  54. package/skills/i3-enterprise-sop/references/engineering-skeleton/catalog.json +0 -17
  55. package/skills/i3-enterprise-sop/references/engineering-skeleton/readme.md +0 -52
  56. package/skills/i3-enterprise-sop/references/handoff-contract.md +0 -196
  57. package/skills/i3-enterprise-sop/references/lifecycle-contract.md +0 -91
  58. package/skills/i3-enterprise-sop/references/plan-handoff.md +0 -73
  59. package/skills/i3-enterprise-sop/references/prd/assets-manifest.json +0 -109
  60. package/skills/i3-enterprise-sop/references/prd/prompts/01-generate.md +0 -64
  61. package/skills/i3-enterprise-sop/references/prd/prompts/02-review.md +0 -49
  62. package/skills/i3-enterprise-sop/references/prd/prompts/03-revise.md +0 -48
  63. package/skills/i3-enterprise-sop/references/prd/prompts/04-recheck.md +0 -47
  64. package/skills/i3-enterprise-sop/references/prd/readme.md +0 -137
  65. package/skills/i3-enterprise-sop/references/prd/template-contracts.md +0 -39
  66. package/skills/i3-enterprise-sop/references/prd/templates/ad.md +0 -17
  67. package/skills/i3-enterprise-sop/references/readme.md +0 -167
  68. package/skills/i3-enterprise-sop/references/recovery-contract.md +0 -65
  69. package/skills/i3-enterprise-sop/references/review-handoff.md +0 -105
  70. package/skills/i3-enterprise-sop/references/selection-handoff.md +0 -55
  71. package/skills/i3-enterprise-sop/references/selection_handoff.py +0 -225
  72. package/skills/i3-enterprise-sop/references/stages.json +0 -271
  73. package/skills/i3-enterprise-sop/references/technical-baseline/assets-manifest.json +0 -84
  74. package/skills/i3-enterprise-sop/references/template-contracts.md +0 -142
  75. package/skills/i3-enterprise-sop/references/testcase/assets-manifest.json +0 -91
  76. package/skills/i3-enterprise-sop/references/testcase/prompts/01-generate.md +0 -53
  77. package/skills/i3-enterprise-sop/references/testcase/prompts/02-review.md +0 -51
  78. package/skills/i3-enterprise-sop/references/testcase/prompts/03-revise.md +0 -49
  79. package/skills/i3-enterprise-sop/references/testcase/prompts/04-recheck.md +0 -51
  80. package/skills/i3-enterprise-sop/references/testcase/readme.md +0 -61
  81. package/skills/i3-enterprise-sop/references/verification/check_execution.py +0 -185
  82. package/skills/i3-enterprise-sop/references/verification/check_native_execution.py +0 -282
  83. package/skills/i3-enterprise-sop/references/verification/check_reads.py +0 -348
  84. package/skills/i3-enterprise-sop/references/verification/readme.md +0 -149
  85. package/skills/i3-enterprise-sop/references/verification/review-output.schema.json +0 -155
  86. package/skills/i3-enterprise-sop/references/verification/review_handoff.py +0 -329
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env python3
2
- """Path-only SOP handoff helper. Never writes business documents or approvals."""
2
+ """Business-document SOP handoff for one EBC per project.
3
+
4
+ Pass spec/handoff.json explicitly for new manifests; relative references use the project root.
5
+ Never writes documents or approvals.
6
+ """
3
7
  import argparse
4
8
  import copy
5
9
  import json
@@ -8,9 +12,11 @@ from pathlib import Path
8
12
  import tempfile
9
13
  import uuid
10
14
 
11
- VERSION = '0.2.0'
12
- GLOBAL_ROLES = {'brd', 'technicalBaseline'}
13
- TOPIC_ROLES = {'prd', 'ad', 'testCase', 'prototype', 'engineeringSkeleton', 'contracts'}
15
+ VERSION = '0.4.0'
16
+ GLOBAL_ROLES = {'brd'}
17
+ TOPIC_ROLES = {'prd', 'ad', 'testCase'}
18
+ DOCUMENT_ROLES = GLOBAL_ROLES | TOPIC_ROLES
19
+ TOPIC_IDENTITY = ('pbcId', 'pbcName', 'prdId')
14
20
 
15
21
 
16
22
  def _unique(pairs):
@@ -26,38 +32,93 @@ def _nonstandard(value):
26
32
  raise ValueError('nonstandard JSON constant: ' + value)
27
33
 
28
34
 
29
- def _load(raw):
30
- value = json.loads(raw, object_pairs_hook=_unique, parse_constant=_nonstandard)
31
- if not isinstance(value, dict) or type(value.get('schemaVersion')) is not int or value['schemaVersion'] != 1:
32
- raise ValueError('unsupported handoff schemaVersion; no automatic migration')
35
+ def _required_text(value, label):
36
+ if not isinstance(value, str) or not value.strip():
37
+ raise ValueError(label + ' must be a nonempty string')
38
+ return value
39
+
40
+
41
+ def _only_fields(value, allowed, label):
42
+ if not isinstance(value, dict):
43
+ raise ValueError(label + ' must be an object')
44
+ unexpected = set(value) - set(allowed)
45
+ if unexpected:
46
+ raise ValueError('unrecognized ' + label + ' fields; preserve the original and resolve their purpose: '
47
+ + ', '.join(sorted(unexpected)))
48
+
49
+
50
+ def _validate(value):
51
+ if not isinstance(value, dict) or type(value.get('schemaVersion')) is not int:
52
+ raise ValueError('unsupported handoff schemaVersion; expected integer 2')
53
+ if value['schemaVersion'] == 1:
54
+ raise ValueError('legacy handoff schemaVersion 1; preserve the original, verify EBC/PBC identities '
55
+ 'and document references before an explicitly requested update; no automatic migration')
56
+ if value['schemaVersion'] != 2:
57
+ raise ValueError('unsupported handoff schemaVersion; expected 2; no automatic migration')
58
+ _only_fields(value, {'schemaVersion', 'scope', 'selected'}, 'handoff')
33
59
  scope = value.get('scope')
34
- if not isinstance(scope, dict) or not isinstance(scope.get('work'), str) or not scope['work'].strip():
35
- raise ValueError('scope.work must identify the actual work')
36
- if not isinstance(value.get('selected'), dict):
37
- raise ValueError('selected must be an object')
38
- topics = value['selected'].get('topics', [])
60
+ _only_fields(scope, {'ebcId', 'ebcName'}, 'scope')
61
+ for key in ('ebcId', 'ebcName'):
62
+ _required_text(scope.get(key), 'scope.' + key)
63
+ selected = value.get('selected')
64
+ _only_fields(selected, GLOBAL_ROLES | {'topics'}, 'selected')
65
+ for role in GLOBAL_ROLES & set(selected):
66
+ _required_text(selected[role], 'selected.' + role)
67
+ topics = selected.get('topics')
39
68
  if not isinstance(topics, list):
40
69
  raise ValueError('selected.topics must be an array')
41
70
  seen = set()
71
+ names = {}
42
72
  for topic in topics:
43
- if not isinstance(topic, dict) or not all(isinstance(topic.get(k), str) and topic[k].strip() for k in ['capabilityId', 'prdId']):
44
- raise ValueError('topic requires capabilityId and prdId')
45
- identity = (topic['capabilityId'], topic['prdId'])
73
+ _only_fields(topic, set(TOPIC_IDENTITY) | TOPIC_ROLES, 'topic')
74
+ for key in TOPIC_IDENTITY:
75
+ _required_text(topic.get(key), 'topic.' + key)
76
+ identity = (topic['pbcId'], topic['prdId'])
46
77
  if identity in seen:
47
78
  raise ValueError('ambiguous duplicate topic selection')
48
79
  seen.add(identity)
80
+ if names.setdefault(topic['pbcId'], topic['pbcName']) != topic['pbcName']:
81
+ raise ValueError('conflicting pbcName for pbcId: ' + topic['pbcId'])
82
+ roles = TOPIC_ROLES & set(topic)
83
+ if not roles:
84
+ raise ValueError('topic must contain at least one selected PRD, AD or TestCase path')
85
+ for role in roles:
86
+ _required_text(topic[role], 'topic.' + role)
49
87
  return value
50
88
 
51
89
 
90
+ def _load(raw):
91
+ return _validate(json.loads(raw, object_pairs_hook=_unique, parse_constant=_nonstandard))
92
+
93
+
52
94
  def _selected_only(value):
53
- """Canonical writes; legacy draft indexes never become selected inputs."""
54
- unexpected = set(value) - {'schemaVersion', 'scope', 'selected', 'candidates', 'delivery'}
55
- if unexpected:
56
- raise ValueError('unrecognized top-level fields; preserve the original and resolve their purpose before writing: '
57
- + ', '.join(sorted(unexpected)))
95
+ """Validate the complete v2 record before any canonical write; never discard fields."""
96
+ _validate(value)
58
97
  return {key: copy.deepcopy(value[key]) for key in ('schemaVersion', 'scope', 'selected')}
59
98
 
60
99
 
100
+ def _check_scope(value, ebc_id, ebc_name):
101
+ if value['scope']['ebcId'] != ebc_id:
102
+ raise ValueError('manifest belongs to a different EBC scope')
103
+ if ebc_name is not None and _required_text(ebc_name, 'ebcName') != value['scope']['ebcName']:
104
+ raise ValueError('ebcName conflicts with the selected EBC; verify the document identity before updating')
105
+
106
+
107
+ def _topic_name(selected, pbc_id, prd_id, pbc_name):
108
+ _required_text(pbc_id, 'pbcId')
109
+ _required_text(prd_id, 'prdId')
110
+ topics = selected['topics']
111
+ matches = [x for x in topics if x['pbcId'] == pbc_id and x['prdId'] == prd_id]
112
+ if pbc_name is None:
113
+ if not matches:
114
+ raise ValueError('new topic requires an explicit pbcName')
115
+ pbc_name = matches[0]['pbcName']
116
+ _required_text(pbc_name, 'pbcName')
117
+ if any(x['pbcId'] == pbc_id and x['pbcName'] != pbc_name for x in topics):
118
+ raise ValueError('pbcName conflicts with the selected PBC; verify the document identity before updating')
119
+ return pbc_name
120
+
121
+
61
122
  def _manifest_path(root, raw):
62
123
  if not raw:
63
124
  raise ValueError('an explicit manifest path is required for this operation')
@@ -75,8 +136,8 @@ def _manifest_path(root, raw):
75
136
  return path
76
137
 
77
138
 
78
- def _reference(root, role, raw, capability_id=None, prd_id=None):
79
- if role not in GLOBAL_ROLES | TOPIC_ROLES:
139
+ def _reference(root, role, raw, pbc_id=None, prd_id=None, pbc_name=None):
140
+ if role not in DOCUMENT_ROLES:
80
141
  raise ValueError('unknown reference role')
81
142
  if not isinstance(raw, str) or not raw.strip():
82
143
  raise ValueError('reference path must be explicit')
@@ -84,86 +145,85 @@ def _reference(root, role, raw, capability_id=None, prd_id=None):
84
145
  resolved = (path if path.is_absolute() else root / path).resolve()
85
146
  if not path.is_absolute() and not resolved.is_relative_to(root):
86
147
  raise ValueError('relative reference escapes project root; use an explicitly adopted absolute source')
87
- if resolved.is_file():
88
- if not resolved.read_text(encoding='utf-8').strip():
89
- raise ValueError('empty document is not a complete handoff reference')
90
- elif role != 'engineeringSkeleton' or not resolved.is_dir():
148
+ if not resolved.is_file():
91
149
  raise ValueError('reference missing or unreadable: ' + raw)
92
- result = {'role': role, 'path': str(resolved.relative_to(root)) if resolved.is_relative_to(root) else str(resolved)}
150
+ if not resolved.read_text(encoding='utf-8').strip():
151
+ raise ValueError('empty document is not a complete handoff reference')
152
+ result = {'role': role,
153
+ 'path': resolved.relative_to(root).as_posix() if resolved.is_relative_to(root) else str(resolved)}
93
154
  if role in TOPIC_ROLES:
94
- if not all(isinstance(x, str) and x.strip() for x in [capability_id, prd_id]):
95
- raise ValueError('topic reference requires explicit capabilityId and prdId')
96
- result.update(capabilityId=capability_id, prdId=prd_id)
155
+ for key, item in [('pbcId', pbc_id), ('pbcName', pbc_name), ('prdId', prd_id)]:
156
+ result[key] = _required_text(item, key)
97
157
  return result
98
158
 
99
159
 
100
- def _assign(selected, reference, *, transient=False):
160
+ def _assign(selected, reference, *, transient=False, replace_document=False):
101
161
  role, path = reference['role'], reference['path']
102
162
  target = selected
103
163
  if role in TOPIC_ROLES:
104
- topics = selected.setdefault('topics', [])
105
- matches = [x for x in topics if x['capabilityId'] == reference['capabilityId'] and x['prdId'] == reference['prdId']]
164
+ topics = selected['topics']
165
+ _topic_name(selected, reference['pbcId'], reference['prdId'], reference['pbcName'])
166
+ matches = [x for x in topics if x['pbcId'] == reference['pbcId'] and x['prdId'] == reference['prdId']]
106
167
  if len(matches) > 1:
107
168
  raise ValueError('ambiguous duplicate topic selection')
108
169
  if matches:
109
170
  target = matches[0]
110
171
  else:
111
- target = {k: reference[k] for k in ['capabilityId', 'prdId']}
172
+ target = {k: reference[k] for k in TOPIC_IDENTITY}
112
173
  topics.append(target)
113
- if role == 'contracts':
114
- values = target.setdefault(role, [])
115
- if not isinstance(values, list) or not all(isinstance(x, str) for x in values):
116
- raise ValueError('contracts must be a path array')
117
- if transient:
118
- target[role] = [path]
119
- elif path not in values:
120
- values.append(path)
121
- else:
122
- if not transient and role in target and target[role] != path:
123
- raise ValueError('existing selection differs; replacement and affected-pair recovery belong to S08')
124
- target[role] = path
125
-
126
-
127
- def _resolve(root, value, capability_id, prd_id, manual):
174
+ previous_path = target.get(role)
175
+ if not transient and previous_path is not None and previous_path != path and not replace_document:
176
+ raise ValueError('updating a selected document requires --ebc-id matching scope.ebcId')
177
+ target[role] = path
178
+ return previous_path
179
+
180
+
181
+ def _resolve(root, value, pbc_id, prd_id, pbc_name, manual):
128
182
  selected = copy.deepcopy(value['selected'])
129
- topics = selected.get('topics', [])
130
- if bool(capability_id) != bool(prd_id):
131
- raise ValueError('provide both capabilityId and prdId')
132
- if not capability_id and len(topics) == 1:
133
- capability_id, prd_id = topics[0]['capabilityId'], topics[0]['prdId']
183
+ topics = selected['topics']
184
+ if (pbc_id is None) != (prd_id is None):
185
+ raise ValueError('provide both pbcId and prdId')
186
+ if pbc_id is not None:
187
+ _required_text(pbc_id, 'pbcId')
188
+ _required_text(prd_id, 'prdId')
189
+ elif len(topics) == 1:
190
+ pbc_id, prd_id = topics[0]['pbcId'], topics[0]['prdId']
191
+ if pbc_name is not None:
192
+ _topic_name(selected, pbc_id, prd_id, pbc_name)
134
193
  manual_roles = set()
135
194
  for item in manual:
136
195
  role, separator, path = item.partition('=')
137
196
  if not separator or role in manual_roles:
138
197
  raise ValueError('manual references require one unambiguous ROLE=PATH per role')
139
198
  manual_roles.add(role)
140
- reference = _reference(root, role, path, capability_id, prd_id)
199
+ name = _topic_name(selected, pbc_id, prd_id, pbc_name) if role in TOPIC_ROLES else None
200
+ reference = _reference(root, role, path, pbc_id, prd_id, name)
141
201
  _assign(selected, reference, transient=True)
142
- topics = selected.get('topics', [])
143
- if not capability_id and len(topics) > 1:
144
- raise ValueError('multiple topics; explicitly choose capabilityId and prdId')
202
+ topics = selected['topics']
203
+ if pbc_id is None and len(topics) > 1:
204
+ raise ValueError('multiple topics; explicitly choose pbcId and prdId')
145
205
  references = []
146
206
  for role in sorted(GLOBAL_ROLES):
147
207
  if role in selected:
148
208
  references.append(_reference(root, role, selected[role]))
149
- matches = [x for x in topics if x['capabilityId'] == capability_id and x['prdId'] == prd_id]
150
- if capability_id and topics and not matches:
209
+ matches = [x for x in topics if x['pbcId'] == pbc_id and x['prdId'] == prd_id]
210
+ if pbc_id is not None and not matches:
151
211
  raise ValueError('requested topic is not selected; no highest-version fallback')
152
212
  for topic in matches:
153
213
  for role in sorted(TOPIC_ROLES):
154
214
  if role not in topic:
155
215
  continue
156
- paths = topic[role] if role == 'contracts' else [topic[role]]
157
- if not isinstance(paths, list):
158
- raise ValueError('contracts must be a path array')
159
- for path in paths:
160
- references.append(_reference(root, role, path, capability_id, prd_id))
216
+ references.append(_reference(root, role, topic[role], pbc_id, prd_id, topic['pbcName']))
161
217
  for reference in references:
162
218
  path = Path(reference['path'])
163
219
  reference['resolvedPath'] = str(path if path.is_absolute() else root / path)
164
- return {'ok': True, 'changed': False, 'source': 'manual' if manual else 'manifest',
165
- 'scope': value['scope'], 'references': references,
166
- 'proof': 'paths read now; caller must verify current identity, upstream, scope and actual human decisions'}
220
+ result = {'ok': True, 'changed': False, 'source': 'manual' if manual else 'manifest',
221
+ 'scope': copy.deepcopy(value['scope']), 'references': references,
222
+ 'proof': 'document paths read now; caller must verify document identities, versions, upstream '
223
+ 'and adopted attachments against the declared EBC/PBC scope and actual human decisions'}
224
+ if matches:
225
+ result['topic'] = {key: matches[0][key] for key in TOPIC_IDENTITY}
226
+ return result
167
227
 
168
228
 
169
229
  def _store(root, path, old, value):
@@ -209,63 +269,74 @@ def _store(root, path, old, value):
209
269
  lock.unlink()
210
270
 
211
271
 
212
- def execute(project_root, manifest, action, *, work=None, role=None, path=None,
213
- capability_id=None, prd_id=None, manual=()):
272
+ def execute(project_root, manifest, action, *, ebc_id=None, ebc_name=None, role=None, path=None,
273
+ pbc_id=None, pbc_name=None, prd_id=None, manual=()):
214
274
  root = Path(project_root).resolve()
215
275
  if not root.is_dir():
216
276
  raise ValueError('explicit project root must already exist')
277
+ _required_text(ebc_id, 'ebcId')
278
+ if action not in {'init', 'select', 'resolve'}:
279
+ raise ValueError('unknown handoff action')
280
+ if action != 'resolve' and manual:
281
+ raise ValueError('manual overrides are read-only resolve inputs')
217
282
  if action == 'resolve':
218
283
  if manifest:
219
284
  target = _manifest_path(root, manifest)
220
285
  if not target.is_file():
221
286
  raise ValueError('manifest missing; no automatic creation in read-only resolve')
222
287
  value = _load(target.read_bytes())
223
- if work is not None and work != value['scope']['work']:
224
- raise ValueError('manifest belongs to a different work scope')
225
- elif manual and isinstance(work, str) and work.strip():
226
- value = {'schemaVersion': 1, 'scope': {'work': work}, 'selected': {}}
288
+ _check_scope(value, ebc_id, ebc_name)
289
+ elif manual:
290
+ value = {'schemaVersion': 2,
291
+ 'scope': {'ebcId': ebc_id, 'ebcName': _required_text(ebc_name, 'ebcName')},
292
+ 'selected': {'topics': []}}
227
293
  else:
228
- raise ValueError('resolve requires an explicit manifest or manual references with work scope')
229
- return _resolve(root, value, capability_id, prd_id, manual)
294
+ raise ValueError('resolve requires an explicit manifest or manual references with EBC identity')
295
+ return _resolve(root, value, pbc_id, prd_id, pbc_name, manual)
230
296
  target = _manifest_path(root, manifest)
231
297
  old = target.read_bytes() if target.is_file() else None
232
298
  if action == 'init':
233
- if not isinstance(work, str) or not work.strip():
234
- raise ValueError('init requires the actual work scope')
235
- value = _load(old) if old is not None else {'schemaVersion': 1, 'scope': {'work': work}, 'selected': {}}
236
- if value['scope']['work'] != work:
237
- raise ValueError('existing manifest belongs to a different work scope')
299
+ _required_text(ebc_name, 'ebcName')
300
+ value = _load(old) if old is not None else {
301
+ 'schemaVersion': 2, 'scope': {'ebcId': ebc_id, 'ebcName': ebc_name}, 'selected': {'topics': []}}
302
+ _check_scope(value, ebc_id, ebc_name)
238
303
  return _store(root, target, old, value)
239
304
  if action == 'select':
240
- if manual:
241
- raise ValueError('manual overrides are read-only resolve inputs')
242
305
  if old is None:
243
- raise ValueError('manifest missing; initialize the explicit work first or use manual resolve')
306
+ raise ValueError('manifest missing; initialize the explicit EBC first or use manual resolve')
244
307
  value = _load(old)
245
- if work is not None and work != value['scope']['work']:
246
- raise ValueError('manifest belongs to a different work scope')
247
- reference = _reference(root, role, path, capability_id, prd_id)
248
- _assign(value['selected'], reference)
249
- return _store(root, target, old, value)
250
- raise ValueError('handoff operation not implemented in this increment')
308
+ _check_scope(value, ebc_id, ebc_name)
309
+ if role in GLOBAL_ROLES and any(item is not None for item in (pbc_id, pbc_name, prd_id)):
310
+ raise ValueError('BRD selection belongs to the EBC and does not take PBC or PRD identity')
311
+ name = _topic_name(value['selected'], pbc_id, prd_id, pbc_name) if role in TOPIC_ROLES else None
312
+ reference = _reference(root, role, path, pbc_id, prd_id, name)
313
+ previous_path = _assign(value['selected'], reference, replace_document=True)
314
+ result = _store(root, target, old, value)
315
+ if previous_path is not None and previous_path != reference['path']:
316
+ result['updatedSelection'] = dict(reference, previousPath=previous_path)
317
+ return result
251
318
 
252
319
 
253
320
  def main():
254
321
  parser = argparse.ArgumentParser(description=__doc__)
255
322
  parser.add_argument('action', choices=['init', 'select', 'resolve'])
256
- parser.add_argument('--project-root', required=True)
257
- parser.add_argument('--manifest')
258
- parser.add_argument('--work')
259
- parser.add_argument('--role', choices=sorted(GLOBAL_ROLES | TOPIC_ROLES))
260
- parser.add_argument('--path')
261
- parser.add_argument('--capability-id')
323
+ parser.add_argument('--project-root', required=True, help='Explicit business project root; one EBC per project')
324
+ parser.add_argument('--manifest', help='Explicit manifest path; new projects use spec/handoff.json; '
325
+ 'omit only for resolve with manual inputs')
326
+ parser.add_argument('--ebc-id', required=True, help='Expected EBC identity; checked on every operation')
327
+ parser.add_argument('--ebc-name', help='Required for init and manual resolve without a manifest')
328
+ parser.add_argument('--role', choices=sorted(DOCUMENT_ROLES))
329
+ parser.add_argument('--path', help='Explicit saved document path; new BRD: spec/<EBC-name>-BRD_v<version>.md; '
330
+ 'new PRD/AD/TestCase: spec/<PBC-name>/...; relative to the business project root')
331
+ parser.add_argument('--pbc-id')
332
+ parser.add_argument('--pbc-name', help='Required when creating a topic; existing names must match')
262
333
  parser.add_argument('--prd-id')
263
334
  parser.add_argument('--manual', action='append', default=[], metavar='ROLE=PATH')
264
335
  args = parser.parse_args()
265
336
  try:
266
- result = execute(args.project_root, args.manifest, args.action, work=args.work,
267
- role=args.role, path=args.path, capability_id=args.capability_id,
268
- prd_id=args.prd_id, manual=args.manual)
337
+ result = execute(args.project_root, args.manifest, args.action, ebc_id=args.ebc_id,
338
+ ebc_name=args.ebc_name, role=args.role, path=args.path,
339
+ pbc_id=args.pbc_id, pbc_name=args.pbc_name, prd_id=args.prd_id, manual=args.manual)
269
340
  except (OSError, ValueError, UnicodeError) as error:
270
341
  print(json.dumps({'ok': False, 'error': str(error)}, ensure_ascii=False))
271
342
  return 1
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """Return one bounded UTF-8 text page; use nextOffset unchanged for the next page."""
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ def render(path, text, start, end):
9
+ return json.dumps({'path': str(path), 'offset': start, 'nextOffset': end,
10
+ 'eof': end == len(text), 'text': text[start:end]},
11
+ ensure_ascii=False, separators=(',', ':'))
12
+
13
+
14
+ def read_page(path, offset=0, max_chars=6000):
15
+ if not isinstance(offset, int) or isinstance(offset, bool) or offset < 0:
16
+ raise ValueError('offset must be a nonnegative integer')
17
+ if not isinstance(max_chars, int) or isinstance(max_chars, bool) or not 512 <= max_chars <= 10000:
18
+ raise ValueError('max-chars must be an integer from 512 to 10000')
19
+ source = Path(path).resolve(strict=True)
20
+ text = source.read_bytes().decode('utf-8')
21
+ if not text.strip():
22
+ raise ValueError('selected text file is empty')
23
+ if offset > len(text):
24
+ raise ValueError('offset is beyond the selected text; use the preceding nextOffset')
25
+ low, high = offset, min(len(text), offset + max_chars)
26
+ if len(render(source, text, offset, offset)) + 1 > max_chars:
27
+ raise ValueError('max-chars is too small for the path metadata')
28
+ while low < high:
29
+ end = (low + high + 1) // 2
30
+ if len(render(source, text, offset, end)) + 1 <= max_chars:
31
+ low = end
32
+ else:
33
+ high = end - 1
34
+ end = low
35
+ if end == offset and offset < len(text):
36
+ raise ValueError('max-chars leaves no room for text')
37
+ if end < len(text):
38
+ newline = text.rfind('\n', offset, end)
39
+ if newline >= offset:
40
+ end = newline + 1
41
+ return render(source, text, offset, end)
42
+
43
+
44
+ def main():
45
+ parser = argparse.ArgumentParser(description=__doc__)
46
+ parser.add_argument('--path', required=True)
47
+ parser.add_argument('--offset', type=int, default=0, help='Character cursor returned by the preceding page; initial value 0')
48
+ parser.add_argument('--max-chars', type=int, default=6000, help='Maximum serialized page length, including metadata and trailing newline')
49
+ args = parser.parse_args()
50
+ try:
51
+ page = read_page(args.path, args.offset, args.max_chars)
52
+ except (OSError, ValueError, UnicodeError) as error:
53
+ print(json.dumps({'ok': False, 'error': str(error)}, ensure_ascii=False))
54
+ return 1
55
+ print(page)
56
+ return 0
57
+
58
+
59
+ if __name__ == '__main__':
60
+ raise SystemExit(main())
@@ -12,19 +12,19 @@
12
12
 
13
13
  | 项目 | 当前内容 |
14
14
  | --- | --- |
15
- | 文档版本 | v0.0.4,2026-10-22;当前分发规约,工程目录规范与采用说明统一为1.0.0 |
16
- | 文档性质 | 根据使用者逐项确认整理的企业共用技术基线;供具体项目明确选用,新稿文字仍可人工审阅修改 |
17
- | 适用范围 | 明确采用本文件的企业业务项目及其前端、业务后端、Agent、任务工程;各工程按实际需求采用 |
18
- | 项目绑定 | 本文件不绑定某一业务项目;项目名称、采用版本、PRD/AD与实际工程在项目采用说明或AD中记录,见 Q01、Q12 |
15
+ | 文档版本 | v0.0.4,2026-10-22;当前分发规约,工程目录规范基线为1.0.0 |
16
+ | 文档性质 | 根据使用者逐项确认整理的企业共用技术基线;作为 i3-enterprise-sop 生成 AD的默认技术输入,项目明确规约优先;新稿仍可人工审阅修改 |
17
+ | 适用范围 | 使用本 Skill默认规约或明确采用本文件的企业业务项目及其前端、业务后端、Agent、任务工程;各工程按实际需求采用 |
18
+ | 项目绑定 | 本文件不绑定某一业务项目;项目名称、采用版本、PRD/AD与实际工程在AD来源与采用表中记录,见 Q01、Q12 |
19
19
  | 直接来源 | 软件架构设计 Specification 模板 v0.6(2026-09-23),含原文明确的技术方向及工程约束 |
20
20
  | 编排依据 | 当前企业 SOP 的公共技术规约模板、AD 模板及交接合同;工程组织引用现有目录规范1.0.0 |
21
21
  | 确认依据 | 2026-10-20确认原有共用规则;2026-10-21确认将推荐版本矩阵补入规约,K8s暂按既有1.28记录。确认技术选择不等于授权升级、安装、部署或通过兼容验收 |
22
22
  | 维护与采用 | 共用规则及选型方向按上述确认维护;具体项目仍需记录采用版本、适用范围、责任和例外,不由本轮确认替代业务认可或开工许可 |
23
23
  | 现有证据 | 已核官方版本/依赖要求及本机工具;已完成Dapr 1.18.7针对K8s 1.28的三组离线Helm渲染。版本矩阵已明确;尚无目标集群连接、工程安装/构建及完整联调证明,见§2.3—§2.6 |
24
24
 
25
- 本文件定位为可复用的企业共用技术基线,已写入确认的选型方向与共用约定。第8节分别记录“已经确认的规则”和“项目仍需落实的事实”,不能把版本、环境或运行结果自动标成已验证。项目明确选用后,AD准确引用本文件路径、文内版本及适用条款;按影响起草和交接,不要求所有项目事实先清零。
25
+ 本文件定位为可复用的企业共用技术基线,已写入确认的选型方向与共用约定。第8节分别记录“已经确认的规则”和“项目仍需落实的事实”,不能把版本、环境或运行结果自动标成已验证。生成AD默认带入本文件时,在AD准确记录其路径、文内版本及适用条款;项目另有明确规约则优先沿用;按影响起草和交接,不要求所有项目事实先清零。
26
26
 
27
- 本文件是业务项目的技术资料,不是 i3-composer-agent 工具自身的运行规范,也不作为所有标准流程或所有企业 SOP 项目的安装默认。项目已有适用规范时,优先核对其权威范围,避免建立第二份相互冲突的规约。
27
+ 本文件是业务项目的技术资料,不是 i3-composer-agent 工具自身的运行规范。显式使用 i3-enterprise-sop生成AD时默认带入,单独安装或标准流程不自动启用;项目已有明确规范时优先沿用,避免建立第二份相互冲突的规约。
28
28
 
29
29
  ### 0.1 来源与整理边界
30
30
 
@@ -35,15 +35,15 @@
35
35
  | 同一来源,第 12—17 章及附录 | 工程质量、环境、流水线、实施交接、验证与未决事项 | 本文件第 6—9 节 |
36
36
  | 使用者本轮逐项确认(2026-10-20) | 共用基线定位、Java/Spring Boot、前端工具链、MaxKey/OIDC、接口惯例、目录采用、部署分工及按项目落实的规则 | 对应第2—9节;不扩展为版本兼容、业务批准或实际环境就绪 |
37
37
  | 2026-10-21官方资料核对、本机只读检查与使用者确认 | 固定首套版本选择、保留K8s 1.28及现有GitLab;记录兼容范围与真实验证边界 | 本文件§2.3—§2.6及Q02—Q06、Q09;不自动执行升级或替项目采用 |
38
- | [公共技术规约模板及采用说明](../readme.md) | 已有规范优先、公共规则与主题设计分工、候选与真实采用分开 | 本文件的范围、采用和版本规则 |
39
- | [当前 AD 模板](../../ad/templates/ad.md) §0.3、§2.2、§9.6 | 基线引用、技术落实和真实骨架来源 | 各主题 AD 的对应条款 |
40
- | [当前交接合同](../../handoff-contract.md) §10 | 选定 AD 及必要配套交原 Plan,再进入获准 Build | 本文件第 9 节 |
41
- | [目录规范1.0.0](../../engineering-skeleton/directory-standard.md)、[采用说明1.0.0](../../engineering-skeleton/adoption.md) | 工程目录与职责、AD映射、原Plan任务路径、获准Build的实际文件落点 | 本文件第1、6—9节;目录规范可先采用,不要求预先取得代码框架模板 |
42
- | [原Ship命令](../../../../../.claude/commands/i3-ship.md)及[原Spec的内容要求](../../../../i3-spec-driven-development/SKILL.md) | 发布前检查/决策/回滚,及原SPEC六项的工程命令和项目依据 | 本文件第6—7、9节;原命令与Skill未改 |
38
+ | [公共技术规约资料](../readme.md) | 已有规范优先、公共规则与主题设计分工、候选与真实采用分开 | 本文件的范围、采用和版本规则 |
39
+ | [当前 AD 模板](../../templates/ad.md) §0.3、§2.2、§9.6 | 基线引用、技术落实和真实骨架来源 | 各主题 AD 的对应条款 |
40
+ | [本 Skill 工程交接说明](../../../SKILL.md) 中的 T2/T3 | 选定 AD及必要配套形成工程 SPEC,再交原 Plan及获准 Build;原命令等效规格方式保留 | 本文件第9节及本 Skill T2/T3;具体技术规则未改 |
41
+ | [目录规范1.0.0](../../engineering-skeleton/directory-standard.md) | 工程目录与职责、AD映射、原Plan任务路径、获准Build的实际文件落点 | 本文件第1、6—9节;目录规范可先采用,不要求预先取得代码框架模板 |
42
+ | 原Ship命令及原Spec的内容要求(通过实际宿主发现;[调用边界](../../../SKILL.md)) | 发布前检查/决策/回滚,及原SPEC六项的工程命令和项目依据 | 本文件第6—7、9节;原命令与Skill未改 |
43
43
 
44
44
  源材料沿用总体 `design.md` 和旧 MVP 产物组织。本文件只提取其中适用的技术内容,并按当前已确认的“公共规约+多主题 PRD/AD”组织;不恢复旧 MVP 流程,不改动源文件,不要求把公共规则复制到每份 AD。原文要求明确写出规则的目的继续保留:AD 必须指出准确规则、适用范围和落实位置,不能只写“沿用旧规范”。
45
45
 
46
- 本规约保留已确认技术条款及其来源说明,仅分发当前版本;工程组织统一采用同一SOP Skill内的1.0.0目录规范与采用说明。来源材料或发行资源后续修改,不自动改变项目已经采用的版本。
46
+ 本规约保留已确认技术条款及其来源说明,仅分发当前版本;工程组织统一采用同一SOP Skill内的1.0.0目录规范,采用信息由AD现有表格记录。来源材料或发行资源后续修改,不自动改变项目已经采用的版本。
47
47
 
48
48
  ## 1. 共用架构与工程边界
49
49
 
@@ -136,7 +136,7 @@ Java若实际采用Spring集成,候选坐标为`io.dapr.spring:dapr-spring-boo
136
136
  | GitLab Runner | 19.2.x,实际补丁待核 | 与GitLab主/次版本匹配;未取得实例信息前不编造精确补丁号或可用Runner |
137
137
  | Docker Desktop | 本机4.70.0 | 4.96.0作为另行评估的更新候选;本轮未启动Docker或升级。CLI版本不证明实际运行的Engine版本 |
138
138
  | Helm及集群部署工具 | 项目实际工具待选择 | 研究中临时使用的Helm 3.19.0不是项目默认工具,也不构成本机全局安装事实 |
139
- | 工程骨架 | 统一采用本SOP Skill内的目录规范/采用说明1.0.0 | 按本次确认统一来源;项目既有采用不自动升级,先核差异、适用范围及真实工程映射。目录规则不等于可运行代码模板 |
139
+ | 工程骨架 | 统一采用本SOP Skill内的目录规范1.0.0 | 按本次确认统一来源;项目既有采用不自动升级,先核差异、适用范围及真实工程映射。目录规则不等于可运行代码模板 |
140
140
 
141
141
  ### 2.4 已取得的环境事实与K8s兼容边界
142
142
 
@@ -251,7 +251,7 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
251
251
 
252
252
  ### 6.1 工程骨架与文件责任
253
253
 
254
- - ENG-01:工程骨架沿用现有[目录规范1.0.0](../../engineering-skeleton/directory-standard.md)和[采用说明1.0.0](../../engineering-skeleton/adoption.md)。`pbc-composite`与`agent-standalone`是组织方式,frontend、backend、agents、jobs及配套契约、共享代码、测试和交付位置按需采用;内部遵循实际语言/框架惯例。
254
+ - ENG-01:工程骨架沿用现有[目录规范1.0.0](../../engineering-skeleton/directory-standard.md)。`pbc-composite`与`agent-standalone`是组织方式,frontend、backend、agents、jobs及配套契约、共享代码、测试和交付位置按需采用;内部遵循实际语言/框架惯例。
255
255
  - ENG-02:目录规范是实际可读的设计输入,可以在工程创建前采用。AD记录规范来源/版本、业务项目根、已有/拟建工程和模块映射;未采用代码框架模板时如实说明,不要求预先取得企业代码模板或冻结首套完整技术栈。
256
256
  - ENG-03:AD §9.2明确源码、模块、配置、测试、契约及装配位置,§9.6说明已有工程与拟建内容;原Plan把预计新增/修改文件路径、依赖和验证写入任务;获准的Build在这些位置生成实际内容,核对文件落点并执行原有测试/构建。
257
257
  - ENG-04:共享底座、契约和文件只有一处维护责任及实际任务来源,其他主题引用依赖;新增 PRD 不自动复制底座。再次生成或更新时保留人工修改、历史及已完成任务,冲突先核范围。
@@ -261,7 +261,7 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
261
261
 
262
262
  既有工程先映射实际目录,不为套规范移动或重建。实际有内容时再建所需位置;项目根的构建/CI配置与框架原有布局允许按实情保留。目录检查、代码存在、构建、启动、业务验证和发布分别取证。
263
263
 
264
- 来源:现有工程骨架目录方案、目录规范/采用说明1.0.0,以及当前AD §9.2/§9.6的对应栏目。本轮只引用这些资源,不修改工程骨架工作树或SOP运行模板;原命令的读取与实际落点继续按对应工程验证任务取证。
264
+ 来源:现有工程骨架目录方案、目录规范1.0.0,以及当前AD §9.2/§9.6的对应栏目。本轮只引用这些资源,不修改工程骨架工作树或SOP运行模板;原命令的读取与实际落点继续按对应工程验证任务取证。
265
265
 
266
266
  ### 6.2 工程命令与编码规范
267
267
 
@@ -312,14 +312,14 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
312
312
 
313
313
  | 编号 | 已确认的共用处理 | 项目仍需落实的内容与影响 | 处理时点 |
314
314
  | --- | --- | --- | --- |
315
- | Q01 | 本文作为可复用企业技术基线,项目明确采用准确版本 | 项目名称、维护责任、范围和既有规范由项目采用说明/AD绑定;不在公共规约预造业务身份 | 实际项目采用时 |
315
+ | Q01 | 本文作为可复用企业技术基线,项目明确采用准确版本 | 项目名称、维护责任、范围和既有规范由AD现有来源与采用表绑定;不在公共规约预造业务身份 | 实际项目采用时 |
316
316
  | Q02 | 首套普通业务后端采用Java+Spring Boot方向,与Python Agent分工 | §2.3已明确JDK/Boot/Maven/JDBC目标;核实际工程依赖、企业框架复用、启动及序列化,不将版本确认写为兼容已通过 | 相关详设和工程任务按依赖核实 |
317
317
  | Q03 | 保留Vue 3+TypeScript+Antdv Next,工具链采用Vite、类型检查和Vitest方向 | §2.3已明确首套前端版本;核实际依赖/锁文件,按需引入路由/状态管理,安装/类型/构建/测试结果仍待验证 | 初始化及依赖安装前核实,生成后验证 |
318
318
  | Q04 | Dapr/Agent按实际需要的能力建立组合版本和验证安排 | 按§2.3固定所用版本;在保留的K8s 1.28上验证实际安装、必要业务链、中断恢复、重复与授权失效,离线渲染不替代运行证据 | 依赖相关能力的设计/工程阶段分别核实 |
319
319
  | Q05 | PostgreSQL优先兼容企业已有受支持版本,逻辑责任与恢复分别设计 | 先定位已有服务端;17.11仅是无既有约束的新建候选,驱动42.7.14已定,扩展/实例/迁移/DDL/恢复仍须真实证据 | 数据工程设计、迁移及运行前 |
320
320
  | Q06 | 统一身份采用MaxKey,通过OIDC接入;业务服务执行最终授权 | 4.1.12为首轮接入基线,投产前核4.2.0安全更新;取得实际实例/类型、应用注册、映射、退出撤权及配置引用,不虚构claims或可用能力 | 身份相关契约和实现前 |
321
321
  | Q07 | API camelCase、数据库snake_case、跨端大整数ID字符串、明确时间点时区、稳定业务码与关联标识 | 正式契约补精确字段、分页上限、格式/精度、主键生成、审计及存量映射,避免擅改已有契约 | 相关公共契约及主题AD定稿前 |
322
- | Q08 | 引用现有目录规范/采用说明1.0.0,沿用原AD→Plan→Build的目录映射链 | 明确业务根及真实/拟建路径;目录规范可先采用,实际代码由获准Build生成验证;只有另行要求特定代码模板才须取得该模板 | AD映射、Plan任务文件路径、Build实际落点依次核对 |
322
+ | Q08 | 引用现有目录规范1.0.0,沿用原AD→Plan→Build的目录映射链 | 明确业务根及真实/拟建路径;目录规范可先采用,实际代码由获准Build生成验证;只有另行要求特定代码模板才须取得该模板 | AD映射、Plan任务文件路径、Build实际落点依次核对 |
323
323
  | Q09 | AD规定部署要求,Plan安排任务,Build形成配置,Ship核验汇总部署准备 | K8s 1.28和GitLab 19.2.4为使用者提供的环境;补完整集群信息、Runner及实际资源/权限/配置,部署资料不冒充平台已就绪 | 构建、联调、部署和发布按需落实 |
324
324
  | Q10 | 保留已有质量下限,新增指标从PRD或真实工程决定取得 | 质量、容量、恢复、预算、保留、告警与适用要求的具体值和测量方法属于项目;不得填造通用数字 | 对应测试设计、处理数据或放行前 |
325
325
  | Q11 | 根据实际情况填写各工程命令、cwd、前提、成功依据和风格 | 已有工程读取真实配置;新工程按任务创建并验证,未验证保持实际状态;不要求用户预先写假命令 | 本次规划与任务执行按依赖补足 |
@@ -333,18 +333,18 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
333
333
 
334
334
  ### 9.1 进入 AD
335
335
 
336
- 1. 在具体业务项目明确采用本规约的路径、版本和适用范围;先核已有规范及冲突,不凭安装或发现文件自动启用。
336
+ 1. 使用 i3-enterprise-sop生成AD时,项目未另有明确规约就默认读取本规约和目录规范,在AD记录路径、版本及范围;已有明确规约优先,冲突先说明。
337
337
  2. 选定准确 PRD 主题和版本。起草/修订 AD 必须按企业入口加载原 Spec Skill,使用完整 AD 模板;本公共规约本身不替代主题规格。
338
- 3. AD §0.3登记本规约及目录规范/采用说明的来源和版本,§2.2落实技术选择,§9.2映射工程及模块位置,§9.6区分现有工程和拟建内容;目录规范可在代码生成前采用,保留三图和全部详设责任。
338
+ 3. AD §0.3登记本规约及目录规范的来源和版本,§2.2落实技术选择,§9.2映射工程及模块位置,§9.6区分现有工程和拟建内容;目录规范可在代码生成前采用,保留三图和全部详设责任。
339
339
  4. 原型按需,可与 AD 并行。记录假设和受影响未决;候选生成、完整回读和人工认可分别处理,保存后遵守原 Spec 停轮。
340
340
 
341
- 以下任务在项目明确取得并采用本版规约后使用;资料由实际加载的SOP Skill的references及辅助清单定位,不要求使用者填写安装内部路径。文件存在不证明项目已采用,已有安装不被假定包含本地候选:
341
+ 以下是明确委托本 Skill生成AD的任务示例;默认资料从本Skill直接定位,项目明确规约优先,不要求用户填写安装内部路径:
342
342
 
343
343
  ```text
344
- /i3-sop ad
345
- 本次采用我明确提供的《企业应用公共技术规约 v0.0.4》作为公共技术依据;按本次选定SOP Skill资源索引定位。
344
+ 使用 i3-enterprise-sop 的 ad 动作。
345
+ 将自带《企业应用公共技术规约 v0.0.4》和目录规范作为默认输入;项目已有明确规约时优先沿用。
346
346
  先确定当前业务项目、我已选定的 PRD 主题和准确版本;存在歧义先问。
347
- 实际读取这份规约、其引用的工程目录规范/采用说明1.0.0及本次必要来源,列出适用条款、已有规范冲突和影响本主题的项目缺口。
347
+ 实际读取这份规约、其引用的工程目录规范1.0.0及本次必要来源,列出适用条款、已有规范冲突和影响本主题的项目缺口。
348
348
  按原 Spec Skill 和完整 AD 模板生成本主题设计,保留三图、工程细节和 SPEC 六项。
349
349
  沿用§2.3已确认的版本矩阵,保留既有K8s 1.28与GitLab 19.2.4;逐项区分目标与实装,未验证的兼容性、具体字段和工程映射按实际来源补足。
350
350
  按现有目录规范明确本主题源码、模块、配置、测试和契约的已有/拟建位置,不要求预先提供一套代码框架模板,也不宣称工程已生成。
@@ -353,14 +353,14 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
353
353
 
354
354
  ### 9.2 交原 Plan
355
355
 
356
- 以选定AD为入口,读取其准确PRD、本规约适用条款、共享契约、目录规范/项目采用说明和已有代码;尚无代码时如实记录拟建,不虚构工程根。按主题加载完整必要内容,核本范围SPEC六项和FR/AC;Plan任务保留真实/拟建文件路径及验证依据,不全量注入无关AD和历史。
356
+ 以选定AD为入口,读取其准确PRD、本规约适用条款、共享契约、目录规范及AD工程映射和已有代码;尚无代码时如实记录拟建,不虚构工程根。按主题加载完整必要内容,核本范围SPEC六项和FR/AC;Plan任务保留真实/拟建文件路径及验证依据,不全量注入无关AD和历史。
357
357
 
358
- 可以手动给实际路径,不必先建立 JSON。用户明确选用并允许登记时,现有清单只在 `selected.technicalBaseline` 保存本规约的实际路径,主题 AD 保存于相应主题引用;清单仍只保留 `schemaVersion`、`scope`、`selected`。本次没有建立或改写业务 JSON,也不把文档完成视为已选用或已批准。
358
+ 本规约的来源标识、准确路径或URL、文内版本或固定提交、适用条款、采用范围与例外、实际读取及缺口,统一记录在AD §0.3;工程映射与实际模板/骨架来源由AD §9.6承接。后续工程SPEC/Plan沿AD引用读取准确原件,不再将规约路径或采用信息写入 `handoff.json`,也不另建采用说明。`handoff.json`仅记录EBC/PBC身份及已选BRD/PRD/AD/TestCase文档路径,主题AD仍按相应文档角色登记。可以手动给出准确路径,不强制创建清单;引用或登记不证明已读,也不表示文档获批。
359
359
 
360
360
  ```text
361
361
  /i3-plan
362
- 以我本轮明确选定的AD为规划入口,实际读取它引用的PRD、公共技术规约适用条款、共享契约、工程目录规范/采用说明及已有代码。
363
- 公共技术规约使用本次明确采用的准确路径和版本,不仅凭清单存在判断已读。
362
+ 以我本轮明确选定的AD为规划入口,实际读取它引用的PRD、公共技术规约适用条款、共享契约、工程目录规范及已有代码。
363
+ 公共技术规约按AD §0.3采用记录中的准确路径、文内版本及适用条款回读,不以AD引用或清单存在代替正文读取;规约采用信息不写入handoff.json。
364
364
  核对本主题需求、验收条件、SPEC 六项、共享依赖和未决影响;TestCase 按需引用。
365
365
  按AD的工程/模块映射,将每项任务预计新增或修改的文件路径、真实来源、初始化或增量内容及验证依据保留在原计划和唯一任务目标中,交我审阅,暂不实施。
366
366
  ```
@@ -373,7 +373,7 @@ PostgreSQL已定位的是Homebrew libpq 18.3工具。在已查常见路径、进
373
373
 
374
374
  ### 9.4 本次交付结论
375
375
 
376
- 本版保留2026-10-20/21确认的共用规则、技术版本矩阵、本机及既有平台事实、官方依据与V01—V06验证安排,并按2026-10-22确认将工程目录及采用说明统一为1.0.0。Q02—Q06、Q09区分已选择版本与仍待取得的环境/运行证据;其他已确认规则保留。K8s 1.28继续保留,三组Dapr离线渲染不作为真实集群通过证明。
376
+ 本版保留2026-10-20/21确认的共用规则、技术版本矩阵、本机及既有平台事实、官方依据与V01—V06验证安排,并按2026-10-22确认采用工程目录规范1.0.0,本副本的采用信息直接由AD记录。Q02—Q06、Q09区分已选择版本与仍待取得的环境/运行证据;其他已确认规则保留。K8s 1.28继续保留,三组Dapr离线渲染不作为真实集群通过证明。
377
377
 
378
378
  本文件可供人工审阅,并可在明确选用后作为 AD 的公共输入。它不证明某项目的整体技术设计已经批准,也不证明所有主题可交 Plan、可直接 Build 或已通过运行验收。
379
379