yaaw-se 0.3.7 → 0.3.8

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 (81) hide show
  1. package/README.md +6 -2
  2. package/dist/installer/migrations/index.d.ts +2 -2
  3. package/dist/installer/migrations/index.js +2 -2
  4. package/dist/installer/migrations/project/index.js +2 -2
  5. package/dist/installer/migrations/project/index.js.map +1 -1
  6. package/dist/installer/migrations/project/v1-to-v2.d.ts +2 -0
  7. package/dist/installer/migrations/project/v1-to-v2.js +64 -0
  8. package/dist/installer/migrations/project/v1-to-v2.js.map +1 -0
  9. package/dist/installer/project-state.js +1 -0
  10. package/dist/installer/project-state.js.map +1 -1
  11. package/dist/installer/transaction.js +5 -0
  12. package/dist/installer/transaction.js.map +1 -1
  13. package/dist/installer/types.d.ts +5 -0
  14. package/dist/payload/bootstrap/claude-code.md +1 -1
  15. package/dist/payload/bootstrap/cline.md +1 -1
  16. package/dist/payload/bootstrap/codex.md +1 -1
  17. package/dist/payload/bootstrap/gemini-cli.md +1 -1
  18. package/dist/payload/integrations/codex/yaaw-runtime.md +1 -1
  19. package/dist/payload/payload-files.json +146 -111
  20. package/dist/payload/payload.json +1 -1
  21. package/dist/payload/skills/yaaw-create-spec/SKILL.md +6 -2
  22. package/dist/payload/skills/yaaw-create-ticket/SKILL.md +6 -2
  23. package/dist/payload/skills/yaaw-create-tickets/SKILL.md +6 -2
  24. package/dist/payload/skills/yaaw-implement/SKILL.md +6 -2
  25. package/dist/payload/skills/yaaw-orchestrator/SKILL.md +6 -2
  26. package/dist/payload/skills/yaaw-planner/SKILL.md +6 -2
  27. package/dist/payload/skills/yaaw-planning-review/SKILL.md +6 -2
  28. package/dist/payload/skills/yaaw-prd/SKILL.md +6 -2
  29. package/dist/payload/skills/yaaw-refine-prd/SKILL.md +6 -2
  30. package/dist/payload/skills/yaaw-repair/SKILL.md +6 -2
  31. package/dist/payload/skills/yaaw-review/SKILL.md +6 -2
  32. package/dist/payload/skills/yaaw-revise-prd/SKILL.md +6 -2
  33. package/dist/payload/yaaw-core/system/core/dispatch-execution.md +16 -53
  34. package/dist/payload/yaaw-core/system/core/invalidation.md +7 -38
  35. package/dist/payload/yaaw-core/system/core/io-contract.md +12 -45
  36. package/dist/payload/yaaw-core/system/core/lifecycle.md +11 -18
  37. package/dist/payload/yaaw-core/system/core/recovery.md +12 -26
  38. package/dist/payload/yaaw-core/system/core/routing.md +16 -31
  39. package/dist/payload/yaaw-core/system/core/state-model.md +7 -27
  40. package/dist/payload/yaaw-core/system/registries/handoff-policy.json +41 -59
  41. package/dist/payload/yaaw-core/system/registries/reconciliation-policy.json +17 -0
  42. package/dist/payload/yaaw-core/system/registries/role-io.json +15 -144
  43. package/dist/payload/yaaw-core/system/registries/skills.json +108 -12
  44. package/dist/payload/yaaw-core/system/roles/implementer.md +10 -13
  45. package/dist/payload/yaaw-core/system/roles/orchestrator.md +12 -17
  46. package/dist/payload/yaaw-core/system/roles/planner.md +9 -18
  47. package/dist/payload/yaaw-core/system/roles/reviewer.md +9 -14
  48. package/dist/payload/yaaw-core/system/schemas/engineering-v1.schema.json +15 -0
  49. package/dist/payload/yaaw-core/system/schemas/engineering-v2.schema.json +59 -0
  50. package/dist/payload/yaaw-core/system/schemas/engineering.schema.json +9 -12
  51. package/dist/payload/yaaw-core/system/schemas/evidence-v3.schema.json +111 -0
  52. package/dist/payload/yaaw-core/system/schemas/evidence.schema.json +3 -0
  53. package/dist/payload/yaaw-core/system/schemas/handoff.schema.json +3 -60
  54. package/dist/payload/yaaw-core/system/schemas/intent.schema.json +63 -8
  55. package/dist/payload/yaaw-core/system/schemas/observed-state.schema.json +55 -61
  56. package/dist/payload/yaaw-core/system/schemas/project-state-v1.schema.json +74 -0
  57. package/dist/payload/yaaw-core/system/schemas/project-state-v2.schema.json +251 -0
  58. package/dist/payload/yaaw-core/system/schemas/project-state.schema.json +9 -71
  59. package/dist/payload/yaaw-core/system/templates/engineering-research.md +2 -2
  60. package/dist/payload/yaaw-core/system/templates/engineering.md +2 -1
  61. package/dist/payload/yaaw-core/system/templates/evidence.json +2 -1
  62. package/dist/payload/yaaw-core/system/templates/handoff.json +7 -8
  63. package/dist/payload/yaaw-core/system/templates/intent.json +4 -3
  64. package/dist/payload/yaaw-core/system/templates/observed-state.json +6 -13
  65. package/dist/payload/yaaw-core/system/templates/project-state.json +2 -2
  66. package/dist/payload/yaaw-core/system/templates/review.md +4 -4
  67. package/dist/payload/yaaw-core/system/templates/spec.md +2 -2
  68. package/dist/payload/yaaw-core/system/templates/ticket.md +3 -3
  69. package/dist/payload/yaaw-core/system/tools/orchestration-engine.mjs +187 -0
  70. package/dist/payload/yaaw-core/system/tools/orchestration-runtime.mjs +46 -688
  71. package/dist/payload/yaaw-core/system/workflows/implementation/implement-ticket.md +9 -17
  72. package/dist/payload/yaaw-core/system/workflows/implementation/repair-ticket.md +7 -12
  73. package/dist/payload/yaaw-core/system/workflows/implementation/verify-ticket.md +9 -10
  74. package/dist/payload/yaaw-core/system/workflows/orchestration/reconcile-state.md +10 -13
  75. package/dist/payload/yaaw-core/system/workflows/orchestration/recover-interruption.md +9 -12
  76. package/dist/payload/yaaw-core/system/workflows/orchestration/route.md +14 -21
  77. package/dist/payload/yaaw-core/system/workflows/planning/create-spec.md +3 -0
  78. package/dist/payload/yaaw-core/system/workflows/planning/create-tickets.md +3 -0
  79. package/dist/payload/yaaw-core/system/workflows/planning/decision-frontier.md +3 -0
  80. package/dist/payload/yaaw-core/system/workflows/planning/replan.md +2 -0
  81. package/package.json +4 -3
