okstra 0.121.1 → 0.123.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 +4 -2
- package/docs/architecture/storage-model.md +14 -0
- package/docs/architecture.md +38 -8
- package/docs/cli.md +45 -5
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +1 -1
- package/docs/for-ai/skills/{okstra-schedule.md → okstra-schedule-gen.md} +3 -3
- package/docs/project-structure-overview.md +4 -3
- package/docs/task-process/implementation-planning.md +33 -9
- package/docs/task-process/implementation.md +21 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/prompts/launch.template.md +9 -6
- package/runtime/prompts/lead/adapters/claude-code.md +104 -0
- package/runtime/prompts/lead/adapters/codex.md +45 -0
- package/runtime/prompts/lead/adapters/external.md +46 -0
- package/runtime/prompts/lead/context-loader.md +5 -5
- package/runtime/prompts/lead/convergence.md +11 -28
- package/runtime/prompts/lead/okstra-lead-contract.md +44 -94
- package/runtime/prompts/lead/plan-body-verification.md +50 -5
- package/runtime/prompts/lead/report-writer.md +59 -54
- package/runtime/prompts/lead/team-contract.md +33 -106
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_implementation-deliverable.md +3 -1
- package/runtime/prompts/profiles/_implementation-executor.md +6 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +2 -0
- package/runtime/prompts/profiles/implementation-planning.md +14 -4
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +44 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +23 -1
- package/runtime/python/okstra_ctl/design_prep.py +1462 -0
- package/runtime/python/okstra_ctl/design_surfaces.py +243 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +33 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +35 -0
- package/runtime/python/okstra_ctl/incremental_carry.py +294 -21
- package/runtime/python/okstra_ctl/incremental_scope.py +51 -5
- package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
- package/runtime/python/okstra_ctl/material.py +1 -1
- package/runtime/python/okstra_ctl/model_discovery.py +98 -0
- package/runtime/python/okstra_ctl/models.py +8 -3
- package/runtime/python/okstra_ctl/render.py +81 -194
- package/runtime/python/okstra_ctl/report_views.py +22 -7
- package/runtime/python/okstra_ctl/run.py +53 -5
- package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
- package/runtime/python/okstra_ctl/user_response.py +67 -2
- package/runtime/python/okstra_ctl/wizard.py +283 -3
- package/runtime/python/okstra_token_usage/report.py +11 -0
- package/runtime/schemas/final-report-v1.0.schema.json +339 -0
- package/runtime/skills/okstra-inspect/SKILL.md +2 -2
- package/runtime/skills/okstra-rollup/SKILL.md +1 -1
- package/runtime/skills/{okstra-schedule → okstra-schedule-gen}/SKILL.md +62 -20
- package/runtime/skills/okstra-user-response/SKILL.md +8 -6
- package/runtime/templates/reports/final-report.template.md +71 -4
- package/runtime/templates/reports/i18n/en.json +32 -1
- package/runtime/templates/reports/i18n/ko.json +32 -1
- package/runtime/validators/validate-run.py +515 -5
- package/runtime/validators/validate-schedule.py +11 -7
- package/runtime/validators/validate_session_conformance.py +58 -33
- package/src/cli-registry.mjs +14 -0
- package/src/commands/inspect/design-prep.mjs +23 -0
- package/src/commands/inspect/stage-map.mjs +100 -0
- package/src/commands/lifecycle/install.mjs +3 -0
- package/src/commands/lifecycle/uninstall.mjs +8 -23
- package/src/lib/skill-catalog.mjs +10 -1
|
@@ -408,6 +408,7 @@
|
|
|
408
408
|
"minItems": 1,
|
|
409
409
|
"items": { "$ref": "#/$defs/ImplementationPlanStage" }
|
|
410
410
|
},
|
|
411
|
+
"designPreparation": { "$ref": "#/$defs/DesignPreparation" },
|
|
411
412
|
"stepwiseExecution": {
|
|
412
413
|
"description": "Legacy flat summary kept for compatibility only. New reports use stageMap/stages.",
|
|
413
414
|
"type": "array",
|
|
@@ -1296,6 +1297,337 @@
|
|
|
1296
1297
|
}
|
|
1297
1298
|
},
|
|
1298
1299
|
|
|
1300
|
+
"DesignPreparation": {
|
|
1301
|
+
"type": "object",
|
|
1302
|
+
"required": ["mode", "reason", "items"],
|
|
1303
|
+
"additionalProperties": false,
|
|
1304
|
+
"properties": {
|
|
1305
|
+
"mode": { "enum": ["assessed", "no-design-inputs"] },
|
|
1306
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
1307
|
+
"items": {
|
|
1308
|
+
"type": "array",
|
|
1309
|
+
"items": { "$ref": "#/$defs/DesignPrepItem" }
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
"allOf": [
|
|
1313
|
+
{
|
|
1314
|
+
"if": {
|
|
1315
|
+
"properties": { "mode": { "const": "no-design-inputs" } }
|
|
1316
|
+
},
|
|
1317
|
+
"then": { "properties": { "items": { "maxItems": 0 } } }
|
|
1318
|
+
}
|
|
1319
|
+
]
|
|
1320
|
+
},
|
|
1321
|
+
|
|
1322
|
+
"DesignPrepKnownFact": {
|
|
1323
|
+
"type": "object",
|
|
1324
|
+
"required": ["statement", "evidence"],
|
|
1325
|
+
"additionalProperties": false,
|
|
1326
|
+
"properties": {
|
|
1327
|
+
"statement": { "type": "string", "minLength": 1 },
|
|
1328
|
+
"evidence": { "type": "string", "minLength": 1 }
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
|
|
1332
|
+
"DesignPrepProposal": {
|
|
1333
|
+
"type": "object",
|
|
1334
|
+
"required": ["summary", "details", "assumptions", "evidence", "confidence"],
|
|
1335
|
+
"additionalProperties": false,
|
|
1336
|
+
"properties": {
|
|
1337
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
1338
|
+
"details": {
|
|
1339
|
+
"type": "array",
|
|
1340
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1341
|
+
},
|
|
1342
|
+
"assumptions": {
|
|
1343
|
+
"type": "array",
|
|
1344
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1345
|
+
},
|
|
1346
|
+
"evidence": {
|
|
1347
|
+
"type": "array",
|
|
1348
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1349
|
+
},
|
|
1350
|
+
"confidence": { "enum": ["low", "medium", "high"] }
|
|
1351
|
+
}
|
|
1352
|
+
},
|
|
1353
|
+
|
|
1354
|
+
"DesignPrepHumanConfirmation": {
|
|
1355
|
+
"type": "object",
|
|
1356
|
+
"required": ["required", "reason", "suggestedAction"],
|
|
1357
|
+
"additionalProperties": false,
|
|
1358
|
+
"properties": {
|
|
1359
|
+
"required": { "type": "boolean" },
|
|
1360
|
+
"reason": { "type": "string", "minLength": 1 },
|
|
1361
|
+
"suggestedAction": { "type": "string", "minLength": 1 }
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
|
|
1365
|
+
"DesignPrepReviewPoint": {
|
|
1366
|
+
"type": "object",
|
|
1367
|
+
"required": ["phase"],
|
|
1368
|
+
"additionalProperties": false,
|
|
1369
|
+
"properties": {
|
|
1370
|
+
"phase": { "enum": ["implementation", "final-verification"] },
|
|
1371
|
+
"stage": { "type": "integer", "minimum": 1 }
|
|
1372
|
+
},
|
|
1373
|
+
"allOf": [
|
|
1374
|
+
{
|
|
1375
|
+
"if": { "properties": { "phase": { "const": "implementation" } } },
|
|
1376
|
+
"then": { "required": ["stage"] }
|
|
1377
|
+
}
|
|
1378
|
+
]
|
|
1379
|
+
},
|
|
1380
|
+
|
|
1381
|
+
"DesignPrepItem": {
|
|
1382
|
+
"type": "object",
|
|
1383
|
+
"required": [
|
|
1384
|
+
"id",
|
|
1385
|
+
"kind",
|
|
1386
|
+
"title",
|
|
1387
|
+
"stageRefs",
|
|
1388
|
+
"status",
|
|
1389
|
+
"need",
|
|
1390
|
+
"knownFacts",
|
|
1391
|
+
"openQuestions",
|
|
1392
|
+
"aiProposal",
|
|
1393
|
+
"humanConfirmation"
|
|
1394
|
+
],
|
|
1395
|
+
"additionalProperties": false,
|
|
1396
|
+
"properties": {
|
|
1397
|
+
"id": { "type": "string", "pattern": "^PREP-[0-9]{3}$" },
|
|
1398
|
+
"kind": {
|
|
1399
|
+
"enum": [
|
|
1400
|
+
"domain-contract",
|
|
1401
|
+
"persistence-schema",
|
|
1402
|
+
"external-interface",
|
|
1403
|
+
"transaction-consistency",
|
|
1404
|
+
"transformation-mapping",
|
|
1405
|
+
"lifecycle-state-machine",
|
|
1406
|
+
"rollout-observability",
|
|
1407
|
+
"manual-user-test",
|
|
1408
|
+
"other"
|
|
1409
|
+
]
|
|
1410
|
+
},
|
|
1411
|
+
"title": { "type": "string", "minLength": 1 },
|
|
1412
|
+
"stageRefs": {
|
|
1413
|
+
"type": "array",
|
|
1414
|
+
"minItems": 1,
|
|
1415
|
+
"uniqueItems": true,
|
|
1416
|
+
"items": { "type": "integer", "minimum": 1 }
|
|
1417
|
+
},
|
|
1418
|
+
"status": {
|
|
1419
|
+
"enum": ["ready", "provisional", "blocked", "not-applicable"]
|
|
1420
|
+
},
|
|
1421
|
+
"need": { "type": "string", "minLength": 1 },
|
|
1422
|
+
"knownFacts": {
|
|
1423
|
+
"type": "array",
|
|
1424
|
+
"items": { "$ref": "#/$defs/DesignPrepKnownFact" }
|
|
1425
|
+
},
|
|
1426
|
+
"openQuestions": {
|
|
1427
|
+
"type": "array",
|
|
1428
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1429
|
+
},
|
|
1430
|
+
"aiProposal": { "$ref": "#/$defs/DesignPrepProposal" },
|
|
1431
|
+
"humanConfirmation": {
|
|
1432
|
+
"$ref": "#/$defs/DesignPrepHumanConfirmation"
|
|
1433
|
+
},
|
|
1434
|
+
"workingAssumption": { "type": "string", "minLength": 1 },
|
|
1435
|
+
"guardrails": {
|
|
1436
|
+
"type": "array",
|
|
1437
|
+
"minItems": 1,
|
|
1438
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1439
|
+
},
|
|
1440
|
+
"reviewAt": { "$ref": "#/$defs/DesignPrepReviewPoint" },
|
|
1441
|
+
"ifStillOpen": { "enum": ["block", "follow-up"] },
|
|
1442
|
+
"requestPath": {
|
|
1443
|
+
"type": "string",
|
|
1444
|
+
"pattern": "^design-prep-requests/design-prep-request-[0-9]+-PREP-[0-9]{3}\\.md$"
|
|
1445
|
+
},
|
|
1446
|
+
"blockReason": { "type": "string", "minLength": 1 },
|
|
1447
|
+
"requiredDecision": { "type": "string", "minLength": 1 },
|
|
1448
|
+
"notApplicableReason": { "type": "string", "minLength": 1 },
|
|
1449
|
+
"replanTriggerFields": {
|
|
1450
|
+
"type": "array",
|
|
1451
|
+
"uniqueItems": true,
|
|
1452
|
+
"items": {
|
|
1453
|
+
"type": "string",
|
|
1454
|
+
"pattern": "^[^.\\s]+(\\.[^.\\s]+)*$"
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
"oneOf": [
|
|
1459
|
+
{
|
|
1460
|
+
"properties": { "status": { "const": "ready" } },
|
|
1461
|
+
"allOf": [
|
|
1462
|
+
{ "not": { "required": ["workingAssumption"] } },
|
|
1463
|
+
{ "not": { "required": ["blockReason"] } },
|
|
1464
|
+
{ "not": { "required": ["requiredDecision"] } },
|
|
1465
|
+
{ "not": { "required": ["notApplicableReason"] } },
|
|
1466
|
+
{ "not": { "required": ["requestPath"] } }
|
|
1467
|
+
]
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
"properties": { "status": { "const": "provisional" } },
|
|
1471
|
+
"required": [
|
|
1472
|
+
"workingAssumption",
|
|
1473
|
+
"guardrails",
|
|
1474
|
+
"reviewAt",
|
|
1475
|
+
"ifStillOpen",
|
|
1476
|
+
"requestPath"
|
|
1477
|
+
],
|
|
1478
|
+
"allOf": [
|
|
1479
|
+
{ "not": { "required": ["blockReason"] } },
|
|
1480
|
+
{ "not": { "required": ["requiredDecision"] } },
|
|
1481
|
+
{ "not": { "required": ["notApplicableReason"] } }
|
|
1482
|
+
]
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
"properties": {
|
|
1486
|
+
"status": { "const": "blocked" },
|
|
1487
|
+
"humanConfirmation": {
|
|
1488
|
+
"properties": { "required": { "const": true } }
|
|
1489
|
+
}
|
|
1490
|
+
},
|
|
1491
|
+
"required": [
|
|
1492
|
+
"blockReason",
|
|
1493
|
+
"requiredDecision",
|
|
1494
|
+
"reviewAt",
|
|
1495
|
+
"ifStillOpen",
|
|
1496
|
+
"requestPath"
|
|
1497
|
+
],
|
|
1498
|
+
"allOf": [
|
|
1499
|
+
{ "not": { "required": ["workingAssumption"] } },
|
|
1500
|
+
{ "not": { "required": ["guardrails"] } },
|
|
1501
|
+
{ "not": { "required": ["notApplicableReason"] } }
|
|
1502
|
+
]
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
"properties": { "status": { "const": "not-applicable" } },
|
|
1506
|
+
"required": ["notApplicableReason"],
|
|
1507
|
+
"allOf": [
|
|
1508
|
+
{ "not": { "required": ["workingAssumption"] } },
|
|
1509
|
+
{ "not": { "required": ["guardrails"] } },
|
|
1510
|
+
{ "not": { "required": ["reviewAt"] } },
|
|
1511
|
+
{ "not": { "required": ["ifStillOpen"] } },
|
|
1512
|
+
{ "not": { "required": ["blockReason"] } },
|
|
1513
|
+
{ "not": { "required": ["requiredDecision"] } },
|
|
1514
|
+
{ "not": { "required": ["requestPath"] } }
|
|
1515
|
+
]
|
|
1516
|
+
}
|
|
1517
|
+
]
|
|
1518
|
+
},
|
|
1519
|
+
|
|
1520
|
+
"TriggerEvidence": {
|
|
1521
|
+
"type": "object",
|
|
1522
|
+
"required": ["step", "field", "match"],
|
|
1523
|
+
"additionalProperties": false,
|
|
1524
|
+
"properties": {
|
|
1525
|
+
"step": {
|
|
1526
|
+
"type": ["integer", "null"],
|
|
1527
|
+
"minimum": 1
|
|
1528
|
+
},
|
|
1529
|
+
"field": { "enum": ["files", "action"] },
|
|
1530
|
+
"match": { "type": "string", "minLength": 1 }
|
|
1531
|
+
}
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1534
|
+
"InlineDesignContract": {
|
|
1535
|
+
"type": "object",
|
|
1536
|
+
"required": ["summary", "details", "evidence"],
|
|
1537
|
+
"additionalProperties": false,
|
|
1538
|
+
"properties": {
|
|
1539
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
1540
|
+
"details": {
|
|
1541
|
+
"type": "array",
|
|
1542
|
+
"minItems": 1,
|
|
1543
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1544
|
+
},
|
|
1545
|
+
"evidence": {
|
|
1546
|
+
"type": "array",
|
|
1547
|
+
"minItems": 1,
|
|
1548
|
+
"items": { "type": "string", "minLength": 1 }
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1553
|
+
"DesignSurfaceCoverage": {
|
|
1554
|
+
"type": "object",
|
|
1555
|
+
"required": ["kind", "triggerEvidence", "disposition"],
|
|
1556
|
+
"additionalProperties": false,
|
|
1557
|
+
"properties": {
|
|
1558
|
+
"kind": {
|
|
1559
|
+
"enum": [
|
|
1560
|
+
"domain-contract",
|
|
1561
|
+
"persistence-schema",
|
|
1562
|
+
"external-interface",
|
|
1563
|
+
"transaction-consistency",
|
|
1564
|
+
"transformation-mapping",
|
|
1565
|
+
"lifecycle-state-machine",
|
|
1566
|
+
"rollout-observability",
|
|
1567
|
+
"manual-user-test",
|
|
1568
|
+
"other"
|
|
1569
|
+
]
|
|
1570
|
+
},
|
|
1571
|
+
"triggerEvidence": {
|
|
1572
|
+
"type": "array",
|
|
1573
|
+
"minItems": 1,
|
|
1574
|
+
"items": { "$ref": "#/$defs/TriggerEvidence" }
|
|
1575
|
+
},
|
|
1576
|
+
"disposition": {
|
|
1577
|
+
"enum": ["inline-contract", "prep-item", "not-applicable"]
|
|
1578
|
+
},
|
|
1579
|
+
"inlineContract": { "$ref": "#/$defs/InlineDesignContract" },
|
|
1580
|
+
"prepItemId": {
|
|
1581
|
+
"type": "string",
|
|
1582
|
+
"pattern": "^PREP-[0-9]{3}$"
|
|
1583
|
+
},
|
|
1584
|
+
"prepItemIds": {
|
|
1585
|
+
"type": "array",
|
|
1586
|
+
"minItems": 1,
|
|
1587
|
+
"uniqueItems": true,
|
|
1588
|
+
"items": { "type": "string", "pattern": "^PREP-[0-9]{3}$" }
|
|
1589
|
+
},
|
|
1590
|
+
"rationale": { "type": "string", "minLength": 1 }
|
|
1591
|
+
},
|
|
1592
|
+
"oneOf": [
|
|
1593
|
+
{
|
|
1594
|
+
"properties": { "disposition": { "const": "inline-contract" } },
|
|
1595
|
+
"required": ["inlineContract"],
|
|
1596
|
+
"allOf": [
|
|
1597
|
+
{ "not": { "required": ["prepItemId"] } },
|
|
1598
|
+
{ "not": { "required": ["prepItemIds"] } },
|
|
1599
|
+
{ "not": { "required": ["rationale"] } }
|
|
1600
|
+
]
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
"properties": { "disposition": { "const": "prep-item" } },
|
|
1604
|
+
"allOf": [
|
|
1605
|
+
{ "not": { "required": ["inlineContract"] } },
|
|
1606
|
+
{ "not": { "required": ["rationale"] } }
|
|
1607
|
+
],
|
|
1608
|
+
"oneOf": [
|
|
1609
|
+
{
|
|
1610
|
+
"required": ["prepItemId"],
|
|
1611
|
+
"not": { "required": ["prepItemIds"] }
|
|
1612
|
+
},
|
|
1613
|
+
{
|
|
1614
|
+
"required": ["prepItemIds"],
|
|
1615
|
+
"not": { "required": ["prepItemId"] }
|
|
1616
|
+
}
|
|
1617
|
+
]
|
|
1618
|
+
},
|
|
1619
|
+
{
|
|
1620
|
+
"properties": { "disposition": { "const": "not-applicable" } },
|
|
1621
|
+
"required": ["rationale"],
|
|
1622
|
+
"allOf": [
|
|
1623
|
+
{ "not": { "required": ["inlineContract"] } },
|
|
1624
|
+
{ "not": { "required": ["prepItemId"] } },
|
|
1625
|
+
{ "not": { "required": ["prepItemIds"] } }
|
|
1626
|
+
]
|
|
1627
|
+
}
|
|
1628
|
+
]
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1299
1631
|
"ImplementationPlanStage": {
|
|
1300
1632
|
"type": "object",
|
|
1301
1633
|
"required": [
|
|
@@ -1364,6 +1696,10 @@
|
|
|
1364
1696
|
"maxItems": 6,
|
|
1365
1697
|
"items": { "$ref": "#/$defs/StageStepRow" }
|
|
1366
1698
|
},
|
|
1699
|
+
"designSurfaceCoverage": {
|
|
1700
|
+
"type": "array",
|
|
1701
|
+
"items": { "$ref": "#/$defs/DesignSurfaceCoverage" }
|
|
1702
|
+
},
|
|
1367
1703
|
"exitContract": { "type": "string", "minLength": 1 },
|
|
1368
1704
|
"stageValidation": { "type": "string", "minLength": 1 }
|
|
1369
1705
|
}
|
|
@@ -1508,6 +1844,7 @@
|
|
|
1508
1844
|
"aborted-non-result"
|
|
1509
1845
|
]
|
|
1510
1846
|
},
|
|
1847
|
+
"selfFixRoundApplied": { "type": "boolean" },
|
|
1511
1848
|
"planItems": {
|
|
1512
1849
|
"type": "array",
|
|
1513
1850
|
"items": {
|
|
@@ -1525,6 +1862,7 @@
|
|
|
1525
1862
|
{ "type": "string", "pattern": "^C-\\d{3,}$" }
|
|
1526
1863
|
]
|
|
1527
1864
|
},
|
|
1865
|
+
"selfFixNote": { "type": "string" },
|
|
1528
1866
|
"verdicts": {
|
|
1529
1867
|
"type": "array",
|
|
1530
1868
|
"items": {
|
|
@@ -1537,6 +1875,7 @@
|
|
|
1537
1875
|
"enum": ["AGREE", "DISAGREE", "SUPPLEMENT", "verification-error"]
|
|
1538
1876
|
},
|
|
1539
1877
|
"breakageKind": { "type": "string" },
|
|
1878
|
+
"fixability": { "enum": ["planner-fixable", "needs-user-input"] },
|
|
1540
1879
|
"note": { "type": "string" }
|
|
1541
1880
|
}
|
|
1542
1881
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-inspect
|
|
3
3
|
description: >-
|
|
4
|
-
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (결론·통과); its elapsed time (소요 시간) or context/read cost (컨텍스트 비용); its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (에러 리포트). Also bundles cross-project okstra errors into an anonymized feedback zip (에러 환류). Use it even for a bare "mark it done" or "where's the report." NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
|
|
4
|
+
Use this for everything that happens AFTER a single okstra task has already run — inspecting it or light bookkeeping on it, never launching new work. The tell is usually a named task id (PROD-1623, dev-9184), often dropped without the word "okstra." Reach for it when the user wants one task's: status, current/next phase, blockers, or approval gate; its final report — where it is or whether it passed (결론·통과); its elapsed time (소요 시간) or context/read cost (컨텍스트 비용); its run history, re-run, or resume; to mark it done / in-progress / blocked / todo; or a failed run's error logs gathered into a report (에러 리포트). Also bundles cross-project okstra errors into an anonymized feedback zip (에러 환류). Use it even for a bare "mark it done" or "where's the report." NOT for starting a run (okstra-run), rollups/schedules (okstra-rollup / okstra-schedule-gen), a brief (okstra-brief-gen), setup (okstra-setup), or cross-project management (okstra-manager).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA Inspect
|
|
@@ -186,7 +186,7 @@ Confirm in Korean:
|
|
|
186
186
|
|
|
187
187
|
Annotate inferred values with `(inferred)` or `(default)`. Do not back-fill on read; only write when the user explicitly issues an update.
|
|
188
188
|
|
|
189
|
-
**Catalog sync note:** the CLI updates `task-manifest.json` only. `discovery/task-catalog.json` may be stale until the next run regenerates it; downstream consumers (e.g. `okstra-schedule`) re-read each manifest directly.
|
|
189
|
+
**Catalog sync note:** the CLI updates `task-manifest.json` only. `discovery/task-catalog.json` may be stale until the next run regenerates it; downstream consumers (e.g. `okstra-schedule-gen`) re-read each manifest directly.
|
|
190
190
|
|
|
191
191
|
---
|
|
192
192
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okstra-rollup
|
|
3
3
|
description: |
|
|
4
|
-
Use when the user wants run results from MULTIPLE okstra tasks collected and summarized at once — a task-group digest or a whole-project roll-up, not a single task. Aggregates per-task run count, elapsed time, error count, and latest report path, plus group-level totals and status/category/phase tallies, then synthesizes a cross-task prose summary from the report files. Make sure to use this skill whenever the user mentions "okstra rollup", "롤업", "task-group 요약", "그룹 단위 리포트", "여러 task 결과 모아", "전체 task 현황 요약", "cross-task summary", "그룹 종합 리포트", "모든 task 정리", "run 결과 한꺼번에", even if they don't say "rollup". For a SINGLE task's report/time/errors/recap use okstra-inspect instead; for a forward-looking work plan over non-done tasks use okstra-schedule.
|
|
4
|
+
Use when the user wants run results from MULTIPLE okstra tasks collected and summarized at once — a task-group digest or a whole-project roll-up, not a single task. Aggregates per-task run count, elapsed time, error count, and latest report path, plus group-level totals and status/category/phase tallies, then synthesizes a cross-task prose summary from the report files. Make sure to use this skill whenever the user mentions "okstra rollup", "롤업", "task-group 요약", "그룹 단위 리포트", "여러 task 결과 모아", "전체 task 현황 요약", "cross-task summary", "그룹 종합 리포트", "모든 task 정리", "run 결과 한꺼번에", even if they don't say "rollup". For a SINGLE task's report/time/errors/recap use okstra-inspect instead; for a forward-looking work plan over non-done tasks use okstra-schedule-gen.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# OKSTRA Rollup
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: okstra-schedule
|
|
2
|
+
name: okstra-schedule-gen
|
|
3
3
|
description: Use when the user asks for a task-group work schedule, a consolidated implementation plan across multiple tasks in a task-group, or wants to generate a "schedule" / "일정" / "작업 계획표" for non-done tasks. Trigger words include "okstra schedule", "<task-group> 일정 만들어", "<task-group> schedule 생성", "task-group 작업 계획".
|
|
4
4
|
model: opus
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# OKSTRA Schedule
|
|
7
|
+
# OKSTRA Schedule Gen
|
|
8
8
|
|
|
9
|
-
Generate a consolidated work schedule for
|
|
9
|
+
Generate a consolidated work schedule for the selected `implementation-planning` stages of every non-done task in a given `task-group` (or a single `task-id`). For each task the skill reads its `implementation-planning` final-report **Stage Map** as the authoritative forward-looking decomposition, asks the user which stages to include, drafts a single Markdown plan, and only writes the final file after an **independent verifier subagent** confirms the draft covers exactly the selected stages. It runs as a **lead + verifier** flow; the frontmatter `model: opus` switches supporting harnesses to Opus-class for the turn — stage-level cross-task synthesis needs that reasoning depth.
|
|
10
10
|
|
|
11
11
|
## When to Use
|
|
12
12
|
|
|
@@ -47,20 +47,23 @@ The installed template `~/.okstra/templates/reports/schedule.template.md` is the
|
|
|
47
47
|
|
|
48
48
|
`~/.okstra/lib/validators/validate-schedule.py` is the enforcement for all of the above (heading order, field labels, controlled vocabulary — e.g. `Med-High` is the canonical risk form — forbidden translations, checkbox bans, Gantt fence rules, unresolved-code detection). The Step 4 ambiguous-classification rationale line is the one rule the validator does not yet enforce — emit it yourself.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
One computation rule the template scaffold cannot carry inline:
|
|
51
51
|
|
|
52
52
|
- **Effort-to-Day mapping**: day ranges per size are defined once in the template's `### Effort Sizing 기준` table. For the At a Glance totals line, sum that table's lower bounds across in-scope tasks for the lower total and upper bounds for the upper total.
|
|
53
|
-
- **`[NEEDS-OKSTRA-RUN]` / `[PARSE-ERROR: <section>]` placement**: put the marker immediately under the per-task heading inside its Phase section and fill only the available fields — never relocate it to a sibling section.
|
|
54
53
|
|
|
55
54
|
## Procedure
|
|
56
55
|
|
|
57
56
|
### Step 1: Resolve task-group and collect tasks
|
|
58
57
|
|
|
59
58
|
1. Read `.okstra/discovery/task-catalog.json`.
|
|
60
|
-
2. **
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
2. **Resolve which task-group to schedule — never silently guess.**
|
|
60
|
+
- The user **explicitly named a task-group** (as the `okstra schedule <task-group>` argument or unambiguously in the request) → use that token; skip the picker and go to sub-step 3.
|
|
61
|
+
- The user **named no task-group, or the named token matches 0 or ≥2 groups** → present a 3-option picker via `AskUserQuestion` and do NOT proceed until the user chooses. Build the options from the catalog: walk `tasks[]` in catalog order (already `updatedAt` desc — see `scripts/okstra_ctl/render.py:654`), collect distinct `taskGroupPathSegment` values that have ≥1 entry whose resolved `workStatus` is **non-done** (defer to Step 2's inference table), and offer the newest **1–2** such groups as recommendations. The **last option is always `직접 입력`** (free-text group token, fed into sub-step 3). Label each recommendation with its non-done task count (e.g. `uploadFont (non-done 3)`).
|
|
62
|
+
- If **zero groups have a non-done task** (or the catalog is empty), do NOT open a picker — emit `해당 task-group의 모든 task가 done 상태입니다. 생성할 schedule이 없습니다.` (or `해당 task-group을 찾을 수 없습니다.` when the catalog has no tasks at all) and stop **without creating a file**.
|
|
63
|
+
3. **Normalise the resolved `<task-group>`:** lowercase it, then strip every character that is not `[a-z0-9]`. Apply the same transform to each entry's `taskGroupPathSegment`. Match on equality — this is the single comparison rule; do NOT also fall back to the raw `taskGroup` field.
|
|
64
|
+
4. If no tasks found, output `해당 task-group을 찾을 수 없습니다.` and stop.
|
|
65
|
+
5. For each matched task, read `.okstra/tasks/<task-group-segment>/<task-id-segment>/task-manifest.json` directly. Catalog data may be stale; the manifest is authoritative.
|
|
66
|
+
6. **Derive `<project-id>`** for the header: prefer `task-catalog.json`'s top-level `projectId`, otherwise the first matched manifest's `projectId`. Do not invent a value.
|
|
64
67
|
|
|
65
68
|
### Step 2: Filter by workStatus
|
|
66
69
|
|
|
@@ -68,18 +71,35 @@ For inference when `workStatus` is missing or empty, defer to the inference tabl
|
|
|
68
71
|
|
|
69
72
|
If 0 tasks remain, output `해당 task-group의 모든 task가 done 상태입니다. 생성할 schedule이 없습니다.` and stop **without creating a file**.
|
|
70
73
|
|
|
71
|
-
### Step 3: Per-task
|
|
74
|
+
### Step 3: Per-task stage extraction (Stage Map source)
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
For each in-scope task, the **authoritative source is its `implementation-planning` final-report**, NOT `latestReportPath`. Resolve and parse it as follows:
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
1. Resolve `<task-root>` from the manifest's `taskRootPath`. The planning report lives under `<task-root>/runs/implementation-planning/**/reports/final-report-*.md`. If several exist, pick the **newest by mtime**.
|
|
79
|
+
2. Do NOT hand-parse the Stage Map markdown. Call `okstra stage-map <task-key> --json` (Task 0). It returns `{ok, taskRoot, stages:[{stage_number,title,depends_on,step_count}], doneStages:[int]}` by wrapping `_load_stage_map` + `read_stage_consumer_state().done_stages`. `stages: []` means no planning Stage Map exists → treat as **no planning report** below.
|
|
80
|
+
3. From each stage row keep `stage_number`, `title`, `depends_on`, `step_count`. Compute `remainingStages = stages − doneStages`.
|
|
81
|
+
4. From `task-manifest.json` still read task-level metadata for the header and phase bucket: `taskId`, `taskGroup`, `taskKey`, `workCategory`, `workStatus`, `taskType`, `workflow.currentPhase`.
|
|
82
|
+
5. From the planning report also read task-level Priority / Risk / Scope / Repos for phase classification and the At a Glance row. Blocking / approval items are **not** extracted (see Audience & authority).
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
**No planning Stage Map** (`stage-map` returned `stages: []`) → this task cannot be stage-scheduled. Tag it `[NEEDS-PLANNING]`, skip the Step 3.5 stage picker for it, and render it under its phase section as a single banner line with task-level metadata only (no Gantt bars, no day total). Continue with the remaining tasks.
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
3.
|
|
82
|
-
|
|
86
|
+
**`remainingStages` is empty** (every stage done but `workStatus` not `done`) → not a scheduling target. Render the task as `_완료 — 남은 stage 없음_` under its phase section; contribute no forward day total.
|
|
87
|
+
|
|
88
|
+
### Step 3.5: Stage selection (per task, user input)
|
|
89
|
+
|
|
90
|
+
Run this **once per in-scope task that has a non-empty `remainingStages`**, sequentially. A single `task-id` invocation runs it once; a `task-group` runs it per task-id in At-a-Glance order.
|
|
91
|
+
|
|
92
|
+
**Build dependency-closed cumulative bundles over `remainingStages`:**
|
|
93
|
+
|
|
94
|
+
1. Order `remainingStages` topologically by `depends_on` (a stage may only appear after all its `depends_on` stages).
|
|
95
|
+
2. Pick natural cut points to form up to **3 cumulative bundles**: `B1` = through the first milestone, `B2` = through the core, `B3` = all remaining. Each bundle is a **dependency-closed prefix** — selecting it never omits a prerequisite stage. Label each bundle with its stage list and summed `step_count` (e.g. `B2: stage 1–4 (steps 12)`).
|
|
96
|
+
3. If `remainingStages` has ≤2 stages, emit only the bundles that are distinct (1–2), never pad to 3.
|
|
97
|
+
|
|
98
|
+
**Render the picker** with `AskUserQuestion` (one question for this task):
|
|
99
|
+
- Options = the distinct bundles + a final `"남은 stage 전부"` option. `AskUserQuestion`'s built-in Other slot serves the `직접 입력` (arbitrary stage subset) case; when the user supplies a custom subset, close it under `depends_on` before accepting.
|
|
100
|
+
- **Degenerate skip:** if only one distinct bundle exists AND it already equals all remaining stages, skip the picker for this task and set `selectedStages = remainingStages` (log `> _Stage picker 생략: 남은 stage가 단일 의존성 체인._`).
|
|
101
|
+
|
|
102
|
+
Record the chosen `selectedStages` for this task. Any custom selection that breaks `depends_on` closure is rejected — re-prompt the same task.
|
|
83
103
|
|
|
84
104
|
### Step 4: Phase classification
|
|
85
105
|
|
|
@@ -94,16 +114,35 @@ If the report file does not exist or cannot be parsed, attempt the **manifest-st
|
|
|
94
114
|
|
|
95
115
|
Priority overrides category: `P0` → Phase 1; `P1`/`P2` → Phase 2; `P3` or multi-repo + infrastructure scope → Phase 3. When still ambiguous, place the task in the closest phase and add a one-line rationale at the top of that phase section (not validator-enforced — emit it yourself).
|
|
96
116
|
|
|
117
|
+
Phase bucketing stays **task-level** (a task lands in one Phase by its `workCategory`/Priority). Within a task's per-task section, the **selected stages become the Work Breakdown rows**: one row per `selectedStages` entry with its `title`, `step_count`-derived effort, and `depends_on`. Stages excluded from `selectedStages` because they are already done are listed once as `> _완료 stage: stage <n>, …_` and carry no forward effort. This keeps the mandatory heading skeleton unchanged while moving the unit of work to the stage.
|
|
118
|
+
|
|
97
119
|
### Step 5: Gantt decision (render by default)
|
|
98
120
|
|
|
99
121
|
`## Gantt Chart` is **rendered by default** — skip ONLY when literally no day signal exists (every task is effort=XXL with no visible decomposition, or all tasks lack both effort sizing and decomposition). Render whenever any of these hold: 2+ tasks with effort sizing; 1 task whose effort yields a range (mid-point bar, or `lo`/`hi` two-bar form); 1 task with Part/Phase/Step decomposition in the source (bars at decomposition-unit level); total estimated effort ≥ 3 days. When per-unit day allocations aren't itemized, split the parent range across the visible units yourself and append the `est` annotation (or add `> 일별 배분은 추정치이며 차단 항목 해소 후 갱신 권장.`). "Range is wide", "single task", "user decisions pending" are NOT skip reasons — render an estimate-tagged chart instead.
|
|
100
122
|
|
|
123
|
+
When the source is a Stage Map, the Gantt **bars are the selected stages** (one bar per `selectedStages` entry), day length split from `step_count` (or the effort range across the stage's steps), and cross-stage `(after stage <n>)` / `(after <TASK-ID>)` edges follow `depends_on`. Already-done stages never get a bar. A task tagged `[NEEDS-PLANNING]` contributes no bars.
|
|
124
|
+
|
|
101
125
|
When you do skip, insert in the section's position exactly: `> _Gantt Chart 생략: <concrete reason referencing the actual data>._`
|
|
102
126
|
|
|
103
127
|
**Directive override (highest priority).** Before applying the heuristic, check for a `## Directive` section, first hit wins: (1) the `--directive-file <abs-path>` argument; (2) `<PROJECT_ROOT>/.okstra/tasks/<task-group-segment>/schedule/instruction-set/analysis-material.md`; (3) none → apply the default heuristic silently. A found directive overrides the render/skip heuristic for the affected section — note it inline as `> _Per Directive directive: <verbatim short excerpt>._` — and its pre-supplied day allocations / phase weights are used verbatim as bar lengths. A directive file without a `## Directive` heading counts as "no directive".
|
|
104
128
|
|
|
129
|
+
### Step 5.5: Draft, verify, gate
|
|
130
|
+
|
|
131
|
+
1. **Draft to staging (never the final path yet).** Write the assembled schedule to `.okstra/tasks/<task-group-segment>/schedule/.draft/<YYYY-MM-DD_HH-MM-SS>.md`. Auto-create the parent.
|
|
132
|
+
2. **Dispatch an independent verifier subagent** with the `Agent` tool. It receives, per task: the `selectedStages` list (stage numbers + titles) and `depends_on` edges, plus the draft file path. It must NOT see the lead's reasoning — only these inputs and the draft. It returns a structured verdict:
|
|
133
|
+
- `coverage`: every `selectedStages` entry appears as a forward Work Breakdown / Gantt unit in the draft (nothing missing).
|
|
134
|
+
- `noExcess`: no non-selected stage and no done stage appears as forward work.
|
|
135
|
+
- `depOrder`: stage ordering and `(after …)` edges honor `depends_on`.
|
|
136
|
+
- `pass = coverage && noExcess && depOrder`; on fail it lists the offending stages as `missing[]` / `extra[]` / `orderViolations[]`.
|
|
137
|
+
3. **Gate:**
|
|
138
|
+
- `pass` → promote the draft to the final path (Step 6) and continue.
|
|
139
|
+
- `fail` → hand the verifier's `missing`/`extra`/`orderViolations` back to the lead, revise the draft in place, and re-dispatch the verifier. **Max 2 revise cycles.**
|
|
140
|
+
- Still failing after the 2nd cycle → do **NOT** write the final file. Remove the staging draft, and report the residual `missing`/`extra`/`orderViolations` to the user in Korean. Stop.
|
|
141
|
+
|
|
105
142
|
### Step 6: Write the schedule file
|
|
106
143
|
|
|
144
|
+
Reached only after Step 5.5 returns `pass`. **Promote** the verified staging draft to the final path (do not re-render from scratch):
|
|
145
|
+
|
|
107
146
|
```
|
|
108
147
|
.okstra/tasks/<task-group-segment>/schedule/<task-group-segment>-plan-<YYYY-MM-DD_HH-MM-SS>.md
|
|
109
148
|
```
|
|
@@ -124,7 +163,7 @@ When you do skip, insert in the section's position exactly: `> _Gantt Chart 생
|
|
|
124
163
|
- 포함 task: N개
|
|
125
164
|
- 제외(done) task: M개
|
|
126
165
|
- 예상 소요: X.X ~ Y.Y days (Effort 합산)
|
|
127
|
-
- 모드:
|
|
166
|
+
- 모드: lead + verifier
|
|
128
167
|
```
|
|
129
168
|
|
|
130
169
|
## Edge Cases
|
|
@@ -133,8 +172,10 @@ When you do skip, insert in the section's position exactly: `> _Gantt Chart 생
|
|
|
133
172
|
|------|----------|
|
|
134
173
|
| `workStatus` absent or empty | Resolve via the `okstra-inspect status.4` inference table; include unless resolved `done` |
|
|
135
174
|
| Filtered task count is 0 | Emit "모든 task가 done" message; do NOT create a file |
|
|
136
|
-
|
|
|
137
|
-
|
|
|
175
|
+
| implementation-planning 리포트 없음 | `[NEEDS-PLANNING]` 배너로만 나열, stage picker·Gantt·day total 없음 |
|
|
176
|
+
| 남은 stage 0개(전부 done, workStatus 미표기) | `_완료 — 남은 stage 없음_`, forward 산출 없음 |
|
|
177
|
+
| 남은 stage ≤2개 | 누적 묶음을 나오는 만큼만(1~2), 단일 체인이면 picker 생략·전부 진행 |
|
|
178
|
+
| verifier 2회 미통과 | 최종 파일 미작성, 잔여 missing/extra/order 사용자 보고 |
|
|
138
179
|
| `task-group` matches no tasks | "해당 task-group을 찾을 수 없습니다." and stop |
|
|
139
180
|
| Catalog and manifest disagree on `workStatus` | Manifest wins (catalog may be stale) |
|
|
140
181
|
| task-group casing / punctuation variants | Normalise both sides (lowercase + strip non-`[a-z0-9]`), compare against `taskGroupPathSegment` only; use the manifest's segment verbatim for path output |
|
|
@@ -144,3 +185,4 @@ When you do skip, insert in the section's position exactly: `> _Gantt Chart 생
|
|
|
144
185
|
- All user-facing messages in Korean; schedule body prose Korean, identifiers/headings/field labels English (template literals).
|
|
145
186
|
- Use project-relative paths in completion messages.
|
|
146
187
|
- Use `taskType` (not `workStatus`) for the At a Glance status column — it carries lifecycle phase information.
|
|
188
|
+
- Per-task section Work Breakdown rows are **selected stages**, not free-form items; done stages are summarized, never scheduled forward.
|
|
@@ -57,19 +57,21 @@ Carry the chosen entry's `reportPath` and `taskKey` forward.
|
|
|
57
57
|
okstra user-response show --report <reportPath>
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Returns `{reportPath, rows: [{id, kind, blocks, status, statement, recommended, alternatives, contextRefs}]}`.
|
|
60
|
+
Returns `{reportPath, rows: [{id, kind, blocks, status, statement, recommended, alternatives, contextRefs, resolvedRefs}]}`. `resolvedRefs` is `[{ref, definition}]` — the CLI has already looked up what each internal token (`RB-002`, `FU-001`, `§4.7`, …) means in the report body; `definition` is `null` only when the report text alone could not resolve it (e.g. a `path:line` pointer).
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
- **`
|
|
62
|
+
**Do NOT paste the raw `statement` as the question.** A `statement` like `Rewrite RB-002 rollback (see §4.7)` is meaningless to the user on its own — that is the whole reason this skill exists. For each open `C-*` row, you MUST present:
|
|
63
|
+
|
|
64
|
+
- **A rewritten, self-contained question** (lead with this): rewrite `statement` in plain language with **every internal token expanded inline** from its `resolvedRefs` `definition` — the user must never have to know what `RB-002` is to answer. If a token's `definition` is `null`, resolve it yourself **using Read** on the report `§`/`path:line` before asking; only if it is genuinely unresolvable do you say so plainly.
|
|
65
|
+
- **`recommended`** — the answer okstra proposed (+ rationale), also stated in plain language. It is only a recommendation.
|
|
66
|
+
- **`alternatives[]`** — list them if present, each in plain language.
|
|
67
|
+
- **Raw source** (secondary, for traceability): the raw `id` + `statement` so the mapping back to the report stays visible.
|
|
66
68
|
|
|
67
69
|
## Step 3: Per-item — collect the user's decision (4 branches)
|
|
68
70
|
|
|
69
71
|
For each open item, the user picks one of four dispositions. You relay and transcribe; you never choose:
|
|
70
72
|
|
|
71
73
|
1. **Answer** — the user accepts the recommendation or gives their own answer. `disposition: "answer"`, `value` = the user's utterance verbatim.
|
|
72
|
-
2. **Ask-to-explain** —
|
|
74
|
+
2. **Ask-to-explain (fallback)** — Step 2 already expanded every internal token, so the question should already be self-contained. If the user still asks "what does this mean?" or a `resolvedRefs` `definition` was `null`, open the report `§`/`path:line` that this item's `contextRefs[]` points at **using Read** and explain it in plain language. Only explain — **do not pick the answer for the user**; after explaining, hand the decision back to the user.
|
|
73
75
|
3. **Free-form** — the user gives a narrative answer that matches neither a recommendation nor an alternative. `value` = that narrative verbatim, with the rationale in `rationale` if needed. `disposition: "answer"`.
|
|
74
76
|
4. **Hold + reframe** — if the user asks to "re-frame this question itself", record it as `disposition: "reframe"`. Put what the user wants re-asked into `value` verbatim. A reframe is not an answer, so it does not satisfy the approval gate — the next run re-frames that question.
|
|
75
77
|
|