cadence-skill-installer 0.2.45 → 0.2.47
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/package.json +1 -1
- package/skill/SKILL.md +7 -6
- package/skill/agents/openai.yaml +4 -2
- package/skill/assets/cadence.json +37 -2
- package/skill/config/commit-conventions.json +6 -0
- package/skill/scripts/ideation_research.py +198 -0
- package/skill/scripts/run-planner.py +32 -0
- package/skill/scripts/run-project-overview.py +436 -0
- package/skill/scripts/run-research-pass.py +279 -6
- package/skill/scripts/scaffold-project.sh +37 -2
- package/skill/skills/planner/SKILL.md +27 -8
- package/skill/skills/planner/agents/openai.yaml +2 -0
- package/skill/skills/project-overview/SKILL.md +48 -0
- package/skill/skills/project-overview/agents/openai.yaml +15 -0
- package/skill/skills/project-progress/SKILL.md +3 -4
- package/skill/skills/project-progress/agents/openai.yaml +3 -2
- package/skill/skills/researcher/SKILL.md +48 -80
- package/skill/skills/researcher/agents/openai.yaml +8 -6
- package/skill/tests/test_ideation_research.py +12 -0
- package/skill/tests/test_run_planner.py +3 -0
- package/skill/tests/test_run_project_overview.py +172 -0
- package/skill/tests/test_run_research_pass.py +152 -0
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -94,10 +94,11 @@ description: Structured project operating system for end-to-end greenfield or br
|
|
|
94
94
|
|
|
95
95
|
## Manual Subskill Safety Gate
|
|
96
96
|
1. If the user manually requests a Cadence subskill, first resolve `PROJECT_ROOT` with `python3 scripts/resolve-project-root.py --project-root "$PWD"`.
|
|
97
|
-
2.
|
|
98
|
-
3.
|
|
99
|
-
4.
|
|
100
|
-
5.
|
|
97
|
+
2. If the requested subskill is the read-only utility `project-overview`, skip route assertion and run it directly.
|
|
98
|
+
3. For all other subskills, run `python3 scripts/assert-workflow-route.py --skill-name <subskill> --project-root "$PROJECT_ROOT"` before executing that subskill.
|
|
99
|
+
4. Ensure that direct subskill execution still applies this skill's Repo Status Gate and Git Checkpoints rules.
|
|
100
|
+
5. If route assertion fails, stop and surface the exact script error.
|
|
101
|
+
6. Do not execute state-changing subskill steps when assertion fails.
|
|
101
102
|
|
|
102
103
|
## Ideation Flow
|
|
103
104
|
1. When scaffold, prerequisite, and project mode intake complete in this same conversation for a net-new project and route advances to `ideator`, force a subskill handoff and end with this exact line: `Start a new chat and either say "help me define my project" or share your project brief.`
|
|
@@ -114,8 +115,8 @@ description: Structured project operating system for end-to-end greenfield or br
|
|
|
114
115
|
|
|
115
116
|
## Research Flow
|
|
116
117
|
1. If the workflow route is `researcher`, invoke `skills/researcher/SKILL.md`.
|
|
117
|
-
2.
|
|
118
|
-
3.
|
|
118
|
+
2. Run bounded multi-pass research in the same conversation while `handoff_required=false`.
|
|
119
|
+
3. Treat `handoff_required=true` from `run-research-pass.py complete` as the context-boundary stop signal (triggered by token-budget estimate or per-chat pass cap); then end with this exact line: `Start a new chat and say "continue research".`
|
|
119
120
|
4. Continue routing to researcher on subsequent chats until workflow reports the research task complete.
|
|
120
121
|
5. For greenfield projects, when research completes and route advances to `planner`, invoke `skills/planner/SKILL.md` in the next routed conversation.
|
|
121
122
|
|
package/skill/agents/openai.yaml
CHANGED
|
@@ -11,10 +11,12 @@ interface:
|
|
|
11
11
|
project mode intake (brownfield-intake) in one continuous run and do not stop between these gates unless a user
|
|
12
12
|
decision is required. In user-facing replies before mode resolution, refer to this step as project mode intake.
|
|
13
13
|
For manual subskill requests, assert route first with
|
|
14
|
-
scripts/assert-workflow-route.py --skill-name <subskill> --project-root "$PROJECT_ROOT"
|
|
14
|
+
scripts/assert-workflow-route.py --skill-name <subskill> --project-root "$PROJECT_ROOT",
|
|
15
|
+
except for read-only project-overview utility requests.
|
|
15
16
|
Never read or edit .cadence/cadence.json directly (including cat/rg/jq/Read); use Cadence scripts for all state reads and writes. Keep replies concise, do not expose internal traces unless asked,
|
|
16
17
|
and do not announce successful internal checks.
|
|
17
18
|
For each successful subskill conversation, run scripts/finalize-skill-checkpoint.py from PROJECT_ROOT with
|
|
18
19
|
that subskill's --scope/--checkpoint and --paths ., allow status=no_changes, and treat checkpoint/push
|
|
19
20
|
failures as blocking.
|
|
20
|
-
Use the exact handoff lines in SKILL.md for ideator, brownfield-documenter,
|
|
21
|
+
Use the exact handoff lines in SKILL.md for ideator, brownfield-documenter, and planner transitions.
|
|
22
|
+
For researcher, use the exact handoff line only when run-research-pass output indicates a context-boundary handoff is required.
|
|
@@ -127,23 +127,58 @@
|
|
|
127
127
|
"planning": {
|
|
128
128
|
"target_effort_per_pass": 12,
|
|
129
129
|
"max_topics_per_pass": 4,
|
|
130
|
+
"max_passes_per_topic": 3,
|
|
131
|
+
"max_total_passes": 120,
|
|
132
|
+
"max_passes_per_chat": 6,
|
|
133
|
+
"context_window_tokens": 128000,
|
|
134
|
+
"handoff_context_threshold_percent": 70,
|
|
135
|
+
"estimated_fixed_tokens_per_chat": 12000,
|
|
136
|
+
"estimated_tokens_in_overhead_per_pass": 1200,
|
|
137
|
+
"estimated_tokens_out_overhead_per_pass": 400,
|
|
130
138
|
"latest_round": 0
|
|
131
139
|
},
|
|
132
140
|
"summary": {
|
|
133
141
|
"topic_total": 0,
|
|
134
142
|
"topic_complete": 0,
|
|
143
|
+
"topic_caveated": 0,
|
|
135
144
|
"topic_needs_followup": 0,
|
|
136
145
|
"topic_pending": 0,
|
|
137
146
|
"pass_pending": 0,
|
|
138
147
|
"pass_complete": 0,
|
|
139
|
-
"next_pass_id": ""
|
|
148
|
+
"next_pass_id": "",
|
|
149
|
+
"context_budget_tokens": 128000,
|
|
150
|
+
"context_threshold_tokens": 89600,
|
|
151
|
+
"context_threshold_percent": 70,
|
|
152
|
+
"context_tokens_in": 0,
|
|
153
|
+
"context_tokens_out": 0,
|
|
154
|
+
"context_tokens_total": 12000,
|
|
155
|
+
"context_percent_estimate": 9.38,
|
|
156
|
+
"context_passes_completed": 0
|
|
140
157
|
},
|
|
141
158
|
"topic_status": {},
|
|
142
159
|
"pass_queue": [],
|
|
143
160
|
"pass_history": [],
|
|
144
161
|
"source_registry": [],
|
|
162
|
+
"chat_context": {
|
|
163
|
+
"session_index": 0,
|
|
164
|
+
"passes_completed": 0,
|
|
165
|
+
"estimated_tokens_fixed": 12000,
|
|
166
|
+
"estimated_tokens_in": 0,
|
|
167
|
+
"estimated_tokens_out": 0,
|
|
168
|
+
"estimated_tokens_total": 12000,
|
|
169
|
+
"estimated_context_percent": 9.38,
|
|
170
|
+
"budget_tokens": 128000,
|
|
171
|
+
"threshold_tokens": 89600,
|
|
172
|
+
"threshold_percent": 70,
|
|
173
|
+
"last_reset_at": "",
|
|
174
|
+
"last_updated_at": "",
|
|
175
|
+
"last_pass_id": "",
|
|
176
|
+
"last_pass_tokens_in": 0,
|
|
177
|
+
"last_pass_tokens_out": 0
|
|
178
|
+
},
|
|
145
179
|
"handoff_required": false,
|
|
146
|
-
"handoff_message": "Start a new chat and say \"continue research\"."
|
|
180
|
+
"handoff_message": "Start a new chat and say \"continue research\".",
|
|
181
|
+
"handoff_reason": ""
|
|
147
182
|
}
|
|
148
183
|
},
|
|
149
184
|
"planning": {
|
|
@@ -128,6 +128,12 @@
|
|
|
128
128
|
"progress-checked": "check progress and route next action"
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
|
+
"project-overview": {
|
|
132
|
+
"description": "Project overview reporting checks",
|
|
133
|
+
"checkpoints": {
|
|
134
|
+
"overview-reviewed": "read full roadmap and report current project position"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
131
137
|
"researcher": {
|
|
132
138
|
"description": "Ideation research pass execution",
|
|
133
139
|
"checkpoints": {
|
|
@@ -93,6 +93,12 @@ def default_research_execution() -> dict[str, Any]:
|
|
|
93
93
|
"max_topics_per_pass": 4,
|
|
94
94
|
"max_passes_per_topic": 3,
|
|
95
95
|
"max_total_passes": 120,
|
|
96
|
+
"max_passes_per_chat": 6,
|
|
97
|
+
"context_window_tokens": 128000,
|
|
98
|
+
"handoff_context_threshold_percent": 70,
|
|
99
|
+
"estimated_fixed_tokens_per_chat": 12000,
|
|
100
|
+
"estimated_tokens_in_overhead_per_pass": 1200,
|
|
101
|
+
"estimated_tokens_out_overhead_per_pass": 400,
|
|
96
102
|
"latest_round": 0,
|
|
97
103
|
},
|
|
98
104
|
"summary": {
|
|
@@ -104,13 +110,39 @@ def default_research_execution() -> dict[str, Any]:
|
|
|
104
110
|
"pass_pending": 0,
|
|
105
111
|
"pass_complete": 0,
|
|
106
112
|
"next_pass_id": "",
|
|
113
|
+
"context_budget_tokens": 128000,
|
|
114
|
+
"context_threshold_tokens": 89600,
|
|
115
|
+
"context_threshold_percent": 70,
|
|
116
|
+
"context_tokens_in": 0,
|
|
117
|
+
"context_tokens_out": 0,
|
|
118
|
+
"context_tokens_total": 12000,
|
|
119
|
+
"context_percent_estimate": 9.38,
|
|
120
|
+
"context_passes_completed": 0,
|
|
107
121
|
},
|
|
108
122
|
"topic_status": {},
|
|
109
123
|
"pass_queue": [],
|
|
110
124
|
"pass_history": [],
|
|
111
125
|
"source_registry": [],
|
|
126
|
+
"chat_context": {
|
|
127
|
+
"session_index": 0,
|
|
128
|
+
"passes_completed": 0,
|
|
129
|
+
"estimated_tokens_fixed": 12000,
|
|
130
|
+
"estimated_tokens_in": 0,
|
|
131
|
+
"estimated_tokens_out": 0,
|
|
132
|
+
"estimated_tokens_total": 12000,
|
|
133
|
+
"estimated_context_percent": 9.38,
|
|
134
|
+
"budget_tokens": 128000,
|
|
135
|
+
"threshold_tokens": 89600,
|
|
136
|
+
"threshold_percent": 70,
|
|
137
|
+
"last_reset_at": "",
|
|
138
|
+
"last_updated_at": "",
|
|
139
|
+
"last_pass_id": "",
|
|
140
|
+
"last_pass_tokens_in": 0,
|
|
141
|
+
"last_pass_tokens_out": 0,
|
|
142
|
+
},
|
|
112
143
|
"handoff_required": False,
|
|
113
144
|
"handoff_message": DEFAULT_RESEARCH_HANDOFF_MESSAGE,
|
|
145
|
+
"handoff_reason": "",
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
|
|
@@ -205,6 +237,50 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
205
237
|
if max_total_passes < 1:
|
|
206
238
|
max_total_passes = 1
|
|
207
239
|
|
|
240
|
+
try:
|
|
241
|
+
max_passes_per_chat = int(planning.get("max_passes_per_chat", 6))
|
|
242
|
+
except (TypeError, ValueError):
|
|
243
|
+
max_passes_per_chat = 6
|
|
244
|
+
if max_passes_per_chat < 1:
|
|
245
|
+
max_passes_per_chat = 1
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
context_window_tokens = int(planning.get("context_window_tokens", 128000))
|
|
249
|
+
except (TypeError, ValueError):
|
|
250
|
+
context_window_tokens = 128000
|
|
251
|
+
if context_window_tokens < 1000:
|
|
252
|
+
context_window_tokens = 1000
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
handoff_context_threshold_percent = int(planning.get("handoff_context_threshold_percent", 70))
|
|
256
|
+
except (TypeError, ValueError):
|
|
257
|
+
handoff_context_threshold_percent = 70
|
|
258
|
+
if handoff_context_threshold_percent < 1:
|
|
259
|
+
handoff_context_threshold_percent = 1
|
|
260
|
+
if handoff_context_threshold_percent > 95:
|
|
261
|
+
handoff_context_threshold_percent = 95
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
estimated_fixed_tokens_per_chat = int(planning.get("estimated_fixed_tokens_per_chat", 12000))
|
|
265
|
+
except (TypeError, ValueError):
|
|
266
|
+
estimated_fixed_tokens_per_chat = 12000
|
|
267
|
+
if estimated_fixed_tokens_per_chat < 0:
|
|
268
|
+
estimated_fixed_tokens_per_chat = 0
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
estimated_tokens_in_overhead_per_pass = int(planning.get("estimated_tokens_in_overhead_per_pass", 1200))
|
|
272
|
+
except (TypeError, ValueError):
|
|
273
|
+
estimated_tokens_in_overhead_per_pass = 1200
|
|
274
|
+
if estimated_tokens_in_overhead_per_pass < 0:
|
|
275
|
+
estimated_tokens_in_overhead_per_pass = 0
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
estimated_tokens_out_overhead_per_pass = int(planning.get("estimated_tokens_out_overhead_per_pass", 400))
|
|
279
|
+
except (TypeError, ValueError):
|
|
280
|
+
estimated_tokens_out_overhead_per_pass = 400
|
|
281
|
+
if estimated_tokens_out_overhead_per_pass < 0:
|
|
282
|
+
estimated_tokens_out_overhead_per_pass = 0
|
|
283
|
+
|
|
208
284
|
try:
|
|
209
285
|
latest_round = int(planning.get("latest_round", 0))
|
|
210
286
|
except (TypeError, ValueError):
|
|
@@ -217,6 +293,12 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
217
293
|
"max_topics_per_pass": max_topics,
|
|
218
294
|
"max_passes_per_topic": max_passes_per_topic,
|
|
219
295
|
"max_total_passes": max_total_passes,
|
|
296
|
+
"max_passes_per_chat": max_passes_per_chat,
|
|
297
|
+
"context_window_tokens": context_window_tokens,
|
|
298
|
+
"handoff_context_threshold_percent": handoff_context_threshold_percent,
|
|
299
|
+
"estimated_fixed_tokens_per_chat": estimated_fixed_tokens_per_chat,
|
|
300
|
+
"estimated_tokens_in_overhead_per_pass": estimated_tokens_in_overhead_per_pass,
|
|
301
|
+
"estimated_tokens_out_overhead_per_pass": estimated_tokens_out_overhead_per_pass,
|
|
220
302
|
"latest_round": latest_round,
|
|
221
303
|
}
|
|
222
304
|
|
|
@@ -277,6 +359,13 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
277
359
|
if planned_effort < 0:
|
|
278
360
|
planned_effort = 0
|
|
279
361
|
|
|
362
|
+
try:
|
|
363
|
+
estimated_tokens_in = int(entry.get("estimated_tokens_in", 0) or 0)
|
|
364
|
+
except (TypeError, ValueError):
|
|
365
|
+
estimated_tokens_in = 0
|
|
366
|
+
if estimated_tokens_in < 0:
|
|
367
|
+
estimated_tokens_in = 0
|
|
368
|
+
|
|
280
369
|
pass_queue.append(
|
|
281
370
|
{
|
|
282
371
|
"pass_id": pass_id,
|
|
@@ -286,6 +375,7 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
286
375
|
"planned_effort": planned_effort,
|
|
287
376
|
"created_at": _string(entry.get("created_at")),
|
|
288
377
|
"started_at": _string(entry.get("started_at")),
|
|
378
|
+
"estimated_tokens_in": estimated_tokens_in,
|
|
289
379
|
}
|
|
290
380
|
)
|
|
291
381
|
|
|
@@ -330,6 +420,88 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
330
420
|
normalized["pass_history"] = pass_history
|
|
331
421
|
normalized["source_registry"] = source_registry
|
|
332
422
|
|
|
423
|
+
chat_context_raw = raw_execution.get("chat_context")
|
|
424
|
+
chat_context_raw = dict(chat_context_raw) if isinstance(chat_context_raw, dict) else {}
|
|
425
|
+
budget_tokens = context_window_tokens
|
|
426
|
+
threshold_tokens = max(1, int((budget_tokens * handoff_context_threshold_percent) / 100.0))
|
|
427
|
+
|
|
428
|
+
try:
|
|
429
|
+
session_index = int(chat_context_raw.get("session_index", 0))
|
|
430
|
+
except (TypeError, ValueError):
|
|
431
|
+
session_index = 0
|
|
432
|
+
if session_index < 0:
|
|
433
|
+
session_index = 0
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
passes_completed = int(chat_context_raw.get("passes_completed", 0))
|
|
437
|
+
except (TypeError, ValueError):
|
|
438
|
+
passes_completed = 0
|
|
439
|
+
if passes_completed < 0:
|
|
440
|
+
passes_completed = 0
|
|
441
|
+
|
|
442
|
+
try:
|
|
443
|
+
estimated_tokens_fixed = int(
|
|
444
|
+
chat_context_raw.get("estimated_tokens_fixed", estimated_fixed_tokens_per_chat)
|
|
445
|
+
)
|
|
446
|
+
except (TypeError, ValueError):
|
|
447
|
+
estimated_tokens_fixed = estimated_fixed_tokens_per_chat
|
|
448
|
+
if estimated_tokens_fixed < 0:
|
|
449
|
+
estimated_tokens_fixed = 0
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
estimated_tokens_in = int(chat_context_raw.get("estimated_tokens_in", 0))
|
|
453
|
+
except (TypeError, ValueError):
|
|
454
|
+
estimated_tokens_in = 0
|
|
455
|
+
if estimated_tokens_in < 0:
|
|
456
|
+
estimated_tokens_in = 0
|
|
457
|
+
|
|
458
|
+
try:
|
|
459
|
+
estimated_tokens_out = int(chat_context_raw.get("estimated_tokens_out", 0))
|
|
460
|
+
except (TypeError, ValueError):
|
|
461
|
+
estimated_tokens_out = 0
|
|
462
|
+
if estimated_tokens_out < 0:
|
|
463
|
+
estimated_tokens_out = 0
|
|
464
|
+
|
|
465
|
+
estimated_tokens_total = estimated_tokens_fixed + estimated_tokens_in + estimated_tokens_out
|
|
466
|
+
estimated_context_percent = round((estimated_tokens_total / float(budget_tokens)) * 100.0, 2)
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
last_pass_tokens_in = int(chat_context_raw.get("last_pass_tokens_in", 0))
|
|
470
|
+
except (TypeError, ValueError):
|
|
471
|
+
last_pass_tokens_in = 0
|
|
472
|
+
if last_pass_tokens_in < 0:
|
|
473
|
+
last_pass_tokens_in = 0
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
last_pass_tokens_out = int(chat_context_raw.get("last_pass_tokens_out", 0))
|
|
477
|
+
except (TypeError, ValueError):
|
|
478
|
+
last_pass_tokens_out = 0
|
|
479
|
+
if last_pass_tokens_out < 0:
|
|
480
|
+
last_pass_tokens_out = 0
|
|
481
|
+
|
|
482
|
+
normalized["chat_context"] = {
|
|
483
|
+
"session_index": session_index,
|
|
484
|
+
"passes_completed": passes_completed,
|
|
485
|
+
"estimated_tokens_fixed": estimated_tokens_fixed,
|
|
486
|
+
"estimated_tokens_in": estimated_tokens_in,
|
|
487
|
+
"estimated_tokens_out": estimated_tokens_out,
|
|
488
|
+
"estimated_tokens_total": estimated_tokens_total,
|
|
489
|
+
"estimated_context_percent": estimated_context_percent,
|
|
490
|
+
"budget_tokens": budget_tokens,
|
|
491
|
+
"threshold_tokens": threshold_tokens,
|
|
492
|
+
"threshold_percent": handoff_context_threshold_percent,
|
|
493
|
+
"last_reset_at": _string(chat_context_raw.get("last_reset_at")),
|
|
494
|
+
"last_updated_at": _string(chat_context_raw.get("last_updated_at")),
|
|
495
|
+
"last_pass_id": _string(chat_context_raw.get("last_pass_id")),
|
|
496
|
+
"last_pass_tokens_in": last_pass_tokens_in,
|
|
497
|
+
"last_pass_tokens_out": last_pass_tokens_out,
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
handoff_reason = _string(raw_execution.get("handoff_reason")).lower()
|
|
501
|
+
if handoff_reason not in {"", "context_budget", "pass_cap"}:
|
|
502
|
+
handoff_reason = ""
|
|
503
|
+
normalized["handoff_reason"] = handoff_reason
|
|
504
|
+
|
|
333
505
|
total_topics = len(topic_status)
|
|
334
506
|
topic_complete = len(
|
|
335
507
|
[entry for entry in topic_status.values() if entry.get("status") in RESEARCH_TOPIC_COMPLETE_STATUSES]
|
|
@@ -366,6 +538,14 @@ def _normalize_research_execution(agenda: dict[str, Any], raw_execution: Any) ->
|
|
|
366
538
|
"pass_pending": len(pass_queue),
|
|
367
539
|
"pass_complete": len(pass_history),
|
|
368
540
|
"next_pass_id": next_pass_id,
|
|
541
|
+
"context_budget_tokens": budget_tokens,
|
|
542
|
+
"context_threshold_tokens": threshold_tokens,
|
|
543
|
+
"context_threshold_percent": handoff_context_threshold_percent,
|
|
544
|
+
"context_tokens_in": estimated_tokens_in,
|
|
545
|
+
"context_tokens_out": estimated_tokens_out,
|
|
546
|
+
"context_tokens_total": estimated_tokens_total,
|
|
547
|
+
"context_percent_estimate": estimated_context_percent,
|
|
548
|
+
"context_passes_completed": passes_completed,
|
|
369
549
|
}
|
|
370
550
|
return normalized
|
|
371
551
|
|
|
@@ -441,6 +621,24 @@ def reset_research_execution(ideation: Any) -> dict[str, Any]:
|
|
|
441
621
|
"pass_pending": 0,
|
|
442
622
|
"pass_complete": 0,
|
|
443
623
|
"next_pass_id": "",
|
|
624
|
+
"context_budget_tokens": execution["planning"]["context_window_tokens"],
|
|
625
|
+
"context_threshold_tokens": int(
|
|
626
|
+
(execution["planning"]["context_window_tokens"] * execution["planning"]["handoff_context_threshold_percent"])
|
|
627
|
+
/ 100.0
|
|
628
|
+
),
|
|
629
|
+
"context_threshold_percent": execution["planning"]["handoff_context_threshold_percent"],
|
|
630
|
+
"context_tokens_in": 0,
|
|
631
|
+
"context_tokens_out": 0,
|
|
632
|
+
"context_tokens_total": execution["planning"]["estimated_fixed_tokens_per_chat"],
|
|
633
|
+
"context_percent_estimate": round(
|
|
634
|
+
(
|
|
635
|
+
execution["planning"]["estimated_fixed_tokens_per_chat"]
|
|
636
|
+
/ float(max(1, execution["planning"]["context_window_tokens"]))
|
|
637
|
+
)
|
|
638
|
+
* 100.0,
|
|
639
|
+
2,
|
|
640
|
+
),
|
|
641
|
+
"context_passes_completed": 0,
|
|
444
642
|
}
|
|
445
643
|
|
|
446
644
|
normalized["research_execution"] = execution
|
|
@@ -201,6 +201,36 @@ def planning_contract() -> dict[str, Any]:
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
|
|
204
|
+
def roadmap_outline(milestones_raw: Any) -> list[dict[str, Any]]:
|
|
205
|
+
milestones = milestones_raw if isinstance(milestones_raw, list) else []
|
|
206
|
+
outline: list[dict[str, Any]] = []
|
|
207
|
+
for milestone in milestones:
|
|
208
|
+
if not isinstance(milestone, dict):
|
|
209
|
+
continue
|
|
210
|
+
milestone_id = _coerce_text(milestone.get("milestone_id"))
|
|
211
|
+
title = _coerce_text(milestone.get("title")) or milestone_id or "Untitled milestone"
|
|
212
|
+
raw_phases = milestone.get("phases")
|
|
213
|
+
phases = raw_phases if isinstance(raw_phases, list) else []
|
|
214
|
+
phase_titles: list[str] = []
|
|
215
|
+
for phase in phases:
|
|
216
|
+
if not isinstance(phase, dict):
|
|
217
|
+
continue
|
|
218
|
+
phase_id = _coerce_text(phase.get("phase_id"))
|
|
219
|
+
phase_title = _coerce_text(phase.get("title")) or phase_id
|
|
220
|
+
if phase_title:
|
|
221
|
+
phase_titles.append(phase_title)
|
|
222
|
+
|
|
223
|
+
outline.append(
|
|
224
|
+
{
|
|
225
|
+
"milestone_id": milestone_id,
|
|
226
|
+
"title": title,
|
|
227
|
+
"phase_count": len(phases),
|
|
228
|
+
"phase_titles": phase_titles,
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
return outline
|
|
232
|
+
|
|
233
|
+
|
|
204
234
|
def summarize_context(data: dict[str, Any]) -> dict[str, Any]:
|
|
205
235
|
ideation = data.get("ideation")
|
|
206
236
|
ideation = ideation if isinstance(ideation, dict) else {}
|
|
@@ -284,6 +314,7 @@ def summarize_context(data: dict[str, Any]) -> dict[str, Any]:
|
|
|
284
314
|
"detail_level": _coerce_text(planning.get("detail_level")),
|
|
285
315
|
"milestone_count": len(milestones),
|
|
286
316
|
"updated_at": _coerce_text(planning.get("updated_at")),
|
|
317
|
+
"milestone_outline": roadmap_outline(milestones),
|
|
287
318
|
},
|
|
288
319
|
"planner_payload_contract": planning_contract(),
|
|
289
320
|
}
|
|
@@ -532,6 +563,7 @@ def complete_flow(args: argparse.Namespace, project_root: Path, data: dict[str,
|
|
|
532
563
|
"milestone_count": len(milestones),
|
|
533
564
|
"phase_count": phase_count,
|
|
534
565
|
"decomposition_pending": bool(normalized.get("decomposition_pending", True)),
|
|
566
|
+
"milestone_outline": roadmap_outline(milestones),
|
|
535
567
|
},
|
|
536
568
|
"next_route": data.get("workflow", {}).get("next_route", {}),
|
|
537
569
|
}
|