@@ -0,0 +1,187 @@
1
+ const EXECUTABLE = new Set(["READY","IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","PASS"]);
2
+ export const normalizeStatus=v=>String(v??"").trim().toLowerCase();
3
+ const num=(a,b)=>Number(a)===Number(b);
4
+ const specId=v=>String(v??"").match(/SPEC-[0-9]+/)?.[0]??null;
5
+ const legal=(t,a,b)=>(t?.legal??[]).some(x=>x.from===a&&x.to===b);
6
+ const currentRepo=(r,repo)=>Boolean(r?.value?.repository?.worktree_digest&&repo?.status==="READY"&&r.value.repository.worktree_digest===repo.worktree_digest);
7
+ const latest=(rows,pred)=>(rows??[]).filter(pred).sort((a,b)=>String(a.path).localeCompare(String(b.path))).at(-1)??null;
8
+ const evidenceRefs=r=>new Set((r?.value?.evidence??[]).map(String));
9
+
10
+ export function ticketSourceStatus(ticket,state,activeSpec){
11
+ if(!ticket||!activeSpec)return "SPEC_SOURCE_STALE";
12
+ if(!num(ticket.product_revision,state?.product?.revision))return "PRODUCT_SOURCE_STALE";
13
+ if(!num(ticket.engineering_revision,state?.planning?.revision))return "ENGINEERING_SOURCE_STALE";
14
+ if(String(ticket.spec)!==String(activeSpec.id))return "SPEC_SOURCE_STALE";
15
+ if(!num(ticket.spec_revision,activeSpec.revision))return "TICKET_SOURCE_STALE";
16
+ return "CURRENT";
17
+ }
18
+ export function currentAcceptedSpecs(state,a){
19
+ return Object.entries(a?.specs??{}).filter(([,x])=>normalizeStatus(x?.meta?.status)==="accepted")
20
+ .filter(([,x])=>num(x.meta.product_revision,state?.product?.revision))
21
+ .filter(([,x])=>num(x.meta.engineering_revision,state?.planning?.revision))
22
+ .filter(([,x])=>String(x.meta.frontier_id??"")===String(state?.planning?.current_frontier??""))
23
+ .sort(([x],[y])=>x.localeCompare(y));
24
+ }
25
+ function activeSpec(state,a){const id=specId(state?.planning?.active_spec);const x=id&&a?.specs?.[id];return x?{id,...x.meta,path:x.path}:null;}
26
+ function ev(rows,id,tr,sr,kind){return latest(rows,x=>x?.value?.ticket===id&&x.value.kind===kind&&num(x.value.ticket_revision,tr)&&num(x.value.spec_revision,sr));}
27
+ function start(rows,id,tr,sr){const x=ev(rows,id,tr,sr,"implementation_start");return x?.value?.schema==="yaaw.evidence/v3"&&x.value.result==="STARTED"?x:null;}
28
+ function verify(rows,id,tr,sr){return ev(rows,id,tr,sr,"implementation_verification");}
29
+ function review(rows,id,tr,sr){return (rows??[]).filter(x=>x?.value?.ticket===id&&num(x.value.ticket_revision,tr)&&num(x.value.spec_revision,sr)).sort((a,b)=>Number(a.value.round)-Number(b.value.round)).at(-1)??null;}
30
+ function transition(id,subject,from,to,reason,evidence=[]){return{id,kind:"STATE",subject,from,to,reason,workflow:"orchestration.reconcile-state",evidence};}
31
+ function currentTicketIds(state,a){const s=activeSpec(state,a);return s?Object.keys(state?.tickets??{}).filter(id=>ticketSourceStatus(a?.tickets?.[id]?.meta,state,s)==="CURRENT").sort():[];}
32
+
33
+ export function selectReconciliation({state,artifacts:a,evidence=[],reviews=[],repository,transitions,reconciliationPolicy,routingPolicy}){
34
+ const order=["PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED","PRODUCT_LEDGER_SYNC","PLANNING_LEDGER_SYNC","ACTIVE_SPEC_UNADOPTED","TICKET_UNREGISTERED","CONTRACT_SOURCE_STALE","IMPLEMENTATION_STARTED","IMPLEMENTATION_VERIFIED","REVIEW_RESULT_UNAPPLIED","ACCEPTANCE_STALE","PROJECT_COMPLETION_SYNC"];
35
+ const declared=(reconciliationPolicy?.priority??[]).map(x=>typeof x==="string"?x:x.id);
36
+ if(declared.length&&JSON.stringify(declared)!==JSON.stringify(order))return{id:"RECONCILIATION_POLICY_DRIFT",kind:"BLOCKED",reason:"reconciliation priority differs from engine contract"};
37
+ if(!state)return{id:"PROJECT_STATE_MISSING",kind:"BLOCKED",reason:"project state missing"};
38
+ if(state.schema!=="yaaw.project-state/v2")return{id:"PROJECT_STATE_SCHEMA_MIGRATION_REQUIRED",kind:"BLOCKED",reason:"project schema requires installer migration"};
39
+
40
+ const p=a?.product?.meta;
41
+ if(p){
42
+ if(Number(state.product.revision)>Number(p.revision))return{id:"PRODUCT_LEDGER_AHEAD",kind:"BLOCKED",reason:"state product revision is ahead of durable product"};
43
+ if(Number(p.revision)>Number(state.product.revision)||normalizeStatus(p.status)!==normalizeStatus(state.product.status))
44
+ return{id:"PRODUCT_LEDGER_SYNC",kind:"STATE",subject:"product",from:String(state.product.revision),to:String(p.revision),reason:"durable product metadata is ahead",workflow:"orchestration.reconcile-state",evidence:[a.product.path],patch:{revision:Number(p.revision),status:p.status}};
45
+ }
46
+ const e=a?.engineering?.meta;
47
+ if(e){
48
+ if(Number(state.planning.revision)>Number(e.revision))return{id:"PLANNING_LEDGER_AHEAD",kind:"BLOCKED",reason:"state planning revision is ahead of durable engineering"};
49
+ const scope=e.scope_status??"UNKNOWN";
50
+ if(Number(e.revision)>Number(state.planning.revision)||normalizeStatus(e.status)!==normalizeStatus(state.planning.status)||normalizeStatus(e.readiness)!==normalizeStatus(state.planning.readiness)||String(e.current_frontier??"")!==String(state.planning.current_frontier??"")||scope!==(state.planning.scope_status??"UNKNOWN"))
51
+ return{id:"PLANNING_LEDGER_SYNC",kind:"STATE",subject:"planning",from:String(state.planning.revision),to:String(e.revision),reason:"durable engineering metadata is ahead",workflow:"orchestration.reconcile-state",evidence:[a.engineering.path],patch:{revision:Number(e.revision),status:e.status,readiness:e.readiness,current_frontier:e.current_frontier??null,scope_status:scope}};
52
+ }
53
+
54
+ const specs=currentAcceptedSpecs(state,a);
55
+ if(specs.length>1)return{id:"MULTIPLE_CURRENT_ACCEPTED_SPECS",kind:"BLOCKED",reason:"multiple current accepted specs"};
56
+ if(specs.length===1&&specId(state.planning.active_spec)!==specs[0][0]){
57
+ const [id,x]=specs[0];return{id:"ACTIVE_SPEC_UNADOPTED",kind:"STATE",subject:id,from:state.planning.active_spec??null,to:x.path,reason:"current accepted spec not adopted",workflow:"orchestration.reconcile-state",evidence:[x.path],patch:{active_spec:x.path}};
58
+ }
59
+
60
+ const s=activeSpec(state,a);
61
+ if(s){
62
+ for(const [id,x] of Object.entries(a?.tickets??{}).sort(([x],[y])=>x.localeCompare(y))){
63
+ if(Object.hasOwn(state.tickets??{},id))continue;
64
+ if(!["DRAFT","READY"].includes(String(x?.meta?.status)))continue;
65
+ if(ticketSourceStatus(x.meta,state,s)!=="CURRENT")continue;
66
+ return{id:"TICKET_UNREGISTERED",kind:"STATE",subject:id,from:null,to:x.meta.status,reason:"current ticket artifact not registered",workflow:"orchestration.reconcile-state",evidence:[x.path],patch:{ticket:id,status:x.meta.status}};
67
+ }
68
+ for(const id of Object.keys(state.tickets??{}).sort()){
69
+ const st=state.tickets[id];if(!EXECUTABLE.has(st))continue;
70
+ const cause=ticketSourceStatus(a?.tickets?.[id]?.meta,state,s);
71
+ if(cause!=="CURRENT"){
72
+ const target=routingPolicy?.invalidation_routes?.[cause]?.state;
73
+ if(target!=="REPLAN_REQUIRED"||!legal(transitions,st,target))return{id:"ILLEGAL_INVALIDATION_TRANSITION",kind:"BLOCKED",reason:st+" cannot transition for "+cause};
74
+ return transition("CONTRACT_SOURCE_STALE",id,st,target,cause,[a?.tickets?.[id]?.path,s.path].filter(Boolean));
75
+ }
76
+ }
77
+ for(const id of Object.keys(state.tickets??{}).sort()){
78
+ const st=state.tickets[id],m=a?.tickets?.[id]?.meta;if(!m)continue;
79
+ const tr=Number(m.revision),sr=Number(s.revision);
80
+ if(st==="READY"){
81
+ const x=start(evidence,id,tr,sr);
82
+ if(x)return transition("IMPLEMENTATION_STARTED",id,"READY","IN_PROGRESS","IMPLEMENTATION_STARTED",[x.path]);
83
+ }
84
+ if(st==="IN_PROGRESS"){
85
+ const x=verify(evidence,id,tr,sr);
86
+ if(x?.value?.schema==="yaaw.evidence/v3"&&x.value.result==="PASS"&&currentRepo(x,repository))
87
+ return transition("IMPLEMENTATION_VERIFIED",id,"IN_PROGRESS","REVIEW_REQUIRED","IMPLEMENTATION_VERIFIED",[x.path]);
88
+ }
89
+ if(st==="REPAIR_REQUIRED"){
90
+ const v=verify(evidence,id,tr,sr),r=review(reviews,id,tr,sr),used=evidenceRefs(r);
91
+ if(v?.value?.schema==="yaaw.evidence/v3"&&v.value.result==="PASS"&&currentRepo(v,repository)&&!used.has(String(v.value.id)))
92
+ return transition("IMPLEMENTATION_VERIFIED",id,"REPAIR_REQUIRED","REVIEW_REQUIRED","REPAIR_VERIFIED",[v.path,r?.path].filter(Boolean));
93
+ }
94
+ if(st==="REVIEW_REQUIRED"){
95
+ const v=verify(evidence,id,tr,sr),r=review(reviews,id,tr,sr),to={PASS:"PASS",REPAIR:"REPAIR_REQUIRED",REPLAN:"REPLAN_REQUIRED",BLOCKED:"BLOCKED"}[r?.value?.result];
96
+ if(r&&to&&currentRepo(r,repository)){
97
+ if(!v||v.value?.schema!=="yaaw.evidence/v3"||v.value.result!=="PASS"||!currentRepo(v,repository))
98
+ return{id:"REVIEW_EVIDENCE_INVALID",kind:"BLOCKED",reason:"current review lacks current PASS verification"};
99
+ if(!evidenceRefs(r).has(String(v.value.id)))
100
+ return{id:"REVIEW_EVIDENCE_INVALID",kind:"BLOCKED",reason:"current review does not reference current PASS verification"};
101
+ if(!legal(transitions,"REVIEW_REQUIRED",to))
102
+ return{id:"ILLEGAL_REVIEW_TRANSITION",kind:"BLOCKED",reason:"review result is not a legal lifecycle transition"};
103
+ return transition("REVIEW_RESULT_UNAPPLIED",id,"REVIEW_REQUIRED",to,"REVIEW_"+r.value.result,[r.path,v.path]);
104
+ }
105
+ }
106
+ if(st==="PASS"){
107
+ const v=verify(evidence,id,tr,sr),r=review(reviews,id,tr,sr);let reason=null;
108
+ if(!r)reason="REVIEW_MISSING";
109
+ else if(!r.value?.repository?.worktree_digest)reason="LEGACY_IDENTITY_UNVERIFIABLE";
110
+ else if(!currentRepo(r,repository))reason="REVIEW_REPOSITORY_STALE";
111
+ else if(!v||v.value?.schema!=="yaaw.evidence/v3"||v.value.result!=="PASS")reason="VERIFICATION_MISSING";
112
+ else if(!currentRepo(v,repository))reason="VERIFICATION_REPOSITORY_STALE";
113
+ else if(!evidenceRefs(r).has(String(v.value.id)))reason="VERIFICATION_MISSING";
114
+ if(reason){
115
+ const target=routingPolicy?.invalidation_routes?.[reason]?.state;
116
+ if(target!=="REVIEW_REQUIRED"||!legal(transitions,"PASS",target))return{id:"ILLEGAL_ACCEPTANCE_INVALIDATION",kind:"BLOCKED",reason};
117
+ return transition("ACCEPTANCE_STALE",id,"PASS",target,reason,[r?.path,v?.path].filter(Boolean));
118
+ }
119
+ }
120
+ }
121
+ }
122
+ const ids=currentTicketIds(state,a),vals=ids.map(id=>state.tickets[id]);
123
+ if(ids.length&&vals.every(x=>x==="PASS"||x==="CANCELLED")&&state.planning.scope_status==="COMPLETE"&&state.phase!=="complete")
124
+ return{id:"PROJECT_COMPLETION_SYNC",kind:"STATE",subject:"project",from:state.phase,to:"complete",reason:"all current admitted tickets terminal and scope COMPLETE",workflow:"orchestration.reconcile-state",evidence:ids};
125
+ return null;
126
+ }
127
+ function deps(id,a,state){return (a?.tickets?.[id]?.meta?.dependencies??[]).every(d=>state.tickets?.[d]==="PASS");}
128
+ function baseRoute(state,a,evidence,p){
129
+ if(state?.blocker)return{kind:"BLOCKED",terminal:"BLOCKED",reason:state.blocker.kind??"PROJECT_BLOCKED"};
130
+ if(normalizeStatus(state?.product?.status)!=="ready")return{kind:"DISPATCH_READY",workflow:p.product_unready_workflow};
131
+ const tickets=state?.tickets??{},replan=Object.keys(tickets).sort().find(id=>tickets[id]==="REPLAN_REQUIRED");if(replan)return{kind:"DISPATCH_READY",workflow:"planning.replan",ticket:replan};
132
+ if(normalizeStatus(state?.planning?.status)!=="ready"||state.planning.readiness!=="PASS")return{kind:"DISPATCH_READY",workflow:p.planning_unready_workflow};
133
+ const s=activeSpec(state,a);if(!s||normalizeStatus(s.status)!=="accepted")return{kind:"DISPATCH_READY",workflow:p.missing_spec_workflow};
134
+ const ids=currentTicketIds(state,a);if(!ids.length)return{kind:"DISPATCH_READY",workflow:p.missing_tickets_workflow};
135
+ for(const [st,w] of [["REPAIR_REQUIRED","implementation.repair-ticket"],["REVIEW_REQUIRED","review.review-ticket"]]){const id=ids.find(x=>tickets[x]===st);if(id)return{kind:"DISPATCH_READY",workflow:w,ticket:id};}
136
+ const ip=ids.find(x=>tickets[x]==="IN_PROGRESS");if(ip){const m=a.tickets[ip].meta;return start(evidence,ip,Number(m.revision),Number(s.revision))?{kind:"DISPATCH_READY",workflow:"implementation.verify-ticket",ticket:ip}:{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",ticket:ip,reason:"IMPLEMENTATION_START_EVIDENCE_MISSING"};}
137
+ const ready=ids.find(x=>tickets[x]==="READY"&&deps(x,a,state));if(ready)return{kind:"DISPATCH_READY",workflow:"implementation.implement-ticket",ticket:ready};
138
+ const vals=ids.map(x=>tickets[x]);if(vals.some(x=>x==="BLOCKED"))return{kind:"BLOCKED",terminal:"BLOCKED",reason:"TICKET_BLOCKED"};
139
+ if(vals.length&&vals.every(x=>x==="PASS"||x==="CANCELLED"))return state.planning.scope_status==="COMPLETE"?{kind:"TERMINAL",terminal:p.complete_terminal}:{kind:"DISPATCH_READY",workflow:p.next_frontier_workflow};
140
+ return{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",reason:"NO_SAFE_SEMANTIC_ROUTE"};
141
+ }
142
+ function legalNow(w,state,a){
143
+ const s=activeSpec(state,a),t=state?.tickets??{},productReady=normalizeStatus(state?.product?.status)==="ready";
144
+ if(w==="prd.route")return true;
145
+ if(["prd.revise","prd.refine"].includes(w))return Boolean(a?.product?.meta);
146
+ if(["planning.route","planning.readiness-review"].includes(w))return productReady;
147
+ if(w==="planning.create-spec")return productReady&&normalizeStatus(state.planning.status)==="ready"&&state.planning.readiness==="PASS"&&!s;
148
+ if(w==="planning.create-tickets")return Boolean(s)&&currentTicketIds(state,a).length===0;
149
+ if(w==="implementation.implement-ticket")return Object.values(t).includes("READY");
150
+ if(w==="implementation.repair-ticket")return Object.values(t).includes("REPAIR_REQUIRED");
151
+ if(w==="review.review-ticket")return Object.values(t).includes("REVIEW_REQUIRED");
152
+ return false;
153
+ }
154
+ export function intentComplete(i,state,a){
155
+ if(!i)return false;const target=String(i.target_artifact??"").match(/TASK-[0-9]+/)?.[0]??null,o=i.desired_outcome,advanced=Number(state?.last_transition?.sequence??0)>Number(i.created_from_transition_sequence??0);
156
+ if(o==="CREATE_SPEC")return currentAcceptedSpecs(state,a).length===1;
157
+ if(o==="CREATE_TICKETS")return currentTicketIds(state,a).length>0;
158
+ if(o==="IMPLEMENT"||o==="REPAIR")return target?state.tickets?.[target]==="REVIEW_REQUIRED":advanced&&state.last_transition?.to==="REVIEW_REQUIRED";
159
+ if(o==="REVIEW")return target?Boolean(state.tickets?.[target]&&state.tickets[target]!=="REVIEW_REQUIRED"):advanced&&state.last_transition?.workflow==="review.record-review";
160
+ if(o==="CONTINUE_PRODUCT")return normalizeStatus(state.product?.status)==="ready";
161
+ if(o==="REVISE_PRODUCT"||o==="REFINE_PRODUCT")return advanced&&state.last_transition?.subject==="product";
162
+ if(o==="CONTINUE_PLANNING")return normalizeStatus(state.planning?.status)==="ready"&&state.planning?.readiness==="PASS";
163
+ if(o==="PLANNING_REVIEW")return ["PASS","MISSING_DECISIONS","PRODUCT_GAP","REPLAN","BLOCKED"].includes(state.planning?.readiness);
164
+ return false;
165
+ }
166
+ export function selectRoute({state,artifacts:a,evidence=[],intent,routingPolicy}){
167
+ const normal=baseRoute(state,a,evidence,routingPolicy);if(!intent||intent.desired_outcome==="CONTINUE")return normal;
168
+ if(intentComplete(intent,state,a))return{kind:"INTENT_COMPLETE",reason:intent.completion_kind};
169
+ if(intent.requested_workflow&&legalNow(intent.requested_workflow,state,a)){const map={"implementation.implement-ticket":"READY","implementation.repair-ticket":"REPAIR_REQUIRED","review.review-ticket":"REVIEW_REQUIRED"},st=map[intent.requested_workflow],ticket=st&&Object.keys(state.tickets??{}).sort().find(id=>state.tickets[id]===st);return{kind:"DISPATCH_READY",workflow:intent.requested_workflow,...(ticket?{ticket}:{})};}
170
+ if(["REVIEW","REPAIR"].includes(intent.desired_outcome))return{kind:"ROOT_ACTION",workflow:"orchestration.recover-interruption",reason:intent.desired_outcome+"_PRECONDITION_UNSATISFIED"};
171
+ return normal;
172
+ }
173
+ export function applyReconciliation(state,r,repo){
174
+ if(!r||r.kind!=="STATE")throw new Error("reconciliation is not applicable");const n=structuredClone(state),seq=Number(n.transition_sequence??0)+1;
175
+ if(r.id==="PRODUCT_LEDGER_SYNC")Object.assign(n.product,r.patch);
176
+ else if(r.id==="PLANNING_LEDGER_SYNC")Object.assign(n.planning,r.patch);
177
+ else if(r.id==="ACTIVE_SPEC_UNADOPTED")n.planning.active_spec=r.patch.active_spec;
178
+ else if(r.id==="TICKET_UNREGISTERED"){n.tickets[r.patch.ticket]=r.patch.status;if(!n.active_ticket&&r.patch.status==="READY")n.active_ticket=r.patch.ticket;}
179
+ else if(r.id==="PROJECT_COMPLETION_SYNC"){n.phase="complete";n.active_ticket=null;}
180
+ else if(r.subject?.startsWith?.("TASK-")){n.tickets[r.subject]=r.to;if(["IN_PROGRESS","REVIEW_REQUIRED","REPAIR_REQUIRED","REPLAN_REQUIRED"].includes(r.to))n.active_ticket=r.subject;if(["PASS","CANCELLED"].includes(r.to)&&n.active_ticket===r.subject)n.active_ticket=null;}
181
+ n.transition_sequence=seq;n.last_transition={sequence:seq,subject:r.subject,from:r.from??null,to:r.to,workflow:r.workflow??"orchestration.reconcile-state",reason:r.reason,evidence:r.evidence??[],observed_commit:repo?.head_commit??null};n.last_observed_commit=repo?.head_commit??null;n.last_workflow=r.workflow??"orchestration.reconcile-state";return n;
182
+ }
183
+ export function planNext(input){
184
+ if(input?.framework?.integrity_status!=="HEALTHY"){const s=input?.framework?.integrity_status??"UNKNOWN";return{kind:"FRAMEWORK_STOP",reason:s==="CONTRACT_INCONSISTENT"?"FRAMEWORK_CONTRACT_INCONSISTENCY":["UNKNOWN","MANIFEST_INVALID"].includes(s)?"FRAMEWORK_INTEGRITY_UNKNOWN":"FRAMEWORK_INTEGRITY_VIOLATION"};}
185
+ const r=selectReconciliation(input);if(r?.kind==="BLOCKED")return{kind:"BLOCKED",terminal:"BLOCKED",reason:r.id,reconciliation:r};if(r)return{kind:"RECONCILE_REQUIRED",workflow:"orchestration.reconcile-state",reconciliation:r};return selectRoute(input);
186
+ }
187
+ export const handoffBasis=({selected,state,intent})=>({workflow:selected.workflow,ticket:selected.ticket??null,desired_intent:intent?.desired_outcome??null,transition_sequence:Number(state?.transition_sequence??0)});