okstra 0.207.0 → 0.208.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +3 -2
  2. package/dist/cli-registry.mjs +6 -0
  3. package/dist/cli-registry.mjs.map +1 -1
  4. package/dist/commands/execute/render-bundle.mjs +1 -1
  5. package/dist/commands/lifecycle/doctor.mjs +1 -1
  6. package/dist/lib/skill-catalog.mjs +1 -0
  7. package/dist/lib/skill-catalog.mjs.map +1 -1
  8. package/docs/architecture/storage-model.md +14 -0
  9. package/docs/architecture.md +30 -9
  10. package/docs/cli.md +26 -22
  11. package/docs/contributor-change-matrix.md +1 -1
  12. package/docs/project-structure-overview.md +15 -8
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/agents/operations/explain-flow.json +6 -0
  16. package/runtime/bin/lib/okstra/cli.sh +1 -5
  17. package/runtime/bin/lib/okstra/globals.sh +0 -2
  18. package/runtime/bin/lib/okstra/usage.sh +5 -3
  19. package/runtime/bin/okstra.sh +0 -2
  20. package/runtime/prompts/duties/business-flow-investigator.json +14 -0
  21. package/runtime/prompts/lead/context-loader.md +1 -1
  22. package/runtime/prompts/lead/convergence.md +22 -7
  23. package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
  24. package/runtime/prompts/lead/report-writer.md +1 -1
  25. package/runtime/prompts/lead/team-contract.md +12 -17
  26. package/runtime/prompts/profiles/_common-contract.md +3 -3
  27. package/runtime/prompts/wizard/prompts.ko.json +0 -91
  28. package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
  29. package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
  30. package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
  31. package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
  32. package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
  33. package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
  34. package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
  35. package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
  36. package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
  37. package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
  38. package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
  39. package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
  40. package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
  41. package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
  42. package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
  43. package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
  44. package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
  45. package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
  46. package/runtime/python/okstra_ctl/convergence.py +173 -2
  47. package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
  48. package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
  49. package/runtime/python/okstra_ctl/coverage_census.py +596 -0
  50. package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
  51. package/runtime/python/okstra_ctl/direct_work.py +1 -1
  52. package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
  53. package/runtime/python/okstra_ctl/doctor.py +12 -6
  54. package/runtime/python/okstra_ctl/domain/role.py +1 -0
  55. package/runtime/python/okstra_ctl/group_context.py +5 -4
  56. package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
  57. package/runtime/python/okstra_ctl/manager_split.py +4 -1
  58. package/runtime/python/okstra_ctl/manager_view.py +9 -3
  59. package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
  60. package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
  61. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
  62. package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
  63. package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
  64. package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
  65. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
  66. package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
  67. package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
  68. package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
  69. package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
  70. package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
  71. package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
  72. package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
  73. package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
  74. package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
  75. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
  76. package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
  77. package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
  78. package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
  79. package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
  80. package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
  81. package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
  82. package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
  83. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
  84. package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
  85. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
  86. package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
  87. package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
  88. package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
  89. package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
  90. package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
  91. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
  92. package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +10 -8
  93. package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
  94. package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
  95. package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
  96. package/runtime/python/okstra_ctl/plan_items.py +1 -1
  97. package/runtime/python/okstra_ctl/render.py +10 -43
  98. package/runtime/python/okstra_ctl/render_final_report.py +3 -0
  99. package/runtime/python/okstra_ctl/report_assembly.py +15 -1
  100. package/runtime/python/okstra_ctl/report_finalize.py +40 -0
  101. package/runtime/python/okstra_ctl/report_html/render.py +3 -0
  102. package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
  103. package/runtime/python/okstra_ctl/run.py +78 -409
  104. package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
  105. package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
  106. package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
  107. package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
  108. package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
  109. package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
  110. package/runtime/python/okstra_ctl/wizard/render.py +2 -2
  111. package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
  112. package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
  113. package/runtime/python/okstra_ctl/wizard/state.py +36 -145
  114. package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
  115. package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
  116. package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
  117. package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
  118. package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
  119. package/runtime/schemas/business-flow-v1.schema.json +847 -0
  120. package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
  121. package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
  122. package/runtime/skills/okstra-inspect/facets/history.md +5 -5
  123. package/runtime/skills/okstra-run/SKILL.md +2 -2
  124. package/runtime/templates/manager/view.template.html +7 -4
  125. package/runtime/templates/reports/business-flow.template.md +106 -0
  126. package/runtime/templates/reports/html/base.template.html +14 -1
  127. package/runtime/templates/reports/html/business-flow.template.html +31 -0
  128. package/runtime/templates/reports/html/i18n/en.json +1 -0
  129. package/runtime/templates/reports/html/i18n/ko.json +1 -0
  130. package/runtime/templates/worker-prompt-preamble.md +11 -2
  131. package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
  132. package/runtime/validators/validate-run.py +70 -21
  133. package/runtime/validators/validate_analysis_report.py +21 -21
  134. package/runtime/python/okstra_ctl/workers.py +0 -133
