@ornexus/neocortex 4.60.41 → 4.60.43

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 (62) hide show
  1. package/dist/sbom.cdx.json +31 -31
  2. package/install.ps1 +1 -1
  3. package/install.sh +1 -1
  4. package/package.json +14 -2
  5. package/packages/client/dist/commands/invoke.js +57 -56
  6. package/packages/client/dist/generated/command-registry.generated.d.ts +66 -23
  7. package/packages/client/dist/generated/command-registry.generated.js +1 -1
  8. package/packages/client/dist/generated/public-projections.generated.d.ts +100 -0
  9. package/packages/client/dist/generated/public-projections.generated.js +1 -0
  10. package/packages/client/dist/git/git-lifecycle-store.d.ts +17 -0
  11. package/packages/client/dist/git/git-lifecycle-store.js +4 -4
  12. package/packages/client/dist/index.d.ts +1 -0
  13. package/packages/client/dist/index.js +1 -1
  14. package/packages/client/dist/migrations/project-identity.js +7 -7
  15. package/packages/client/dist/migrations/state-json.d.ts +14 -0
  16. package/packages/client/dist/migrations/state-json.js +4 -0
  17. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.d.ts +2 -0
  18. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -1
  19. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +8 -8
  20. package/packages/client/dist/runner/headless/run-yoloop.d.ts +7 -1
  21. package/packages/client/dist/runner/headless/run-yoloop.js +2 -2
  22. package/packages/client/dist/state/project-state-snapshot.js +1 -1
  23. package/packages/client/dist/state/state-json-repair.js +3 -3
  24. package/packages/client/dist/yoloop/invoke-hooks.d.ts +2 -3
  25. package/packages/client/dist/yoloop/invoke-hooks.js +9 -8
  26. package/packages/client/dist/yoloop/shared-discover-epics.d.ts +3 -3
  27. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1
  28. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +87 -1
  29. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +27 -3
  30. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  31. package/packages/shared/dist/command-registry-contract.js +38 -0
  32. package/packages/shared/dist/continuity/goal-operation-authority.js +48 -0
  33. package/packages/shared/dist/executable-graph-catalog/contracts.js +935 -0
  34. package/packages/shared/dist/generated/command-registry.generated.js +82 -23
  35. package/packages/shared/dist/generated/invocation-schema.generated.js +12 -4
  36. package/packages/shared/dist/generated/public-projections.generated.js +149 -0
  37. package/packages/shared/dist/invocation/executable-policy-recovery.d.ts +83 -0
  38. package/packages/shared/dist/invocation/executable-policy-recovery.js +272 -0
  39. package/packages/shared/dist/invocation/index.d.ts +3 -1
  40. package/packages/shared/dist/invocation/index.js +40 -1
  41. package/packages/shared/dist/invocation/recovery-route-compiler.d.ts +56 -0
  42. package/packages/shared/dist/invocation/recovery-route-compiler.js +214 -0
  43. package/packages/shared/dist/state/index.d.ts +1 -0
  44. package/packages/shared/dist/state/index.js +1 -0
  45. package/packages/shared/dist/state/state-migration.d.ts +58 -0
  46. package/packages/shared/dist/state/state-migration.js +404 -0
  47. package/packages/shared/dist/yoloop/canonical.js +15 -0
  48. package/packages/shared/dist/yoloop/operational-budget.js +164 -0
  49. package/packages/shared/dist/yoloop/recovery-types.js +39 -0
  50. package/targets-stubs/antigravity/gemini.md +1 -1
  51. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  52. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  53. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  54. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  55. package/targets-stubs/claude-code/neocortex.md +2 -2
  56. package/targets-stubs/codex/AGENTS.md +11 -2
  57. package/targets-stubs/codex/README.md +7 -4
  58. package/targets-stubs/codex/neocortex.toml +53 -6
  59. package/targets-stubs/cursor/agent.md +2 -2
  60. package/targets-stubs/gemini-cli/agent.md +2 -2
  61. package/targets-stubs/opencode/neocortex-root.md +1 -1
  62. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,3 +1,3 @@
