okstra 0.143.0 → 0.145.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 (46) hide show
  1. package/README.md +4 -1
  2. package/docs/architecture.md +18 -2
  3. package/docs/cli.md +39 -2
  4. package/docs/project-structure-overview.md +19 -6
  5. package/package.json +1 -1
  6. package/runtime/BUILD.json +2 -2
  7. package/runtime/prompts/coding-preflight/overview.md +1 -1
  8. package/runtime/prompts/lead/convergence.md +11 -3
  9. package/runtime/prompts/lead/okstra-lead-contract.md +7 -1
  10. package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
  11. package/runtime/prompts/profiles/_common-contract.md +1 -1
  12. package/runtime/prompts/profiles/_implementation-verifier.md +48 -2
  13. package/runtime/prompts/profiles/change-impact-analysis.md +24 -0
  14. package/runtime/prompts/profiles/feature-analysis.md +24 -0
  15. package/runtime/prompts/profiles/forbidden-actions.json +18 -0
  16. package/runtime/prompts/profiles/project-analysis.md +24 -0
  17. package/runtime/prompts/wizard/prompts.ko.json +44 -1
  18. package/runtime/python/okstra_ctl/analysis_inputs.py +369 -0
  19. package/runtime/python/okstra_ctl/clarification_items.py +74 -1
  20. package/runtime/python/okstra_ctl/mutation_probe.py +1263 -0
  21. package/runtime/python/okstra_ctl/render.py +77 -4
  22. package/runtime/python/okstra_ctl/render_final_report.py +13 -4
  23. package/runtime/python/okstra_ctl/report_views.py +134 -3
  24. package/runtime/python/okstra_ctl/run.py +118 -0
  25. package/runtime/python/okstra_ctl/run_context.py +34 -2
  26. package/runtime/python/okstra_ctl/schema_excerpt.py +12 -4
  27. package/runtime/python/okstra_ctl/self_mock_signals.py +183 -0
  28. package/runtime/python/okstra_ctl/user_response.py +309 -3
  29. package/runtime/python/okstra_ctl/wizard.py +545 -32
  30. package/runtime/python/okstra_ctl/worker_prompt_policy.py +3 -0
  31. package/runtime/python/okstra_ctl/workflow.py +22 -0
  32. package/runtime/schemas/final-report-v1.0.schema.json +849 -3
  33. package/runtime/skills/okstra-run/SKILL.md +13 -1
  34. package/runtime/templates/reports/change-impact-analysis-input.template.md +58 -0
  35. package/runtime/templates/reports/feature-analysis-input.template.md +59 -0
  36. package/runtime/templates/reports/final-report.template.md +220 -0
  37. package/runtime/templates/reports/i18n/en.json +8 -0
  38. package/runtime/templates/reports/i18n/ko.json +8 -0
  39. package/runtime/templates/reports/project-analysis-input.template.md +58 -0
  40. package/runtime/templates/reports/report.js +84 -5
  41. package/runtime/templates/reports/user-response.template.md +19 -1
  42. package/runtime/validators/detect_self_mock.py +220 -0
  43. package/runtime/validators/validate-report-views.py +61 -7
  44. package/runtime/validators/validate-run.py +518 -0
  45. package/runtime/validators/validate_analysis_report.py +864 -0
  46. package/src/commands/execute/render-bundle.mjs +3 -0
@@ -115,7 +115,8 @@ That is the entire interactive flow. The wizard handles:
115
115
 
116
116
  - new-vs-existing task split (remaining work — `workStatus != done` — top-3 newest recommendations + Enter directly), task-group / task-id slug validation (task-group offers the top-3 newest candidates combining recent task use + recent `.okstra/briefs/<group>/` creation activity + Enter directly; task-id offers the top-3 recent candidates from the same group + Enter directly),
117
117
  - task-type pick (3 recommendations — `nextRecommendedPhase` recommended / re-run the current phase / the lifecycle's next step — + Enter directly; Enter directly is validated against the full task-type whitelist in a follow-up `text` step),
118
- - brief path — **asked only for entry task-types (requirements-discovery / error-analysis / improvement-discovery)** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, sorted by the newer of file-created/modified time and latest task-catalog use; direct input last; `Keep / Change` for existing entry tasks). A downstream task-type auto carries in the manifest's brief, and when no registered brief exists a `brief_carry` 3-option prompt appears (recommend switching to entry / Enter directly / Abort). `release-handoff` has no brief step at all — prepare generates the input document that cites the verification report,
118
+ - brief path — **asked only for entry task-types (requirements-discovery / error-analysis / improvement-discovery / project-analysis / feature-analysis / change-impact-analysis)** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, sorted by the newer of file-created/modified time and latest task-catalog use; direct input last; `Keep / Change` for existing entry tasks). `project-analysis`, `feature-analysis`, and `change-impact-analysis` are brief entry task types. A downstream lifecycle task-type auto carries in the manifest's brief, and when no registered brief exists a `brief_carry` 3-option prompt appears (recommend switching to entry / Enter directly / Abort). `release-handoff` has no brief step at all — prepare generates the input document that cites the verification report,
119
+ - analysis-input sub-flow — `project-analysis` has no target or evidence step. `feature-analysis` runs `project_evidence_pick` / `project_evidence`, then `analysis_target_pick` / `analysis_target`; the target is required even when the user skips project evidence. `change-impact-analysis` runs `feature_evidence_pick` / `feature_evidence`, then `project_evidence_pick` / `project_evidence`. The evidence steps show accepted compatible reports first and retain direct-path entry; do not invent a different relation or reorder these steps,
119
120
  - base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
