dic-workflow-kit 1.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.
- package/.codex-plugin/plugin.json +37 -0
- package/INSTRUCTION.md +82 -0
- package/README.md +202 -0
- package/README.zh-CN.md +172 -0
- package/adapters/contest/README.md +13 -0
- package/adapters/generic/README.md +18 -0
- package/adapters/openspec/README.md +24 -0
- package/agents/code-repairer.md +17 -0
- package/agents/contract-oracle.md +17 -0
- package/agents/final-reviewer.md +19 -0
- package/agents/flow-auditor.md +15 -0
- package/agents/impl-inspector.md +15 -0
- package/agents/profile-builder.md +17 -0
- package/agents/repair-planner.md +17 -0
- package/agents/spec-reader.md +23 -0
- package/agents/validation-runner.md +16 -0
- package/bin/dic-workflow-kit.mjs +150 -0
- package/package.json +45 -0
- package/schemas/agent-handoff.schema.json +20 -0
- package/schemas/contract-obligations.schema.json +55 -0
- package/schemas/final-result.schema.json +18 -0
- package/schemas/project-profile.schema.json +66 -0
- package/scripts/dic_workflow.py +507 -0
- package/skills/knowledge-bdd/SKILL.md +22 -0
- package/skills/knowledge-openspec/SKILL.md +29 -0
- package/skills/knowledge-repair-distill/SKILL.md +26 -0
- package/skills/workflow-core/SKILL.md +29 -0
- package/skills/workflow-intake/SKILL.md +25 -0
- package/skills/workflow-profile/SKILL.md +24 -0
- package/skills/workflow-repair/SKILL.md +22 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Portable helper skeleton for DIC Workflow Kit.
|
|
3
|
+
|
|
4
|
+
This script intentionally uses only the Python standard library.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import shlex
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
VERSION = "1.0.0"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def write_text(path: Path, text: str) -> None:
|
|
23
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
path.write_text(text, encoding="utf-8", newline="\n")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def find_workflow_root(start: Path) -> Path:
|
|
28
|
+
current = start.resolve()
|
|
29
|
+
if current.is_file():
|
|
30
|
+
current = current.parent
|
|
31
|
+
for candidate in [current, *current.parents]:
|
|
32
|
+
if (candidate / "INSTRUCTION.md").exists():
|
|
33
|
+
return candidate
|
|
34
|
+
return current
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def rel(path: Path, root: Path) -> str:
|
|
38
|
+
try:
|
|
39
|
+
return path.resolve().relative_to(root.resolve()).as_posix()
|
|
40
|
+
except ValueError:
|
|
41
|
+
return path.as_posix()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_validation_commands(text: str, source: str) -> list[dict[str, Any]]:
|
|
45
|
+
commands = []
|
|
46
|
+
seen = set()
|
|
47
|
+
for value in re.findall(r"`([^`\r\n]+)`", text):
|
|
48
|
+
if not re.match(
|
|
49
|
+
r"^(?:npm\s+(?:run|test|exec)\b|npx\s+|pnpm\s+|yarn\s+|"
|
|
50
|
+
r"openspec\s+validate\b|pytest(?:\s|$)|python\s+|mvn\s+)",
|
|
51
|
+
value,
|
|
52
|
+
):
|
|
53
|
+
continue
|
|
54
|
+
try:
|
|
55
|
+
command = shlex.split(value)
|
|
56
|
+
except ValueError:
|
|
57
|
+
continue
|
|
58
|
+
key = tuple(command)
|
|
59
|
+
if key in seen:
|
|
60
|
+
continue
|
|
61
|
+
seen.add(key)
|
|
62
|
+
commands.append(
|
|
63
|
+
{
|
|
64
|
+
"name": value,
|
|
65
|
+
"command": command,
|
|
66
|
+
"cwd": ".",
|
|
67
|
+
"source": source,
|
|
68
|
+
"kind": "suggested",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
return commands
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def obligation_id(capability_id: str, kind: str, title: str) -> str:
|
|
75
|
+
digest = hashlib.sha256(
|
|
76
|
+
f"{capability_id}\0{kind}\0{title}".encode("utf-8")
|
|
77
|
+
).hexdigest()[:12]
|
|
78
|
+
return f"obl-{digest}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def extract_openspec_obligations(
|
|
82
|
+
spec_path: Path,
|
|
83
|
+
project_root: Path,
|
|
84
|
+
capability_id: str,
|
|
85
|
+
authority: str,
|
|
86
|
+
) -> list[dict[str, Any]]:
|
|
87
|
+
lines = spec_path.read_text(encoding="utf-8").splitlines()
|
|
88
|
+
obligations = []
|
|
89
|
+
current_requirement = None
|
|
90
|
+
index = 0
|
|
91
|
+
while index < len(lines):
|
|
92
|
+
line = lines[index]
|
|
93
|
+
requirement_match = re.match(r"^###\s+Requirement:\s*(.+?)\s*$", line)
|
|
94
|
+
scenario_match = re.match(r"^####\s+Scenario:\s*(.+?)\s*$", line)
|
|
95
|
+
if requirement_match:
|
|
96
|
+
title = requirement_match.group(1)
|
|
97
|
+
statement_lines = []
|
|
98
|
+
cursor = index + 1
|
|
99
|
+
while cursor < len(lines) and not re.match(
|
|
100
|
+
r"^#{3,4}\s+(?:Requirement|Scenario):", lines[cursor]
|
|
101
|
+
):
|
|
102
|
+
value = lines[cursor].strip()
|
|
103
|
+
if value and not value.startswith("#"):
|
|
104
|
+
statement_lines.append(value)
|
|
105
|
+
cursor += 1
|
|
106
|
+
current_requirement = obligation_id(capability_id, "requirement", title)
|
|
107
|
+
obligations.append(
|
|
108
|
+
{
|
|
109
|
+
"id": current_requirement,
|
|
110
|
+
"capabilityId": capability_id,
|
|
111
|
+
"kind": "requirement",
|
|
112
|
+
"title": title,
|
|
113
|
+
"statement": " ".join(statement_lines),
|
|
114
|
+
"source": rel(spec_path, project_root),
|
|
115
|
+
"line": index + 1,
|
|
116
|
+
"authority": authority,
|
|
117
|
+
"parentId": None,
|
|
118
|
+
"acceptanceClauses": [],
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
elif scenario_match:
|
|
122
|
+
title = scenario_match.group(1)
|
|
123
|
+
clauses = []
|
|
124
|
+
cursor = index + 1
|
|
125
|
+
while cursor < len(lines) and not re.match(
|
|
126
|
+
r"^#{3,4}\s+(?:Requirement|Scenario):", lines[cursor]
|
|
127
|
+
):
|
|
128
|
+
clause_match = re.match(
|
|
129
|
+
r"^\s*-\s+\*\*(GIVEN|WHEN|THEN|AND|BUT)\*\*\s*(.+?)\s*$",
|
|
130
|
+
lines[cursor],
|
|
131
|
+
flags=re.IGNORECASE,
|
|
132
|
+
)
|
|
133
|
+
if clause_match:
|
|
134
|
+
clauses.append(
|
|
135
|
+
{
|
|
136
|
+
"keyword": clause_match.group(1).upper(),
|
|
137
|
+
"text": clause_match.group(2),
|
|
138
|
+
"line": cursor + 1,
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
cursor += 1
|
|
142
|
+
obligations.append(
|
|
143
|
+
{
|
|
144
|
+
"id": obligation_id(capability_id, "scenario", title),
|
|
145
|
+
"capabilityId": capability_id,
|
|
146
|
+
"kind": "scenario",
|
|
147
|
+
"title": title,
|
|
148
|
+
"statement": " ".join(
|
|
149
|
+
f"{item['keyword']} {item['text']}" for item in clauses
|
|
150
|
+
),
|
|
151
|
+
"source": rel(spec_path, project_root),
|
|
152
|
+
"line": index + 1,
|
|
153
|
+
"authority": authority,
|
|
154
|
+
"parentId": current_requirement,
|
|
155
|
+
"acceptanceClauses": clauses,
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
index += 1
|
|
159
|
+
return obligations
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def build_obligation_catalog(
|
|
163
|
+
project_root: Path, design_entry: dict[str, Any]
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
use_baseline = design_entry["lifecycle"] == "archived" and design_entry["baselineSpecs"]
|
|
166
|
+
selected_specs = (
|
|
167
|
+
design_entry["baselineSpecs"] if use_baseline else design_entry["deltaSpecs"]
|
|
168
|
+
)
|
|
169
|
+
authority = "current-baseline" if use_baseline else "active-change"
|
|
170
|
+
obligations = []
|
|
171
|
+
for spec in selected_specs:
|
|
172
|
+
spec_path = project_root / spec
|
|
173
|
+
capability_id = spec_path.parent.name
|
|
174
|
+
obligations.extend(
|
|
175
|
+
extract_openspec_obligations(
|
|
176
|
+
spec_path, project_root, capability_id, authority
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
return {
|
|
180
|
+
"schema": "dic.contract-obligations.v1",
|
|
181
|
+
"version": VERSION,
|
|
182
|
+
"changeId": design_entry["changeId"],
|
|
183
|
+
"changeLifecycle": design_entry["lifecycle"],
|
|
184
|
+
"authorityPolicy": (
|
|
185
|
+
"Archived changes use promoted current baseline specs."
|
|
186
|
+
if use_baseline
|
|
187
|
+
else "Active changes use their delta specs."
|
|
188
|
+
),
|
|
189
|
+
"sources": selected_specs,
|
|
190
|
+
"counts": {
|
|
191
|
+
"requirements": sum(item["kind"] == "requirement" for item in obligations),
|
|
192
|
+
"scenarios": sum(item["kind"] == "scenario" for item in obligations),
|
|
193
|
+
"total": len(obligations),
|
|
194
|
+
},
|
|
195
|
+
"obligations": obligations,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def render_obligation_catalog(catalog: dict[str, Any]) -> str:
|
|
200
|
+
lines = [
|
|
201
|
+
"# Contract Obligations",
|
|
202
|
+
"",
|
|
203
|
+
f"- Change: `{catalog['changeId']}`",
|
|
204
|
+
f"- Lifecycle: {catalog['changeLifecycle']}",
|
|
205
|
+
f"- Authority: {catalog['authorityPolicy']}",
|
|
206
|
+
f"- Requirements: {catalog['counts']['requirements']}",
|
|
207
|
+
f"- Scenarios: {catalog['counts']['scenarios']}",
|
|
208
|
+
"",
|
|
209
|
+
]
|
|
210
|
+
for item in catalog["obligations"]:
|
|
211
|
+
lines.extend(
|
|
212
|
+
[
|
|
213
|
+
f"## {item['id']} — {item['title']}",
|
|
214
|
+
"",
|
|
215
|
+
f"- Kind: {item['kind']}",
|
|
216
|
+
f"- Source: `{item['source']}:{item['line']}`",
|
|
217
|
+
f"- Authority: {item['authority']}",
|
|
218
|
+
]
|
|
219
|
+
)
|
|
220
|
+
if item["parentId"]:
|
|
221
|
+
lines.append(f"- Parent: `{item['parentId']}`")
|
|
222
|
+
lines.extend(["", item["statement"] or "_No normative body extracted._", ""])
|
|
223
|
+
return "\n".join(lines)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def resolve_design_map(project_root: Path, capability_ids: list[str]) -> dict[str, Any] | None:
|
|
227
|
+
map_path = project_root / "openspec" / "designs" / "spec-to-design-map.md"
|
|
228
|
+
if not map_path.exists():
|
|
229
|
+
return None
|
|
230
|
+
text = map_path.read_text(encoding="utf-8")
|
|
231
|
+
for line in text.splitlines():
|
|
232
|
+
if not line.lstrip().startswith("|"):
|
|
233
|
+
continue
|
|
234
|
+
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
|
235
|
+
if len(cells) < 5:
|
|
236
|
+
continue
|
|
237
|
+
capability = cells[0].strip("` ")
|
|
238
|
+
if capability not in capability_ids:
|
|
239
|
+
continue
|
|
240
|
+
linked_paths = []
|
|
241
|
+
for cell in cells[1:4]:
|
|
242
|
+
linked_paths.extend(re.findall(r"`(openspec/[^`]+\.md)`", cell))
|
|
243
|
+
linked_paths = list(dict.fromkeys(linked_paths))
|
|
244
|
+
return {
|
|
245
|
+
"source": rel(map_path, project_root),
|
|
246
|
+
"capabilityId": capability,
|
|
247
|
+
"architectureDesigns": [
|
|
248
|
+
path for path in linked_paths if "/designs/architecture/" in path
|
|
249
|
+
],
|
|
250
|
+
"moduleDesigns": [
|
|
251
|
+
path for path in linked_paths if "/designs/modules/" in path
|
|
252
|
+
],
|
|
253
|
+
"adrs": [path for path in linked_paths if "/designs/adr/" in path],
|
|
254
|
+
"validationSummary": cells[4],
|
|
255
|
+
"validationCommands": extract_validation_commands(cells[4], rel(map_path, project_root)),
|
|
256
|
+
}
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def resolve_openspec_change(project_root: Path, change_id: str) -> dict[str, Any]:
|
|
261
|
+
candidates = [
|
|
262
|
+
("active", project_root / "openspec" / "changes" / change_id),
|
|
263
|
+
("archived", project_root / "openspec" / "changes" / "archive" / change_id),
|
|
264
|
+
]
|
|
265
|
+
for lifecycle, change_root in candidates:
|
|
266
|
+
if not change_root.is_dir():
|
|
267
|
+
continue
|
|
268
|
+
delta_specs = sorted(change_root.glob("specs/**/spec.md"))
|
|
269
|
+
capability_ids = [
|
|
270
|
+
path.relative_to(change_root / "specs").parts[0] for path in delta_specs
|
|
271
|
+
]
|
|
272
|
+
baseline_specs = []
|
|
273
|
+
for delta_spec in delta_specs:
|
|
274
|
+
relative_spec = delta_spec.relative_to(change_root / "specs")
|
|
275
|
+
baseline = project_root / "openspec" / "specs" / relative_spec
|
|
276
|
+
if baseline.exists():
|
|
277
|
+
baseline_specs.append(baseline)
|
|
278
|
+
documents = [
|
|
279
|
+
path
|
|
280
|
+
for path in [
|
|
281
|
+
change_root / "proposal.md",
|
|
282
|
+
change_root / "design.md",
|
|
283
|
+
change_root / "tasks.md",
|
|
284
|
+
]
|
|
285
|
+
if path.exists()
|
|
286
|
+
]
|
|
287
|
+
result = {
|
|
288
|
+
"type": "openspec-change",
|
|
289
|
+
"changeId": change_id,
|
|
290
|
+
"lifecycle": lifecycle,
|
|
291
|
+
"changeRoot": rel(change_root, project_root),
|
|
292
|
+
"documents": [rel(path, project_root) for path in documents],
|
|
293
|
+
"deltaSpecs": [rel(path, project_root) for path in delta_specs],
|
|
294
|
+
"baselineSpecs": [rel(path, project_root) for path in baseline_specs],
|
|
295
|
+
}
|
|
296
|
+
design_map = resolve_design_map(project_root, capability_ids)
|
|
297
|
+
if design_map:
|
|
298
|
+
result["designMap"] = design_map
|
|
299
|
+
return result
|
|
300
|
+
raise SystemExit(
|
|
301
|
+
f"OpenSpec change not found: {change_id} "
|
|
302
|
+
f"(checked active and archived changes under {project_root})"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def detect_project(root: Path, change_id: str = "") -> dict[str, Any]:
|
|
307
|
+
project_root = root
|
|
308
|
+
openspec = list(project_root.glob("**/openspec/specs/**/spec.md")) + list(
|
|
309
|
+
project_root.glob("**/openspec/changes/**/proposal.md")
|
|
310
|
+
)
|
|
311
|
+
design_entry = resolve_openspec_change(project_root, change_id) if change_id else None
|
|
312
|
+
if design_entry:
|
|
313
|
+
selected = (
|
|
314
|
+
design_entry["documents"]
|
|
315
|
+
+ design_entry["deltaSpecs"]
|
|
316
|
+
+ design_entry["baselineSpecs"]
|
|
317
|
+
)
|
|
318
|
+
design_map = design_entry.get("designMap")
|
|
319
|
+
if design_map:
|
|
320
|
+
selected += [
|
|
321
|
+
design_map["source"],
|
|
322
|
+
*design_map["architectureDesigns"],
|
|
323
|
+
*design_map["moduleDesigns"],
|
|
324
|
+
*design_map["adrs"],
|
|
325
|
+
]
|
|
326
|
+
design_sources = [project_root / path for path in selected]
|
|
327
|
+
else:
|
|
328
|
+
design_sources = []
|
|
329
|
+
for pattern in ["README*", "docs/**/*.md", "design/**/*.md", "spec/**/*.md", "api/**/*.md", "schemas/**/*"]:
|
|
330
|
+
design_sources.extend(project_root.glob(pattern))
|
|
331
|
+
for path in openspec:
|
|
332
|
+
if path not in design_sources:
|
|
333
|
+
design_sources.append(path)
|
|
334
|
+
|
|
335
|
+
impl_roots = [p for p in [project_root / "src", project_root / "app", project_root / "code"] if p.exists()]
|
|
336
|
+
test_roots = [p for p in [project_root / "test", project_root / "tests", project_root / "test-cases"] if p.exists()]
|
|
337
|
+
project_types = []
|
|
338
|
+
adapters = ["generic"]
|
|
339
|
+
if openspec:
|
|
340
|
+
project_types.append("openspec")
|
|
341
|
+
adapters.append("openspec")
|
|
342
|
+
if (project_root / "pom.xml").exists() or list(project_root.glob("**/pom.xml")):
|
|
343
|
+
project_types.append("maven")
|
|
344
|
+
if (project_root / "package.json").exists() or list(project_root.glob("**/package.json")):
|
|
345
|
+
project_types.append("node")
|
|
346
|
+
|
|
347
|
+
validation_commands = []
|
|
348
|
+
if design_entry:
|
|
349
|
+
for document in design_entry["documents"]:
|
|
350
|
+
path = project_root / document
|
|
351
|
+
validation_commands.extend(
|
|
352
|
+
extract_validation_commands(path.read_text(encoding="utf-8"), document)
|
|
353
|
+
)
|
|
354
|
+
if design_entry.get("designMap"):
|
|
355
|
+
validation_commands.extend(design_entry["designMap"]["validationCommands"])
|
|
356
|
+
if design_entry["lifecycle"] == "archived":
|
|
357
|
+
validation_commands = [
|
|
358
|
+
item
|
|
359
|
+
for item in validation_commands
|
|
360
|
+
if not (
|
|
361
|
+
item["command"][:2] == ["openspec", "validate"]
|
|
362
|
+
and "--all" not in item["command"]
|
|
363
|
+
)
|
|
364
|
+
]
|
|
365
|
+
unique_commands = {}
|
|
366
|
+
for item in validation_commands:
|
|
367
|
+
unique_commands.setdefault(tuple(item["command"]), item)
|
|
368
|
+
validation_commands = list(unique_commands.values())
|
|
369
|
+
|
|
370
|
+
profile = {
|
|
371
|
+
"schema": "dic.project-profile.v1",
|
|
372
|
+
"version": VERSION,
|
|
373
|
+
"projectRoot": project_root.as_posix(),
|
|
374
|
+
"projectTypes": sorted(set(project_types)) or ["generic"],
|
|
375
|
+
"adapters": adapters,
|
|
376
|
+
"designSources": [rel(p, project_root) for p in sorted(set(design_sources))],
|
|
377
|
+
"implementationRoots": [rel(p, project_root) for p in impl_roots],
|
|
378
|
+
"testRoots": [rel(p, project_root) for p in test_roots],
|
|
379
|
+
"protectedPaths": [rel(p, project_root) for p in sorted(set(design_sources))],
|
|
380
|
+
"validationCommands": validation_commands,
|
|
381
|
+
"riskFamilies": ["requirements", "api-contract", "flow-completeness", "validation"],
|
|
382
|
+
"outputContract": {
|
|
383
|
+
"finalResultJson": "reports/FINAL_RESULT.json",
|
|
384
|
+
"finalOutputMd": "result/output.md",
|
|
385
|
+
"contractObligationsJson": "reports/contract-obligations.json",
|
|
386
|
+
"contractObligationsMd": "reports/contract-obligations.md",
|
|
387
|
+
"traceDir": "logs/trace"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if design_entry:
|
|
391
|
+
profile["designEntry"] = design_entry
|
|
392
|
+
profile["deltaSpecs"] = design_entry["deltaSpecs"]
|
|
393
|
+
profile["baselineSpecs"] = design_entry["baselineSpecs"]
|
|
394
|
+
return profile
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def command_init(args: argparse.Namespace) -> int:
|
|
398
|
+
root = Path(args.root or os.getcwd()).resolve()
|
|
399
|
+
output_root = Path(args.output_root).resolve() if args.output_root else root
|
|
400
|
+
for directory in ["reports", "result", "logs/trace"]:
|
|
401
|
+
(output_root / directory).mkdir(parents=True, exist_ok=True)
|
|
402
|
+
profile = detect_project(root, args.change)
|
|
403
|
+
profile_path = output_root / "reports" / "project-profile.json"
|
|
404
|
+
write_text(profile_path, json.dumps(profile, indent=2, ensure_ascii=False))
|
|
405
|
+
if profile.get("designEntry"):
|
|
406
|
+
catalog = build_obligation_catalog(root, profile["designEntry"])
|
|
407
|
+
write_text(
|
|
408
|
+
output_root / "reports" / "contract-obligations.json",
|
|
409
|
+
json.dumps(catalog, indent=2, ensure_ascii=False),
|
|
410
|
+
)
|
|
411
|
+
write_text(
|
|
412
|
+
output_root / "reports" / "contract-obligations.md",
|
|
413
|
+
render_obligation_catalog(catalog),
|
|
414
|
+
)
|
|
415
|
+
output = [
|
|
416
|
+
"# DIC Workflow Init",
|
|
417
|
+
"",
|
|
418
|
+
f"- Version: {VERSION}",
|
|
419
|
+
f"- Project root: `{root.as_posix()}`",
|
|
420
|
+
f"- Output root: `{output_root.as_posix()}`",
|
|
421
|
+
f"- Project profile: `{profile_path.as_posix()}`",
|
|
422
|
+
f"- Adapters: {', '.join(profile['adapters'])}",
|
|
423
|
+
]
|
|
424
|
+
if profile.get("designEntry"):
|
|
425
|
+
entry = profile["designEntry"]
|
|
426
|
+
output.extend(
|
|
427
|
+
[
|
|
428
|
+
f"- Design entry: OpenSpec Change `{entry['changeId']}`",
|
|
429
|
+
f"- Change lifecycle: {entry['lifecycle']}",
|
|
430
|
+
]
|
|
431
|
+
)
|
|
432
|
+
output.append("")
|
|
433
|
+
write_text(output_root / "logs" / "trace" / "init.md", "\n".join(output))
|
|
434
|
+
print(f"DIC_WORKFLOW_VERSION: {VERSION}")
|
|
435
|
+
print(f"PROJECT_ROOT: {root}")
|
|
436
|
+
print(f"OUTPUT_ROOT: {output_root}")
|
|
437
|
+
print(f"PROJECT_PROFILE: {profile_path}")
|
|
438
|
+
return 0
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def command_finalize(args: argparse.Namespace) -> int:
|
|
442
|
+
root = find_workflow_root(Path(args.root or os.getcwd()))
|
|
443
|
+
status = args.status.upper()
|
|
444
|
+
if status not in {"PASS", "PARTIAL", "BLOCKED", "FAIL"}:
|
|
445
|
+
raise SystemExit(f"invalid status: {args.status}")
|
|
446
|
+
result = {
|
|
447
|
+
"schema": "dic.final-result.v1",
|
|
448
|
+
"status": status,
|
|
449
|
+
"final": True,
|
|
450
|
+
"summary": args.summary,
|
|
451
|
+
"artifacts": [
|
|
452
|
+
"reports/project-profile.json",
|
|
453
|
+
"reports/contract-obligations.json",
|
|
454
|
+
"reports/contract-obligations.md",
|
|
455
|
+
"reports/final-consistency-report.md",
|
|
456
|
+
"reports/FINAL_RESULT.json",
|
|
457
|
+
"result/output.md",
|
|
458
|
+
"logs/trace/"
|
|
459
|
+
],
|
|
460
|
+
"validation": [],
|
|
461
|
+
"changedFiles": [],
|
|
462
|
+
"residualRisks": []
|
|
463
|
+
}
|
|
464
|
+
write_text(root / "reports" / "FINAL_RESULT.json", json.dumps(result, indent=2, ensure_ascii=False))
|
|
465
|
+
lines = [
|
|
466
|
+
"# DIC Final Result",
|
|
467
|
+
"",
|
|
468
|
+
f"FINAL_EXECUTION_RESULT: {status}",
|
|
469
|
+
"FINAL_RESULT_FINAL: true",
|
|
470
|
+
"",
|
|
471
|
+
args.summary,
|
|
472
|
+
"",
|
|
473
|
+
"## Artifacts",
|
|
474
|
+
"",
|
|
475
|
+
"- `reports/FINAL_RESULT.json`",
|
|
476
|
+
"- `reports/contract-obligations.json`",
|
|
477
|
+
"- `reports/contract-obligations.md`",
|
|
478
|
+
"- `result/output.md`",
|
|
479
|
+
"- `logs/trace/`",
|
|
480
|
+
"",
|
|
481
|
+
]
|
|
482
|
+
write_text(root / "result" / "output.md", "\n".join(lines))
|
|
483
|
+
print(f"FINAL_EXECUTION_RESULT: {status}")
|
|
484
|
+
return 0
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def main() -> int:
|
|
488
|
+
parser = argparse.ArgumentParser(prog="dic_workflow")
|
|
489
|
+
parser.add_argument("--root", default="")
|
|
490
|
+
parser.add_argument("--output-root", default="")
|
|
491
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
492
|
+
|
|
493
|
+
p = sub.add_parser("init")
|
|
494
|
+
p.add_argument("--change", default="")
|
|
495
|
+
p.set_defaults(func=command_init)
|
|
496
|
+
|
|
497
|
+
p = sub.add_parser("finalize")
|
|
498
|
+
p.add_argument("--status", default="PARTIAL")
|
|
499
|
+
p.add_argument("--summary", default="DIC workflow finished with recorded evidence.")
|
|
500
|
+
p.set_defaults(func=command_finalize)
|
|
501
|
+
|
|
502
|
+
args = parser.parse_args()
|
|
503
|
+
return int(args.func(args))
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
if __name__ == "__main__":
|
|
507
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: knowledge-bdd
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Derive Given-When-Then acceptance scenarios from confirmed design rules and flow contracts.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Knowledge: BDD
|
|
8
|
+
|
|
9
|
+
Use this Skill after requirements and flows are confirmed.
|
|
10
|
+
|
|
11
|
+
For every confirmed rule, consider:
|
|
12
|
+
|
|
13
|
+
- happy path
|
|
14
|
+
- boundary
|
|
15
|
+
- exception
|
|
16
|
+
- state transition
|
|
17
|
+
- authorization or eligibility
|
|
18
|
+
- idempotency or retry
|
|
19
|
+
- integration side effects
|
|
20
|
+
- concurrency or ordering
|
|
21
|
+
|
|
22
|
+
BDD scenarios are acceptance criteria. They are not a license to invent behavior outside source truth.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: knowledge-openspec
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: OpenSpec knowledge pack for proposals, specs, scenarios, tasks, changes, archived capabilities, validation, and design-to-implementation mapping.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Knowledge: OpenSpec
|
|
8
|
+
|
|
9
|
+
Use this Skill when the project contains OpenSpec files or the user says design work is managed with OpenSpec.
|
|
10
|
+
|
|
11
|
+
Read candidates:
|
|
12
|
+
|
|
13
|
+
- `openspec/specs/**/spec.md`
|
|
14
|
+
- `openspec/changes/**/proposal.md`
|
|
15
|
+
- `openspec/changes/**/design.md`
|
|
16
|
+
- `openspec/changes/**/tasks.md`
|
|
17
|
+
- `openspec/changes/**/specs/**/spec.md`
|
|
18
|
+
- archived specs, only as historical context
|
|
19
|
+
|
|
20
|
+
Checks:
|
|
21
|
+
|
|
22
|
+
- Proposal intent is reflected in specs.
|
|
23
|
+
- Requirements and scenarios map to implementation.
|
|
24
|
+
- Tasks are complete or terminally classified.
|
|
25
|
+
- Tests or payload evidence cover scenarios.
|
|
26
|
+
- Implementation does not contradict current specs.
|
|
27
|
+
- Archived specs do not override current accepted specs.
|
|
28
|
+
|
|
29
|
+
Do not treat OpenSpec examples as implementation details unless current specs require them.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: knowledge-repair-distill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Generic repair convergence guidance, gap taxonomy, model steering hints, and validation gates for design-implementation consistency work.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Knowledge: Repair Distill
|
|
8
|
+
|
|
9
|
+
Use this Skill before repair planning or when a model drifts.
|
|
10
|
+
|
|
11
|
+
Common gap families:
|
|
12
|
+
|
|
13
|
+
- missing behavior
|
|
14
|
+
- wrong state transition
|
|
15
|
+
- incorrect numeric or money rule
|
|
16
|
+
- authorization or eligibility gap
|
|
17
|
+
- API contract mismatch
|
|
18
|
+
- persistence invariant gap
|
|
19
|
+
- side-effect or integration timing gap
|
|
20
|
+
- idempotency, retry, or concurrency gap
|
|
21
|
+
- validation/error contract mismatch
|
|
22
|
+
- overfit to public examples
|
|
23
|
+
|
|
24
|
+
Convergence rule:
|
|
25
|
+
|
|
26
|
+
Do not chase compile or test failures mechanically after a repair. Re-check whether the changed code still satisfies the governing source rule, then apply only mechanical fixes that preserve the rule.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-core
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Core Design-Implementation Consistency workflow contract, evidence model, handoff rules, and final output expectations.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Workflow Core
|
|
8
|
+
|
|
9
|
+
Use this Skill to understand the package-level workflow mechanism.
|
|
10
|
+
|
|
11
|
+
Principles:
|
|
12
|
+
|
|
13
|
+
- Source truth wins over implementation.
|
|
14
|
+
- Public tests are examples, not the full contract.
|
|
15
|
+
- Every repair must trace to a source-backed obligation.
|
|
16
|
+
- Subagents are useful only when they produce consumed evidence.
|
|
17
|
+
- Final success requires validation evidence, residual-risk classification, and final artifacts.
|
|
18
|
+
|
|
19
|
+
Required durable outputs:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
reports/project-profile.json
|
|
23
|
+
reports/final-consistency-report.md
|
|
24
|
+
reports/FINAL_RESULT.json
|
|
25
|
+
result/output.md
|
|
26
|
+
logs/trace/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Do not hardcode local paths, shell syntax, or a single project layout.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-intake
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Discover authoritative design sources, implementation roots, test roots, protected paths, validation commands, and adapter hints.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Workflow Intake
|
|
8
|
+
|
|
9
|
+
Use this Skill before any implementation inspection or repair.
|
|
10
|
+
|
|
11
|
+
Intake must identify:
|
|
12
|
+
|
|
13
|
+
- workflow root
|
|
14
|
+
- project root
|
|
15
|
+
- design sources
|
|
16
|
+
- implementation roots
|
|
17
|
+
- test roots
|
|
18
|
+
- protected paths
|
|
19
|
+
- validation commands
|
|
20
|
+
- adapter hints
|
|
21
|
+
- available external tools
|
|
22
|
+
|
|
23
|
+
Write the result to `reports/project-profile.json` when possible and mirror reasoning under `logs/trace/intake/`.
|
|
24
|
+
|
|
25
|
+
Do not assume OpenSpec, Maven, npm, Java, Node, or Spring. Detect them from repository evidence.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-profile
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Build a normalized project profile for design-implementation consistency work without hardcoding a repository or technology stack.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Workflow Profile
|
|
8
|
+
|
|
9
|
+
Use this Skill after intake to create a stable profile consumed by downstream agents.
|
|
10
|
+
|
|
11
|
+
The profile should follow `schemas/project-profile.schema.json` and include:
|
|
12
|
+
|
|
13
|
+
- source hierarchy
|
|
14
|
+
- project type hints
|
|
15
|
+
- adapters selected
|
|
16
|
+
- design source paths
|
|
17
|
+
- implementation root paths
|
|
18
|
+
- test root paths
|
|
19
|
+
- protected paths
|
|
20
|
+
- validation commands
|
|
21
|
+
- risk families
|
|
22
|
+
- output contract
|
|
23
|
+
|
|
24
|
+
If information is missing, record `unknown` or `blocked` with evidence instead of guessing.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-repair
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Guide source-backed implementation inspection, repair planning, code repair, validation loops, and final consistency evidence.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Workflow Repair
|
|
8
|
+
|
|
9
|
+
Use this Skill after project profile and source obligations are known.
|
|
10
|
+
|
|
11
|
+
Repair loop:
|
|
12
|
+
|
|
13
|
+
1. Confirm obligation from source.
|
|
14
|
+
2. Inspect implementation.
|
|
15
|
+
3. Classify gap.
|
|
16
|
+
4. Plan the smallest safe repair slice.
|
|
17
|
+
5. Modify implementation only inside allowed boundaries.
|
|
18
|
+
6. Run focused validation.
|
|
19
|
+
7. Update trace and residual risk.
|
|
20
|
+
8. Repeat or finalize.
|
|
21
|
+
|
|
22
|
+
Never repair from failing test names alone. Convert validation failures into source-backed hypotheses first.
|