@@ -0,0 +1,268 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ from dataclasses import dataclass
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any, Literal, TypedDict
9
+
10
+ from ..json_boundary import load_owned_object, write_owned_object_atomic
11
+ from ..paths import find_asset_root
12
+ from ..report_language import is_language_tag
13
+
14
+ OPERATION = "explain-flow"
15
+ MODES = ("current", "pre", "post", "contribute", "reconcile")
16
+ LEVELS = ("static", "execution-verified", "expected", "unknown")
17
+
18
+
19
+ class BusinessEvidence(TypedDict):
20
+ projectId: str
21
+ path: str
22
+ line: int
23
+ endLine: int
24
+ excerpt: str
25
+
26
+
27
+ class _BusinessFactRequired(TypedDict):
28
+ key: str
29
+ business: str
30
+ value: str
31
+ level: Literal["static", "execution-verified", "expected", "unknown"]
32
+ evidence: list[BusinessEvidence]
33
+
34
+
35
+ class BusinessFact(_BusinessFactRequired, total=False):
36
+ executionEvidence: list[str]
37
+ relatedKeys: list[str]
38
+ aliases: list[str]
39
+
40
+
41
+ class BusinessStep(TypedDict):
42
+ id: str
43
+ title: str
44
+ purpose: str
45
+ projectIds: list[str]
46
+ inputs: list[str]
47
+ outputs: list[str]
48
+ rules: list[str]
49
+ states: list[str]
50
+ failures: list[str]
51
+ retries: list[str]
52
+ recovery: list[str]
53
+ factKeys: list[str]
54
+ before: str
55
+ after: str
56
+ risks: list[str]
57
+ sideEffects: list[str]
58
+
59
+
60
+ BusinessRelationship = TypedDict(
61
+ "BusinessRelationship",
62
+ {
63
+ "from": str,
64
+ "to": str,
65
+ "kind": Literal["call", "event", "queue", "shared-data", "file", "batch"],
66
+ "data": str,
67
+ "factKeys": list[str],
68
+ },
69
+ )
70
+
71
+
72
+ class BusinessResolution(TypedDict):
73
+ claimIds: list[str]
74
+ acceptedClaimId: str
75
+ reason: str
76
+
77
+
78
+ class BusinessTerm(TypedDict):
79
+ term: str
80
+ meaning: str
81
+
82
+
83
+ class BusinessResult(TypedDict):
84
+ title: str
85
+ summary: str
86
+ terms: list[BusinessTerm]
87
+ steps: list[BusinessStep]
88
+ relationships: list[BusinessRelationship]
89
+ facts: list[BusinessFact]
90
+ inspectedProjects: list[str]
91
+ gaps: list[str]
92
+ questions: list[str]
93
+ resolutions: list[BusinessResolution]
94
+
95
+
96
+ class ProjectCandidate(TypedDict):
97
+ projectId: str
98
+ root: str
99
+
100
+
101
+ class _SourceSnapshotRequired(ProjectCandidate):
102
+ head: str
103
+ digest: str
104
+ files: dict[str, str]
105
+ dirty: dict[str, str]
106
+ gaps: list[str]
107
+
108
+
109
+ class SourceSnapshot(_SourceSnapshotRequired, total=False):
110
+ indexDigest: str
111
+ indexPatch: str
112
+ statusDigest: str
113
+
114
+
115
+ class FlowRequestPayload(TypedDict):
116
+ project_root: str
117
+ question: str
118
+ mode: str
119
+ task_key: str
120
+ baseline: str
121
+ source_root: str | None
122
+ report_language: str
123
+ source_artifacts: list[str]
124
+ host_runtime: str
125
+
126
+
127
+ class _FlowExecutionRequired(TypedDict):
128
+ schemaVersion: int
129
+ id: str
130
+ createdAt: str
131
+ request: FlowRequestPayload
132
+ projects: list[SourceSnapshot]
133
+ status: str
134
+ readOnlyAudit: dict[str, object]
135
+
136
+
137
+ class FlowExecution(_FlowExecutionRequired, total=False):
138
+ sourceArtifactDigests: dict[str, str]
139
+ changes: list[dict[str, object]]
140
+ metadataPath: str
141
+ result: BusinessResult
142
+ reports: dict[str, str]
143
+ contribution: dict[str, list[str]]
144
+ reconciliation: dict[str, object]
145
+ error: str
146
+
147
+
148
+ class TaskLinkRow(TypedDict):
149
+ id: str
150
+ mode: str
151
+ status: str
152
+ inputDigest: str
153
+ reports: dict[str, str]
154
+ summary: str
155
+ error: str
156
+
157
+
158
+ class TaskLink(TypedDict):
159
+ schemaVersion: int
160
+ taskKey: str
161
+ executions: list[TaskLinkRow]
162
+
163
+
164
+ class ClaimProducer(TypedDict):
165
+ executionId: str
166
+ taskKey: str
167
+ mode: str
168
+ metadataPath: str
169
+
170
+
171
+ class SourceState(TypedDict):
172
+ root: str
173
+ digest: str
174
+ head: str
175
+
176
+
177
+ class StoredClaim(BusinessFact):
178
+ id: str
179
+ sourceStates: dict[str, SourceState]
180
+ producer: ClaimProducer
181
+ producers: list[ClaimProducer]
182
+ createdAt: str
183
+ status: Literal["accepted", "conflict", "superseded"]
184
+
185
+
186
+ class BusinessKnowledge(TypedDict):
187
+ schemaVersion: int
188
+ claims: list[StoredClaim]
189
+ resolutions: list[dict[str, object]]
190
+
191
+
192
+ class BusinessFlowError(ValueError):
193
+ pass
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class FlowRequest:
198
+ project_root: Path
199
+ question: str
200
+ mode: str = "current"
201
+ task_key: str = ""
202
+ baseline: str = ""
203
+ source_root: Path | None = None
204
+ report_language: str = "en"
205
+ source_artifacts: tuple[str, ...] = ()
206
+ host_runtime: str = "claude-code"
207
+
208
+ def validate(self) -> None:
209
+ if not self.project_root.is_dir() or not self.question.strip():
210
+ raise BusinessFlowError(
211
+ "a project directory and business question are required"
212
+ )
213
+ if self.mode not in MODES:
214
+ raise BusinessFlowError(f"unknown explanation mode: {self.mode}")
215
+ if self.source_root is not None and not self.source_root.is_dir():
216
+ raise BusinessFlowError(f"source directory unavailable: {self.source_root}")
217
+ if not is_language_tag(self.report_language):
218
+ raise BusinessFlowError(
219
+ "report language must be a language tag such as en or ko"
220
+ )
221
+
222
+
223
+ def now_iso() -> str:
224
+ return datetime.now(timezone.utc).isoformat(timespec="seconds")
225
+
226
+
227
+ def fingerprint(value: object) -> str:
228
+ body = json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False)
229
+ return hashlib.sha256(body.encode("utf-8")).hexdigest()
230
+
231
+
232
+ def business_root(project_root: Path) -> Path:
233
+ project = project_root.resolve(strict=True)
234
+ root = project / ".okstra" / "business"
235
+ if not root.resolve().is_relative_to(project):
236
+ raise BusinessFlowError("business artifact root escapes project")
237
+ return root
238
+
239
+
240
+ def artifact_path(project_root: Path, *parts: str) -> Path:
241
+ root = business_root(project_root)
242
+ target = root.joinpath(*parts)
243
+ if not target.resolve().is_relative_to(root.resolve()):
244
+ raise BusinessFlowError("business artifact path escapes its root")
245
+ return target
246
+
247
+
248
+ def schema_definition(name: str) -> dict[str, Any]:
249
+ relative = ("schemas", "business-flow-v1.schema.json")
250
+ root = find_asset_root(relative)
251
+ if root is None:
252
+ raise BusinessFlowError("business flow schema asset is unavailable")
253
+ schema = load_owned_object(
254
+ root.joinpath(*relative), artifact="business flow schema"
255
+ )
256
+ return {"$ref": f"#/$defs/{name}", "$defs": schema["$defs"]}
257
+
258
+
259
+ def read_artifact(path: Path, name: str) -> dict[str, Any]:
260
+ return load_owned_object(
261
+ path, artifact=f"business flow {name}", schema=schema_definition(name)
262
+ )
263
+
264
+
265
+ def write_artifact(path: Path, payload: dict[str, Any], name: str) -> None:
266
+ write_owned_object_atomic(
267
+ path, payload, artifact=f"business flow {name}", schema=schema_definition(name)
268
+ )