120
121
  - `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number) + executor pick. When an approved plan is selected and a `## APPROVAL` sidecar exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
121
122
  - `release-handoff`-only sub-flow: after the approved plan auto-resolves, a `handoff_stage_pick` multi-select — choose an eligible stage bundle (stage-group) or the whole task (when an accepted whole-task verification report exists); the result goes out as render-args' `stages` key (csv, empty when whole-task),
@@ -124,6 +125,15 @@ That is the entire interactive flow. The wizard handles:
124
125
  - `release-handoff` PR template override + persist scope,
125
126
  - final `Proceed / Edit` confirmation; on `Edit` the wizard asks which step to rewind to and clears every later answer.
126
127
 
128
+ ### Analysis sidetrack execution rules
129
+
130
+ When the selected task type is `project-analysis`, `feature-analysis`, or `change-impact-analysis`:
131
+
132
+ 1. Treat the target project as strictly read-only. No edits, tests, builds, migrations, or deployments are allowed, even if the user asks for verification while starting the analysis run.
133
+ 2. Pass the wizard's resolved target and evidence values through the render flags exactly as emitted. `project-analysis` emits both values empty; never synthesize an evidence path from a brief or a prior run.
134
+ 3. A `revision-requested` report is not evidence. The wizard prioritizes its same-task, same-type full rerun and carries the review sidecar through the existing clarification-response channel; preserve that choice instead of substituting a newer report of another type.
135
+ 4. The rendered lead contract reanalyzes the whole confirmed scope and records a resolution for every affected ID. Do not narrow the rerun to only the disputed rows or mutate the previous report.
136
+
127
137
  Do not second-guess the wizard. If the next prompt seems out of place, the bug is in `wizard.py`, not in your interpretation of the user's input.
128
138
 
129
139
  ## Step 4: Show the confirmation block before the final Proceed
@@ -178,6 +188,8 @@ okstra render-bundle \
178
188
  --task-id "<args.task-id>" \
179
189
  --task-type "<args.task-type>" \
180
190
  --task-brief "<args.task-brief>" \
191
+ --analysis-target "<args.analysis-target>" \
192
+ --evidence-inputs "<args.evidence-inputs>" \
181
193
  --executor "<args.executor>" \
182
194
  --critic "<args.critic>" \
183
195
  --approved-plan "<args.approved-plan>" \
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: OKSTRA Change Impact Analysis Input - {{TASK_KEY}}
3
+ id: {{FM_ID}}
4
+ tags: {{FM_TAGS}}
5
+ status: ready-for-agent
6
+ aliases: {{FM_ALIASES}}
7
+ date: {{TASK_DATE}}
8
+ task-id: "{{TASK_ID}}"
9
+ task-group: "{{TASK_GROUP}}"
10
+ project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
12
+ ---
13
+
14
+ # OKSTRA Change Impact Analysis Input
15
+
16
+ ## Identity
17
+
18
+ - Project ID:
19
+ - Task Group:
20
+ - Task ID:
21
+ - Related Tasks:
22
+ - Issue / Ticket:
23
+ - Task Type: `change-impact-analysis`
24
+ - Requested Outcome:
25
+
26
+ ## Proposed Change and User Intent
27
+
28
+ - Change being assessed (without prescribing an implementation):
29
+ - User intent and decision this analysis should support:
30
+ - Explicitly excluded changes or systems:
31
+
32
+ ## Impact Scope
33
+
34
+ - Directly affected components, interfaces, or integrations:
35
+ - Potential dependency propagation:
36
+ - Expected test and operational impact:
37
+
38
+ ## Preserved Behavior and Constraints
39
+
40
+ - Behavior, contracts, or outcomes that must be preserved:
41
+ - Compatibility, security, or operational constraints:
42
+ - Open decisions that a later planning phase must resolve:
43
+
44
+ ## Questions for Analysers
45
+
46
+ 1. Which behaviors must be preserved and which items are affected?
47
+ 2. How could impacts propagate through dependencies, tests, and operations?
48
+ 3. Which constraints and open decisions should be passed to implementation-planning without producing an execution plan?
49
+
50
+ ## Input Limit
51
+
52
+ - Collect user intent, impact scope, and preserved behavior only.
53
+ - Do not add worker-produced result tables to this input.
54
+ - The final report `data.json` is the result source of truth.
55
+
56
+ ## Conversion Note
57
+
58
+ - This input can be used as a starting draft before creating `okstra-task-brief.md`.
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: OKSTRA Feature Analysis Input - {{TASK_KEY}}
3
+ id: {{FM_ID}}
4
+ tags: {{FM_TAGS}}
5
+ status: ready-for-agent
6
+ aliases: {{FM_ALIASES}}
7
+ date: {{TASK_DATE}}
8
+ task-id: "{{TASK_ID}}"
9
+ task-group: "{{TASK_GROUP}}"
10
+ project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
12
+ ---
13
+
14
+ # OKSTRA Feature Analysis Input
15
+
16
+ ## Identity
17
+
18
+ - Project ID:
19
+ - Task Group:
20
+ - Task ID:
21
+ - Related Tasks:
22
+ - Issue / Ticket:
23
+ - Task Type: `feature-analysis`
24
+ - Requested Outcome:
25
+
26
+ ## User Intent and Confirmed Target
27
+
28
+ - Confirmed feature, journey, or capability to analyse:
29
+ - User intent and expected value:
30
+ - Explicitly excluded feature areas:
31
+
32
+ ## Behavior Scope
33
+
34
+ - Normal flow to preserve or understand:
35
+ - Alternate flows:
36
+ - Failure flows:
37
+ - Rules, state changes, or external calls that need attention:
38
+
39
+ ## Preserved Behavior
40
+
41
+ - Contracts or outcomes that must remain unchanged:
42
+ - Compatibility, security, or operational constraints:
43
+ - Unknown behavior that requires clarification:
44
+
45
+ ## Questions for Analysers
46
+
47
+ 1. What normal, alternate, and failure flows define the confirmed target?
48
+ 2. Which rules, state transitions, and external calls are in scope?
49
+ 3. What test coverage scope should a later task consider without proposing implementation changes?
50
+
51
+ ## Input Limit
52
+
53
+ - Collect user intent, behavior scope, and preserved behavior only.
54
+ - Do not add worker-produced result tables to this input.
55
+ - The final report `data.json` is the result source of truth.
56
+
57
+ ## Conversion Note
58
+
59
+ - This input can be used as a starting draft before creating `okstra-task-brief.md`.
@@ -837,6 +837,226 @@ Single scan of every agreed check that this run did NOT confirm — requirement
837
837
  - All agreed checks were executed or covered — nothing left unverified.
