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.
@@ -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.