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.
- package/README.md +3 -2
- package/dist/cli-registry.mjs +6 -0
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +1 -1
- package/dist/commands/lifecycle/doctor.mjs +1 -1
- package/dist/lib/skill-catalog.mjs +1 -0
- package/dist/lib/skill-catalog.mjs.map +1 -1
- package/docs/architecture/storage-model.md +14 -0
- package/docs/architecture.md +30 -9
- package/docs/cli.md +26 -22
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/project-structure-overview.md +15 -8
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/explain-flow.json +6 -0
- package/runtime/bin/lib/okstra/cli.sh +1 -5
- package/runtime/bin/lib/okstra/globals.sh +0 -2
- package/runtime/bin/lib/okstra/usage.sh +5 -3
- package/runtime/bin/okstra.sh +0 -2
- package/runtime/prompts/duties/business-flow-investigator.json +14 -0
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +22 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -6
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/lead/team-contract.md +12 -17
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +0 -91
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +6 -0
- package/runtime/python/okstra_ctl/agent/invocation.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +1 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +11 -125
- package/runtime/python/okstra_ctl/agent/standalone.py +183 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +39 -8
- package/runtime/python/okstra_ctl/assignment_resolver.py +7 -1
- package/runtime/python/okstra_ctl/brief_frontmatter.py +10 -0
- package/runtime/python/okstra_ctl/business_flow/__init__.py +4 -0
- package/runtime/python/okstra_ctl/business_flow/cli.py +134 -0
- package/runtime/python/okstra_ctl/business_flow/contracts.py +268 -0
- package/runtime/python/okstra_ctl/business_flow/engine.py +518 -0
- package/runtime/python/okstra_ctl/business_flow/hooks.py +221 -0
- package/runtime/python/okstra_ctl/business_flow/invocation.py +170 -0
- package/runtime/python/okstra_ctl/business_flow/report.py +49 -0
- package/runtime/python/okstra_ctl/business_flow/source.py +206 -0
- package/runtime/python/okstra_ctl/business_flow/store.py +388 -0
- package/runtime/python/okstra_ctl/convergence.py +173 -2
- package/runtime/python/okstra_ctl/convergence_critic_verify_prompt.py +18 -0
- package/runtime/python/okstra_ctl/convergence_provenance.py +8 -0
- package/runtime/python/okstra_ctl/coverage_census.py +596 -0
- package/runtime/python/okstra_ctl/design_surfaces.py +4 -0
- package/runtime/python/okstra_ctl/direct_work.py +1 -1
- package/runtime/python/okstra_ctl/dispatch_core.py +7 -3
- package/runtime/python/okstra_ctl/doctor.py +12 -6
- package/runtime/python/okstra_ctl/domain/role.py +1 -0
- package/runtime/python/okstra_ctl/group_context.py +5 -4
- package/runtime/python/okstra_ctl/legacy_model_selection.py +7 -51
- package/runtime/python/okstra_ctl/manager_split.py +4 -1
- package/runtime/python/okstra_ctl/manager_view.py +9 -3
- package/runtime/python/okstra_ctl/model_io/lines.py +1 -24
- package/runtime/python/okstra_ctl/model_io/renderers.py +54 -41
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/profile.md +6 -8
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/feature_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/final_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -8
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/profile.md +0 -6
- package/runtime/python/okstra_ctl/phases/implementation/report_assets/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +4 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +1 -14
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/profile.md +5 -8
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/validation.py +15 -5
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +9 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md +4 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +25 -9
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/implementation_planning/profile.md +7 -10
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/improvement_discovery/profile.md +4 -11
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/project_analysis/profile.md +0 -8
- package/runtime/python/okstra_ctl/phases/release_handoff/profile.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/requirements_discovery/profile.md +10 -8
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +3 -3
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.json +1 -1
- package/runtime/python/okstra_ctl/phases/technical_verification/profile.md +0 -4
- package/runtime/python/okstra_ctl/plan_items.py +1 -1
- package/runtime/python/okstra_ctl/render.py +10 -43
- package/runtime/python/okstra_ctl/render_final_report.py +3 -0
- package/runtime/python/okstra_ctl/report_assembly.py +15 -1
- package/runtime/python/okstra_ctl/report_finalize.py +40 -0
- package/runtime/python/okstra_ctl/report_html/render.py +3 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +1 -2
- package/runtime/python/okstra_ctl/run.py +78 -409
- package/runtime/python/okstra_ctl/wizard/__init__.py +2 -24
- package/runtime/python/okstra_ctl/wizard/cli.py +3 -6
- package/runtime/python/okstra_ctl/wizard/confirmation.py +3 -35
- package/runtime/python/okstra_ctl/wizard/engine.py +2 -4
- package/runtime/python/okstra_ctl/wizard/ids.py +1 -88
- package/runtime/python/okstra_ctl/wizard/registry.py +36 -228
- package/runtime/python/okstra_ctl/wizard/render.py +2 -2
- package/runtime/python/okstra_ctl/wizard/roles.py +1 -3
- package/runtime/python/okstra_ctl/wizard/sources.py +9 -40
- package/runtime/python/okstra_ctl/wizard/state.py +36 -145
- package/runtime/python/okstra_ctl/wizard/statefile.py +27 -128
- package/runtime/python/okstra_ctl/wizard/steps_identity.py +22 -10
- package/runtime/python/okstra_ctl/wizard/steps_options.py +5 -4
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +15 -565
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +9 -2
- package/runtime/schemas/business-flow-v1.schema.json +847 -0
- package/runtime/schemas/convergence-groups-v2.0.schema.json +7 -0
- package/runtime/skills/okstra-explain-flow/SKILL.md +42 -0
- package/runtime/skills/okstra-inspect/facets/history.md +5 -5
- package/runtime/skills/okstra-run/SKILL.md +2 -2
- package/runtime/templates/manager/view.template.html +7 -4
- package/runtime/templates/reports/business-flow.template.md +106 -0
- package/runtime/templates/reports/html/base.template.html +14 -1
- package/runtime/templates/reports/html/business-flow.template.html +31 -0
- package/runtime/templates/reports/html/i18n/en.json +1 -0
- package/runtime/templates/reports/html/i18n/ko.json +1 -0
- package/runtime/templates/worker-prompt-preamble.md +11 -2
- package/runtime/validators/checks/validate-prompt-metadata-01.py +10 -10
- package/runtime/validators/validate-run.py +70 -21
- package/runtime/validators/validate_analysis_report.py +21 -21
- 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
|
+
)
|