838
838
  {%- endif %}
839
839
 
840
+ {% endif %}
841
+ {% if analysisCommon %}
842
+ ## 5.9 Analysis Basis
843
+
844
+ {{ t("analysis.basisIntro") }}
845
+
846
+ ### 5.9.1 Snapshot and Scope
847
+
848
+ | Item | Value |
849
+ |------|-------|
850
+ | Run sequence | `{{ analysisCommon.runSeq | mdcell }}` |
851
+ | Source commit | `{{ analysisCommon.sourceCommit | mdcell }}` |
852
+ | Included paths | {{ analysisCommon.scope.includedPaths | join(', ') | mdcell }} |
853
+ | Excluded paths | {{ analysisCommon.scope.excludedPaths | join(', ') | mdcell }} |
854
+ | Unscanned paths | {{ (analysisCommon.scope.unscannedPaths | join(', ') or '--') | mdcell }} |
855
+ | Resolved target mode | `{{ (analysisCommon.scope.resolvedTarget.inputMode or '--') | mdcell }}` |
856
+ | Resolved target | {{ (analysisCommon.scope.resolvedTarget.requestedValue or '--') | mdcell }} |
857
+
858
+ ### 5.9.2 Evidence Inputs
859
+
860
+ {% if analysisCommon.evidenceInputs | length == 0 -%}
861
+ {{ t("analysis.empty") }}
862
+ {%- else %}
863
+ | Task key | Task type | Report path | Run | Source commit | Relation | Freshness | Review status |
864
+ |----------|-----------|-------------|-----|---------------|----------|-----------|---------------|
865
+ {% for row in analysisCommon.evidenceInputs -%}
866
+ | {{ row.taskKey | mdcell }} | `{{ row.taskType | mdcell }}` | `{{ row.reportPath | mdcell }}` | `{{ row.runSeq | mdcell }}` | `{{ row.sourceCommit | mdcell }}` | `{{ row.relation | mdcell }}` | `{{ row.freshness | mdcell }}` | `{{ row.reviewStatus | mdcell }}` |
867
+ {% endfor %}
868
+ {%- endif %}
869
+
870
+ ### 5.9.3 Confirmed Facts
871
+
872
+ {% if analysisCommon.confirmedFacts | length == 0 -%}
873
+ {{ t("analysis.empty") }}
874
+ {%- else %}
875
+ | ID | Claim | Current code evidence | Confidence | Review resolution |
876
+ |----|-------|-----------------------|------------|-------------------|
877
+ {% for row in analysisCommon.confirmedFacts -%}
878
+ {% set review = namespace(value=t("analysis.reviewNotRecorded")) -%}
879
+ {% for resolution in analysisCommon.analysisReviewResolution if resolution.affectedId == row.id -%}
880
+ {% set review.value = resolution.outcome ~ ': ' ~ resolution.resolution -%}
881
+ {% endfor -%}
882
+ | {{ row.id | mdcell }} | {{ row.statement | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} | confirmed | {{ review.value | mdcell }} |
883
+ {% endfor %}
884
+ {%- endif %}
885
+
886
+ ### 5.9.4 Inferences and Unknowns
887
+
888
+ {% if analysisCommon.inferences | length == 0 -%}
889
+ {{ t("analysis.empty") }}
890
+ {%- else %}
891
+ | ID | Claim | Evidence IDs | Confidence |
892
+ |----|-------|--------------|------------|
893
+ {% for row in analysisCommon.inferences -%}
894
+ | {{ row.id | mdcell }} | {{ row.statement | mdcell }} | {{ row.evidenceIds | join(', ') | mdcell }} | `{{ row.confidence | mdcell }}` |
895
+ {% endfor %}
896
+ {%- endif %}
897
+
898
+ {% if analysisCommon.unknowns | length > 0 %}
899
+ | ID | Question | Reason | Affects verdict |
900
+ |----|----------|--------|-----------------|
901
+ {% for row in analysisCommon.unknowns -%}
902
+ | {{ row.id | mdcell }} | {{ row.question | mdcell }} | {{ row.reason | mdcell }} | `{{ row.affectsVerdict | mdcell }}` |
903
+ {% endfor %}
904
+ {% endif %}
905
+
906
+ ### 5.9.5 Analysis Review Resolution
907
+
908
+ {% if analysisCommon.analysisReviewResolution | length == 0 -%}
909
+ {{ t("analysis.empty") }}
910
+ {%- else %}
911
+ | Affected ID | Outcome | Resolution | Evidence |
912
+ |-------------|---------|------------|----------|
913
+ {% for row in analysisCommon.analysisReviewResolution -%}
914
+ | {{ row.affectedId | mdcell }} | `{{ row.outcome | mdcell }}` | {{ row.resolution | mdcell }} | {{ row.evidence | join('<br>') | mdcell }} |
915
+ {% endfor %}
916
+ {%- endif %}
917
+
918
+ {% endif %}
919
+ {% if projectAnalysis %}
920
+ ## 5.10 Project Analysis
921
+
922
+ {{ t("analysis.projectIntro") }}
923
+
924
+ ### 5.10.1 Components
925
+
926
+ | ID | Name | Responsibility | Paths | Current code evidence |
927
+ |----|------|----------------|-------|-----------------------|
928
+ {% for row in projectAnalysis.components -%}
929
+ | {{ row.id | mdcell }} | {{ row.name | mdcell }} | {{ row.responsibility | mdcell }} | {{ row.paths | join(', ') | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
930
+ {% endfor %}
931
+
932
+ ### 5.10.2 Dependencies and Boundaries
933
+
934
+ | From | To | Direction | Current code evidence |
935
+ |------|----|-----------|-----------------------|
936
+ {% for row in projectAnalysis.dependencies -%}
937
+ | {{ row.fromComponentId | mdcell }} | {{ row.toComponentId | mdcell }} | {{ row.direction | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
938
+ {% endfor %}
939
+
940
+ | Kind | Path | Symbol | Role | Current code evidence |
941
+ |------|------|--------|------|-----------------------|
942
+ {% for row in projectAnalysis.entryPoints -%}
943
+ | {{ row.kind | mdcell }} | `{{ row.path | mdcell }}` | `{{ row.symbol | mdcell }}` | {{ row.role | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
944
+ {% endfor %}
945
+
946
+ ### 5.10.3 Stores and External Systems
947
+
948
+ | Store | Owner | Read boundary | Write boundary | Current code evidence |
949
+ |-------|-------|---------------|----------------|-----------------------|
950
+ {% for row in projectAnalysis.dataStores -%}
951
+ | {{ row.name | mdcell }} | {{ row.ownerComponentId | mdcell }} | {{ row.readBoundary | mdcell }} | {{ row.writeBoundary | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
952
+ {% endfor %}
953
+
954
+ | External system | Adapter | Direction | Current code evidence |
955
+ |-----------------|---------|-----------|-----------------------|
956
+ {% for row in projectAnalysis.externalSystems -%}
957
+ | {{ row.name | mdcell }} | {{ row.adapter | mdcell }} | {{ row.direction | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
958
+ {% endfor %}
959
+
960
+ ### 5.10.4 Feature Index
961
+
962
+ | ID | Name | Description | Representative entry point | Related components | Current code evidence |
963
+ |----|------|-------------|----------------------------|--------------------|-----------------------|
964
+ {% for row in projectAnalysis.featureIndex -%}
965
+ | {{ row.id | mdcell }} | {{ row.name | mdcell }} | {{ row.description | mdcell }} | `{{ row.representativeEntryPoint.path | mdcell }}:{{ row.representativeEntryPoint.symbol | mdcell }}` | {{ row.componentIds | join(', ') | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
966
+ {% endfor %}
967
+
968
+ - Scan coverage: `{{ projectAnalysis.scanCoverage.scannedPathCount }}` scanned / `{{ projectAnalysis.scanCoverage.unscannedPathCount }}` unscanned — {{ projectAnalysis.scanCoverage.reason }}
969
+
970
+ {% endif %}
971
+ {% if featureAnalysis %}
972
+ ## 5.11 Feature Analysis
973
+
974
+ {{ t("analysis.featureIntro") }}
975
+
976
+ ### 5.11.1 Confirmed Target
977
+
978
+ | Mode | Requested value | Feature ID | Entry points |
979
+ |------|-----------------|------------|--------------|
980
+ | `{{ featureAnalysis.target.inputMode | mdcell }}` | {{ featureAnalysis.target.requestedValue | mdcell }} | {{ (featureAnalysis.target.featureId or '--') | mdcell }} | {% for row in featureAnalysis.target.entryPoints %}`{{ row.path | mdcell }}:{{ row.symbol | mdcell }}`{% if not loop.last %}<br>{% endif %}{% endfor %} |
981
+
982
+ ### 5.11.2 Flows, Rules, and State Changes
983
+
984
+ | ID | Kind | Claim | Current code evidence | Source reports |
985
+ |----|------|-------|-----------------------|----------------|
986
+ {% for row in featureAnalysis.flows -%}
987
+ | {{ row.id | mdcell }} | `{{ row.kind | mdcell }}` | {% for step in row.steps %}{{ step.sequence | mdcell }}. {{ step.action | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} | {{ row.sourceReportRefs | join(', ') | mdcell }} |
988
+ {% endfor %}
989
+ {% for row in featureAnalysis.domainRules -%}
990
+ | {{ row.id | mdcell }} | `rule` | {{ row.condition | mdcell }} → {{ row.outcome | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} | {{ row.sourceReportRefs | join(', ') | mdcell }} |
991
+ {% endfor %}
992
+ {% for row in featureAnalysis.stateChanges -%}
993
+ | {{ row.id | mdcell }} | `state-change` | {{ row.dataStore | mdcell }}.{{ row.field | mdcell }} — {{ row.change | mdcell }}{% if row.sideEffects %}<br>Side effects: {{ row.sideEffects | join(', ') | mdcell }}{% endif %} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} | {{ row.sourceReportRefs | join(', ') | mdcell }} |
994
+ {% endfor %}
995
+
996
+ ### 5.11.3 External Interactions and Tests
997
+
998
+ | Target | Input | Output | Failure handling | Current code evidence |
999
+ |--------|-------|--------|------------------|-----------------------|
1000
+ {% for row in featureAnalysis.externalInteractions -%}
1001
+ | {{ row.target | mdcell }} | {{ row.input | mdcell }} | {{ row.output | mdcell }} | {{ row.failureHandling | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1002
+ {% endfor %}
1003
+
1004
+ | ID | Target IDs | Test paths | Gaps |
1005
+ |----|------------|------------|------|
1006
+ {% for row in featureAnalysis.testCoverage -%}
1007
+ | {{ row.id | mdcell }} | {{ row.targetIds | join(', ') | mdcell }} | {{ row.testPaths | join(', ') | mdcell }} | {{ (row.gaps | join(', ') or '--') | mdcell }} |
1008
+ {% endfor %}
1009
+
1010
+ {% endif %}
1011
+ {% if changeImpactAnalysis %}
1012
+ ## 5.12 Change Impact Analysis
1013
+
1014
+ {{ t("analysis.changeImpactIntro") }}
1015
+
1016
+ ### 5.12.1 Change and Preserved Behavior
1017
+
1018
+ - Change request: {{ changeImpactAnalysis.changeRequest.summary }} — evidence: `{{ changeImpactAnalysis.changeRequest.briefEvidence }}`
1019
+
1020
+ | Preserved behavior | Current code evidence |
1021
+ |--------------------|-----------------------|
1022
+ {% for row in changeImpactAnalysis.preservedBehaviors -%}
1023
+ | {{ row.statement | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1024
+ {% endfor %}
1025
+
1026
+ ### 5.12.2 Impact Items and Blast Radius
1027
+
1028
+ | ID | Target | Impact kind | Level | Confidence | Source reports | Current code evidence |
1029
+ |----|--------|-------------|-------|------------|----------------|-----------------------|
1030
+ {% for row in changeImpactAnalysis.impactItems -%}
1031
+ | {{ row.id | mdcell }} | {{ row.target | mdcell }} | {{ row.impactKind | mdcell }} | `{{ row.level | mdcell }}` | `{{ row.confidence | mdcell }}` | {{ row.sourceReportRefs | join(', ') | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1032
+ {% endfor %}
1033
+
1034
+ | Source impact ID | Affected target | Direction | Current code evidence |
1035
+ |------------------|-----------------|-----------|-----------------------|
1036
+ {% for row in changeImpactAnalysis.dependencyBlastRadius -%}
1037
+ | {{ row.sourceImpactId | mdcell }} | {{ row.affectedTarget | mdcell }} | {{ row.direction | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1038
+ {% endfor %}
1039
+
1040
+ ### 5.12.3 Test, Operational, and Planning Inputs
1041
+
1042
+ | Test scope | Action | Test paths | Current code evidence |
1043
+ |------------|--------|------------|-----------------------|
1044
+ {% for row in changeImpactAnalysis.testImpact -%}
1045
+ | {{ row.scope | mdcell }} | `{{ row.action | mdcell }}` | {{ row.testPaths | join(', ') | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1046
+ {% endfor %}
1047
+
1048
+ | Operational area | Impact | Current code evidence |
1049
+ |------------------|--------|-----------------------|
1050
+ {% for row in changeImpactAnalysis.operationalImpact -%}
1051
+ | {{ row.area | mdcell }} | {{ row.impact | mdcell }} | {% for evidence in row.currentCodeEvidence %}`{{ evidence.path | mdcell }}:{{ evidence.line | mdcell }}` — {{ evidence.claim | mdcell }}{% if not loop.last %}<br>{% endif %}{% endfor %} |
1052
+ {% endfor %}
1053
+
1054
+ | Planning constraint | Unknown |
1055
+ |---------------------|---------|
1056
+ {% for row in changeImpactAnalysis.planningInputs -%}
1057
+ | {{ row.constraint | mdcell }} | `{{ row.unknown | mdcell }}` |
1058
+ {% endfor %}
1059
+
840
1060
  {% endif %}
841
1061
  {# `improvement-discovery` is not in the data.json TaskType enum — its
842
1062
  `## 5.9 Improvement Candidates` report is authored directly by the report
@@ -60,6 +60,14 @@
60
60
  "readerSummary": "The shortest reader path: what was decided, what a human must do next, and which audit details can wait until a deeper review.",
61
61
  "endStateCoverage": "One row per end-state id the brief pinned, and how this phase accounted for it. A row that is not `addressed` must say why."
62
62
  },
63
+ "analysis": {
64
+ "basisIntro": "Immutable run snapshot, bounded scan scope, current-code facts, inferences, unknowns, and review disposition for this analysis.",
65
+ "projectIntro": "Project components, boundaries, entry points, dependencies, stores, external systems, and the shallow feature index.",
66
+ "featureIntro": "The confirmed feature target's flows, rules, state changes, external interactions, and test coverage.",
67
+ "changeImpactIntro": "The proposed change's preserved behavior, impacted targets, dependency propagation, test and operational effects, and planning constraints.",
68
+ "empty": "- No rows recorded.",
69
+ "reviewNotRecorded": "not reviewed"
70
+ },
63
71
  "tokenSummary": {
64
72
  "heading": "Token Usage Summary",
65
73
  "tableHeaderItem": "Item",
@@ -60,6 +60,14 @@
60
60
  "readerSummary": "가장 짧은 읽기 경로입니다. 무엇이 결정됐는지, 사람이 다음에 무엇을 해야 하는지, 어떤 감사 세부사항은 나중에 봐도 되는지 먼저 보여줍니다.",
61
61
  "endStateCoverage": "브리프가 고정한 종료 상태 id 별로 이번 phase 가 어떻게 처리했는지 기록합니다. `addressed` 가 아닌 행은 사유가 있어야 합니다."
62
62
  },
63
+ "analysis": {
64
+ "basisIntro": "이 분석의 불변 실행 스냅샷, 제한된 스캔 범위, 현재 코드 사실, 추론, 미확인 항목, 검토 처리 결과입니다.",
65
+ "projectIntro": "프로젝트 구성요소, 경계, 진입점, 의존성, 저장소, 외부 시스템, 얕은 기능 인덱스입니다.",
66
+ "featureIntro": "확인된 기능 대상의 흐름, 규칙, 상태 변화, 외부 상호작용, 테스트 범위입니다.",
67
+ "changeImpactIntro": "제안 변경의 보존 동작, 영향 대상, 의존성 전파, 테스트·운영 영향, 계획 제약입니다.",
68
+ "empty": "- 기록된 행이 없습니다.",
69
+ "reviewNotRecorded": "검토되지 않음"
70
+ },
63
71
  "tokenSummary": {
64
72
  "heading": "토큰 사용량 요약",
65
73
  "tableHeaderItem": "항목",
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: OKSTRA Project Analysis Input - {{TASK_KEY}}
3
+ id: {{FM_ID}}
4
+ tags: {{FM_TAGS}}
5
+ status: ready-for-agent
6
+ aliases: {{FM_ALIASES}}
7
+ date: {{TASK_DATE}}
8
+ task-id: "{{TASK_ID}}"
9
+ task-group: "{{TASK_GROUP}}"
10
+ project-id: "{{PROJECT_ID}}"
11
+ taskType: "{{FM_TASK_TYPE}}"
12
+ ---
13
+
14
+ # OKSTRA Project Analysis Input
15
+
16
+ ## Identity
17
+
18
+ - Project ID:
19
+ - Task Group:
20
+ - Task ID:
21
+ - Related Tasks:
22
+ - Issue / Ticket:
23
+ - Task Type: `project-analysis`
24
+ - Requested Outcome:
25
+
26
+ ## User Intent
27
+
28
+ - What decision or understanding should this project map support?
29
+ - Who will use the analysis and for what next action?
30
+
31
+ ## Scan Scope
32
+
33
+ - Repositories and directories to inspect:
34
+ - Entry points or components to prioritise:
35
+ - External systems or integrations to include:
36
+ - Explicitly excluded repositories, directories, or systems:
37
+
38
+ ## Preserved Boundaries
39
+
40
+ - Behavior or contracts that must be treated as unchanged:
41
+ - Ownership, security, compatibility, or operational boundaries:
42
+ - Unknown boundaries that require clarification:
43
+
44
+ ## Questions for Analysers
45
+
46
+ 1. Which components, dependency directions, entry points, repositories, and external integrations are in scope?
47
+ 2. What shallow feature index best helps a later task navigate this area?
48
+ 3. Which scan boundaries prevent the analysis from becoming a detailed feature or implementation plan?
49
+
50
+ ## Input Limit
51
+
52
+ - Collect user intent, scan scope, and preserved boundaries only.
53
+ - Do not add worker-produced result tables to this input.
54
+ - The final report `data.json` is the result source of truth.
55
+
56
+ ## Conversion Note
57
+
58
+ - This input can be used as a starting draft before creating `okstra-task-brief.md`.
@@ -8,7 +8,8 @@
8
8
  * data-response-id> (everything else / fallback).
9
9
  * 2. Serialise the entries into markdown whose bytes are IDENTICAL
10
10
  * to scripts/okstra_ctl/report_views.py serialize_user_response.
11
- * 3. Write the result to <pre id="user-response-output">, offer a
11
+ * 3. Collect the analysis-only Accept / Request revision / Reject control.
12
+ * 4. Write the result to <pre id="user-response-output">, offer a
12
13
  * [Copy] button, and download it as
13
14
  * user-response-<task-type>-<seq>.md — the user drops that file in
14
15
  * runs/<task-type>/user-responses/ and --resume-clarification
@@ -105,6 +106,47 @@
105
106
  return { approved: true, implementationOption: option };
106
107
  }
107
108
 
109
+ function validateAnalysisReview(review) {
110
+ if (!review) return null;
111
+ var allowed = ["accepted", "revision-requested", "rejected"];
112
+ if (allowed.indexOf(review.status) < 0) {
113
+ throw new Error("ANALYSIS REVIEW Status is invalid");
114
+ }
115
+ if (
116
+ (review.status === "revision-requested" || review.status === "rejected") &&
117
+ ((!review.affectedIds || review.affectedIds.length === 0) || !trimMultiline(review.reason))
118
+ ) {
119
+ throw new Error(
120
+ "ANALYSIS REVIEW " + review.status + " requires Affected-IDs and Reason"
121
+ );
122
+ }
123
+ return review;
124
+ }
125
+
126
+ function collectAnalysisReview() {
127
+ var selected = document.querySelector(
128
+ 'input[name="analysis-review-status"]:checked'
129
+ );
130
+ if (!selected) return null;
131
+ var affected = document.getElementById("analysis-review-affected-ids");
132
+ var affectedIds = [];
133
+ if (affected) {
134
+ for (var i = 0; i < affected.options.length; i++) {
135
+ if (affected.options[i].selected) affectedIds.push(affected.options[i].value);
136
+ }
137
+ }
138
+ var reason = document.getElementById("analysis-review-reason");
139
+ var evidence = document.getElementById("analysis-review-evidence");
140
+ var scope = document.getElementById("analysis-review-scope-change");
141
+ return validateAnalysisReview({
142
+ status: selected.value,
143
+ affectedIds: affectedIds,
144
+ reason: reason ? trimMultiline(reason.value) : "",
145
+ additionalEvidence: evidence ? trimMultiline(evidence.value) : "",
146
+ requestedScopeChange: scope ? trimMultiline(scope.value) : "",
147
+ });
148
+ }
149
+
108
150
  // Toggle the visibility of the "기타" companion input next to each
109
151
  // select whose current value is "__other__". Wired at bind() time and
110
152
  // also called once for the initial state.
@@ -124,7 +166,27 @@
124
166
  }
125
167
  }
126
168
 
127
- function buildUserResponseMarkdown(runMeta, entries, createdAt, approval) {
169
+ function quotedReviewField(label, value) {
170
+ var cleaned = trimMultiline(value);
171
+ if (!cleaned) return "- " + label + ":\n";
172
+ return "- " + label + ":\n" + cleaned.split("\n").map(function (line) {
173
+ return " > " + line + "\n";
174
+ }).join("");
175
+ }
176
+
177
+ function serialiseAnalysisReview(review) {
178
+ review = validateAnalysisReview(review);
179
+ return (
180
+ "\n## ANALYSIS REVIEW\n" +
181
+ "- Status: " + review.status + "\n" +
182
+ "- Affected-IDs: " + (review.affectedIds || []).join(", ") + "\n" +
183
+ quotedReviewField("Reason", review.reason) +
184
+ quotedReviewField("Additional-Evidence", review.additionalEvidence) +
185
+ quotedReviewField("Requested-Scope-Change", review.requestedScopeChange)
186
+ );
187
+ }
188
+
189
+ function buildUserResponseMarkdown(runMeta, entries, createdAt, approval, analysisReview) {
128
190
  var head =
129
191
  "---\n" +
130
192
  "task-key: " + (runMeta["task-key"] || "") + "\n" +
@@ -139,6 +201,7 @@
139
201
 
140
202
  entries = entries || [];
141
203
  var hasApproval = !!(approval && approval.approved);
204
+ var hasAnalysisReview = !!analysisReview;
142
205
  var chunks = "";
143
206
  for (var i = 0; i < entries.length; i++) {
144
207
  var e = entries[i];
@@ -155,7 +218,7 @@
155
218
  }
156
219
  chunks += chunk;
157
220
  }
158
- if (entries.length === 0 && !hasApproval) {
221
+ if (entries.length === 0 && !hasApproval && !hasAnalysisReview) {
159
222
  chunks += "\n_(No user responses recorded.)_\n";
160
223
  }
161
224
  if (hasApproval) {
@@ -165,6 +228,9 @@
165
228
  "- Implementation-Option: " + trimMultiline(approval.implementationOption) + "\n";
166
229
  }
167
230
  }
231
+ if (hasAnalysisReview) {
232
+ chunks += serialiseAnalysisReview(analysisReview);
233
+ }
168
234
  return head + chunks;
169
235
  }
170
236
 
@@ -187,13 +253,25 @@
187
253
  var runMeta = readRunMeta();
188
254
  var entries = collectEntries();
189
255
  var approval = collectApproval();
190
- var md = buildUserResponseMarkdown(runMeta, entries, isoNowUtc(), approval);
191
256
  var out = document.getElementById("user-response-output");
257
+ var analysisReview;
258
+ try {
259
+ analysisReview = collectAnalysisReview();
260
+ } catch (e) {
261
+ if (out) out.textContent = e.message || String(e);
262
+ showDismiss(true);
263
+ return "";
264
+ }
265
+ var md = buildUserResponseMarkdown(
266
+ runMeta, entries, isoNowUtc(), approval, analysisReview
267
+ );
192
268
  if (out) out.textContent = md;
193
269
  showDismiss(true);
194
270
  // Nothing answered yet — show the empty serialisation as feedback but
195
271
  // don't download a useless file.
196
- if (entries.length > 0 || approval) downloadUserResponse(md, runMeta);
272
+ if (entries.length > 0 || approval || analysisReview) {
273
+ downloadUserResponse(md, runMeta);
274
+ }
197
275
  return md;
198
276
  }
199
277
 
@@ -312,6 +390,7 @@
312
390
  buildUserResponseMarkdown: buildUserResponseMarkdown,
313
391
  collectEntries: collectEntries,
314
392
  collectApproval: collectApproval,
393
+ collectAnalysisReview: collectAnalysisReview,
315
394
  exportUserResponse: exportUserResponse,
316
395
  setReaderMode: setReaderMode,
317
396
  };
@@ -9,7 +9,7 @@ This file defines the standard format of the markdown produced by the **Export u
9
9
 
10
10
  ```yaml
11
11
  task-key: <task-group>/<task-id>
12
- task-type: <requirements-discovery | error-analysis | implementation-planning | implementation | final-verification | release-handoff>
12
+ task-type: <requirements-discovery | error-analysis | implementation-planning | implementation | final-verification | release-handoff | project-analysis | feature-analysis | change-impact-analysis>
13
13
  seq: <3-digit zero-padded run sequence>
14
14
  source-report: <project-relative path to the final-report .md the HTML was derived from>
15
15
  created-by: user
@@ -78,6 +78,24 @@ When you check approval in the Plan Approval widget and Export, the following bl
78
78
  - The parser is `parse_user_response_approval` in `scripts/okstra_ctl/user_response.py`, and it accepts only the lowercase `Approved: true` that is byte-identical to the producer output (hand-edited values such as `TRUE` are rejected fail-closed).
79
79
  - `--resume-clarification` attaches the sidecar verbatim, so the APPROVAL block is carried along as-is — it is harmless residual information in a planning re-run input.
80
80
 
81
+ ## ANALYSIS REVIEW block (analysis reports only)
82
+
83
+ The Analysis Review control appends one decision block. `revision-requested` and `rejected` require at least one affected structured analysis ID and a reason. Optional empty values keep their field lines but do not emit an empty quote line.
84
+
85
+ ```markdown
86
+ ## ANALYSIS REVIEW
87
+ - Status: <accepted | revision-requested | rejected>
88
+ - Affected-IDs: <comma-separated structured analysis IDs>
89
+ - Reason:
90
+ > <one quoted line per input line>
91
+ - Additional-Evidence:
92
+ > <optional evidence>
93
+ - Requested-Scope-Change:
94
+ > <optional scope change>
95
+ ```
96
+
97
+ The block is the review decision's sole storage location. Export never changes the source final-report markdown. A later analysis rerun receives the sidecar through `--clarification-response` and records one `analysisReviewResolution` row for each imported affected ID.
98
+
81
99
  ## Compatibility Rules
82
100
 
83
101
  - If `Kind` has an unknown value, the form renders with a `<textarea>` fallback, and the received `Kind` string is preserved as-is during serialization.