1
- import{existsSync as q,mkdirSync as B,readFileSync as H,renameSync as K,writeFileSync as W,copyFileSync as V}from"node:fs";import{join as E}from"node:path";import{randomBytes as Z}from"node:crypto";import{readGitLifecycleRuntimeSync as U,verifyAcceptedGitLifecycleStoryCheckpointSync as Y}from"../git/git-lifecycle-store.js";class b extends Error{reasonCode;constructor(t,o){super(t),this.reasonCode=o,this.name="StateRepairHardStopError"}}const Q="P189-GAP-STATE-SHAPE",h={reconciled:"epic_aggregate_reconciled",totalRecounted:"epic_total_stories_recounted",completedRecounted:"epic_completed_stories_recounted",statusPromotedDone:"epic_status_promoted_done",completedAtSet:"epic_completed_at_set",stateNotObject:"epic_aggregate_state_not_object",epicsNotObject:"epic_aggregate_epics_not_object",storiesNotObject:"epic_aggregate_stories_not_object",scopeProjected:"epic_aggregate_scope_projected",epicNotObject:"epic_aggregate_epic_not_object",epicIdConflict:"epic_aggregate_epic_id_conflict",storyListNotArray:"epic_aggregate_story_list_not_array",storyRefInvalid:"epic_aggregate_story_ref_invalid",storyRefDuplicate:"epic_aggregate_story_ref_duplicate",storyMissing:"epic_aggregate_story_missing",storyNotObject:"epic_aggregate_story_not_object",storyIdConflict:"epic_aggregate_story_id_conflict",storyEpicConflict:"epic_aggregate_story_epic_conflict",terminalIncompleteConflict:"epic_aggregate_terminal_incomplete_conflict"},X=new Set(["yolo","implement","diagnose","research","write-spec","create-tasks","refactor-suggest","property-tests","validate-tests","qa-gate","update-project-memory","commit-push","sync-main","create-pr","review-pr","merge-pr","review"]),ee=new Set(["yoloop","auto-yolo","epic-runner","story-runner","loop-yolo","auto-epic"]);function l(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function x(){return new Date().toISOString()}function te(){return x().replace(/[:.]/g,"-")}function k(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"&&t.length>0):[]}function $(e){const[t]=e.trim().split(/\s+/,1);return t?t.replace(/^@?\*/,"").replace(/^@/,"").toLowerCase():null}function oe(e){const[t]=e.trim().split(/\s+/,1);if(!t)return!1;const o=t.replace(/^@/,"");return/^[A-Z]?\d+(?:\.\d+)+$/i.test(o)||/(?:^|\/)[A-Z]?\d+(?:\.\d+)+\.story\.md$/i.test(o)}function O(e){const t=e.match(/(?:^|[/@\s])([A-Z]?\d+(?:\.\d+)+)\.story\.md\b/i);return t?.[1]?t[1]:e.match(/(?:^|\s|@)([A-Z]?\d+(?:\.\d+)+)(?=$|\s)/i)?.[1]??null}function P(e,t){if(typeof e!="string")return!1;const o=t??$(e);return(o==="yolo"||o==="yolo-inline")&&T(e).length>0&&O(e)===null}function T(e){const t=e.trim(),o=t.search(/\s/);return o===-1?"":t.slice(o).trim()}function re(e){const t=T(e).split(/\s+/).find(o=>o.length>0&&!o.startsWith("-"));return t?t.replace(/^@/,"").replace(/^['"]|['"]$/g,""):null}function D(e,t){const r=e.replace(/^@/,"").replace(/^epic-/i,"").replace(/\.md$/i,""),s=[r,r.toUpperCase(),/^p\d+$/i.test(r)?`P${r.replace(/^p/i,"")}`:null,/^\d+$/.test(r)?`P${r}`:null].filter(a=>typeof a=="string"&&a.length>0);for(const a of s)if(l(t[a]))return a;return null}function se(e,t){const o=e.match(/(?:^|[/@\s])epic-(P?\d+)\.md\b/i);return o?.[1]?D(o[1],t):null}function L(e,t,o){if(typeof t!="string"||t.trim().length===0||!l(e.epics)||!l(e.stories))return;const r=t.trim(),s=o??$(r);if(s!==null&&X.has(s)||oe(r)){const u=O(r),p=u?e.stories[u]:void 0;if(l(p)&&typeof p.epic_id=="string"&&l(e.epics[p.epic_id]))return p.epic_id;const n=u?.split(".")[0];if(n&&l(e.epics[n]))return n}if(s!==null&&ee.has(s)){const u=se(r,e.epics);if(u)return u;const p=re(r);if(p){const n=D(p,e.epics);if(n)return n}}}const R="pr_merged_issue_closed_reconciled",ne="invalid_workflow_issue_removed";function w(e){return typeof e=="string"?e.trim().toLowerCase().replace(/[_\s]+/g,"-"):""}const ie=new Set(["done","completed","merged"]),ce=new Set(["done","completed","merged"]);function M(e){return ie.has(w(e))}function G(e){return ce.has(w(e))}function S(e,t){throw new b(`${t} (${Q} if this shape is intentionally new).`,e)}function ae(e){return JSON.parse(JSON.stringify(e))}function pe(e,t){const o=t.scope??e.scope;if(!l(o))return;const r=typeof o.kind=="string"?o.kind:null,s=typeof o.omittedStoryCount=="number"?o.omittedStoryCount:0,a=typeof o.omittedEpicCount=="number"?o.omittedEpicCount:0;(r==="projected"||s>0||a>0)&&S(h.scopeProjected,"[Neocortex] state.json aggregate reconciliation stopped: projected or partial state coverage cannot prove epic aggregates.")}function le(e,t){const o=[],r=new Set;for(const s of t)(typeof s!="string"||s.length===0)&&S(h.storyRefInvalid,`[Neocortex] state.json aggregate reconciliation stopped: epic "${y(e)}" has a non-string or empty story reference.`),r.has(s)&&S(h.storyRefDuplicate,`[Neocortex] state.json aggregate reconciliation stopped: epic "${y(e)}" lists story "${y(s)}" more than once.`),r.add(s),o.push(s);return o}function de(e,t,o){const r=o[t];return r===void 0&&S(h.storyMissing,`[Neocortex] state.json aggregate reconciliation stopped: epic "${y(e)}" references missing story "${y(t)}".`),l(r)||S(h.storyNotObject,`[Neocortex] state.json aggregate reconciliation stopped: story "${y(t)}" is not an object.`),r.id!==t&&S(h.storyIdConflict,`[Neocortex] state.json aggregate reconciliation stopped: story key "${y(t)}" conflicts with its id field.`),r.epic_id!==e&&S(h.storyEpicConflict,`[Neocortex] state.json aggregate reconciliation stopped: story "${y(t)}" does not belong to epic "${y(e)}".`),r}function C(e,t){e.includes(t)||e.push(t)}function ue(e,t={}){l(e)||S(h.stateNotObject,"[Neocortex] state.json aggregate reconciliation stopped: top-level state must be an object."),l(e.epics)||S(h.epicsNotObject,"[Neocortex] state.json aggregate reconciliation stopped: `epics` must be an object before counts are authoritative."),l(e.stories)||S(h.storiesNotObject,"[Neocortex] state.json aggregate reconciliation stopped: `stories` must be an object before counts are authoritative."),pe(e,t);const o=ae(e),r=o.epics,s=o.stories,a=[],u=[],p=new Set(t.effectiveDoneStoryIds??[]);for(const[n,c]of Object.entries(r)){if(t.targetEpicId&&n!==t.targetEpicId)continue;l(c)||S(h.epicNotObject,`[Neocortex] state.json aggregate reconciliation stopped: epic "${y(n)}" is not an object.`),c.id!==n&&S(h.epicIdConflict,`[Neocortex] state.json aggregate reconciliation stopped: epic key "${y(n)}" conflicts with its id field.`),Array.isArray(c.stories)||S(h.storyListNotArray,`[Neocortex] state.json aggregate reconciliation stopped: epic "${y(n)}" has non-array stories.`);const g=le(n,c.stories),i=g.length,d=g.filter(j=>{const _=de(n,j,s);return M(_.status)||p.has(j)}).length,f=i>0&&d===i;if(G(c.status)&&!f&&i>0&&S(h.terminalIncompleteConflict,`[Neocortex] state.json aggregate reconciliation stopped: terminal epic "${y(n)}" has incomplete listed stories.`),t.preserveGitLifecycleAggregates&&(c.branch_mode==="epic"||typeof c.execution_branch=="string"))continue;const m=[],v={total_stories:c.total_stories,completed_stories:c.completed_stories,status:c.status,completed_at:c.completed_at};if(c.total_stories!==i&&(c.total_stories=i,C(m,h.totalRecounted)),c.completed_stories!==d&&(c.completed_stories=d,C(m,h.completedRecounted)),!G(c.status)&&f&&(c.status="done",C(m,h.statusPromotedDone),(c.completed_at===void 0||c.completed_at===null)&&(c.completed_at=t.nowIso??x(),C(m,h.completedAtSet))),m.length>0){C(m,h.reconciled);for(const j of m)C(a,j);u.push({epicId:n,reasonCodes:m,previous:v,next:{total_stories:i,completed_stories:d,status:c.status,completed_at:c.completed_at}})}}return{mutated:JSON.stringify(o)!==JSON.stringify(e),state:o,reasonCodes:a,changes:u}}function A(e,t){const o=e[t];return l(o)?o:null}function _e(e){const t=A(e,"issueCloseAudit")??A(e,"issue_close_audit");if(t){const s=w(t.event).replace(/-/g,"_"),a=t.publicSafe===!0||t.public_safe===!0,u=t.rawLogStorage==="forbidden"||t.raw_log_storage==="forbidden";if(s==="story_issue_closed"&&a&&u)return!0}const o=A(e,"completionEvidence")??A(e,"completion_evidence"),r=w(o?.issueState??o?.issue_state??o?.state);return e.issue_closed===!0||w(e.issue_state??e.issueState??e.workflow_issue_state)==="closed"||r==="closed"}function z(e){const t=A(e,"completionEvidence")??A(e,"completion_evidence"),o=A(e,"scopedCiEvidence")??A(e,"scoped_ci_evidence"),r=[e.physicalMergeStatus,e.pr_state,e.prState,e.pull_request_state,e.pullRequestState,t?.prState,t?.pr_state,t?.pullRequestState,o?.prState].map(w);return e.merged===!0||r.includes("merged")||w(o?.finalState)==="can-merge"&&e.merged===!0}function fe(e){const t=A(e,"completionEvidence")??A(e,"completion_evidence"),o=[e.pr_state,e.prState,e.pull_request_state,e.pullRequestState,t?.prState,t?.pr_state,t?.pullRequestState].map(w),r=z(e),s=["denied-external-gate","denied-local-or-repo","not-attempted"].includes(w(e.physicalMergeStatus)),a=o.includes("closed")||o.includes("closed-unmerged");return r&&(e.merged===!1||s||a)||e.merged===!0&&s}function ge(e,t,o){const r=`state-completion-reconciliation:${y(t)}`,s=e.filter(l).map(n=>({...n})),a={id:r,status:"open",reason:"stale_state_completion_evidence_observed",reason_codes:[R],category:"state/completion-reconciliation",story_id:y(t),public_safe:!0,raw_log_storage:"forbidden",created_at:o,last_seen_at:o,occurrences:1},u=s.findIndex(n=>n.id===r);if(u<0)return[...s,a];const p=s[u];return s[u]={...p,...a,created_at:typeof p.created_at=="string"?p.created_at:o,occurrences:Math.max(1,typeof p.occurrences=="number"?p.occurrences:1)+1},s}function me(e,t,o,r){const s=l(e.stories)?e.stories:null;if(!s)return;const a=[],u=x();for(const[p,n]of Object.entries(s)){if(!l(n))continue;const c=typeof n.epic_id=="string"?n.epic_id:p.split(".")[0];if(r&&c!==r)continue;const g=w(n.status);if(fe(n))throw new b(`[Neocortex] state.json repair stopped: story "${p}" has conflicting PR merge completion evidence. Resolve .neocortex/state.json manually before invoking Neocortex.`,"conflicting_completion_evidence");if(g!=="pr-reviewed"||!z(n)||!_e(n))continue;const i=typeof n.status=="string"?n.status:g,d=k(n.steps_completed);n.status="done",n.last_step="step-c-12-merge-pr",n.steps_completed=Array.from(new Set([...d,"step-c-12-merge-pr"])),n.merged=!0,n.physicalMergeStatus="merged",n.completion_reconciled_at=u,t.push(`reconciled completion evidence for story ${p}`),o.push(R),a.push({schemaVersion:1,publicSafe:!0,rawLogStorage:"forbidden",storyId:y(p),previousStatus:i,nextStatus:"done",reasonCodes:[R],reconciledAt:u}),e.state_repair_debt=ge(Array.isArray(e.state_repair_debt)?e.state_repair_debt:[],p,u)}a.length>0&&(e.state_completion_reconciliations=[...Array.isArray(e.state_completion_reconciliations)?e.state_completion_reconciliations.filter(l):[],...a])}function ye(e,t,o){const r=l(e.stories)?e.stories:null;if(r)for(const[s,a]of Object.entries(r))!l(a)||!Object.prototype.hasOwnProperty.call(a,"workflow_issue")||typeof a.workflow_issue=="number"&&Number.isFinite(a.workflow_issue)||(delete a.workflow_issue,t.push(`removed invalid workflow_issue from story ${y(s)}`),o.push(ne))}function he(e,t,o){const r=o[e];if(!l(r))return;const s=r.epic_id;if(typeof s=="string"&&s!==t)throw new b(`[Neocortex] state.json repair stopped: story "${e}" belongs to epic "${s}" but legacy planning maps it to "${t}". Resolve the conflicting IDs in .neocortex/state.json, then rerun the command.`,"conflicting_story_epic_id")}function Se(e,t={}){if(Array.isArray(e.stories))throw new b("[Neocortex] state.json repair stopped: `stories` is an Array. Convert it to an object keyed by story ID or run `*migrate-state --preview` for guidance.","stories_array_ambiguous");if(l(e.epics)){for(const[i,d]of Object.entries(e.epics))if(l(d)&&d.stories!==void 0&&!Array.isArray(d.stories))throw new b(`[Neocortex] state.json repair stopped: epic "${i}" has non-array \`stories\`. Repair this ambiguous shape manually before invoking Neocortex.`,"epic_stories_not_array")}if(e.schema_version!==void 0&&e.schema_version!==1)throw new b(`[Neocortex] state.json repair stopped: schema_version=${String(e.schema_version)} is not a safe automatic downgrade target. Run \`*migrate-state --preview\` or repair the schema version manually.`,"unsupported_schema_version");if(P(t.args,t.canonicalCommandId)){for(const i of["epics","stories"]){const d=e[i];if(d!==void 0){if(!l(d))throw new b("[Neocortex] inline materialization stopped: invalid state collection.","inline_state_collection_invalid");for(const[f,m]of Object.entries(d)){if(!l(m))throw new b("[Neocortex] inline materialization stopped: invalid state record.","inline_state_record_invalid");if(m.id!==void 0&&m.id!==f)throw new b("[Neocortex] inline materialization stopped: conflicting state identity.","inline_state_identity_conflict")}}}return{mutated:!1,state:e,reasonCodes:[],changes:[]}}let o=JSON.parse(JSON.stringify(e));const r=[],s=[];let a=[];l(o.epics)||(o.epics={},r.push("initialized epics{}"),s.push("initialized_epics_object")),l(o.stories)||(o.stories={},r.push("initialized stories{}"),s.push("initialized_stories_object"));const u=o.epics,p=o.stories,n=L(o,t.args,t.canonicalCommandId),c=l(o.planning)?o.planning:null;if(c&&l(c.current)){const i=c.current,d=typeof i.epic_id=="string"?i.epic_id:null;if(!d)throw new b("[Neocortex] state.json repair stopped: legacy planning.current has no epic_id. Add the missing epic_id or run `*migrate-state --preview`.","planning_current_missing_epic_id");const f=Array.isArray(i.stories)?i.stories:[],m=f.map(_=>l(_)&&typeof _.id=="string"?_.id:typeof _=="string"?_:null).filter(_=>!!_);for(const _ of m)he(_,d,p);l(u[d])||(u[d]={id:d,title:typeof i.title=="string"?i.title:`Epic ${d}`,status:i.status==="done"?"done":"in-progress",stories:m,created_at:typeof i.created_at=="string"?i.created_at:x(),completed_at:typeof i.completed_at=="string"?i.completed_at:null,completed_stories:typeof i.completed_stories=="number"?i.completed_stories:0,total_stories:typeof i.total_stories=="number"?i.total_stories:m.length,source:"client-auto-repair-P156.05"},r.push(`migrated planning.current to epics["${d}"]`),s.push("legacy_planning_current_migrated"));for(const _ of f)!l(_)||typeof _.id!="string"||l(p[_.id])||(p[_.id]={..._,id:_.id,epic_id:d,title:typeof _.title=="string"?_.title:_.id,status:typeof _.status=="string"?_.status:"backlog",depends_on:k(_.depends_on),created_at:typeof _.created_at=="string"?_.created_at:x()},r.push(`migrated embedded story ${_.id}`),s.push("legacy_planning_story_migrated"));const v=typeof i.status=="string"?i.status.trim().toLowerCase().replace(/[_\s]+/g,"-"):"",I=new Set(["epic_id","title","status","created_at","updated_at","completed_at","completed_stories","total_stories","stories"]),j=Object.keys(i).filter(_=>!I.has(_));v==="generated"&&j.length===0?(delete c.current,r.push("removed safe legacy planning.current generated container"),s.push("legacy_planning_current_generated_removed")):j.length>0&&(r.push(`preserved planning.current unknown keys: ${j.sort().join(", ")}`),s.push("unknown_planning_current_keys_preserved"))}if(c&&Array.isArray(c.history)){let i=!0;const d=new Set(["epic_id","title","status","created_at","completed_at","completed_stories","total_stories","stories"]);for(const f of c.history){if(!l(f)){i=!1;continue}Object.keys(f).filter(I=>!d.has(I)).length>0&&(i=!1);const v=typeof f.epic_id=="string"?f.epic_id:null;if(!v){i=!1;continue}l(u[v])||(u[v]={id:v,title:typeof f.title=="string"?f.title:`Epic ${v}`,status:"done",stories:k(f.stories),created_at:typeof f.created_at=="string"?f.created_at:x(),completed_at:typeof f.completed_at=="string"?f.completed_at:x(),completed_stories:typeof f.completed_stories=="number"?f.completed_stories:0,total_stories:typeof f.total_stories=="number"?f.total_stories:0,source:"client-auto-repair-P156.05"},r.push(`migrated planning.history to epics["${v}"]`),s.push("legacy_planning_history_migrated"))}i?(delete c.history,r.push("removed safe legacy planning.history container"),s.push("legacy_planning_history_removed")):(r.push("preserved planning.history because one or more entries were not safely removable"),s.push("legacy_planning_history_preserved"))}if(c&&Object.keys(c).length===0&&(delete o.planning,r.push("removed empty planning container"),s.push("empty_planning_removed")),o.schema_version!==1&&(o.schema_version=1,r.push("set schema_version=1"),s.push("schema_version_added")),ye(o,r,s),me(o,r,s,n),typeof t.args=="string"){const i=ue(o,{nowIso:t.nowIso,targetEpicId:n,effectiveDoneStoryIds:t.effectiveDoneStoryIds,preserveGitLifecycleAggregates:!0});if(i.mutated){o=i.state,a=i.changes;for(const d of i.reasonCodes)s.push(d);for(const d of i.changes)r.push(`reconciled aggregate for epic ${y(d.epicId)}`)}}const g=JSON.stringify(o)!==JSON.stringify(e);if(g){const i=t.nowIso??x(),d=Array.from(new Set(s));a.length>0&&we(o,a,i),o.last_updated=i,o.state_migration_repairs=[...Array.isArray(o.state_migration_repairs)?o.state_migration_repairs:[],{schemaVersion:1,action:"client_auto_repair",reasonCodes:d,repairedAt:i}],o.state_repair_debt=Ae(Array.isArray(o.state_repair_debt)?o.state_repair_debt:[],d,i)}return{mutated:g,state:o,reasonCodes:Array.from(new Set(s)),changes:r}}function F(e){return typeof e=="number"&&Number.isFinite(e)?e:null}function J(e){const t=w(e);return t.length>0?N(t).slice(0,80):null}function be(e,t){return{schemaVersion:1,publicSafe:!0,epicId:y(e.epicId),reasonCodes:e.reasonCodes.map(N).filter(Boolean),previous:{total_stories:F(e.previous.total_stories),completed_stories:F(e.previous.completed_stories),status:J(e.previous.status),completed_at_present:typeof e.previous.completed_at=="string"&&e.previous.completed_at.length>0},next:{total_stories:e.next.total_stories,completed_stories:e.next.completed_stories,status:J(e.next.status),completed_at_present:typeof e.next.completed_at=="string"&&e.next.completed_at.length>0},repairedAt:t}}function ve(e,t,o){let r=e.filter(l).map(s=>({...s}));for(const s of t){const a=y(s.epicId),u=Array.from(new Set(s.reasonCodes.map(N).filter(Boolean))).sort(),p=`state-aggregate-reconciliation:${a}`,n={id:p,status:"open",reason:"epic_aggregate_reconciled",reason_codes:u,category:"state/epic-aggregate-reconciliation",epic_id:a,created_at:o,last_seen_at:o,occurrences:1},c=r.findIndex(i=>i.id===p);if(c<0){r=[...r,n];continue}const g=r[c];r[c]={...g,...n,created_at:typeof g.created_at=="string"?g.created_at:o,occurrences:Math.max(1,typeof g.occurrences=="number"?g.occurrences:1)+1}}return r}function we(e,t,o){e.state_aggregate_reconciliations=[...Array.isArray(e.state_aggregate_reconciliations)?e.state_aggregate_reconciliations.filter(l):[],...t.map(r=>be(r,o))],e.state_repair_debt=ve(Array.isArray(e.state_repair_debt)?e.state_repair_debt:[],t,o)}function Ae(e,t,o){const r=Array.from(new Set(t.map(N).filter(Boolean))).sort(),s=`state-drift-repair:${r.join("+")||"unknown"}`,a=e.filter(l).map(c=>({...c})),u={id:s,status:"open",reason:"state_json_drift_repaired",reason_codes:r,category:"state/drift-repair",created_at:o,last_seen_at:o,occurrences:1},p=a.findIndex(c=>c.id===s);if(p<0)return[...a,u];const n=a[p];return a[p]={...n,...u,created_at:typeof n.created_at=="string"?n.created_at:o,occurrences:Math.max(1,typeof n.occurrences=="number"?n.occurrences:1)+1},a}function N(e){return e.replace(/\b(sk|ghp|gho|github_pat|xoxb|AKIA)[A-Za-z0-9_-]{8,}\b/g,"[redacted]").replace(/(password|passwd|secret|token|api[_-]?key)\s*[:=]\s*[^,\s)]+/gi,"$1=[redacted]").replace(/https?:\/\/[^\s)]+/gi,"[url-redacted]").replace(/[^a-z0-9_+-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,120)}function y(e){return e.replace(/[^a-z0-9_.:-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,120)||"unknown"}function Ne(e,t={}){const o=E(e,".neocortex"),r=E(o,"state.json");if(!q(r))return{repaired:!1,reasonCodes:[],changes:[]};let s;try{s=JSON.parse(H(r,"utf8"))}catch(d){const f=d instanceof Error?d.message:String(d);throw new b(`[Neocortex] state.json repair stopped: invalid JSON in .neocortex/state.json (${f}). No files were overwritten. Fix the JSON or restore a backup, then rerun.`,"invalid_json")}if(!l(s))throw new b("[Neocortex] state.json repair stopped: top-level state must be a JSON object. Restore a valid .neocortex/state.json before invoking Neocortex.","state_not_object");const a=L(s,t.args,t.canonicalCommandId),u=P(t.args,t.canonicalCommandId)?[]:je(e,s,a),p=Se(s,{...t,effectiveDoneStoryIds:u});if(!p.mutated)return{repaired:!1,reasonCodes:[],changes:[]};B(o,{recursive:!0});const n=te(),c=E(o,`state.json.backup-${n}`),g=E(o,`state.json.tmp.${Z(6).toString("hex")}`);V(r,c),W(g,`${JSON.stringify(p.state,null,2)}
2
- `,"utf8"),K(g,r);const i=`.neocortex/state.json.backup-${n}`;return process.stderr.write(`[Neocortex P156.05] state.json auto-repaired before invoke; backup=${i}; reasonCodes=${p.reasonCodes.join(",")}; changes=${p.changes.join("; ")}
3
- `),{repaired:!0,reasonCodes:p.reasonCodes,backupPath:i,changes:p.changes}}function je(e,t,o){const r=U(e),s=l(t.epics)?t.epics:{},a=l(t.stories)?t.stories:{},u=[];for(const[p,n]of Object.entries(r.stories)){if(!l(n)||!M(n.status)||!n.checkpoint)continue;const c=l(a[p])?a[p]:null,g=typeof c?.epic_id=="string"?c.epic_id:p.split(".")[0];if(!g||o&&g!==o)continue;const i=l(s[g])?s[g]:null,d=l(r.epics[g])?r.epics[g]:null,f=d?.branch_mode??i?.branch_mode,m=d?.execution_branch??i?.execution_branch;if(f!=="epic"||typeof m!="string")continue;Y(e,n.checkpoint,p,g,m).verdict==="verified"&&u.push(p)}return u.sort()}export{h as EPIC_AGGREGATE_REASON_CODES,Q as P189_STATE_SHAPE_GAP_CODE,b as StateRepairHardStopError,P as isInlineYoloMaterialization,M as isP189DoneLikeStoryStatus,Ne as maybeRepairStateJsonBeforeInvoke,ue as reconcileEpicAggregatesInState};
1
+ import{existsSync as F,mkdirSync as J,readFileSync as q,renameSync as B,writeFileSync as H,copyFileSync as W}from"node:fs";import{join as x}from"node:path";import{randomBytes as V}from"node:crypto";import{planStateMigration as Z,StateMigrationHardStopError as U}from"../../../shared/dist/state/index.js";import{applyStateMigration as K,previewStateMigration as Y,StateMigrationApplyError as Q}from"../migrations/state-json.js";import{readGitLifecycleRuntimeSync as X,verifyAcceptedGitLifecycleStoryCheckpointSync as ee}from"../git/git-lifecycle-store.js";class S extends Error{reasonCode;constructor(t,o){super(t),this.reasonCode=o,this.name="StateRepairHardStopError"}}const te="P189-GAP-STATE-SHAPE",f={reconciled:"epic_aggregate_reconciled",totalRecounted:"epic_total_stories_recounted",completedRecounted:"epic_completed_stories_recounted",statusPromotedDone:"epic_status_promoted_done",completedAtSet:"epic_completed_at_set",stateNotObject:"epic_aggregate_state_not_object",epicsNotObject:"epic_aggregate_epics_not_object",storiesNotObject:"epic_aggregate_stories_not_object",scopeProjected:"epic_aggregate_scope_projected",epicNotObject:"epic_aggregate_epic_not_object",epicIdConflict:"epic_aggregate_epic_id_conflict",storyListNotArray:"epic_aggregate_story_list_not_array",storyRefInvalid:"epic_aggregate_story_ref_invalid",storyRefDuplicate:"epic_aggregate_story_ref_duplicate",storyMissing:"epic_aggregate_story_missing",storyNotObject:"epic_aggregate_story_not_object",storyIdConflict:"epic_aggregate_story_id_conflict",storyEpicConflict:"epic_aggregate_story_epic_conflict",terminalIncompleteConflict:"epic_aggregate_terminal_incomplete_conflict"},oe=new Set(["yolo","implement","diagnose","research","write-spec","create-tasks","refactor-suggest","property-tests","validate-tests","qa-gate","update-project-memory","commit-push","sync-main","create-pr","review-pr","merge-pr","review"]),re=new Set(["yoloop","auto-yolo","epic-runner","story-runner","loop-yolo","auto-epic"]);function d(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function j(){return new Date().toISOString()}function ne(){return j().replace(/[:.]/g,"-")}function ie(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"&&t.length>0):[]}function k(e){const[t]=e.trim().split(/\s+/,1);return t?t.replace(/^@?\*/,"").replace(/^@/,"").toLowerCase():null}function se(e){const[t]=e.trim().split(/\s+/,1);if(!t)return!1;const o=t.replace(/^@/,"");return/^[A-Z]?\d+(?:\.\d+)+$/i.test(o)||/(?:^|\/)[A-Z]?\d+(?:\.\d+)+\.story\.md$/i.test(o)}function R(e){const t=e.match(/(?:^|[/@\s])([A-Z]?\d+(?:\.\d+)+)\.story\.md\b/i);return t?.[1]?t[1]:e.match(/(?:^|\s|@)([A-Z]?\d+(?:\.\d+)+)(?=$|\s)/i)?.[1]??null}function I(e,t){if(typeof e!="string")return!1;const o=t??k(e);return(o==="yolo"||o==="yolo-inline")&&$(e).length>0&&R(e)===null}function $(e){const t=e.trim(),o=t.search(/\s/);return o===-1?"":t.slice(o).trim()}function ce(e){const t=$(e).split(/\s+/).find(o=>o.length>0&&!o.startsWith("-"));return t?t.replace(/^@/,"").replace(/^['"]|['"]$/g,""):null}function O(e,t){const r=e.replace(/^@/,"").replace(/^epic-/i,"").replace(/\.md$/i,""),s=[r,r.toUpperCase(),/^p\d+$/i.test(r)?`P${r.replace(/^p/i,"")}`:null,/^\d+$/.test(r)?`P${r}`:null].filter(c=>typeof c=="string"&&c.length>0);for(const c of s)if(d(t[c]))return c;return null}function ae(e,t){const o=e.match(/(?:^|[/@\s])epic-(P?\d+)\.md\b/i);return o?.[1]?O(o[1],t):null}function P(e,t,o){if(typeof t!="string"||t.trim().length===0||!d(e.epics)||!d(e.stories))return;const r=t.trim(),s=o??k(r);if(s!==null&&oe.has(s)||se(r)){const l=R(r),a=l?e.stories[l]:void 0;if(d(a)&&typeof a.epic_id=="string"&&d(e.epics[a.epic_id]))return a.epic_id;const n=l?.split(".")[0];if(n&&d(e.epics[n]))return n}if(s!==null&&re.has(s)){const l=ae(r,e.epics);if(l)return l;const a=ce(r);if(a){const n=O(a,e.epics);if(n)return n}}}const E="pr_merged_issue_closed_reconciled",pe="invalid_workflow_issue_removed";function b(e){return typeof e=="string"?e.trim().toLowerCase().replace(/[_\s]+/g,"-"):""}const le=new Set(["done","completed","merged"]),de=new Set(["done","completed","merged"]);function T(e){return le.has(b(e))}function M(e){return de.has(b(e))}function h(e,t){throw new S(`${t} (${te} if this shape is intentionally new).`,e)}function ue(e){return JSON.parse(JSON.stringify(e))}function fe(e,t){const o=t.scope??e.scope;if(!d(o))return;const r=typeof o.kind=="string"?o.kind:null,s=typeof o.omittedStoryCount=="number"?o.omittedStoryCount:0,c=typeof o.omittedEpicCount=="number"?o.omittedEpicCount:0;(r==="projected"||s>0||c>0)&&h(f.scopeProjected,"[Neocortex] state.json aggregate reconciliation stopped: projected or partial state coverage cannot prove epic aggregates.")}function ge(e,t){const o=[],r=new Set;for(const s of t)(typeof s!="string"||s.length===0)&&h(f.storyRefInvalid,`[Neocortex] state.json aggregate reconciliation stopped: epic "${u(e)}" has a non-string or empty story reference.`),r.has(s)&&h(f.storyRefDuplicate,`[Neocortex] state.json aggregate reconciliation stopped: epic "${u(e)}" lists story "${u(s)}" more than once.`),r.add(s),o.push(s);return o}function _e(e,t,o){const r=o[t];return r===void 0&&h(f.storyMissing,`[Neocortex] state.json aggregate reconciliation stopped: epic "${u(e)}" references missing story "${u(t)}".`),d(r)||h(f.storyNotObject,`[Neocortex] state.json aggregate reconciliation stopped: story "${u(t)}" is not an object.`),r.id!==t&&h(f.storyIdConflict,`[Neocortex] state.json aggregate reconciliation stopped: story key "${u(t)}" conflicts with its id field.`),r.epic_id!==e&&h(f.storyEpicConflict,`[Neocortex] state.json aggregate reconciliation stopped: story "${u(t)}" does not belong to epic "${u(e)}".`),r}function v(e,t){e.includes(t)||e.push(t)}function me(e,t={}){d(e)||h(f.stateNotObject,"[Neocortex] state.json aggregate reconciliation stopped: top-level state must be an object."),d(e.epics)||h(f.epicsNotObject,"[Neocortex] state.json aggregate reconciliation stopped: `epics` must be an object before counts are authoritative."),d(e.stories)||h(f.storiesNotObject,"[Neocortex] state.json aggregate reconciliation stopped: `stories` must be an object before counts are authoritative."),fe(e,t);const o=ue(e),r=o.epics,s=o.stories,c=[],l=[],a=new Set(t.effectiveDoneStoryIds??[]);for(const[n,i]of Object.entries(r)){if(t.targetEpicId&&n!==t.targetEpicId)continue;d(i)||h(f.epicNotObject,`[Neocortex] state.json aggregate reconciliation stopped: epic "${u(n)}" is not an object.`),i.id!==n&&h(f.epicIdConflict,`[Neocortex] state.json aggregate reconciliation stopped: epic key "${u(n)}" conflicts with its id field.`),Array.isArray(i.stories)||h(f.storyListNotArray,`[Neocortex] state.json aggregate reconciliation stopped: epic "${u(n)}" has non-array stories.`);const p=ge(n,i.stories),g=p.length,m=p.filter(A=>{const z=_e(n,A,s);return T(z.status)||a.has(A)}).length,_=g>0&&m===g;if(M(i.status)&&!_&&g>0&&h(f.terminalIncompleteConflict,`[Neocortex] state.json aggregate reconciliation stopped: terminal epic "${u(n)}" has incomplete listed stories.`),t.preserveGitLifecycleAggregates&&(i.branch_mode==="epic"||typeof i.execution_branch=="string"))continue;const y=[],N={total_stories:i.total_stories,completed_stories:i.completed_stories,status:i.status,completed_at:i.completed_at};if(i.total_stories!==g&&(i.total_stories=g,v(y,f.totalRecounted)),i.completed_stories!==m&&(i.completed_stories=m,v(y,f.completedRecounted)),!M(i.status)&&_&&(i.status="done",v(y,f.statusPromotedDone),(i.completed_at===void 0||i.completed_at===null)&&(i.completed_at=t.nowIso??j(),v(y,f.completedAtSet))),y.length>0){v(y,f.reconciled);for(const A of y)v(c,A);l.push({epicId:n,reasonCodes:y,previous:N,next:{total_stories:g,completed_stories:m,status:i.status,completed_at:i.completed_at}})}}return{mutated:JSON.stringify(o)!==JSON.stringify(e),state:o,reasonCodes:c,changes:l}}function w(e,t){const o=e[t];return d(o)?o:null}function ye(e){const t=w(e,"issueCloseAudit")??w(e,"issue_close_audit");if(t){const s=b(t.event).replace(/-/g,"_"),c=t.publicSafe===!0||t.public_safe===!0,l=t.rawLogStorage==="forbidden"||t.raw_log_storage==="forbidden";if(s==="story_issue_closed"&&c&&l)return!0}const o=w(e,"completionEvidence")??w(e,"completion_evidence"),r=b(o?.issueState??o?.issue_state??o?.state);return e.issue_closed===!0||b(e.issue_state??e.issueState??e.workflow_issue_state)==="closed"||r==="closed"}function D(e){const t=w(e,"completionEvidence")??w(e,"completion_evidence"),o=w(e,"scopedCiEvidence")??w(e,"scoped_ci_evidence"),r=[e.physicalMergeStatus,e.pr_state,e.prState,e.pull_request_state,e.pullRequestState,t?.prState,t?.pr_state,t?.pullRequestState,o?.prState].map(b);return e.merged===!0||r.includes("merged")||b(o?.finalState)==="can-merge"&&e.merged===!0}function he(e){const t=w(e,"completionEvidence")??w(e,"completion_evidence"),o=[e.pr_state,e.prState,e.pull_request_state,e.pullRequestState,t?.prState,t?.pr_state,t?.pullRequestState].map(b),r=D(e),s=["denied-external-gate","denied-local-or-repo","not-attempted"].includes(b(e.physicalMergeStatus)),c=o.includes("closed")||o.includes("closed-unmerged");return r&&(e.merged===!1||s||c)||e.merged===!0&&s}function Se(e,t,o){const r=`state-completion-reconciliation:${u(t)}`,s=e.filter(d).map(n=>({...n})),c={id:r,status:"open",reason:"stale_state_completion_evidence_observed",reason_codes:[E],category:"state/completion-reconciliation",story_id:u(t),public_safe:!0,raw_log_storage:"forbidden",created_at:o,last_seen_at:o,occurrences:1},l=s.findIndex(n=>n.id===r);if(l<0)return[...s,c];const a=s[l];return s[l]={...a,...c,created_at:typeof a.created_at=="string"?a.created_at:o,occurrences:Math.max(1,typeof a.occurrences=="number"?a.occurrences:1)+1},s}function be(e,t,o,r){const s=d(e.stories)?e.stories:null;if(!s)return;const c=[],l=j();for(const[a,n]of Object.entries(s)){if(!d(n))continue;const i=typeof n.epic_id=="string"?n.epic_id:a.split(".")[0];if(r&&i!==r)continue;const p=b(n.status);if(he(n))throw new S(`[Neocortex] state.json repair stopped: story "${a}" has conflicting PR merge completion evidence. Resolve .neocortex/state.json manually before invoking Neocortex.`,"conflicting_completion_evidence");if(p!=="pr-reviewed"||!D(n)||!ye(n))continue;const g=typeof n.status=="string"?n.status:p,m=ie(n.steps_completed);n.status="done",n.last_step="step-c-12-merge-pr",n.steps_completed=Array.from(new Set([...m,"step-c-12-merge-pr"])),n.merged=!0,n.physicalMergeStatus="merged",n.completion_reconciled_at=l,t.push(`reconciled completion evidence for story ${a}`),o.push(E),c.push({schemaVersion:1,publicSafe:!0,rawLogStorage:"forbidden",storyId:u(a),previousStatus:g,nextStatus:"done",reasonCodes:[E],reconciledAt:l}),e.state_repair_debt=Se(Array.isArray(e.state_repair_debt)?e.state_repair_debt:[],a,l)}c.length>0&&(e.state_completion_reconciliations=[...Array.isArray(e.state_completion_reconciliations)?e.state_completion_reconciliations.filter(d):[],...c])}function we(e,t,o){const r=d(e.stories)?e.stories:null;if(r)for(const[s,c]of Object.entries(r))!d(c)||!Object.prototype.hasOwnProperty.call(c,"workflow_issue")||typeof c.workflow_issue=="number"&&Number.isFinite(c.workflow_issue)||(delete c.workflow_issue,t.push(`removed invalid workflow_issue from story ${u(s)}`),o.push(pe))}function ve(e,t={}){if(Array.isArray(e.stories))throw new S("[Neocortex] state.json repair stopped: `stories` is an Array. Convert it to an object keyed by story ID or run `*migrate-state --preview` for guidance.","stories_array_ambiguous");if(d(e.epics)){for(const[i,p]of Object.entries(e.epics))if(d(p)&&p.stories!==void 0&&!Array.isArray(p.stories))throw new S(`[Neocortex] state.json repair stopped: epic "${i}" has non-array \`stories\`. Repair this ambiguous shape manually before invoking Neocortex.`,"epic_stories_not_array")}if(e.schema_version!==void 0&&e.schema_version!==0&&e.schema_version!==1)throw new S(`[Neocortex] state.json repair stopped: schema_version=${String(e.schema_version)} is not a safe automatic downgrade target. Run \`*migrate-state --preview\` or repair the schema version manually.`,"unsupported_schema_version");if(I(t.args,t.canonicalCommandId)){for(const i of["epics","stories"]){const p=e[i];if(p!==void 0){if(!d(p))throw new S("[Neocortex] inline materialization stopped: invalid state collection.","inline_state_collection_invalid");for(const[g,m]of Object.entries(p)){if(!d(m))throw new S("[Neocortex] inline materialization stopped: invalid state record.","inline_state_record_invalid");if(m.id!==void 0&&m.id!==g)throw new S("[Neocortex] inline materialization stopped: conflicting state identity.","inline_state_identity_conflict")}}}return{mutated:!1,state:e,reasonCodes:[],changes:[]}}let o;try{o=Z(e)}catch(i){throw i instanceof U?new S(`[Neocortex] state.json repair stopped by shared migration planner: ${i.message}`,i.reasonCode):i}let r=JSON.parse(JSON.stringify(o.kind==="migrate"?o.plan.targetState:e));const s=o.kind==="migrate"?o.plan.steps.map(i=>`shared migration ${i.kind}: ${i.target}`):[],c=o.kind==="migrate"?["state_migration_shared_plan"]:[];let l=[];const a=P(r,t.args,t.canonicalCommandId);if(we(r,s,c),be(r,s,c,a),typeof t.args=="string"){const i=me(r,{nowIso:t.nowIso,targetEpicId:a,effectiveDoneStoryIds:t.effectiveDoneStoryIds,preserveGitLifecycleAggregates:!0});if(i.mutated){r=i.state,l=i.changes;for(const p of i.reasonCodes)c.push(p);for(const p of i.changes)s.push(`reconciled aggregate for epic ${u(p.epicId)}`)}}const n=JSON.stringify(r)!==JSON.stringify(e);if(n){const i=t.nowIso??j(),p=Array.from(new Set(c));l.length>0&&je(r,l,i),r.last_updated=i,r.state_migration_repairs=[...Array.isArray(r.state_migration_repairs)?r.state_migration_repairs:[],{schemaVersion:1,action:"client_auto_repair",reasonCodes:p,repairedAt:i}],r.state_repair_debt=Ce(Array.isArray(r.state_repair_debt)?r.state_repair_debt:[],p,i)}return{mutated:n,state:r,reasonCodes:Array.from(new Set(c)),changes:s}}function G(e){return typeof e=="number"&&Number.isFinite(e)?e:null}function L(e){const t=b(e);return t.length>0?C(t).slice(0,80):null}function Ae(e,t){return{schemaVersion:1,publicSafe:!0,epicId:u(e.epicId),reasonCodes:e.reasonCodes.map(C).filter(Boolean),previous:{total_stories:G(e.previous.total_stories),completed_stories:G(e.previous.completed_stories),status:L(e.previous.status),completed_at_present:typeof e.previous.completed_at=="string"&&e.previous.completed_at.length>0},next:{total_stories:e.next.total_stories,completed_stories:e.next.completed_stories,status:L(e.next.status),completed_at_present:typeof e.next.completed_at=="string"&&e.next.completed_at.length>0},repairedAt:t}}function xe(e,t,o){let r=e.filter(d).map(s=>({...s}));for(const s of t){const c=u(s.epicId),l=Array.from(new Set(s.reasonCodes.map(C).filter(Boolean))).sort(),a=`state-aggregate-reconciliation:${c}`,n={id:a,status:"open",reason:"epic_aggregate_reconciled",reason_codes:l,category:"state/epic-aggregate-reconciliation",epic_id:c,created_at:o,last_seen_at:o,occurrences:1},i=r.findIndex(g=>g.id===a);if(i<0){r=[...r,n];continue}const p=r[i];r[i]={...p,...n,created_at:typeof p.created_at=="string"?p.created_at:o,occurrences:Math.max(1,typeof p.occurrences=="number"?p.occurrences:1)+1}}return r}function je(e,t,o){e.state_aggregate_reconciliations=[...Array.isArray(e.state_aggregate_reconciliations)?e.state_aggregate_reconciliations.filter(d):[],...t.map(r=>Ae(r,o))],e.state_repair_debt=xe(Array.isArray(e.state_repair_debt)?e.state_repair_debt:[],t,o)}function Ce(e,t,o){const r=Array.from(new Set(t.map(C).filter(Boolean))).sort(),s=`state-drift-repair:${r.join("+")||"unknown"}`,c=e.filter(d).map(i=>({...i})),l={id:s,status:"open",reason:"state_json_drift_repaired",reason_codes:r,category:"state/drift-repair",created_at:o,last_seen_at:o,occurrences:1},a=c.findIndex(i=>i.id===s);if(a<0)return[...c,l];const n=c[a];return c[a]={...n,...l,created_at:typeof n.created_at=="string"?n.created_at:o,occurrences:Math.max(1,typeof n.occurrences=="number"?n.occurrences:1)+1},c}function C(e){return e.replace(/\b(sk|ghp|gho|github_pat|xoxb|AKIA)[A-Za-z0-9_-]{8,}\b/g,"[redacted]").replace(/(password|passwd|secret|token|api[_-]?key)\s*[:=]\s*[^,\s)]+/gi,"$1=[redacted]").replace(/https?:\/\/[^\s)]+/gi,"[url-redacted]").replace(/[^a-z0-9_+-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,120)}function u(e){return e.replace(/[^a-z0-9_.:-]+/gi,"_").replace(/^_+|_+$/g,"").slice(0,120)||"unknown"}function Te(e,t={}){const o=x(e,".neocortex"),r=x(o,"state.json");if(!F(r))return{repaired:!1,reasonCodes:[],changes:[]};let s;if(!I(t.args,t.canonicalCommandId))try{const _=Y(e);_&&(s=K(e,_))}catch(_){const y=_ instanceof Q?_.reasonCode:"state_migration_apply_failed";throw new S(`[Neocortex] state.json migration stopped before invoke: ${_ instanceof Error?_.message:String(_)}. No unverified state was used.`,y)}let c;try{c=JSON.parse(q(r,"utf8"))}catch(_){const y=_ instanceof Error?_.message:String(_);throw new S(`[Neocortex] state.json repair stopped: invalid JSON in .neocortex/state.json (${y}). No files were overwritten. Fix the JSON or restore a backup, then rerun.`,"invalid_json")}if(!d(c))throw new S("[Neocortex] state.json repair stopped: top-level state must be a JSON object. Restore a valid .neocortex/state.json before invoking Neocortex.","state_not_object");const l=P(c,t.args,t.canonicalCommandId),a=I(t.args,t.canonicalCommandId)?[]:Ie(e,c,l),n=ve(c,{...t,effectiveDoneStoryIds:a});if(!n.mutated)return s?{repaired:!0,reasonCodes:["state_migration_transaction_applied"],backupPath:s.backupHash?`.neocortex/state.json.migration-backup-${s.operationId}`:void 0,changes:[`shared state migration ${s.outcome}`]}:{repaired:!1,reasonCodes:[],changes:[]};J(o,{recursive:!0});const i=ne(),p=x(o,`state.json.backup-${i}`),g=x(o,`state.json.tmp.${V(6).toString("hex")}`);W(r,p),H(g,`${JSON.stringify(n.state,null,2)}
2
+ `,"utf8"),B(g,r);const m=`.neocortex/state.json.backup-${i}`;return process.stderr.write(`[Neocortex P156.05] state.json auto-repaired before invoke; backup=${m}; reasonCodes=${n.reasonCodes.join(",")}; changes=${n.changes.join("; ")}
3
+ `),{repaired:!0,reasonCodes:n.reasonCodes,backupPath:m,changes:n.changes}}function Ie(e,t,o){const r=X(e),s=d(t.epics)?t.epics:{},c=d(t.stories)?t.stories:{},l=[];for(const[a,n]of Object.entries(r.stories)){if(!d(n)||!T(n.status)||!n.checkpoint)continue;const i=d(c[a])?c[a]:null,p=typeof i?.epic_id=="string"?i.epic_id:a.split(".")[0];if(!p||o&&p!==o)continue;const g=d(s[p])?s[p]:null,m=d(r.epics[p])?r.epics[p]:null,_=m?.branch_mode??g?.branch_mode,y=m?.execution_branch??g?.execution_branch;if(_!=="epic"||typeof y!="string")continue;ee(e,n.checkpoint,a,p,y).verdict==="verified"&&l.push(a)}return l.sort()}export{f as EPIC_AGGREGATE_REASON_CODES,te as P189_STATE_SHAPE_GAP_CODE,S as StateRepairHardStopError,I as isInlineYoloMaterialization,T as isP189DoneLikeStoryStatus,Te as maybeRepairStateJsonBeforeInvoke,me as reconcileEpicAggregatesInState};
@@ -93,9 +93,8 @@ export declare function persistYoloopFromResponse(metadata: Record<string, unkno
93
93
  * on crash mid-write. Mirrors `YoloopClientStateStore.writeState()`
94
94
  * pattern.
95
95
  *
96
- * Migration filesystem failures remain fail-soft. Unsafe/partial replacement
97
- * payloads and inline materialization failures return `blocking: true` so the
98
- * invoke boundary can fail closed.
96
+ * Migration directives and filesystem failures return `blocking: true`; the
97
+ * invoke boundary cannot continue after an unverified state write or rollback.
99
98
  */
100
99
  export interface StateWritebackResult {
101
100
  readonly applied: boolean;
@@ -1,8 +1,9 @@
1
- import{YoloopClientStateStore as A}from"./yoloop-client-state-store.js";import{isYoloopInvocation as N}from"./target-normalization.js";function I(e){return N(e)}const D=/^\s*\*?(yoloop|auto-yolo|epic-runner|story-runner|loop-yolo|auto-epic)\b/i;function G(e){if(typeof e.args!="string"||e.args.length===0)return;const t=e.args.match(D);if(!t)return;const r=t[0];if(e.args.slice(r.length).trim()!=="")return;let i=!1;try{const l=require("node:fs"),a=require("node:path").join(e.projectRoot,"docs","epics");i=l.statSync(a).isDirectory()}catch(l){const p=l instanceof Error?l.message:String(l);e.logger?.warn?.(`[Neocortex] Zero-config *yoloop hook (P121.05): could not stat docs/epics/: ${p}. Proceeding with raw args.`);return}i&&(e.args=`${r} @docs/epics/`,e.logger?.info?.("[Neocortex] Zero-config *yoloop (P121.05): auto-resolved empty args to @docs/epics/"))}async function H(e,t,r){if(I(e))try{return await new A(t).readAll(r)}catch(o){process.stderr.write(`[yoloop] Failed to read local state: ${o.message}. Continuing without state.
2
- `);return}}async function K(e,t,r){if(!e)return;const o=e.yoloopStateUpdate;if(!(!o||!Array.isArray(o.operations)||o.operations.length===0))try{await new A(t).applyUpdate(o,r)}catch(i){const l=i.message??String(i);process.stderr.write(`[yoloop] Failed to persist state: ${l}. Your next *yoloop may repeat or skip an operation. Run *yoloop again to retry.
3
- `)}}function s(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function E(e){return s(e.scope)?e.scope.kind==="projected"||typeof e.scope.omittedStoryCount=="number"&&e.scope.omittedStoryCount>0||typeof e.scope.omittedEpicCount=="number"&&e.scope.omittedEpicCount>0:!1}function O(e){const t={...e};return delete t.scope,delete t.stateSummary,delete t.graphRetrieval,delete t.runnerBootstrap,delete t.projectEvidence,delete t.continuity,delete t.implementCheckpoint,t}function T(e){return typeof e=="string"&&["done","completed","merged"].includes(e.trim().toLowerCase())}async function M(e,t){const r=e.syntheticStory;if(!s(r))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-mutation-missing"};const o=r.storyId,i=r.storyPath,l=r.deterministicKey,p=r.storyContent;if(typeof o!="string"||!/^P\d+(?:\.\d+){2}$/.test(o)||typeof i!="string"||i!==`docs/stories/${o}.story.md`||typeof l!="string"||!/^[a-f0-9]{16}$/.test(l)||typeof p!="string"||p.length===0||p.length>131072||r.publicSafe!==!0||r.sanitized!==!0)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-mutation-invalid"};const a=await import("node:fs/promises"),u=await import("node:path"),k=await import("node:crypto"),w=u.join(t,".neocortex"),b=u.join(w,"state.json"),f=u.join(t,...i.split("/"));let S;try{S=await a.readFile(b,"utf8")}catch{return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-missing"}}let d;try{d=JSON.parse(S)}catch{return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-corrupt"}}if(!s(d)||!s(d.stories)||!s(d.epics)||E(d))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-not-full"};const n=d.stories[o];if(n!==void 0&&(!s(n)||n.epic_id!=="P174"||n.story_file!==i||!s(n.inlineYolo)||n.inlineYolo.deterministicKey!==l))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-conflict"};const y=d.epics.P174;if(y!==void 0&&(!s(y)||y.id!==void 0&&y.id!=="P174"||!Array.isArray(y.stories)))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-conflict"};const C=s(y)?y:{},g=Array.isArray(C.stories)?C.stories:[];if(g.some(c=>typeof c!="string")||new Set(g).size!==g.length)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-story-list-invalid"};for(const c of g){const h=d.stories[c];if(!s(h)||h.id!==c||h.epic_id!=="P174")return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-ownership-conflict"}}try{const c=await a.readFile(f,"utf8"),h=c.includes(`id: ${o}`)&&c.includes(`synthetic_key: ${l}`);if(c!==p&&!h)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-file-conflict"}}catch(c){if(c.code!=="ENOENT")return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-file-unreadable"}}const m=g.includes(o)?[...g]:[...g,o],P={...d.stories,[o]:{...s(n)?n:{},id:o,epic_id:"P174",title:"Synthetic inline USER STORY for yolo",status:s(n)&&typeof n.status=="string"?n.status:"backlog",steps_completed:s(n)&&Array.isArray(n.steps_completed)?n.steps_completed:[],last_step:s(n)?n.last_step??null:null,branch_name:s(n)?n.branch_name??null:null,story_file:i,source:"inline-yolo-materialization",inlineYolo:{...s(n)&&s(n.inlineYolo)?n.inlineYolo:{},schemaVersion:1,deterministicKey:l,publicSafe:!0,sanitized:!0,materializedBeforeExecution:!0,ephemeralExecutionForbidden:!0}}},j=m.filter(c=>s(P[c])&&T(P[c].status)).length,$=s(d.inlineYolo)?d.inlineYolo:{},F=s($.materializations)?$.materializations:{},R=O({...d,stories:P,epics:{...d.epics,P174:{...C,id:"P174",status:j===m.length&&m.length>0?"done":"in-progress",stories:m,total_stories:m.length,completed_stories:j,...j===m.length&&m.length>0?{}:{completed_at:null}}},inlineYolo:{...$,schemaVersion:1,materializations:{...F,[o]:{schemaVersion:1,storyId:o,storyPath:i,deterministicKey:l,publicSafe:!0,sanitized:!0,materializedBeforeExecution:!0,ephemeralExecutionForbidden:!0,pipelineTrigger:`*yolo @${i}`,reasonCodes:Array.isArray(r.reasonCodes)?r.reasonCodes:[]}}},last_updated:new Date().toISOString()}),_=`${Date.now()}-${k.randomBytes(6).toString("hex")}`,z=u.join(w,`state.json.tmp.${_}`),x=`${f}.tmp.${_}`,v=u.join(w,`state.json.backup-${new Date().toISOString().replace(/[:.]/g,"-")}`);let Y=!1;try{await a.mkdir(w,{recursive:!0}),await a.mkdir(u.dirname(f),{recursive:!0});try{await a.access(f)}catch{await a.writeFile(x,p,{encoding:"utf8",flag:"wx"}),await a.rename(x,f),Y=!0}return await a.copyFile(b,v),await a.writeFile(z,JSON.stringify(R,null,2),{encoding:"utf8",flag:"wx"}),await a.rename(z,b),process.stderr.write(`[Neocortex] inline yolo story materialized with bounded full-state merge; story=${o}; backup=${u.basename(v)}.
4
- `),{applied:!0,blocking:!1,reasonCode:"inline-yolo-materialized"}}catch(c){await Promise.allSettled([a.unlink(z),a.unlink(x)]),Y&&await a.unlink(f).catch(()=>{});const h=c.message??String(c);return process.stderr.write(`[Neocortex] inline yolo materialization failed: ${h}.
5
- `),{applied:!1,blocking:!0,reasonCode:"inline-yolo-persistence-failed"}}}async function L(e,t){if(!e||e.stateMutated!==!0)return{applied:!1,blocking:!1,reasonCode:"state-writeback-not-requested"};if(e.stateMutationStrategy==="merge-inline-yolo-v1"||e.stateMutationReason==="inline-yolo-materialized-synthetic-story")return M(e,t);const r=e.migratedState;if(!r||typeof r!="object"||Array.isArray(r))return{applied:!1,blocking:!1,reasonCode:"state-writeback-payload-missing"};if(E(r))return process.stderr.write(`[Neocortex] Refused projected/partial state replacement; canonical state was not modified.
6
- `),{applied:!1,blocking:!0,reasonCode:"state-writeback-partial-rejected"};try{const o=await import("node:fs/promises"),i=await import("node:path"),l=await import("node:crypto"),p=i.join(t,".neocortex"),a=i.join(p,"state.json"),u=i.join(p,`state.json.backup-${new Date().toISOString().replace(/[:.]/g,"-")}`),k=i.join(p,`state.json.tmp.${l.randomBytes(6).toString("hex")}`);await o.mkdir(p,{recursive:!0});try{await o.copyFile(a,u)}catch(f){if(f?.code!=="ENOENT")throw f}const w=O(r),b=JSON.stringify(w,null,2);return await o.writeFile(k,b,"utf8"),await o.rename(k,a),process.stderr.write(`[Neocortex P125.03] state.json auto-migrated to canonical v1 schema by server. Legacy planning.* fields converted to epics{} + stories{}; backup=${i.basename(u)}.
7
- `),{applied:!0,blocking:!1,reasonCode:"state-writeback-applied"}}catch(o){const i=o.message??String(o);return process.stderr.write(`[Neocortex P125.03] Failed to persist auto-migrated state: ${i}. Your next invoke will retry (server runs migrate idempotently). If the warning persists, run \`neocortex invoke "*migrate-state --apply"\` manually.
8
- `),{applied:!1,blocking:!1,reasonCode:"state-writeback-failed-soft"}}}async function J(e){if(!e.featureFlag)return{applied:!1,added:Object.freeze([]),skipped:Object.freeze([]),storiesAdded:0,reason:"flag off"};try{return await(await import("./discovery-hook.js")).runDiscoveryHook(e)}catch(t){const r=t instanceof Error?t.message:String(t);return e.logger?.warn?.(`[neocortex] discovery hook (lazy load) failed: ${r}. Proceeding without enrichment.`),{applied:!1,added:Object.freeze([]),skipped:Object.freeze([]),storiesAdded:0,reason:`lazy load error: ${r}`}}}async function U(e){if(!e.featureFlag)return{applied:!1,reason:"flag_off"};try{return await(await import("../graph-retrieval/pre-command-hook.js")).runGraphRetrievalHook(e)}catch(t){const r=t instanceof Error?t.message:String(t);return e.logger?.warn?.(`[neocortex] graph retrieval hook (lazy load) failed: ${r}. Proceeding without enrichment.`),{applied:!1,reason:"hook_error",warning:r}}}export{I as isYoloopTrigger,G as maybeAutoResolveYoloopArgs,J as maybeRunDiscoveryHook,U as maybeRunGraphRetrievalHook,L as persistMigratedStateFromResponse,K as persistYoloopFromResponse,H as readYoloopForRequest};
1
+ import{statSync as F}from"node:fs";import{join as T}from"node:path";import{YoloopClientStateStore as j}from"./yoloop-client-state-store.js";import{isYoloopInvocation as M}from"./target-normalization.js";import{applyStateMigration as N,StateMigrationApplyError as V}from"../migrations/state-json.js";function H(e){return M(e)}const D=/^\s*\*?(yoloop|auto-yolo|epic-runner|story-runner|loop-yolo|auto-epic)\b/i;function Q(e){if(typeof e.args!="string"||e.args.length===0)return;const r=e.args.match(D);if(!r)return;const i=r[0];if(e.args.slice(i.length).trim()!=="")return;let a=!1;try{const o=T(e.projectRoot,"docs","epics");a=F(o).isDirectory()}catch(o){const c=o instanceof Error?o.message:String(o);e.logger?.warn?.(`[Neocortex] Zero-config *yoloop hook (P121.05): could not stat docs/epics/: ${c}. Proceeding with raw args.`);return}a&&(e.args=`${i} @docs/epics/`,e.logger?.info?.("[Neocortex] Zero-config *yoloop (P121.05): auto-resolved empty args to @docs/epics/"))}async function W(e,r,i){if(H(e))try{return await new j(r).readAll(i)}catch(t){process.stderr.write(`[yoloop] Failed to read local state: ${t.message}. Continuing without state.
2
+ `);return}}async function X(e,r,i){if(!e)return;const t=e.yoloopStateUpdate;if(!(!t||!Array.isArray(t.operations)||t.operations.length===0))try{await new j(r).applyUpdate(t,i)}catch(a){const o=a.message??String(a);process.stderr.write(`[yoloop] Failed to persist state: ${o}. Your next *yoloop may repeat or skip an operation. Run *yoloop again to retry.
3
+ `)}}function n(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function z(e,r){return Array.isArray(e)&&e.length<=r&&e.every(i=>typeof i=="string"&&i.length>0&&i.length<=160&&/^[A-Za-z0-9_.:+-]+$/.test(i))}function R(e){return n(e.scope)?e.scope.kind==="projected"||typeof e.scope.omittedStoryCount=="number"&&e.scope.omittedStoryCount>0||typeof e.scope.omittedEpicCount=="number"&&e.scope.omittedEpicCount>0:!1}function G(e){const r={...e};return delete r.scope,delete r.stateSummary,delete r.graphRetrieval,delete r.runnerBootstrap,delete r.projectEvidence,delete r.continuity,delete r.implementCheckpoint,r}function U(e){return typeof e=="string"&&["done","completed","merged"].includes(e.trim().toLowerCase())}async function K(e,r){const i=e.syntheticStory;if(!n(i))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-mutation-missing"};const t=i.storyId,a=i.storyPath,o=i.deterministicKey,c=i.storyContent;if(typeof t!="string"||!/^P\d+(?:\.\d+){2}$/.test(t)||typeof a!="string"||a!==`docs/stories/${t}.story.md`||typeof o!="string"||!/^[a-f0-9]{16}$/.test(o)||typeof c!="string"||c.length===0||c.length>131072||i.publicSafe!==!0||i.sanitized!==!0)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-mutation-invalid"};const d=await import("node:fs/promises"),u=await import("node:path"),O=await import("node:crypto"),b=u.join(r,".neocortex"),w=u.join(b,"state.json"),h=u.join(r,...a.split("/"));let I;try{I=await d.readFile(w,"utf8")}catch{return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-missing"}}let p;try{p=JSON.parse(I)}catch{return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-corrupt"}}if(!n(p)||!n(p.stories)||!n(p.epics)||R(p))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-state-not-full"};const s=p.stories[t];if(s!==void 0&&(!n(s)||s.epic_id!=="P174"||s.story_file!==a||!n(s.inlineYolo)||s.inlineYolo.deterministicKey!==o))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-conflict"};const g=p.epics.P174;if(g!==void 0&&(!n(g)||g.id!==void 0&&g.id!=="P174"||!Array.isArray(g.stories)))return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-conflict"};const k=n(g)?g:{},f=Array.isArray(k.stories)?k.stories:[];if(f.some(l=>typeof l!="string")||new Set(f).size!==f.length)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-story-list-invalid"};for(const l of f){const m=p.stories[l];if(!n(m)||m.id!==l||m.epic_id!=="P174")return{applied:!1,blocking:!0,reasonCode:"inline-yolo-epic-ownership-conflict"}}try{const l=await d.readFile(h,"utf8"),m=l.includes(`id: ${t}`)&&l.includes(`synthetic_key: ${o}`);if(l!==c&&!m)return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-file-conflict"}}catch(l){if(l.code!=="ENOENT")return{applied:!1,blocking:!0,reasonCode:"inline-yolo-story-file-unreadable"}}const y=f.includes(t)?[...f]:[...f,t],S={...p.stories,[t]:{...n(s)?s:{},id:t,epic_id:"P174",title:"Synthetic inline USER STORY for yolo",status:n(s)&&typeof s.status=="string"?s.status:"backlog",steps_completed:n(s)&&Array.isArray(s.steps_completed)?s.steps_completed:[],last_step:n(s)?s.last_step??null:null,branch_name:n(s)?s.branch_name??null:null,story_file:a,source:"inline-yolo-materialization",inlineYolo:{...n(s)&&n(s.inlineYolo)?s.inlineYolo:{},schemaVersion:1,deterministicKey:o,publicSafe:!0,sanitized:!0,materializedBeforeExecution:!0,ephemeralExecutionForbidden:!0}}},C=y.filter(l=>n(S[l])&&U(S[l].status)).length,P=n(p.inlineYolo)?p.inlineYolo:{},Y=n(P.materializations)?P.materializations:{},x=G({...p,stories:S,epics:{...p.epics,P174:{...k,id:"P174",status:C===y.length&&y.length>0?"done":"in-progress",stories:y,total_stories:y.length,completed_stories:C,...C===y.length&&y.length>0?{}:{completed_at:null}}},inlineYolo:{...P,schemaVersion:1,materializations:{...Y,[t]:{schemaVersion:1,storyId:t,storyPath:a,deterministicKey:o,publicSafe:!0,sanitized:!0,materializedBeforeExecution:!0,ephemeralExecutionForbidden:!0,pipelineTrigger:`*yolo @${a}`,reasonCodes:Array.isArray(i.reasonCodes)?i.reasonCodes:[]}}},last_updated:new Date().toISOString()}),_=`${Date.now()}-${O.randomBytes(6).toString("hex")}`,A=u.join(b,`state.json.tmp.${_}`),$=`${h}.tmp.${_}`,E=u.join(b,`state.json.backup-${new Date().toISOString().replace(/[:.]/g,"-")}`);let v=!1;try{await d.mkdir(b,{recursive:!0}),await d.mkdir(u.dirname(h),{recursive:!0});try{await d.access(h)}catch{await d.writeFile($,c,{encoding:"utf8",flag:"wx"}),await d.rename($,h),v=!0}return await d.copyFile(w,E),await d.writeFile(A,JSON.stringify(x,null,2),{encoding:"utf8",flag:"wx"}),await d.rename(A,w),process.stderr.write(`[Neocortex] inline yolo story materialized with bounded full-state merge; story=${t}; backup=${u.basename(E)}.
4
+ `),{applied:!0,blocking:!1,reasonCode:"inline-yolo-materialized"}}catch(l){await Promise.allSettled([d.unlink(A),d.unlink($)]),v&&await d.unlink(h).catch(()=>{});const m=l.message??String(l);return process.stderr.write(`[Neocortex] inline yolo materialization failed: ${m}.
5
+ `),{applied:!1,blocking:!0,reasonCode:"inline-yolo-persistence-failed"}}}async function ee(e,r){if(!e||e.stateMutated!==!0)return{applied:!1,blocking:!1,reasonCode:"state-writeback-not-requested"};if(e.stateMutationStrategy==="merge-inline-yolo-v1"||e.stateMutationReason==="inline-yolo-materialized-synthetic-story")return K(e,r);if(e.stateMutationStrategy==="state-migration-v1"||e.stateMigrationPlan!==void 0){const t=e.stateMigrationPlan,a=new Set(["schemaVersion","operationId","sourceSchemaVersion","targetSchemaVersion","sourceCanonicalHash","targetCanonicalHash","steps","migratedEpicIds","migratedStoryIds","preservedUnknownFieldCount","reasonCodes"]);if(!n(t)||Object.keys(t).some(o=>!a.has(o))||t.schemaVersion!=="StateMigrationPlan/1.0"||typeof t.operationId!="string"||!/^smig_[a-f0-9]{32}$/.test(t.operationId)||typeof t.sourceCanonicalHash!="string"||!/^[a-f0-9]{64}$/.test(t.sourceCanonicalHash)||typeof t.targetCanonicalHash!="string"||!/^[a-f0-9]{64}$/.test(t.targetCanonicalHash)||![0,1,"absent"].includes(t.sourceSchemaVersion)||t.targetSchemaVersion!==1||!Array.isArray(t.steps)||t.steps.length>8192||t.steps.some(o=>!n(o)||Object.keys(o).some(c=>!["kind","source","target"].includes(c))||!["initialize-collection","merge-epic","merge-story","preserve-legacy","remove-legacy","set-schema-version"].includes(String(o.kind))||typeof o.target!="string"||o.target.length>512||!/^[A-Za-z0-9_.:[\]-]+$/.test(o.target)||o.source!==void 0&&(typeof o.source!="string"||o.source.length>512||!/^[A-Za-z0-9_.:[\]-]+$/.test(o.source)))||!z(t.migratedEpicIds,4096)||!z(t.migratedStoryIds,4096)||!z(t.reasonCodes,32)||!Number.isSafeInteger(t.preservedUnknownFieldCount)||t.preservedUnknownFieldCount<0)return{applied:!1,blocking:!0,reasonCode:"state-migration-plan-missing-or-invalid"};try{const o=N(r,t),c=`.neocortex/state.json.migration-backup-${o.operationId}`;return process.stderr.write(`[P220-EV-STATE-MIGRATION] outcome=${o.outcome}; operation=${o.operationId}; backup=${o.backupHash?c:"none"}.
6
+ `),{applied:o.outcome==="applied",blocking:!1,reasonCode:o.outcome==="applied"?"state-migration-applied":"state-migration-replay-verified"}}catch(o){const c=o instanceof V?o.reasonCode:"state-migration-apply-failed";return process.stderr.write(`[P220-EV-STATE-MIGRATION] blocked=${c}.
7
+ `),{applied:!1,blocking:!0,reasonCode:c}}}const i=e.migratedState;return!i||typeof i!="object"||Array.isArray(i)?{applied:!1,blocking:!0,reasonCode:"state-writeback-payload-missing"}:R(i)?(process.stderr.write(`[Neocortex] Refused projected/partial state replacement; canonical state was not modified.
8
+ `),{applied:!1,blocking:!0,reasonCode:"state-writeback-partial-rejected"}):(process.stderr.write(`[P220-EV-STATE-MIGRATION] blocked=state-migration-plan-missing-or-invalid; canonical state was not modified.
9
+ `),{applied:!1,blocking:!0,reasonCode:"state-migration-plan-missing-or-invalid"})}async function te(e){if(!e.featureFlag)return{applied:!1,added:Object.freeze([]),skipped:Object.freeze([]),storiesAdded:0,reason:"flag off"};try{return await(await import("./discovery-hook.js")).runDiscoveryHook(e)}catch(r){const i=r instanceof Error?r.message:String(r);return e.logger?.warn?.(`[neocortex] discovery hook (lazy load) failed: ${i}. Proceeding without enrichment.`),{applied:!1,added:Object.freeze([]),skipped:Object.freeze([]),storiesAdded:0,reason:`lazy load error: ${i}`}}}async function oe(e){if(!e.featureFlag)return{applied:!1,reason:"flag_off"};try{return await(await import("../graph-retrieval/pre-command-hook.js")).runGraphRetrievalHook(e)}catch(r){const i=r instanceof Error?r.message:String(r);return e.logger?.warn?.(`[neocortex] graph retrieval hook (lazy load) failed: ${i}. Proceeding without enrichment.`),{applied:!1,reason:"hook_error",warning:i}}}export{H as isYoloopTrigger,Q as maybeAutoResolveYoloopArgs,te as maybeRunDiscoveryHook,oe as maybeRunGraphRetrievalHook,ee as persistMigratedStateFromResponse,X as persistYoloopFromResponse,W as readYoloopForRequest};
@@ -271,9 +271,9 @@ export interface MergeResult {
271
271
  * preserved). Critical for `/loop` iteration which may re-invoke discovery
272
272
  * between stories.
273
273
  *
274
- * **Mutation semantics**: state is mutated in place for performance (state.json
275
- * can have hundreds of entries). Caller (P120.04 client hook) is responsible
276
- * for deep cloning before merge if needed for diffing/audit.
274
+ * **Mutation semantics**: merge work is staged on cloned maps and committed to
275
+ * the caller-owned container only after every identity and aggregate invariant
276
+ * passes. Validation failures therefore provide strong exception safety.
277
277
  *
278
278
  * @param state target state object (`null`/`undefined` ok -- treated as empty)
279
279
  * @param discovered discovery result (epics + warnings + skipped)
@@ -1 +1 @@
1
- const me=Object.freeze({epicId:"P{number}",epicFilename:"docs/epics/epic-P{number}.md",storyId:"P{number}.NN",storyFilename:"docs/stories/P{number}.NN.story.md",acceptedOnly:Object.freeze({epicFilenames:Object.freeze(["docs/epics/epic-{number}.md","docs/epics/EPIC-{number}-{slug}.md","docs/epics/EPIC-{number}_{slug}.md","docs/epics/epic-{slug}.md"]),storyFilenames:Object.freeze(["docs/stories/{number}.NN.story.md","docs/stories/story-P{number}.NN.md","docs/stories/story-{number}.NN.md","docs/stories/EPIC-{number}.STORY-{number}.md"])})}),E=Object.freeze([/^epic-(P?\d+(?:\.\d+)?)\.md$/i,/^EPIC-(\d+)(?:[-_].*)?\.md$/,/^epic-([a-z0-9][a-z0-9_-]*)\.md$/i]),j=Object.freeze([/^(P?\d+\.\d+(?:\.\d+)?)\.story\.md$/i,/^story-(P?\d+\.\d+)\.md$/i,/^EPIC-(\d+)\.STORY-(\d+)\.md$/]),k=Object.freeze([".archive","_archive","_archives","_drafts","archive","archives","archived","drafts"]),A=/^---\s*\n([\s\S]*?)\n---/;function w(t,e){const i=new RegExp(`^${e}:\\s*(.+?)\\s*$`,"mi"),n=t.match(i);if(!n)return;let c=n[1].trim();return(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'"))&&(c=c.slice(1,-1)),c||void 0}function M(t){if(typeof t!="string"||t.length===0)return{};const e=t.match(A);if(!e)return{};const i=e[1];return{id:w(i,"id"),title:w(i,"title"),status:w(i,"status"),epic_id:w(i,"epic_id"),depends_on:Q(i),files_to_modify:R(i,"files_to_modify"),total_stories:D(w(i,"total_stories"))}}const C=32,G=4096,Y=/^\s*\*\*([^*\r\n]+?):\*\*\s*(.*?)\s*$/,B=new Set(["backlog","pending","ready","planning","in-progress","done","completed","merged","cancelled","canceled","abandoned","draft","template","archived","branch-ready","diagnose","research","write-spec","create-tasks","implemented","refactor-suggest-done","property-tests-emitted","tests-validated","qa-gate-passed","memory-updated","committed","synced","pr-created","pr-reviewed","partial"]);function W(t){let e=t.trim();return e.startsWith("`")&&e.endsWith("`")&&e.length>=2&&(e=e.slice(1,-1).trim()),(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1).trim()),e}function D(t){if(t===void 0||!/^\d{1,5}$/.test(t.trim()))return;const e=Number(t);return Number.isSafeInteger(e)?e:void 0}function H(t){const e=W(t).toLowerCase().replace(/[_\s]+/g,"-"),n={planned:"planning",complete:"completed",closed:"done",finished:"done"}[e]??e;return B.has(n)?n:void 0}function U(t){const e=t.trim().toLowerCase().replace(/[_-]+/g," ").replace(/\s+/g," ");if(e==="id"||e==="epic id")return"id";if(e==="title"||e==="titulo")return"title";if(e==="status")return"status";if(e==="total stories"||e==="total de stories")return"total_stories"}function Z(t){const e=t.find(c=>/^#\s+\S/.test(c));if(!e)return;const i=e.replace(/^#\s+/,"").trim();return(i.replace(/^(?:Epic|Story)\s+\S+\s*(?:(?:--?|:)|[\u2013\u2014])\s*/i,"")||i).slice(0,200)}function v(t,e,i,n){const c=[...new Set(t.map(W).filter(Boolean))];if(c.length>1){n.push(`legacy metadata ambiguous for '${e}' in '${i}': conflicting values ignored`);return}return c[0]}function X(t,e){const i=[],n=[],c=t.slice(0,G).split(/\r?\n/,C);for(const d of c){if(n.length>0&&(/^\s*---\s*$/.test(d)||/^##\s+/.test(d)))break;n.push(d)}const o=new Map;for(const d of n){const g=d.match(Y);if(!g)continue;const m=U(g[1]);if(!m)continue;const u=o.get(m)??[];u.push(g[2]),o.set(m,u)}const l=v(o.get("id")??[],"id",e,i),f=v(o.get("title")??[],"title",e,i),h=v(o.get("status")??[],"status",e,i),_=v(o.get("total_stories")??[],"total_stories",e,i),p=h===void 0?void 0:H(h);h!==void 0&&p===void 0&&i.push(`legacy metadata status unrecognized in '${e}': value ignored`);const y=D(_);_!==void 0&&y===void 0&&i.push(`legacy metadata total_stories invalid in '${e}': value ignored`);const b=l&&/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(l)?l:void 0;l!==void 0&&b===void 0&&i.push(`legacy metadata id invalid in '${e}': value ignored`);const r=Z(n),s=f??r;return{metadata:{...b?{id:b}:{},...s?{title:s.slice(0,200)}:{},...p?{status:p}:{},...y!==void 0?{total_stories:y}:{}},source:"legacy-metadata",warnings:Object.freeze(i)}}function q(t){return/^[A-Za-z0-9._-]{1,160}$/.test(t)?t:"markdown-target.md"}function K(t){return new TextEncoder().encode(t).length}function V(t){let e=!1;for(const i of t.split(/\r?\n/)){if(!i.trim()||i.trimStart().startsWith("#"))continue;const n=i.match(/^\s+-\s+(.+?)\s*$/);if(n){if(!e)return!0;const l=n[1].trim();if(l.startsWith('"')&&!l.endsWith('"')||l.startsWith("'")&&!l.endsWith("'"))return!0;continue}if(/^\s+/.test(i))return!0;const c=i.match(/^[A-Za-z0-9_-]+:\s*(.*?)\s*$/);if(!c)return!0;const o=c[1].trim();if(e=o.length===0,o.startsWith('"')&&!o.endsWith('"')||o.startsWith("'")&&!o.endsWith("'"))return!0}return!1}function J(t,e,i){const n=q(e),c=t.match(A);if(!c)return Object.freeze([`metadata-frontmatter reason=frontmatter-malformed file='${n}': repair the bounded leading YAML envelope; source content omitted`]);const o=c[1];return K(o)>8*1024||o.split(/\r?\n/).length>128?Object.freeze([`metadata-frontmatter reason=frontmatter-oversized file='${n}': reduce the leading YAML envelope; source content omitted`]):V(o)||o.trim().length>0&&Object.keys(i).length===0?Object.freeze([`metadata-frontmatter reason=frontmatter-unsupported-value file='${n}': use bounded scalar fields or string lists; source content omitted`]):Object.freeze([])}function F(t,e){if(/^---(?:\s*\r?\n|\s*$)/.test(t)){const i=M(t);return{metadata:i,source:"frontmatter",warnings:J(t,e,i)}}return typeof t!="string"||t.length===0?{metadata:{},source:"none",warnings:Object.freeze([])}:X(t,e)}function L(t){let e=t.trim();if((e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1).trim()),!!e&&!/^(null|undefined|true|false)$/i.test(e))return e}function Q(t){return R(t,"depends_on")}function R(t,e){const i=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),c=t.match(new RegExp(`^${i}:[ \\t]*(.+?)[ \\t]*$`,"mi"))?.[1]?.trim();if(c!==void 0){const _=(c.startsWith("[")&&c.endsWith("]")?c.slice(1,-1).split(","):[c]).map(L).filter(p=>typeof p=="string"&&p.length>0);return _.length>0?Object.freeze(_):void 0}const o=t.split(/\r?\n/),l=[];let f=!1;for(const h of o){if(!f){new RegExp(`^${i}:\\s*$`,"i").test(h.trim())&&(f=!0);continue}if(/^[A-Za-z0-9_-]+:\s*/.test(h))break;const _=h.match(/^\s*-\s*(.+?)\s*$/);if(!_)continue;const p=L(_[1]);p&&l.push(p)}return l.length>0?Object.freeze(l):void 0}function N(t){return t.startsWith("_")||t.startsWith(".")}function ee(t){return k.includes(t)}async function ge(){const t=await import("node:fs"),e=await import("node:path");return{isDirectory(i){try{return t.statSync(i).isDirectory()}catch{return!1}},readdir(i){try{return t.readdirSync(i)}catch{return[]}},readFile(i){try{return t.readFileSync(i,"utf8")}catch{return""}},join(...i){try{return e.join(...i)}catch{return i.join("/")}}}}function te(t,e){let i=!1,n=null,c=null;for(let o=0;o<E.length;o++){const l=E[o],f=t.match(l);if(f){i=!0,n=f[1],c=o===2?"filename-slug":"filename-canonical";break}}return i?e.id&&e.id.length>0?{id:e.id,source:"frontmatter"}:{id:n,source:c}:null}function ie(t){for(const e of E){const i=t.match(e);if(i)return i[1]}return null}function O(t){const e=t.trim();return/^\d+(?:\.\d+)*$/i.test(e)?`P${e}`.toUpperCase():e.toUpperCase()}function ne(t){const e=O(t);return/^P\d+(?:\.\d+)?$/i.test(e)?`epic-${e}.md`:`epic-${t}.md`}function se(t){const e=O(t);return/^P\d+\.\d+(?:\.\d+)?$/i.test(e)?`${e}.story.md`:`${t}.story.md`}function P(t,e){return!t||!e?!1:O(t)!==O(e)}function re(t,e,i){return`frontmatter/filename mismatch: epic '${t}' has frontmatter id '${e}' but filename implies '${i}'. Repair by using generated canonical '${ne(e)}' or changing frontmatter id to '${i}'.`}function oe(t,e,i){return`frontmatter/filename mismatch: story '${t}' has frontmatter id '${e}' but filename implies '${i}'. Repair by using generated canonical '${se(e)}' or changing frontmatter id to '${i}'.`}function ce(t){let e=t.match(j[0]);if(e){const i=e[1],n=i.indexOf(".");return{id:i,epic_id_hint:n>0?i.slice(0,n):null}}if(e=t.match(j[1]),e){const i=e[1],n=i.indexOf(".");return{id:i,epic_id_hint:n>0?i.slice(0,n):null}}if(e=t.match(j[2]),e){const i=e[1],n=e[2];return{id:`${i}.${n}`,epic_id_hint:i}}return null}function ae(t,e){return e.join(t,"..","stories")}function he(t,e){const i=[],n=[],c=[],o=new Date().toISOString();if(typeof t!="string"||t.length===0)return Object.freeze({epics:Object.freeze([]),warnings:Object.freeze([]),skipped:Object.freeze([])});if(!e.isDirectory(t))return Object.freeze({epics:Object.freeze([]),warnings:Object.freeze([]),skipped:Object.freeze([])});const l=[...e.readdir(t)].sort(),f=[];for(const r of l){const s=e.join(t,r);if(N(r)){n.push(`${r}: ignored (draft or hidden prefix)`);continue}if(e.isDirectory(s)){if(ee(r)){n.push(`${r}: ignored subdirectory`);continue}n.push(`${r}: subdirectory not recursed`);continue}if(!r.endsWith(".md"))continue;if(!E.some(S=>S.test(r))){n.push(`${r}: did not match any EPIC_FILE_PATTERNS`);continue}const d=e.readFile(s),g=F(d,r);i.push(...g.warnings);const m=g.metadata,u=ie(r);m.id&&P(m.id,u)&&i.push(g.source==="frontmatter"?re(r,m.id,u):`legacy metadata/filename mismatch: epic '${r}' id does not match its filename`),f.push({filename:r,fullPath:s,metadata:m,metadataSource:g.source})}const h=new Map,_=[];for(const r of f){const s=te(r.filename,r.metadata);if(s===null){n.push(`${r.filename}: id resolution returned null`);continue}let a=s.id;const d=a,g=h.get(d)??0;g>0&&(a=`${d}-${g+1}`,i.push(`epic ID collision: '${d}' resolved to '${a}' for ${r.filename}`)),h.set(d,g+1),_.push({candidate:r,finalId:a,idSource:r.metadataSource==="legacy-metadata"&&r.metadata.id?"legacy-metadata":s.source})}const p=new Map,y=ae(t,e);if(e.isDirectory(y)){const r=[...e.readdir(y)].sort();for(const s of r){if(N(s)||!s.endsWith(".md"))continue;const a=e.join(y,s);if(e.isDirectory(a))continue;const d=ce(s);if(!d)continue;const g=e.readFile(a),m=F(g,s);i.push(...m.warnings);const u=m.metadata;u.id&&P(u.id,d.id)&&i.push(m.source==="frontmatter"?oe(s,u.id,d.id):`legacy metadata/filename mismatch: story '${s}' id does not match its filename`);const S=u.epic_id??d.epic_id_hint;if(!S){i.push(`story '${s}' has no epic_id (frontmatter or filename pattern)`);continue}const x=Object.freeze({id:d.id,epic_id:S,filename:s,path:a,...u.title?{title:u.title}:{},...u.status?{status:u.status}:{},...u.depends_on?{depends_on:u.depends_on}:{},...u.files_to_modify?{files_to_modify:u.files_to_modify}:{}});p.has(S)||p.set(S,[]),p.get(S).push(x)}}for(const r of _){const s=p.get(r.finalId)??[],a=Object.freeze({id:r.finalId,filename:r.candidate.filename,path:r.candidate.fullPath,...r.candidate.metadata.title?{title:r.candidate.metadata.title}:{},...r.candidate.metadata.status?{status:r.candidate.metadata.status}:{},...r.candidate.metadata.total_stories!==void 0?{total_stories:r.candidate.metadata.total_stories}:{},...r.candidate.metadata.depends_on?{depends_on:r.candidate.metadata.depends_on}:{},source:"discovered",discovered_at:o,stories:Object.freeze([...s]),t:r.idSource,e:r.candidate.metadataSource});c.push(a)}const b=new Set(c.map(r=>r.id));for(const[r,s]of p.entries())if(!b.has(r))for(const a of s)i.push(`orphan story '${a.filename}' references unknown epic '${r}'`);return Object.freeze({epics:Object.freeze(c),warnings:Object.freeze(i),skipped:Object.freeze(n)})}function de(t){return I(t)}function I(t){const e=ue(t);return e==="done"||e==="completed"||e==="merged"}function z(t){return I(t)}function ue(t){return typeof t=="string"?t.trim().toLowerCase().replace(/[_\s]+/g,"-"):""}function T(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}function $(t){throw new Error(`epic-aggregate-writer:${t}`)}function fe(t){const e=[],i=new Set;for(const n of t)(typeof n!="string"||n.length===0)&&$("epic_aggregate_story_ref_invalid"),i.has(n)&&$("epic_aggregate_story_ref_duplicate"),i.add(n),e.push(n);return e}function le(t,e,i){const n=i[e];return n===void 0&&$("epic_aggregate_story_missing"),T(n)||$("epic_aggregate_story_not_object"),n.id!==e&&$("epic_aggregate_story_id_conflict"),n.epic_id!==t&&$("epic_aggregate_story_epic_conflict"),n}function pe(t,e,i,n,c){const o=e[t];T(o)||$("epic_aggregate_epic_not_object"),o.id!==t&&$("epic_aggregate_epic_id_conflict"),Array.isArray(o.stories)||$("epic_aggregate_story_list_not_array");const l=fe(o.stories),f=l.length,h=l.filter(s=>I(le(t,s,i).status)).length,_=c?.total_stories,p=c?.e==="legacy-metadata"&&z(o.status),y=p?_??f:f>0?f:_??0,b=p||f===0&&y>0&&z(o.status)?y:h,r=f>0&&h===f;z(o.status)&&!p&&!r&&f>0&&$("epic_aggregate_terminal_incomplete_conflict"),o.total_stories=y,o.completed_stories=b,!z(o.status)&&r&&(o.status="done",(o.completed_at===void 0||o.completed_at===null)&&(o.completed_at=n))}function _e(t,e,i){const n=t??{};(!n.epics||typeof n.epics!="object")&&(n.epics={}),(!n.stories||typeof n.stories!="object")&&(n.stories={});const c=n.epics,o=n.stories,l=[],f=[],h=new Set,_=new Map;let p=0;const y=i?.forceOverwrite===!0,b=i?.nowIso??new Date().toISOString(),r=new Set;for(const s of e.epics)r.add(s.id),_.set(s.id,s);for(const s of e.epics){const a=c[s.id],d=a;if(d&&(d.branch_mode==="epic"||typeof d.execution_branch=="string")){f.push({id:s.id,reason:"epic Git lifecycle preserved"});continue}if(a&&a.source==="create-epic"&&!y){f.push({id:s.id,reason:"create-epic source preserved (defense)"});continue}if(a&&a.source==="discovered"&&!y){const m=a;m.depends_on=s.depends_on??[],m.stories=s.stories.map(u=>u.id),s.title!==void 0&&(m.title=s.title),s.status!==void 0&&!z(m.status)&&(m.status=s.status),h.add(s.id);continue}const g={id:s.id,title:s.title??"",status:s.status??"planning",source:"discovered",discovered_at:b,merged_at:b,stories:s.stories.map(m=>m.id),depends_on:s.depends_on??[]};c[s.id]=g,h.add(s.id),l.push(s.id)}for(const s of e.epics)for(const a of s.stories){const d=o[a.id],g=c[s.id];if(d&&g&&(g.branch_mode==="epic"||typeof g.execution_branch=="string")){f.push({id:a.id,reason:"story Git lifecycle preserved"});continue}if(d&&d.source==="create-epic"&&!y){f.push({id:a.id,reason:"create-epic source preserved (defense, story)"});continue}if(d&&de(d.status)&&!y){const u=d;u.depends_on=a.depends_on??[],u.files_to_modify=a.files_to_modify??[];continue}if(d&&d.source==="discovered"&&!y){const u=d;u.depends_on=a.depends_on??[],u.files_to_modify=a.files_to_modify??[];continue}const m={id:a.id,epic_id:a.epic_id,title:a.title??"",status:a.status??"backlog",source:"discovered",discovered_at:b,depends_on:a.depends_on??[],files_to_modify:a.files_to_modify??[]};o[a.id]=m,p++}for(const s of h)pe(s,c,o,b,_.get(s));return{state:n,added:Object.freeze([...l]),skipped:Object.freeze([...f]),storiesAdded:p}}export{E as EPIC_FILE_PATTERNS,me as GENERATED_NAMING_CONTRACT,j as STORY_FILE_PATTERNS,ge as buildDefaultDiscoveryFs,he as discoverEpicsFromDir,te as getEpicIdFromDescriptor,_e as mergeDiscoveredIntoState};
1
+ const ge=Object.freeze({epicId:"P{number}",epicFilename:"docs/epics/epic-P{number}.md",storyId:"P{number}.NN",storyFilename:"docs/stories/P{number}.NN.story.md",acceptedOnly:Object.freeze({epicFilenames:Object.freeze(["docs/epics/epic-{number}.md","docs/epics/EPIC-{number}-{slug}.md","docs/epics/EPIC-{number}_{slug}.md","docs/epics/epic-{slug}.md"]),storyFilenames:Object.freeze(["docs/stories/{number}.NN.story.md","docs/stories/story-P{number}.NN.md","docs/stories/story-{number}.NN.md","docs/stories/EPIC-{number}.STORY-{number}.md"])})}),O=Object.freeze([/^epic-(P?\d+(?:\.\d+)?)\.md$/i,/^EPIC-(\d+)(?:[-_].*)?\.md$/,/^epic-([a-z0-9][a-z0-9_-]*)\.md$/i]),D=Object.freeze([/^(P?\d+\.\d+(?:\.\d+)?)\.story\.md$/i,/^story-(P?\d+\.\d+)\.md$/i,/^EPIC-(\d+)\.STORY-(\d+)\.md$/]),k=Object.freeze([".archive","_archive","_archives","_drafts","archive","archives","archived","drafts"]),F=/^---\s*\n([\s\S]*?)\n---/;function z(t,e){const i=new RegExp(`^${e}:\\s*(.+?)\\s*$`,"mi"),n=t.match(i);if(!n)return;let c=n[1].trim();return(c.startsWith('"')&&c.endsWith('"')||c.startsWith("'")&&c.endsWith("'"))&&(c=c.slice(1,-1)),c||void 0}function G(t){if(typeof t!="string"||t.length===0)return{};const e=t.match(F);if(!e)return{};const i=e[1];return{id:z(i,"id"),title:z(i,"title"),status:z(i,"status"),epic_id:z(i,"epic_id"),depends_on:te(i),files_to_modify:P(i,"files_to_modify"),total_stories:x(z(i,"total_stories"))}}const Y=32,B=4096,H=/^\s*\*\*([^*\r\n]+?):\*\*\s*(.*?)\s*$/,U=new Set(["backlog","pending","ready","planning","in-progress","done","completed","merged","cancelled","canceled","abandoned","draft","template","archived","branch-ready","diagnose","research","write-spec","create-tasks","implemented","refactor-suggest-done","property-tests-emitted","tests-validated","qa-gate-passed","memory-updated","committed","synced","pr-created","pr-reviewed","partial"]);function L(t){let e=t.trim();return e.startsWith("`")&&e.endsWith("`")&&e.length>=2&&(e=e.slice(1,-1).trim()),(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1).trim()),e}function x(t){if(t===void 0||!/^\d{1,5}$/.test(t.trim()))return;const e=Number(t);return Number.isSafeInteger(e)?e:void 0}function Z(t){const e=L(t).toLowerCase().replace(/[_\s]+/g,"-"),n={planned:"planning",complete:"completed",closed:"done",finished:"done"}[e]??e;return U.has(n)?n:void 0}function X(t){const e=t.trim().toLowerCase().replace(/[_-]+/g," ").replace(/\s+/g," ");if(e==="id"||e==="epic id")return"id";if(e==="title"||e==="titulo")return"title";if(e==="status")return"status";if(e==="total stories"||e==="total de stories")return"total_stories"}function q(t){const e=t.find(c=>/^#\s+\S/.test(c));if(!e)return;const i=e.replace(/^#\s+/,"").trim();return(i.replace(/^(?:Epic|Story)\s+\S+\s*(?:(?:--?|:)|[\u2013\u2014])\s*/i,"")||i).slice(0,200)}function j(t,e,i,n){const c=[...new Set(t.map(L).filter(Boolean))];if(c.length>1){n.push(`legacy metadata ambiguous for '${e}' in '${i}': conflicting values ignored`);return}return c[0]}function K(t,e){const i=[],n=[],c=t.slice(0,B).split(/\r?\n/,Y);for(const m of c){if(n.length>0&&(/^\s*---\s*$/.test(m)||/^##\s+/.test(m)))break;n.push(m)}const o=new Map;for(const m of n){const r=m.match(H);if(!r)continue;const a=X(r[1]);if(!a)continue;const d=o.get(a)??[];d.push(r[2]),o.set(a,d)}const f=j(o.get("id")??[],"id",e,i),l=j(o.get("title")??[],"title",e,i),_=j(o.get("status")??[],"status",e,i),g=j(o.get("total_stories")??[],"total_stories",e,i),p=_===void 0?void 0:Z(_);_!==void 0&&p===void 0&&i.push(`legacy metadata status unrecognized in '${e}': value ignored`);const b=x(g);g!==void 0&&b===void 0&&i.push(`legacy metadata total_stories invalid in '${e}': value ignored`);const $=f&&/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(f)?f:void 0;f!==void 0&&$===void 0&&i.push(`legacy metadata id invalid in '${e}': value ignored`);const s=q(n),u=l??s;return{metadata:{...$?{id:$}:{},...u?{title:u.slice(0,200)}:{},...p?{status:p}:{},...b!==void 0?{total_stories:b}:{}},source:"legacy-metadata",warnings:Object.freeze(i)}}function V(t){return/^[A-Za-z0-9._-]{1,160}$/.test(t)?t:"markdown-target.md"}function J(t){return new TextEncoder().encode(t).length}function Q(t){let e=!1;for(const i of t.split(/\r?\n/)){if(!i.trim()||i.trimStart().startsWith("#"))continue;const n=i.match(/^\s+-\s+(.+?)\s*$/);if(n){if(!e)return!0;const f=n[1].trim();if(f.startsWith('"')&&!f.endsWith('"')||f.startsWith("'")&&!f.endsWith("'"))return!0;continue}if(/^\s+/.test(i))return!0;const c=i.match(/^[A-Za-z0-9_-]+:\s*(.*?)\s*$/);if(!c)return!0;const o=c[1].trim();if(e=o.length===0,o.startsWith('"')&&!o.endsWith('"')||o.startsWith("'")&&!o.endsWith("'"))return!0}return!1}function ee(t,e,i){const n=V(e),c=t.match(F);if(!c)return Object.freeze([`metadata-frontmatter reason=frontmatter-malformed file='${n}': repair the bounded leading YAML envelope; source content omitted`]);const o=c[1];return J(o)>8*1024||o.split(/\r?\n/).length>128?Object.freeze([`metadata-frontmatter reason=frontmatter-oversized file='${n}': reduce the leading YAML envelope; source content omitted`]):Q(o)||o.trim().length>0&&Object.keys(i).length===0?Object.freeze([`metadata-frontmatter reason=frontmatter-unsupported-value file='${n}': use bounded scalar fields or string lists; source content omitted`]):Object.freeze([])}function N(t,e){if(/^---(?:\s*\r?\n|\s*$)/.test(t)){const i=G(t);return{metadata:i,source:"frontmatter",warnings:ee(t,e,i)}}return typeof t!="string"||t.length===0?{metadata:{},source:"none",warnings:Object.freeze([])}:K(t,e)}function R(t){let e=t.trim();if((e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))&&(e=e.slice(1,-1).trim()),!!e&&!/^(null|undefined|true|false)$/i.test(e))return e}function te(t){return P(t,"depends_on")}function P(t,e){const i=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),c=t.match(new RegExp(`^${i}:[ \\t]*(.+?)[ \\t]*$`,"mi"))?.[1]?.trim();if(c!==void 0){const g=(c.startsWith("[")&&c.endsWith("]")?c.slice(1,-1).split(","):[c]).map(R).filter(p=>typeof p=="string"&&p.length>0);return g.length>0?Object.freeze(g):void 0}const o=t.split(/\r?\n/),f=[];let l=!1;for(const _ of o){if(!l){new RegExp(`^${i}:\\s*$`,"i").test(_.trim())&&(l=!0);continue}if(/^[A-Za-z0-9_-]+:\s*/.test(_))break;const g=_.match(/^\s*-\s*(.+?)\s*$/);if(!g)continue;const p=R(g[1]);p&&f.push(p)}return f.length>0?Object.freeze(f):void 0}function T(t){return t.startsWith("_")||t.startsWith(".")}function ie(t){return k.includes(t)}async function he(){const t=await import("node:fs"),e=await import("node:path");return{isDirectory(i){try{return t.statSync(i).isDirectory()}catch{return!1}},readdir(i){try{return t.readdirSync(i)}catch{return[]}},readFile(i){try{return t.readFileSync(i,"utf8")}catch{return""}},join(...i){try{return e.join(...i)}catch{return i.join("/")}}}}function ne(t,e){let i=!1,n=null,c=null;for(let o=0;o<O.length;o++){const f=O[o],l=t.match(f);if(l){i=!0,n=l[1],c=o===2?"filename-slug":"filename-canonical";break}}return i?e.id&&e.id.length>0?{id:e.id,source:"frontmatter"}:{id:n,source:c}:null}function se(t){for(const e of O){const i=t.match(e);if(i)return i[1]}return null}function I(t){const e=t.trim();return/^\d+(?:\.\d+)*$/i.test(e)?`P${e}`.toUpperCase():e.toUpperCase()}function re(t){const e=I(t);return/^P\d+(?:\.\d+)?$/i.test(e)?`epic-${e}.md`:`epic-${t}.md`}function oe(t){const e=I(t);return/^P\d+\.\d+(?:\.\d+)?$/i.test(e)?`${e}.story.md`:`${t}.story.md`}function C(t,e){return!t||!e?!1:I(t)!==I(e)}function ce(t,e,i){return`frontmatter/filename mismatch: epic '${t}' has frontmatter id '${e}' but filename implies '${i}'. Repair by using generated canonical '${re(e)}' or changing frontmatter id to '${i}'.`}function ae(t,e,i){return`frontmatter/filename mismatch: story '${t}' has frontmatter id '${e}' but filename implies '${i}'. Repair by using generated canonical '${oe(e)}' or changing frontmatter id to '${i}'.`}function de(t){let e=t.match(D[0]);if(e){const i=e[1],n=i.indexOf(".");return{id:i,epic_id_hint:n>0?i.slice(0,n):null}}if(e=t.match(D[1]),e){const i=e[1],n=i.indexOf(".");return{id:i,epic_id_hint:n>0?i.slice(0,n):null}}if(e=t.match(D[2]),e){const i=e[1],n=e[2];return{id:`${i}.${n}`,epic_id_hint:i}}return null}function ue(t,e){return e.join(t,"..","stories")}function _e(t,e){const i=[],n=[],c=[],o=new Date().toISOString();if(typeof t!="string"||t.length===0)return Object.freeze({epics:Object.freeze([]),warnings:Object.freeze([]),skipped:Object.freeze([])});if(!e.isDirectory(t))return Object.freeze({epics:Object.freeze([]),warnings:Object.freeze([]),skipped:Object.freeze([])});const f=[...e.readdir(t)].sort(),l=[];for(const s of f){const u=e.join(t,s);if(T(s)){n.push(`${s}: ignored (draft or hidden prefix)`);continue}if(e.isDirectory(u)){if(ie(s)){n.push(`${s}: ignored subdirectory`);continue}n.push(`${s}: subdirectory not recursed`);continue}if(!s.endsWith(".md"))continue;if(!O.some(h=>h.test(s))){n.push(`${s}: did not match any EPIC_FILE_PATTERNS`);continue}const m=e.readFile(u),r=N(m,s);i.push(...r.warnings);const a=r.metadata,d=se(s);a.id&&C(a.id,d)&&i.push(r.source==="frontmatter"?ce(s,a.id,d):`legacy metadata/filename mismatch: epic '${s}' id does not match its filename`),l.push({filename:s,fullPath:u,metadata:a,metadataSource:r.source})}const _=new Map,g=[];for(const s of l){const u=ne(s.filename,s.metadata);if(u===null){n.push(`${s.filename}: id resolution returned null`);continue}let y=u.id;const m=y,r=_.get(m)??0;r>0&&(y=`${m}-${r+1}`,i.push(`epic ID collision: '${m}' resolved to '${y}' for ${s.filename}`)),_.set(m,r+1),g.push({candidate:s,finalId:y,idSource:s.metadataSource==="legacy-metadata"&&s.metadata.id?"legacy-metadata":u.source})}const p=new Map,b=ue(t,e);if(e.isDirectory(b)){const s=[...e.readdir(b)].sort();for(const u of s){if(T(u)||!u.endsWith(".md"))continue;const y=e.join(b,u);if(e.isDirectory(y))continue;const m=de(u);if(!m)continue;const r=e.readFile(y),a=N(r,u);i.push(...a.warnings);const d=a.metadata;d.id&&C(d.id,m.id)&&i.push(a.source==="frontmatter"?ae(u,d.id,m.id):`legacy metadata/filename mismatch: story '${u}' id does not match its filename`);const h=d.epic_id??m.epic_id_hint;if(!h){i.push(`story '${u}' has no epic_id (frontmatter or filename pattern)`);continue}const S=Object.freeze({id:m.id,epic_id:h,filename:u,path:y,...d.title?{title:d.title}:{},...d.status?{status:d.status}:{},...d.depends_on?{depends_on:d.depends_on}:{},...d.files_to_modify?{files_to_modify:d.files_to_modify}:{}});p.has(h)||p.set(h,[]),p.get(h).push(S)}}for(const s of g){const u=p.get(s.finalId)??[],y=Object.freeze({id:s.finalId,filename:s.candidate.filename,path:s.candidate.fullPath,...s.candidate.metadata.title?{title:s.candidate.metadata.title}:{},...s.candidate.metadata.status?{status:s.candidate.metadata.status}:{},...s.candidate.metadata.total_stories!==void 0?{total_stories:s.candidate.metadata.total_stories}:{},...s.candidate.metadata.depends_on?{depends_on:s.candidate.metadata.depends_on}:{},source:"discovered",discovered_at:o,stories:Object.freeze([...u]),t:s.idSource,e:s.candidate.metadataSource});c.push(y)}const $=new Set(c.map(s=>s.id));for(const[s,u]of p.entries())if(!$.has(s))for(const y of u)i.push(`orphan story '${y.filename}' references unknown epic '${s}'`);return Object.freeze({epics:Object.freeze(c),warnings:Object.freeze(i),skipped:Object.freeze(n)})}function M(t){const e=fe(t);return e==="done"||e==="completed"||e==="merged"}function A(t){return M(t)}function fe(t){return typeof t=="string"?t.trim().toLowerCase().replace(/[_\s]+/g,"-"):""}function E(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}function w(t){throw new Error(`epic-aggregate-writer:${t}`)}function le(t){const e=[],i=new Set;for(const n of t)(typeof n!="string"||n.length===0)&&w("epic_aggregate_story_ref_invalid"),i.has(n)&&w("epic_aggregate_story_ref_duplicate"),i.add(n),e.push(n);return e}function pe(t,e,i){const n=i[e];return n===void 0&&w("epic_aggregate_story_missing"),E(n)||w("epic_aggregate_story_not_object"),n.id!==e&&w("epic_aggregate_story_id_conflict"),n.epic_id!==t&&w("epic_aggregate_story_epic_conflict"),n}function me(t,e,i,n,c){const o=e[t];E(o)||w("epic_aggregate_epic_not_object"),o.id!==t&&w("epic_aggregate_epic_id_conflict"),Array.isArray(o.stories)||w("epic_aggregate_story_list_not_array");const f=le(o.stories),l=f.length,_=f.filter(u=>M(pe(t,u,i).status)).length,g=c?.total_stories,p=c?.e==="legacy-metadata"&&A(o.status),b=p?g??l:l>0?l:g??0,$=p||l===0&&b>0&&A(o.status)?b:_,s=l>0&&_===l;A(o.status)&&!p&&!s&&l>0&&w("epic_aggregate_terminal_incomplete_conflict"),o.total_stories=b,o.completed_stories=$,!A(o.status)&&s&&(o.status="done",(o.completed_at===void 0||o.completed_at===null)&&(o.completed_at=n))}function ye(t,e,i){const n=t??{},c=E(n.epics)?n.epics:{},o=E(n.stories)?n.stories:{},f=structuredClone(c),l=structuredClone(o),_=[],g=[],p=new Set,b=new Map;let $=0;const s=i?.forceOverwrite===!0,u=i?.nowIso??new Date().toISOString(),y=new Set,m=new Map;for(const r of e.epics){if(!r.id||y.has(r.id))throw new Error("discovery_epic_identity_conflict");y.add(r.id),b.set(r.id,r);const a=f[r.id];if(a!==void 0&&(!E(a)||a.id!==void 0&&a.id!==r.id))throw new Error("discovery_epic_identity_conflict");for(const d of r.stories){if(!d.id||d.epic_id!==r.id||m.has(d.id))throw new Error("discovery_story_identity_conflict");m.set(d.id,r.id);const h=l[d.id];if(h!==void 0&&(!E(h)||h.id!==void 0&&h.id!==d.id||h.epic_id!==void 0&&h.epic_id!==r.id))throw new Error("discovery_story_identity_conflict")}}for(const r of e.epics){const a=f[r.id],d=a;if(d&&(d.branch_mode==="epic"||typeof d.execution_branch=="string")){g.push({id:r.id,reason:"epic Git lifecycle preserved"});continue}if(a&&a.source==="create-epic"&&!s){g.push({id:r.id,reason:"create-epic source preserved (defense)"});continue}if(a){const S=a;S.depends_on=r.depends_on??[];const v=Array.isArray(S.stories)?S.stories.filter(W=>typeof W=="string"):[];S.stories=[...new Set([...v,...r.stories.map(W=>W.id)])],r.title!==void 0&&(S.title=r.title),a.source!=="create-epic"&&p.add(r.id);continue}const h={id:r.id,title:r.title??"",status:r.status??"planning",source:"discovered",discovered_at:u,merged_at:u,stories:r.stories.map(S=>S.id),depends_on:r.depends_on??[]};f[r.id]=h,p.add(r.id),_.push(r.id)}for(const r of e.epics)for(const a of r.stories){const d=l[a.id],h=f[r.id];if(d&&h&&(h.branch_mode==="epic"||typeof h.execution_branch=="string")){g.push({id:a.id,reason:"story Git lifecycle preserved"});continue}if(d&&d.source==="create-epic"&&!s){g.push({id:a.id,reason:"create-epic source preserved (defense, story)"});continue}if(d){const v=d;a.title!==void 0&&(v.title=a.title),v.depends_on=a.depends_on??[],v.files_to_modify=a.files_to_modify??[];continue}const S={id:a.id,epic_id:a.epic_id,title:a.title??"",status:"backlog",source:"discovered",discovered_at:u,depends_on:a.depends_on??[],files_to_modify:a.files_to_modify??[]};l[a.id]=S,$++}for(const r of p)me(r,f,l,u,b.get(r));return n.epics=f,n.stories=l,{state:n,added:Object.freeze([..._]),skipped:Object.freeze([...g]),storiesAdded:$}}export{O as EPIC_FILE_PATTERNS,ge as GENERATED_NAMING_CONTRACT,D as STORY_FILE_PATTERNS,he as buildDefaultDiscoveryFs,_e as discoverEpicsFromDir,ne as getEpicIdFromDescriptor,ye as mergeDiscoveredIntoState};
@@ -81,6 +81,78 @@ export interface YoloopPersistedSession {
81
81
  /** P96.05 AC5: storyIds skipped due to `*yolo` timeout. */
82
82
  readonly skippedStories?: readonly string[];
83
83
  }
84
+ /** P220.11 additive run-control wire. Kept structural for standalone clients. */
85
+ export type YoloopRunStatus = 'created' | 'running' | 'pausing' | 'paused' | 'resuming' | 'cancelling' | 'success' | 'partial' | 'blocked' | 'failure' | 'exhausted' | 'cancelled';
86
+ export type YoloopOperationalBudgetDimension = 'deadline' | 'wall' | 'turns' | 'tokens' | 'cost' | 'toolCalls' | 'attempts' | 'feedback' | 'depth' | 'concurrency' | 'risk';
87
+ export type YoloopOperationalBudgetValues = Readonly<Record<YoloopOperationalBudgetDimension, number>>;
88
+ export interface YoloopPersistedOperationalBudgetV1 {
89
+ readonly schemaVersion: 'YoloopOperationalBudget/1.0';
90
+ readonly startedAtMs: number;
91
+ readonly limits: YoloopOperationalBudgetValues;
92
+ }
93
+ export interface YoloopPersistedProgressSnapshotV1 {
94
+ readonly schemaVersion: 'ProgressSnapshot/1.0';
95
+ readonly runId: string;
96
+ readonly snapshotId: string;
97
+ readonly sequence: number;
98
+ readonly revision: number;
99
+ readonly operationId: string;
100
+ readonly boundary: 'budget' | 'retry' | 'pause' | 'resume' | 'cancel' | 'terminal';
101
+ readonly phase: 'before' | 'after';
102
+ readonly status: YoloopRunStatus;
103
+ readonly occurredAt: string;
104
+ readonly parentSnapshotHash: string | null;
105
+ readonly snapshotHash: string;
106
+ readonly budget: {
107
+ readonly schemaVersion: 'YoloopOperationalBudgetSummary/1.0';
108
+ readonly limits: YoloopOperationalBudgetValues;
109
+ readonly usage: YoloopOperationalBudgetValues;
110
+ readonly exhaustedDimension: YoloopOperationalBudgetDimension | null;
111
+ };
112
+ readonly reasonCodes: readonly string[];
113
+ readonly publicSafe: true;
114
+ }
115
+ export interface YoloopPersistedRunEventV1 {
116
+ readonly schemaVersion: 'YoloopRunEvent/1.0';
117
+ readonly runId: string;
118
+ readonly operationId: string;
119
+ readonly eventId: string;
120
+ readonly kind: 'transition' | 'budget' | 'retry' | 'intent' | 'conflict' | 'terminal';
121
+ readonly sequence: number;
122
+ readonly revision: number;
123
+ readonly occurredAt: string;
124
+ readonly fromStatus: YoloopRunStatus;
125
+ readonly toStatus: YoloopRunStatus;
126
+ readonly reasonCodes: readonly string[];
127
+ readonly budgetDimension: YoloopOperationalBudgetDimension | null;
128
+ readonly parentEventHash: string | null;
129
+ readonly eventHash: string;
130
+ readonly publicSafe: true;
131
+ }
132
+ export interface YoloopRunTransitionReceiptV1 {
133
+ readonly schemaVersion: 'YoloopRunTransitionReceipt/1.0';
134
+ readonly runId: string;
135
+ readonly operationId: string;
136
+ readonly idempotencyKey: string;
137
+ readonly payloadHash: string;
138
+ readonly revision: number;
139
+ readonly snapshotHash: string | null;
140
+ readonly status: YoloopRunStatus;
141
+ }
142
+ export interface YoloopRunControlV1 {
143
+ readonly schemaVersion: 'YoloopRunControl/1.0';
144
+ readonly runId: string;
145
+ readonly revision: number;
146
+ readonly status: YoloopRunStatus;
147
+ readonly startedAt: string;
148
+ readonly deadlineAt: string;
149
+ readonly operationalBudget: YoloopPersistedOperationalBudgetV1;
150
+ readonly usage: YoloopOperationalBudgetValues;
151
+ readonly events: readonly YoloopPersistedRunEventV1[];
152
+ readonly progressSnapshots: readonly YoloopPersistedProgressSnapshotV1[];
153
+ readonly latestSnapshotHash: string | null;
154
+ readonly receipts: readonly YoloopRunTransitionReceiptV1[];
155
+ }
84
156
  /**
85
157
  * Forward-declared opaque reference to `YoloopRecoveryAttempt` (canonical lives
86
158
  * in `@neocortex/shared/yoloop/recovery-types`). Structural mirror to avoid
@@ -100,7 +172,7 @@ export interface YoloopRecoveryAttemptRef {
100
172
  * Mirrors server-internal `YoloopRuntimeState` minus lock-related fields.
101
173
  */
102
174
  export interface YoloopPersistedCounters {
103
- readonly version: '1.0' | '1.1';
175
+ readonly version: '1.0' | '1.1' | '1.2';
104
176
  readonly lastResetAt: string;
105
177
  readonly storiesRunToday: number;
106
178
  readonly iterationsToday: number;
@@ -111,6 +183,8 @@ export interface YoloopPersistedCounters {
111
183
  readonly totalRunsAllTime: number;
112
184
  readonly totalStoriesAllTime: number;
113
185
  readonly currentSession: YoloopPersistedSession | null;
186
+ /** P220.11: absent for readable v1.0/v1.1 state. */
187
+ readonly runControl?: YoloopRunControlV1;
114
188
  /** P205.08 semantic continuation policy. New state writes v2; absence is legacy v1. */
115
189
  readonly continuationPolicy?: 'p137-resume-first-v1' | 'p205-finite-resume-v2';
116
190
  /** Opaque, public-safe frozen session/certificate state owned by the root. */
@@ -144,6 +218,9 @@ export type YoloopStateOperation = {
144
218
  readonly lock: YoloopPersistedLock;
145
219
  } | {
146
220
  readonly type: 'release_lock';
221
+ readonly expectedRunId?: string;
222
+ readonly expectedSessionId?: string;
223
+ readonly expectedLeaseId?: string;
147
224
  } | {
148
225
  readonly type: 'write_state';
149
226
  readonly state: YoloopPersistedCounters;
@@ -169,6 +246,15 @@ export type YoloopStateOperation = {
169
246
  readonly type: 'switch_continuation_policy';
170
247
  readonly policy: 'p137-resume-first-v1' | 'p205-finite-resume-v2';
171
248
  readonly session: Readonly<Record<string, unknown>>;
249
+ } | {
250
+ readonly type: 'transition_run';
251
+ readonly runId: string;
252
+ readonly operationId: string;
253
+ readonly idempotencyKey: string;
254
+ readonly expectedRevision: number;
255
+ readonly expectedSnapshotHash: string | null;
256
+ readonly payloadHash: string;
257
+ readonly next: YoloopRunControlV1;
172
258
  } | {
173
259
  readonly type: 'recovery_attempted';
174
260
  readonly attempt: YoloopRecoveryAttemptRef;
@@ -22,7 +22,7 @@
22
22
  * Cross-platform: Unix chmod + Windows icacls via existing
23
23
  * setSecureFilePermissions helper from secure-config.ts.
24
24
  */
25
- import type { YoloopPersistedCounters, YoloopPersistedState, YoloopRecoveryAttemptRef, YoloopStateUpdate } from './shared-yoloop-types.js';
25
+ import type { YoloopPersistedCounters, YoloopPersistedState, YoloopRecoveryAttemptRef, YoloopRunControlV1, YoloopRunTransitionReceiptV1, YoloopStateOperation, YoloopStateUpdate } from './shared-yoloop-types.js';
26
26
  /** Minimal Pino-compatible logger subset. */
27
27
  export interface ClientYoloopLogger {
28
28
  warn(payload: object, msg?: string): void;
@@ -37,6 +37,7 @@ export declare class YoloopClientStateStore {
37
37
  private readonly stateDir;
38
38
  private readonly statePath;
39
39
  private readonly lockPath;
40
+ private readonly mutationLockPath;
40
41
  private readonly logger;
41
42
  constructor(projectRoot: string, logger?: ClientYoloopLogger);
42
43
  /**
@@ -55,6 +56,18 @@ export declare class YoloopClientStateStore {
55
56
  * single atomic filesystem mutation. Next invoke's readAll() reconciles.
56
57
  */
57
58
  applyUpdate(update: YoloopStateUpdate, nowIso?: string): Promise<void>;
59
+ /**
60
+ * Local typed lifecycle-control API. Server transitions and local
61
+ * pause/resume/cancel requests share this serialized CAS boundary.
62
+ */
63
+ requestRunTransition(operation: Extract<YoloopStateOperation, {
64
+ readonly type: 'transition_run';
65
+ }>, nowIso?: string): Promise<YoloopRunTransitionReceiptV1>;
66
+ /** Persist a cooperative control intent before the next orchestration effect. */
67
+ requestLifecycleAction(action: 'pause' | 'resume' | 'cancel', options?: {
68
+ readonly operationId?: string;
69
+ readonly idempotencyKey?: string;
70
+ }, nowIso?: string): Promise<YoloopRunTransitionReceiptV1>;
58
71
  private applyOperation;
59
72
  private reserveContinuationAuthority;
60
73
  private acknowledgeContinuationAuthority;
@@ -83,13 +96,13 @@ export declare class YoloopClientStateStore {
83
96
  * is the authoritative writer of pid/hostname; server can't know them).
84
97
  */
85
98
  private acquireLock;
86
- /** Release the lock. Idempotent (ENOENT swallowed). */
99
+ /** Release the lock. V1.2 active runs require compare-bound ownership. */
87
100
  private releaseLock;
88
101
  /** Read lock file. Returns null on ENOENT / parse error. */
89
102
  private readLock;
90
103
  /** Read state file; returns defaults on any I/O or parse error. */
91
104
  private readState;
92
- /** Atomic state write: temp + rename. */
105
+ /** Durable atomic state write: same-filesystem temp + fsync + rename + dir fsync. */
93
106
  private writeState;
94
107
  /**
95
108
  * Advance to next epic: read-modify-write on currentSession.currentEpicIndex.
@@ -98,6 +111,9 @@ export declare class YoloopClientStateStore {
98
111
  */
99
112
  private advanceEpic;
100
113
  private ensureDir;
114
+ private syncStateDirectory;
115
+ private withMutationLock;
116
+ private transitionRun;
101
117
  /**
102
118
  * Fire-and-forget chmod/ACL. Uses sync helpers from secure-config.ts
103
119
  * which are fail-open by contract (never throw).
@@ -120,6 +136,14 @@ export declare class YoloopClientStateStore {
120
136
  /** Remove `yoloop-state.json.tmp.*` orphans from crashed writes. */
121
137
  private cleanupOrphanTempFiles;
122
138
  }
139
+ export declare function buildYoloopRunTransitionPayloadHash(input: {
140
+ readonly runId: string;
141
+ readonly operationId: string;
142
+ readonly idempotencyKey: string;
143
+ readonly expectedRevision: number;
144
+ readonly expectedSnapshotHash: string | null;
145
+ readonly next: YoloopRunControlV1;
146
+ }): string;
123
147
  /**
124
148
  * Enforce FIFO bound on `recovery_history[]`. Returns a new array of length
125
149
  * <= `RECOVERY_HISTORY_CAP`, preserving the most recent entries (drops from
@@ -1 +1 @@
1
- import{promises as c}from"node:fs";import{createHash as k}from"node:crypto";import{join as y,dirname as S}from"node:path";import{hostname as _}from"node:os";import{setSecureFilePermissions as E,setSecureDirPermissions as P}from"../config/secure-config.js";import{RECOVERY_HISTORY_CAP as g}from"./shared-yoloop-types.js";const T=".neocortex",v="yoloop-state.json",R="yoloop.lock",C=360*60*1e3,I={warn:()=>{},info:()=>{},error:()=>{}};function f(i){const e=i.slice(0,10);return{version:"1.1",lastResetAt:i,storiesRunToday:0,iterationsToday:0,iterationsTodayDate:e,tokensUsedToday:0,lastRunAt:null,coolDownUntil:null,totalRunsAllTime:0,totalStoriesAllTime:0,currentSession:null,continuationPolicy:"p205-finite-resume-v2",pendingContinuationAuthorities:[],continuationReceipts:[],continuationRecoveryReceipts:[]}}function U(i){return{lock:null,state:f(i)}}class q{stateDir;statePath;lockPath;logger;constructor(e,t=I){this.stateDir=y(e,T),this.statePath=y(this.stateDir,v),this.lockPath=y(this.stateDir,R),this.logger=t}async readAll(e){const t=e??new Date().toISOString();await this.cleanupOrphanTempFiles();const[o,r]=await Promise.all([this.readLock(),this.readState(t)]);return{lock:await this.prepareLockForSnapshot(o,t),state:r}}async applyUpdate(e,t){const o=t??new Date().toISOString();await this.ensureDir();for(const r of e.operations)await this.applyOperation(r,o)}async applyOperation(e,t){switch(e.type){case"acquire_lock":await this.acquireLock(e.lock,t);return;case"release_lock":await this.releaseLock();return;case"write_state":await this.writeState(e.state);return;case"advance_epic":await this.advanceEpic(e.nextEpicIndex,e.nextEpicId,t);return;case"no_op":this.logger.info({event:"yoloop_noop"},"yoloop server sent no-op");return;case"reserve_continuation_authority":await this.reserveContinuationAuthority(e.authority,t);return;case"acknowledge_continuation_authority":await this.acknowledgeContinuationAuthority(e.idempotencyKey,e.receipt,t);return;case"repair_continuation_authority":await this.repairContinuationAuthority(e.beforeHash,e.authority,e.receipt,t);return;case"switch_continuation_policy":await this.switchContinuationPolicy(e.policy,e.session,t);return;case"recovery_attempted":await this.appendRecoveryAttempt(e.attempt,t);return;case"recovery_resolved":await this.resolveRecoveryAttempt(e.attempt_id,e.outcome,t);return;default:{const o=e;this.logger.warn({event:"yoloop_unknown_op",op:o},"unknown yoloop operation; skipping");return}}}async reserveContinuationAuthority(e,t){const o=await this.readState(t),r=o.pendingContinuationAuthorities??[],n=typeof e.idempotencyKey=="string"?e.idempotencyKey:"";if(!n)throw new Error("YOLOOP_CONTINUATION_AUTHORITY_INVALID");const s=r.find(a=>a.idempotencyKey===n);if(s){if(u(s)===u(e))return;throw new Error("YOLOOP_CONTINUATION_AUTHORITY_CONFLICT")}if(r.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED");await this.writeState({...o,pendingContinuationAuthorities:[...r,e]})}async acknowledgeContinuationAuthority(e,t,o){const r=await this.readState(o),n=r.pendingContinuationAuthorities??[],s=n.findIndex(p=>p.idempotencyKey===e);if(s<0)throw new Error("YOLOOP_CONTINUATION_RECEIPT_ORPHAN");const a=n[s];if(t.idempotencyKey!==e||t.sessionId!==a.sessionId||JSON.stringify(t.storyIds)!==JSON.stringify(a.storyIds))throw new Error("YOLOOP_CONTINUATION_RECEIPT_MISMATCH");const l=n.filter((p,h)=>h!==s),d=r.continuationReceipts??[];await this.writeState({...r,pendingContinuationAuthorities:l,continuationReceipts:[...d.slice(-63),t]})}async repairContinuationAuthority(e,t,o,r){const n=await this.readState(r),s=n.pendingContinuationAuthorities??[],a=n.continuationRecoveryReceipts??[],l=typeof o.idempotencyKey=="string"?o.idempotencyKey:"",d=a.find(O=>O.idempotencyKey===l);if(d){if(u(d)===u(o))return;throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_CONFLICT")}if(s.length!==1||w("p208-legacy-authority-before-v1",s[0])!==e)throw new Error("YOLOOP_CONTINUATION_RECOVERY_BEFORE_MISMATCH");const p=w("p208-legacy-authority-after-v1",t);if(o.beforeHash!==e||o.afterHash!==p)throw new Error("YOLOOP_CONTINUATION_RECOVERY_HASH_MISMATCH");const h={...o};if(delete h.idempotencyKey,!l||w("p208-legacy-authority-recovery-receipt-v1",h)!==l)throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_INVALID");await this.writeState({...n,pendingContinuationAuthorities:[t],continuationRecoveryReceipts:[...a.slice(-63),o]})}async switchContinuationPolicy(e,t,o){const r=await this.readState(o);if(r.currentSession||(r.pendingContinuationAuthorities?.length??0)>0)throw new Error("YOLOOP_POLICY_SWITCH_ACTIVE");try{await c.copyFile(this.statePath,`${this.statePath}.policy-backup`)}catch(n){if(n.code!=="ENOENT")throw n}await this.writeState({...r,continuationPolicy:e,continuationSession:t})}async appendRecoveryAttempt(e,t){const o=await this.readState(t),r=o.recovery_history??[];if(r.some(l=>l.attempted_at===e.attempted_at&&l.story_id===e.story_id)){this.logger.warn({event:"recovery_attempted_duplicate",story_id:e.story_id},"duplicate recovery_attempted op; skipping");return}const s=m([...r,e]),a={...o,recovery_history:s};await this.writeState(a),this.logger.info({event:"recovery_attempted_persisted",story_id:e.story_id,history_size:s.length},"recovery attempt persisted")}async resolveRecoveryAttempt(e,t,o){const r=await this.readState(o),n=r.recovery_history??[],s=n.findIndex(p=>p.recovery_step_id_tokenized===e);if(s===-1){this.logger.warn({event:"recovery_resolve_orphan",attempt_id:e},"recovery_resolved op had no matching attempt; skipping");return}const a=n[s];if(a.outcome===t&&a.finished_at){this.logger.info({event:"recovery_resolved_duplicate",attempt_id:e,outcome:t},"duplicate recovery_resolved op; skipping");return}const l=n.slice();l[s]={...a,outcome:t,finished_at:o};const d={...r,recovery_history:l};await this.writeState(d),this.logger.info({event:"recovery_resolved_persisted",attempt_id:e,outcome:t},"recovery attempt resolved")}async acquireLock(e,t){const o={...e,pid:process.pid,hostname:_(),ownerKind:"client",startedAt:e.startedAt||t,leaseId:e.leaseId??N(e.sessionId,t),leaseState:"active",leaseAcquiredAt:e.leaseAcquiredAt??t,leaseDurationMs:e.leaseDurationMs??C,leaseExpiresAt:e.leaseExpiresAt??e.ttlExpiresAt},r=JSON.stringify(o,null,2);try{const n=await c.open(this.lockPath,"wx");try{await n.writeFile(r,"utf8")}finally{await n.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired",pid:o.pid,sessionId:o.sessionId},"yoloop lock acquired");return}catch(n){const s=n;if(s.code!=="EEXIST")throw this.logger.error({event:"yoloop_lock_acquire_error",err:s.message},"failed to acquire yoloop lock"),n;const a=await this.readLock();if(a&&this.isSameClientSessionLock(a,o)){this.logger.info({event:"yoloop_lock_acquire_duplicate",sessionId:o.sessionId},"duplicate acquire_lock for current yoloop session; treating as no-op");return}if(!await this.maybeRecoverOrphanLock(t))throw this.logger.warn({event:"yoloop_lock_held"},"yoloop lock held by another live process"),n;const d=await c.open(this.lockPath,"wx");try{await d.writeFile(r,"utf8")}finally{await d.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired_after_recovery",pid:o.pid},"yoloop lock acquired after orphan recovery")}}async releaseLock(){try{await c.unlink(this.lockPath),this.logger.info({event:"yoloop_lock_released"},"yoloop lock released")}catch(e){const t=e;if(t.code==="ENOENT"){this.logger.info({event:"yoloop_lock_release_noop"},"yoloop lock release noop (file absent)");return}this.logger.warn({event:"yoloop_lock_release_error",err:t.message},"unexpected error releasing yoloop lock")}}async readLock(){try{const e=await c.readFile(this.lockPath,"utf8"),t=JSON.parse(e),o=t.currentEpicId??t.epicId??"";return{pid:t.pid??0,hostname:t.hostname??"",...t.ownerKind?{ownerKind:t.ownerKind}:{},...t.ownerPidState?{ownerPidState:t.ownerPidState}:{},...typeof t.leaseId=="string"?{leaseId:t.leaseId}:{},...t.leaseState?{leaseState:t.leaseState}:{},...typeof t.leaseAcquiredAt=="string"?{leaseAcquiredAt:t.leaseAcquiredAt}:{},...typeof t.leaseDurationMs=="number"?{leaseDurationMs:t.leaseDurationMs}:{},...typeof t.leaseExpiresAt=="string"?{leaseExpiresAt:t.leaseExpiresAt}:{},startedAt:t.startedAt??"",sessionId:t.sessionId??"",mode:t.mode??"epic-id",inputPath:t.inputPath??"",epicQueue:t.epicQueue??(o?[o]:[]),currentEpicIndex:t.currentEpicIndex??0,currentEpicId:o,ttlExpiresAt:t.ttlExpiresAt??"",epicId:t.epicId??o}}catch{return null}}async readState(e){try{const t=await c.readFile(this.statePath,"utf8"),o=JSON.parse(t);return D(o,e)}catch(t){const o=t;return o.code!=="ENOENT"&&this.logger.warn({event:"yoloop_state_read_error",err:o.message},"yoloop state file unreadable; using defaults"),f(e)}}async writeState(e){const t=`${this.statePath}.tmp.${process.pid}.${Date.now()}`,o=JSON.stringify(e,null,2);try{await c.writeFile(t,o,"utf8"),this.trySetFilePerms(t),await c.rename(t,this.statePath)}catch(r){const n=r;this.logger.error({event:"yoloop_state_write_error",err:n.message},"failed to write yoloop state");try{await c.unlink(t)}catch{}throw r}}async advanceEpic(e,t,o){const r=await this.readState(o);if(!r.currentSession){this.logger.warn({event:"yoloop_advance_no_session",nextEpicId:t},"advance_epic with no active session; skipping");return}const n={...r,currentSession:{...r.currentSession,currentEpicIndex:e}};await this.writeState(n),this.logger.info({event:"yoloop_epic_advance",nextEpicId:t,nextIndex:e},"yoloop advanced to next epic")}async ensureDir(){try{await c.mkdir(this.stateDir,{recursive:!0}),this.trySetDirPerms(this.stateDir)}catch(e){const t=e;throw this.logger.error({event:"yoloop_mkdir_error",err:t.message,path:this.stateDir},"failed to ensure .neocortex/ directory"),e}}trySetFilePerms(e){try{E(e)}catch{}}trySetDirPerms(e){try{P(e)}catch{}}async prepareLockForSnapshot(e,t){if(!e)return null;if(e.ownerKind!=="client")return e;const o=A(e,t),r={...e,leaseState:o};if(o==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"lease_expired"},"expired client-owned yoloop lock lease recovered before snapshot echo"),null;if(!r.pid||r.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"invalid_pid"},"invalid PID in client-owned yoloop lock; recovered"),null;if(r.hostname&&r.hostname!==_())return{...r,ownerPidState:"unknown"};try{return process.kill(r.pid,0),{...r,ownerPidState:"live"}}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"dead_pid"},"stale client-owned yoloop lock recovered before snapshot echo"),null}}async maybeRecoverOrphanLock(e){const t=await this.readLock();if(!t)return!0;if(A(t,e)==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"lease_expired"},"expired yoloop lock lease recovered"),!0;if(!t.pid||t.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"invalid_pid"},"invalid PID in lock file; recovered"),!0;if(t.hostname&&t.hostname!==_())return this.logger.warn({event:"yoloop_lock_remote_host",ownerKind:t.ownerKind??"server",ownerPidState:"unknown"},"lock held by a remote host; honoring"),!1;try{return process.kill(t.pid,0),!1}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"dead_pid"},"orphan yoloop lock recovered"),!0}}async deleteLockBestEffort(){try{await c.unlink(this.lockPath)}catch{}}isSameClientSessionLock(e,t){return e.ownerKind==="client"&&e.sessionId===t.sessionId&&e.pid===process.pid&&e.hostname===_()}async cleanupOrphanTempFiles(){try{const e=S(this.statePath),t=await c.readdir(e),o=`${v}.tmp.`,r=t.filter(n=>n.startsWith(o));for(const n of r)try{await c.unlink(y(e,n))}catch{}}catch{}}}function u(i){return i===null||typeof i!="object"?JSON.stringify(i):Array.isArray(i)?`[${i.map(u).join(",")}]`:`{${Object.entries(i).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${u(t)}`).join(",")}}`}function w(i,e){return k("sha256").update(`${i}\0${u(e)}`).digest("hex")}function N(i,e){return`lease-${L(`${i}:${e}`)}`}function L(i){let e=2166136261;for(let t=0;t<i.length;t+=1)e^=i.charCodeAt(t),e=Math.imul(e,16777619)>>>0;return e.toString(36).padStart(7,"0")}function A(i,e){const t=i.leaseExpiresAt??i.ttlExpiresAt,o=Date.parse(t),r=Date.parse(e);return!Number.isFinite(o)||!Number.isFinite(r)?"unknown":o<=r?"expired":"active"}function D(i,e){const t=f(e);return{...i,version:"1.1",lastResetAt:i.lastResetAt??t.lastResetAt,storiesRunToday:i.storiesRunToday??0,iterationsToday:i.iterationsToday??0,iterationsTodayDate:i.iterationsTodayDate??t.iterationsTodayDate,tokensUsedToday:i.tokensUsedToday??0,lastRunAt:i.lastRunAt??null,coolDownUntil:i.coolDownUntil??null,totalRunsAllTime:i.totalRunsAllTime??0,totalStoriesAllTime:i.totalStoriesAllTime??0,currentSession:i.currentSession??null,...i.continuationPolicy!==void 0?{continuationPolicy:i.continuationPolicy}:{},...i.continuationSession!==void 0?{continuationSession:i.continuationSession}:{},...i.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:i.pendingContinuationAuthorities}:{},...i.continuationReceipts!==void 0?{continuationReceipts:i.continuationReceipts}:{},...i.continuationRecoveryReceipts!==void 0?{continuationRecoveryReceipts:i.continuationRecoveryReceipts}:{},recovery_history:Array.isArray(i.recovery_history)?m(i.recovery_history):[]}}function m(i){return i.length<=g?i:i.slice(i.length-g)}export{q as YoloopClientStateStore,m as capRecoveryHistory,f as createDefaultCounters,U as createDefaultState};
1
+ import{promises as u}from"node:fs";import{createHash as P}from"node:crypto";import{join as w,dirname as C}from"node:path";import{hostname as f}from"node:os";import{setSecureFilePermissions as k,setSecureDirPermissions as x}from"../config/secure-config.js";import{RECOVERY_HISTORY_CAP as R}from"./shared-yoloop-types.js";const D=".neocortex",m="yoloop-state.json",H="yoloop.lock",Y="yoloop-state.mutation.lock",U=360*60*1e3,K=3e4,b=200,F={warn:()=>{},info:()=>{},error:()=>{}};function I(o){const e=o.slice(0,10);return{version:"1.1",lastResetAt:o,storiesRunToday:0,iterationsToday:0,iterationsTodayDate:e,tokensUsedToday:0,lastRunAt:null,coolDownUntil:null,totalRunsAllTime:0,totalStoriesAllTime:0,currentSession:null,continuationPolicy:"p205-finite-resume-v2",pendingContinuationAuthorities:[],continuationReceipts:[],continuationRecoveryReceipts:[]}}function oe(o){return{lock:null,state:I(o)}}class ne{stateDir;statePath;lockPath;mutationLockPath;logger;constructor(e,t=F){this.stateDir=w(e,D),this.statePath=w(this.stateDir,m),this.lockPath=w(this.stateDir,H),this.mutationLockPath=w(this.stateDir,Y),this.logger=t}async readAll(e){const t=e??new Date().toISOString();await this.cleanupOrphanTempFiles();const[n,r]=await Promise.all([this.readLock(),this.readState(t)]);return{lock:await this.prepareLockForSnapshot(n,t),state:r}}async applyUpdate(e,t){const n=t??new Date().toISOString();await this.ensureDir();for(const r of e.operations)await this.applyOperation(r,n)}async requestRunTransition(e,t){const n=t??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(()=>this.transitionRun(e,n))}async requestLifecycleAction(e,t={},n){const r=n??new Date().toISOString();return await this.ensureDir(),this.withMutationLock(async()=>{const i=(await this.readState(r)).runControl;if(!i)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const a=Date.parse(r).toString(36),c=t.operationId??`op-${e}-${a}`,l=t.idempotencyKey??`idem-${e}-${a}`,d=i.receipts.find(_=>_.operationId===c||_.idempotencyKey===l);if(d){if(d.operationId===c&&d.idempotencyKey===l)return d;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}const p=M(i.status,e);if(!p)throw new Error(g.has(i.status)?"YOLOOP_RUN_TERMINAL":"YOLOOP_RUN_TRANSITION_INVALID");return this.transitionRun(V({current:i,action:e,target:p,operationId:c,idempotencyKey:l,occurredAt:r}),r)})}async applyOperation(e,t){switch(e.type){case"acquire_lock":await this.acquireLock(e.lock,t);return;case"release_lock":await this.releaseLock(e);return;case"write_state":await this.withMutationLock(async()=>{const n=await this.readState(t),{runControl:r,...s}=e.state;await this.writeState({...s,...n.runControl?{version:"1.2",runControl:n.runControl}:{}})});return;case"advance_epic":await this.advanceEpic(e.nextEpicIndex,e.nextEpicId,t);return;case"no_op":this.logger.info({event:"yoloop_noop"},"yoloop server sent no-op");return;case"reserve_continuation_authority":await this.reserveContinuationAuthority(e.authority,t);return;case"acknowledge_continuation_authority":await this.acknowledgeContinuationAuthority(e.idempotencyKey,e.receipt,t);return;case"repair_continuation_authority":await this.repairContinuationAuthority(e.beforeHash,e.authority,e.receipt,t);return;case"switch_continuation_policy":await this.switchContinuationPolicy(e.policy,e.session,t);return;case"transition_run":await this.requestRunTransition(e,t);return;case"recovery_attempted":await this.appendRecoveryAttempt(e.attempt,t);return;case"recovery_resolved":await this.resolveRecoveryAttempt(e.attempt_id,e.outcome,t);return;default:{const n=e;this.logger.warn({event:"yoloop_unknown_op",op:n},"unknown yoloop operation; skipping");return}}}async reserveContinuationAuthority(e,t){const n=await this.readState(t),r=n.pendingContinuationAuthorities??[],s=typeof e.idempotencyKey=="string"?e.idempotencyKey:"";if(!s)throw new Error("YOLOOP_CONTINUATION_AUTHORITY_INVALID");const i=r.find(a=>a.idempotencyKey===s);if(i){if(h(i)===h(e))return;throw new Error("YOLOOP_CONTINUATION_AUTHORITY_CONFLICT")}if(r.length>=64)throw new Error("YOLOOP_PENDING_AUTHORITY_CAPACITY_EXCEEDED");await this.writeState({...n,pendingContinuationAuthorities:[...r,e]})}async acknowledgeContinuationAuthority(e,t,n){const r=await this.readState(n),s=r.pendingContinuationAuthorities??[],i=s.findIndex(d=>d.idempotencyKey===e);if(i<0)throw new Error("YOLOOP_CONTINUATION_RECEIPT_ORPHAN");const a=s[i];if(t.idempotencyKey!==e||t.sessionId!==a.sessionId||JSON.stringify(t.storyIds)!==JSON.stringify(a.storyIds))throw new Error("YOLOOP_CONTINUATION_RECEIPT_MISMATCH");const c=s.filter((d,p)=>p!==i),l=r.continuationReceipts??[];await this.writeState({...r,pendingContinuationAuthorities:c,continuationReceipts:[...l.slice(-63),t]})}async repairContinuationAuthority(e,t,n,r){const s=await this.readState(r),i=s.pendingContinuationAuthorities??[],a=s.continuationRecoveryReceipts??[],c=typeof n.idempotencyKey=="string"?n.idempotencyKey:"",l=a.find(_=>_.idempotencyKey===c);if(l){if(h(l)===h(n))return;throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_CONFLICT")}if(i.length!==1||y("p208-legacy-authority-before-v1",i[0])!==e)throw new Error("YOLOOP_CONTINUATION_RECOVERY_BEFORE_MISMATCH");const d=y("p208-legacy-authority-after-v1",t);if(n.beforeHash!==e||n.afterHash!==d)throw new Error("YOLOOP_CONTINUATION_RECOVERY_HASH_MISMATCH");const p={...n};if(delete p.idempotencyKey,!c||y("p208-legacy-authority-recovery-receipt-v1",p)!==c)throw new Error("YOLOOP_CONTINUATION_RECOVERY_RECEIPT_INVALID");await this.writeState({...s,pendingContinuationAuthorities:[t],continuationRecoveryReceipts:[...a.slice(-63),n]})}async switchContinuationPolicy(e,t,n){const r=await this.readState(n);if(r.currentSession||(r.pendingContinuationAuthorities?.length??0)>0)throw new Error("YOLOOP_POLICY_SWITCH_ACTIVE");try{await u.copyFile(this.statePath,`${this.statePath}.policy-backup`)}catch(s){if(s.code!=="ENOENT")throw s}await this.writeState({...r,continuationPolicy:e,continuationSession:t})}async appendRecoveryAttempt(e,t){const n=await this.readState(t),r=n.recovery_history??[];if(r.some(c=>c.attempted_at===e.attempted_at&&c.story_id===e.story_id)){this.logger.warn({event:"recovery_attempted_duplicate",story_id:e.story_id},"duplicate recovery_attempted op; skipping");return}const i=T([...r,e]),a={...n,recovery_history:i};await this.writeState(a),this.logger.info({event:"recovery_attempted_persisted",story_id:e.story_id,history_size:i.length},"recovery attempt persisted")}async resolveRecoveryAttempt(e,t,n){const r=await this.readState(n),s=r.recovery_history??[],i=s.findIndex(d=>d.recovery_step_id_tokenized===e);if(i===-1){this.logger.warn({event:"recovery_resolve_orphan",attempt_id:e},"recovery_resolved op had no matching attempt; skipping");return}const a=s[i];if(a.outcome===t&&a.finished_at){this.logger.info({event:"recovery_resolved_duplicate",attempt_id:e,outcome:t},"duplicate recovery_resolved op; skipping");return}const c=s.slice();c[i]={...a,outcome:t,finished_at:n};const l={...r,recovery_history:c};await this.writeState(l),this.logger.info({event:"recovery_resolved_persisted",attempt_id:e,outcome:t},"recovery attempt resolved")}async acquireLock(e,t){const n={...e,pid:process.pid,hostname:f(),ownerKind:"client",startedAt:e.startedAt||t,leaseId:e.leaseId??j(e.sessionId,t),leaseState:"active",leaseAcquiredAt:e.leaseAcquiredAt??t,leaseDurationMs:e.leaseDurationMs??U,leaseExpiresAt:e.leaseExpiresAt??e.ttlExpiresAt},r=JSON.stringify(n,null,2);try{const s=await u.open(this.lockPath,"wx");try{await s.writeFile(r,"utf8")}finally{await s.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired",pid:n.pid,sessionId:n.sessionId},"yoloop lock acquired");return}catch(s){const i=s;if(i.code!=="EEXIST")throw this.logger.error({event:"yoloop_lock_acquire_error",err:i.message},"failed to acquire yoloop lock"),s;const a=await this.readLock();if(a&&this.isSameClientSessionLock(a,n)){this.logger.info({event:"yoloop_lock_acquire_duplicate",sessionId:n.sessionId},"duplicate acquire_lock for current yoloop session; treating as no-op");return}if(!await this.maybeRecoverOrphanLock(t))throw this.logger.warn({event:"yoloop_lock_held"},"yoloop lock held by another live process"),s;const l=await u.open(this.lockPath,"wx");try{await l.writeFile(r,"utf8")}finally{await l.close()}this.trySetFilePerms(this.lockPath),this.logger.info({event:"yoloop_lock_acquired_after_recovery",pid:n.pid},"yoloop lock acquired after orphan recovery")}}async releaseLock(e){const t=await this.readState(new Date().toISOString()),n=await this.readLock();if(t.runControl){if(!e.expectedRunId||!e.expectedSessionId||!e.expectedLeaseId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(e.expectedRunId!==t.runControl.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if(!n||e.expectedSessionId!==n.sessionId||e.expectedLeaseId!==n.leaseId)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}try{await u.unlink(this.lockPath),this.logger.info({event:"yoloop_lock_released"},"yoloop lock released")}catch(r){const s=r;if(s.code==="ENOENT"){this.logger.info({event:"yoloop_lock_release_noop"},"yoloop lock release noop (file absent)");return}this.logger.warn({event:"yoloop_lock_release_error",err:s.message},"unexpected error releasing yoloop lock")}}async readLock(){try{const e=await u.readFile(this.lockPath,"utf8"),t=JSON.parse(e),n=t.currentEpicId??t.epicId??"";return{pid:t.pid??0,hostname:t.hostname??"",...t.ownerKind?{ownerKind:t.ownerKind}:{},...t.ownerPidState?{ownerPidState:t.ownerPidState}:{},...typeof t.leaseId=="string"?{leaseId:t.leaseId}:{},...t.leaseState?{leaseState:t.leaseState}:{},...typeof t.leaseAcquiredAt=="string"?{leaseAcquiredAt:t.leaseAcquiredAt}:{},...typeof t.leaseDurationMs=="number"?{leaseDurationMs:t.leaseDurationMs}:{},...typeof t.leaseExpiresAt=="string"?{leaseExpiresAt:t.leaseExpiresAt}:{},startedAt:t.startedAt??"",sessionId:t.sessionId??"",mode:t.mode??"epic-id",inputPath:t.inputPath??"",epicQueue:t.epicQueue??(n?[n]:[]),currentEpicIndex:t.currentEpicIndex??0,currentEpicId:n,ttlExpiresAt:t.ttlExpiresAt??"",epicId:t.epicId??n}}catch{return null}}async readState(e){try{const t=await u.readFile(this.statePath,"utf8"),n=JSON.parse(t);return G(n,e)}catch(t){const n=t;return n.code!=="ENOENT"&&this.logger.warn({event:"yoloop_state_read_error",err:n.message},"yoloop state file unreadable; using defaults"),I(e)}}async writeState(e){const t=`${this.statePath}.tmp.${process.pid}.${Date.now()}`,n=JSON.stringify(e,null,2);try{const r=await u.open(t,"w",384);try{await r.writeFile(n,"utf8"),await r.sync()}finally{await r.close()}this.trySetFilePerms(t),await u.rename(t,this.statePath),await this.syncStateDirectory()}catch(r){const s=r;this.logger.error({event:"yoloop_state_write_error",err:s.message},"failed to write yoloop state");try{await u.unlink(t)}catch{}throw r}}async advanceEpic(e,t,n){const r=await this.readState(n);if(!r.currentSession){this.logger.warn({event:"yoloop_advance_no_session",nextEpicId:t},"advance_epic with no active session; skipping");return}const s={...r,currentSession:{...r.currentSession,currentEpicIndex:e}};await this.writeState(s),this.logger.info({event:"yoloop_epic_advance",nextEpicId:t,nextIndex:e},"yoloop advanced to next epic")}async ensureDir(){try{await u.mkdir(this.stateDir,{recursive:!0}),this.trySetDirPerms(this.stateDir)}catch(e){const t=e;throw this.logger.error({event:"yoloop_mkdir_error",err:t.message,path:this.stateDir},"failed to ensure .neocortex/ directory"),e}}async syncStateDirectory(){try{const e=await u.open(this.stateDir,"r");try{await e.sync()}finally{await e.close()}}catch(e){const t=e.code;if(t!=="EINVAL"&&t!=="EISDIR"&&t!=="ENOTSUP"&&t!=="EPERM")throw e}}async withMutationLock(e){let t=null;for(let n=0;n<b;n+=1)try{t=await u.open(this.mutationLockPath,"wx",384),await t.writeFile(JSON.stringify({pid:process.pid,acquiredAt:new Date().toISOString()})),await t.sync();break}catch(r){if(r.code!=="EEXIST")throw r;try{const s=await u.stat(this.mutationLockPath);if(Date.now()-s.mtimeMs>K){await u.unlink(this.mutationLockPath);continue}}catch{continue}await new Promise(s=>setTimeout(s,5))}if(!t)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");try{return await e()}finally{await t.close().catch(()=>{}),await u.unlink(this.mutationLockPath).catch(()=>{})}}async transitionRun(e,t){$(e);const n=await this.readState(t),r=n.runControl,s=r?.receipts.find(l=>l.operationId===e.operationId||l.idempotencyKey===e.idempotencyKey);if(s){if(s.operationId===e.operationId&&s.idempotencyKey===e.idempotencyKey&&s.payloadHash===e.payloadHash)return s;throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}if(r&&r.runId!==e.runId)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.revision??0)!==e.expectedRevision)throw new Error("YOLOOP_RUN_REVISION_CONFLICT");if((r?.latestSnapshotHash??null)!==e.expectedSnapshotHash)throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT");B(r,e);const c=e.next.receipts.at(-1);if(!c)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");return await this.writeState({...n,version:"1.2",runControl:e.next}),this.logger.info({event:"yoloop_run_transition_committed",runId:e.runId,operationId:e.operationId,revision:c.revision,status:c.status},"yoloop run transition committed"),c}trySetFilePerms(e){try{k(e)}catch{}}trySetDirPerms(e){try{x(e)}catch{}}async prepareLockForSnapshot(e,t){if(!e)return null;if(e.ownerKind!=="client")return e;const n=N(e,t),r={...e,leaseState:n};if(n==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"lease_expired"},"expired client-owned yoloop lock lease recovered before snapshot echo"),null;if(!r.pid||r.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"invalid_pid"},"invalid PID in client-owned yoloop lock; recovered"),null;if(r.hostname&&r.hostname!==f())return{...r,ownerPidState:"unknown"};try{return process.kill(r.pid,0),{...r,ownerPidState:"live"}}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_client_lock_recovered",reason:"dead_pid"},"stale client-owned yoloop lock recovered before snapshot echo"),null}}async maybeRecoverOrphanLock(e){const t=await this.readLock();if(!t)return!0;if(N(t,e)==="expired")return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"lease_expired"},"expired yoloop lock lease recovered"),!0;if(!t.pid||t.pid<=0)return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"invalid_pid"},"invalid PID in lock file; recovered"),!0;if(t.hostname&&t.hostname!==f())return this.logger.warn({event:"yoloop_lock_remote_host",ownerKind:t.ownerKind??"server",ownerPidState:"unknown"},"lock held by a remote host; honoring"),!1;try{return process.kill(t.pid,0),!1}catch{return await this.deleteLockBestEffort(),this.logger.warn({event:"yoloop_orphan_lock_recovered",reason:"dead_pid"},"orphan yoloop lock recovered"),!0}}async deleteLockBestEffort(){try{await u.unlink(this.lockPath)}catch{}}isSameClientSessionLock(e,t){return e.ownerKind==="client"&&e.sessionId===t.sessionId&&e.pid===process.pid&&e.hostname===f()}async cleanupOrphanTempFiles(){try{const e=C(this.statePath),t=await u.readdir(e),n=`${m}.tmp.`,r=t.filter(s=>s.startsWith(n));for(const s of r)try{await u.unlink(w(e,s))}catch{}}catch{}}}function h(o){return o===null||typeof o!="object"?JSON.stringify(o):Array.isArray(o)?`[${o.map(h).join(",")}]`:`{${Object.entries(o).filter(([,e])=>e!==void 0).sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${JSON.stringify(e)}:${h(t)}`).join(",")}}`}function y(o,e){return P("sha256").update(`${o}\0${h(e)}`).digest("hex")}function A(o){const{receipts:e,...t}=o.next;return y("p220-yoloop-run-transition-payload-v1",{runId:o.runId,operationId:o.operationId,idempotencyKey:o.idempotencyKey,expectedRevision:o.expectedRevision,expectedSnapshotHash:o.expectedSnapshotHash,next:t})}const g=new Set(["success","partial","blocked","failure","exhausted","cancelled"]),q={created:["running","cancelling","exhausted"],running:["pausing","cancelling","success","partial","blocked","failure","exhausted"],pausing:["paused","cancelling","failure","exhausted"],paused:["resuming","cancelling","exhausted"],resuming:["running","cancelling","failure","exhausted"],cancelling:["cancelled","failure","exhausted"]};function M(o,e){return e==="pause"?o==="running"?"pausing":null:e==="resume"?o==="paused"?"resuming":null:g.has(o)||o==="cancelling"?null:"cancelling"}function V(o){const e=o.current.revision+1,t=o.action==="cancel"?["YOLOOP_RUN_CANCEL_REQUESTED"]:[],n={schemaVersion:"YoloopOperationalBudgetSummary/1.0",limits:o.current.operationalBudget.limits,usage:o.current.usage,exhaustedDimension:null},r={schemaVersion:"ProgressSnapshot/1.0",runId:o.current.runId,sequence:o.current.progressSnapshots.length+1,revision:e,operationId:o.operationId,boundary:o.action,phase:"before",status:o.current.status,occurredAt:o.occurredAt,parentSnapshotHash:o.current.latestSnapshotHash,budget:n,reasonCodes:t,publicSafe:!0},s=y("p220-yoloop-progress-snapshot-id-v1",{runId:r.runId,operationId:r.operationId,sequence:r.sequence,phase:r.phase}),i={...r,snapshotId:s,snapshotHash:y("p220-yoloop-progress-snapshot-v1",{...r,snapshotId:s})},a={...r,sequence:i.sequence+1,phase:"after",status:o.target,parentSnapshotHash:i.snapshotHash},c=y("p220-yoloop-progress-snapshot-id-v1",{runId:a.runId,operationId:a.operationId,sequence:a.sequence,phase:a.phase}),l={...a,snapshotId:c,snapshotHash:y("p220-yoloop-progress-snapshot-v1",{...a,snapshotId:c})},d={schemaVersion:"YoloopRunEvent/1.0",runId:o.current.runId,operationId:o.operationId,kind:"intent",sequence:o.current.events.length+1,revision:e,occurredAt:o.occurredAt,fromStatus:o.current.status,toStatus:o.target,reasonCodes:t,budgetDimension:null,parentEventHash:o.current.events.at(-1)?.eventHash??null,publicSafe:!0},p=y("p220-yoloop-run-event-id-v1",{runId:d.runId,operationId:d.operationId,sequence:d.sequence}),_={...d,eventId:p,eventHash:y("p220-yoloop-run-event-v1",{...d,eventId:p})},O={...o.current,revision:e,status:o.target,events:[...o.current.events,_],progressSnapshots:[...o.current.progressSnapshots,i,l],latestSnapshotHash:l.snapshotHash,receipts:[...o.current.receipts]},v={runId:o.current.runId,operationId:o.operationId,idempotencyKey:o.idempotencyKey,expectedRevision:o.current.revision,expectedSnapshotHash:o.current.latestSnapshotHash,next:O},S=A(v),L={schemaVersion:"YoloopRunTransitionReceipt/1.0",runId:o.current.runId,operationId:o.operationId,idempotencyKey:o.idempotencyKey,payloadHash:S,revision:e,snapshotHash:l.snapshotHash,status:o.target};return{type:"transition_run",...v,payloadHash:S,next:{...O,receipts:[...O.receipts,L]}}}function $(o){const e=/^[A-Za-z0-9._:-]{1,128}$/;if(!e.test(o.runId)||!e.test(o.operationId)||!e.test(o.idempotencyKey)||!/^[a-f0-9]{64}$/.test(o.payloadHash)||!Number.isSafeInteger(o.expectedRevision)||o.expectedRevision<0)throw new Error("YOLOOP_RUN_OPERATION_CONFLICT");if(o.payloadHash!==A(o))throw new Error("YOLOOP_RUN_OPERATION_CONFLICT")}function B(o,e){const t=e.next,n=o?.events??[],r=o?.progressSnapshots??[],s=o?.receipts??[];if(t.schemaVersion!=="YoloopRunControl/1.0"||t.runId!==e.runId||t.revision!==e.expectedRevision+1||t.events.length!==n.length+1||t.progressSnapshots.length!==r.length+2||t.receipts.length!==s.length+1||h(t.events.slice(0,n.length))!==h(n)||h(t.progressSnapshots.slice(0,r.length))!==h(r)||h(t.receipts.slice(0,s.length))!==h(s))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(o){if(g.has(o.status))throw new Error("YOLOOP_RUN_TERMINAL");const d=t.progressSnapshots.at(-2)?.boundary,p=o.status===t.status&&(d==="budget"||d==="retry");if(!(q[o.status]??[]).includes(t.status)&&!p)throw new Error("YOLOOP_RUN_TRANSITION_INVALID");if(o.startedAt!==t.startedAt||o.deadlineAt!==t.deadlineAt)throw new Error("YOLOOP_RUN_TRANSITION_INVALID")}else if(t.status!=="created")throw new Error("YOLOOP_RUN_TRANSITION_INVALID");const i=t.progressSnapshots.at(-2),a=t.progressSnapshots.at(-1),c=t.events.at(-1),l=t.receipts.at(-1);if(!i||!a||!c||!l||i.phase!=="before"||a.phase!=="after"||i.operationId!==e.operationId||a.operationId!==e.operationId||c.operationId!==e.operationId||i.runId!==e.runId||a.runId!==e.runId||c.runId!==e.runId||i.revision!==t.revision||a.revision!==t.revision||c.revision!==t.revision||i.parentSnapshotHash!==(o?.latestSnapshotHash??null)||a.parentSnapshotHash!==i.snapshotHash||t.latestSnapshotHash!==a.snapshotHash||l.schemaVersion!=="YoloopRunTransitionReceipt/1.0"||l.runId!==e.runId||l.operationId!==e.operationId||l.idempotencyKey!==e.idempotencyKey||l.payloadHash!==e.payloadHash||l.revision!==t.revision||l.snapshotHash!==t.latestSnapshotHash||l.status!==t.status||Date.parse(t.deadlineAt)<=Date.parse(t.startedAt))throw new Error("YOLOOP_RUN_TRANSITION_INVALID");E(i),E(a),J(c,n.at(-1)?.eventHash??null)}function E(o){const{snapshotHash:e,...t}=o;if(e!==y("p220-yoloop-progress-snapshot-v1",t))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function J(o,e){const{eventHash:t,...n}=o;if(o.parentEventHash!==e||t!==y("p220-yoloop-run-event-v1",n))throw new Error("YOLOOP_RUN_SNAPSHOT_CONFLICT")}function j(o,e){return`lease-${W(`${o}:${e}`)}`}function W(o){let e=2166136261;for(let t=0;t<o.length;t+=1)e^=o.charCodeAt(t),e=Math.imul(e,16777619)>>>0;return e.toString(36).padStart(7,"0")}function N(o,e){const t=o.leaseExpiresAt??o.ttlExpiresAt,n=Date.parse(t),r=Date.parse(e);return!Number.isFinite(n)||!Number.isFinite(r)?"unknown":n<=r?"expired":"active"}function G(o,e){const t=I(e);return{...o,version:o.version==="1.2"?"1.2":"1.1",lastResetAt:o.lastResetAt??t.lastResetAt,storiesRunToday:o.storiesRunToday??0,iterationsToday:o.iterationsToday??0,iterationsTodayDate:o.iterationsTodayDate??t.iterationsTodayDate,tokensUsedToday:o.tokensUsedToday??0,lastRunAt:o.lastRunAt??null,coolDownUntil:o.coolDownUntil??null,totalRunsAllTime:o.totalRunsAllTime??0,totalStoriesAllTime:o.totalStoriesAllTime??0,currentSession:o.currentSession??null,...o.runControl!==void 0?{runControl:o.runControl}:{},...o.continuationPolicy!==void 0?{continuationPolicy:o.continuationPolicy}:{},...o.continuationSession!==void 0?{continuationSession:o.continuationSession}:{},...o.pendingContinuationAuthorities!==void 0?{pendingContinuationAuthorities:o.pendingContinuationAuthorities}:{},...o.continuationReceipts!==void 0?{continuationReceipts:o.continuationReceipts}:{},...o.continuationRecoveryReceipts!==void 0?{continuationRecoveryReceipts:o.continuationRecoveryReceipts}:{},recovery_history:Array.isArray(o.recovery_history)?T(o.recovery_history):[]}}function T(o){return o.length<=R?o:o.slice(o.length-R)}export{ne as YoloopClientStateStore,A as buildYoloopRunTransitionPayloadHash,T as capRecoveryHistory,I as createDefaultCounters,oe as createDefaultState};