claude-code-session-manager 0.96.0 → 0.97.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{DataModel-T0D00BpK.js → DataModel-D93FoaOm.js} +1 -1
- package/dist/assets/{History-DuAovTr7.js → History-CXxxqNP1.js} +1 -1
- package/dist/assets/{Hooks-CgP9jsAN.js → Hooks-CzeYFQjq.js} +1 -1
- package/dist/assets/{HostBilko-DHfz3Mxa.js → HostBilko-nESrTRIg.js} +1 -1
- package/dist/assets/{Library--poVP-d1.js → Library-D2zPkwOC.js} +1 -1
- package/dist/assets/{MarkdownEditor-C56-Pc4c.js → MarkdownEditor-DbinP78A.js} +1 -1
- package/dist/assets/{McpServers-D-ykP4Vi.js → McpServers-Bu_sVGLr.js} +1 -1
- package/dist/assets/{Memory-BrkJjRL8.js → Memory-BgiI6LZ_.js} +1 -1
- package/dist/assets/{Permissions-BMpAaxJu.js → Permissions-C1MMzZRm.js} +1 -1
- package/dist/assets/{Plugins-OuEe5S_T.js → Plugins-Dhu6wAZi.js} +2 -2
- package/dist/assets/{ProvenanceBadge-4eNbDYe8.js → ProvenanceBadge-C3AcWKMG.js} +1 -1
- package/dist/assets/{SaveBar-BQ8y9Lkv.js → SaveBar-plwYm0iO.js} +1 -1
- package/dist/assets/{Scheduler-DcMT5sT_.js → Scheduler-BVYTeh39.js} +1 -1
- package/dist/assets/{ScopeSwitcher-XdxtVKP-.js → ScopeSwitcher-DFy0vv_o.js} +1 -1
- package/dist/assets/{Settings-FdaPUq2t.js → Settings-DIhGgdfs.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-CJzvvc0_.js → SkillReferenceGraph-DsI7hRPB.js} +1 -1
- package/dist/assets/{Skills-Wvm9OE1e.js → Skills-3E748UfB.js} +1 -1
- package/dist/assets/{SystemPrompt--LYw_69j.js → SystemPrompt-BqmfUJOd.js} +1 -1
- package/dist/assets/{TagLibrary-Be6HbMMx.js → TagLibrary-CPz6fO8Q.js} +1 -1
- package/dist/assets/{TiptapBody-DzFlWMgO.js → TiptapBody-D9EoBQ1P.js} +1 -1
- package/dist/assets/{Toggle-BxtAFE1i.js → Toggle-BHAmZNDn.js} +1 -1
- package/dist/assets/{index-XkrLB67D.js → index-tn5JIVPj.js} +3 -3
- package/dist/assets/{settingsSchema-BIvDZ1Cj.js → settingsSchema-Crqy_aNz.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +12 -5
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +39 -0
- package/src/main/__tests__/planValidator.test.cjs +54 -0
- package/src/main/build-info.json +4 -4
- package/src/main/ipcSchemas.cjs +10 -6
- package/src/main/lib/planValidator.cjs +59 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
import{az as b,ak as n,S as N,aR as k,ax as K}from"./index-XkrLB67D.js";function T({node:e,targetScope:r,onOverride:s,schema:a,rootLabel:m}){const[f,x]=b.useState(""),[u,o]=b.useState(!0),g=[],d=e.kind==="object"&&Object.keys(e.children).length===0,y=((a==null?void 0:a.rootKeys())??[]).length>0;return n.jsxs("div",{className:"h-full flex flex-col",children:[n.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-2 text-xs",children:[n.jsxs("span",{className:"text-fg-faint",children:["merged · overrides → ",n.jsx("span",{className:"text-fg-dim",children:N[r].label})]}),y&&n.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[n.jsx("input",{type:"checkbox",checked:u,onChange:t=>o(t.target.checked),className:"accent-accent"}),"show unset keys"]}),n.jsx("div",{className:"flex-1"}),n.jsx("input",{value:f,onChange:t=>x(t.target.value),placeholder:"filter keys or descriptions…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-56"})]}),n.jsx("div",{className:"flex-1 overflow-auto px-3 py-2 font-mono text-xs",children:d&&!u?n.jsx("div",{className:"text-fg-faint italic px-2 py-4",children:"no settings resolved — all scope files empty or missing"}):n.jsx(E,{node:e,path:g,label:m??"(root)",targetScope:r,onOverride:s,filter:f,depth:0,schema:a,showGhosts:u,defaultOpen:!0})})]})}function E(e){return e.node.kind==="object"?n.jsx(P,{...e,node:e.node}):e.node.kind==="array"?n.jsx(R,{...e,node:e.node}):n.jsx(J,{...e,node:e.node})}function A(e,r){return r.trim()?e.toLowerCase().includes(r.toLowerCase()):!0}function w(e,r,s){return!!(!s.trim()||e.some(a=>A(a,s))||r!=null&&r.description&&A(r.description,s))}function P({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u,showGhosts:o,defaultOpen:g}){const[d,h]=b.useState(g??x<2),y=(u==null?void 0:u.at(r))??null,t=new Set(Object.keys(e.children)),i=o?(y==null?void 0:y.knownChildren)??[]:[],c=Array.from(new Set([...t,...i])).sort(),l=b.useMemo(()=>f.trim()?c.filter(p=>{const v=(u==null?void 0:u.at([...r,p]))??null;return w([...r,p],v,f)?!0:t.has(p)?L(e.children[p],[...r,p],f,u):!1}):c,[c.join(","),f,e,r.join("."),u]);if(f.trim()&&l.length===0&&!w(r,y,f))return null;if(r.length===0)return n.jsx("div",{children:l.map(p=>j(p))});return n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:x*14},onClick:()=>h(!d),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:d?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" {",t.size,i.length>t.size?`/${new Set([...t,...i]).size}`:"","}"]}),(y==null?void 0:y.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:y.description,children:["— ",y.description]})]}),d&&l.map(p=>j(p))]});function j(p){if(t.has(p))return n.jsx(E,{node:e.children[p],path:[...r,p],label:p,targetScope:a,onOverride:m,filter:f,depth:x+1,schema:u,showGhosts:o},p);const v=(u==null?void 0:u.at([...r,p]))??null;return v?n.jsx(I,{path:[...r,p],label:p,info:v,targetScope:a,onSet:m,depth:x+1,filter:f},p):null}}function L(e,r,s,a){const m=(a==null?void 0:a.at(r))??null;if(w(r,m,s))return!0;if(e.kind!=="object")return!1;for(const f of Object.keys(e.children))if(L(e.children[f],[...r,f],s,a))return!0;return!1}function R({node:e,path:r,label:s,filter:a,depth:m,schema:f}){const[x,u]=b.useState(!0),o=(f==null?void 0:f.at(r))??null;return w(r,o,a)?n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:m*14},onClick:()=>u(!x),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:x?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" [",e.items.length,"]"]}),n.jsx(S,{tone:"warn",children:"accumulated"}),(o==null?void 0:o.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:o.description,children:["— ",o.description]})]}),x&&e.items.map((g,d)=>n.jsxs("div",{className:"flex items-start gap-2 py-0.5",style:{paddingLeft:(m+1)*14},children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0"}),n.jsxs("span",{className:"text-fg-faint",children:[d,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-xl",children:O(g.value)}),n.jsx(C,{scope:g.fromScope})]},d))]}):null}function J({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u}){const[o,g]=b.useState(!1),d=(u==null?void 0:u.at(r))??null;if(!w(r,d,f))return null;const h=e.winner!==a,y=e.shadowed.length>0;return n.jsxs("div",{children:[n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded",style:{paddingLeft:x*14},children:[n.jsx("span",{className:`w-3 shrink-0 ${y?"text-fg-faint cursor-pointer":""}`,onClick:()=>y&&g(!o),children:y?o?"▾":"▸":""}),n.jsxs("span",{className:"text-fg",children:[s,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-md",children:O(e.value)}),n.jsx(C,{scope:e.winner}),(d==null?void 0:d.deprecated)&&n.jsx(S,{tone:"warn",children:"deprecated"}),(d==null?void 0:d.default)!==void 0&&_(e.value,d.default)&&n.jsx(S,{tone:"dim",children:"= default"}),e.typeMismatch&&n.jsx("span",{className:"text-yellow-500/80",title:"shadowed scopes have a different value type",children:"⚠"}),(d==null?void 0:d.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:d.description,children:["— ",d.description]}),n.jsx("div",{className:"flex-1"}),(d==null?void 0:d.enum)&&n.jsx(F,{values:d.enum}),n.jsx("button",{disabled:!h,onClick:()=>m(r,e.value),className:`opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border rounded transition-opacity ${h?"border-accent/50 text-accent hover:bg-accent hover:text-bg":"border-line text-fg-faint cursor-not-allowed"}`,title:h?`copy into ${N[a].label} draft`:`already winning in ${N[a].label}`,children:"override"})]}),o&&e.shadowed.map((t,i)=>n.jsxs("div",{className:"flex items-center gap-2 py-0.5 text-fg-faint line-through",style:{paddingLeft:(x+1)*14+12},children:[n.jsx("span",{children:"shadowed:"}),n.jsx("span",{className:"truncate max-w-xl",children:O(t.value)}),n.jsx(C,{scope:t.scope,dim:!0})]},i))]})}function I({path:e,label:r,info:s,targetScope:a,onSet:m,depth:f,filter:x}){if(!w(e,s,x))return null;const u=s.default!==void 0?s.default:M(s.type),o=Array.isArray(s.type)?s.type.join("|"):s.type??"?";return n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded opacity-60",style:{paddingLeft:f*14},children:[n.jsx("span",{className:"w-3 shrink-0"}),n.jsx("span",{className:"text-fg-dim",children:r}),n.jsx(S,{tone:"dim",children:o}),s.required&&n.jsx(S,{tone:"warn",children:"required"}),s.default!==void 0&&n.jsxs("span",{className:"text-fg-faint",children:["default: ",O(s.default)]}),s.description&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:s.description,children:["— ",s.description]}),n.jsx("div",{className:"flex-1"}),s.enum&&n.jsx(F,{values:s.enum}),n.jsx("button",{onClick:()=>m(e,u),className:"opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg transition-opacity",title:`write to ${N[a].label} draft`,children:"set"})]})}function _(e,r){return e===r?!0:typeof e!=typeof r?!1:e&&r&&typeof e=="object"?JSON.stringify(e)===JSON.stringify(r):!1}function M(e){switch(Array.isArray(e)?e[0]:e){case"boolean":return!1;case"integer":case"number":return 0;case"string":return"";case"array":return[];case"object":return{};case"null":return null;default:return null}}function O(e){if(typeof e=="string")return JSON.stringify(e);if(e===null)return"null";if(typeof e=="object"){const r=JSON.stringify(e);return r.length>100?r.slice(0,100)+"…":r}return String(e)}function C({scope:e,dim:r}){const s=e==="user"?"bg-bg-hi text-fg-dim border-line":e==="project"?"bg-blue-950/30 text-blue-400/80 border-blue-900/40":"bg-purple-950/30 text-purple-400/80 border-purple-900/40";return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s} ${r?"opacity-60":""}`,children:N[e].label})}function S({children:e,tone:r="default"}){const s={default:"bg-bg-hi text-fg-dim border-line",warn:"bg-yellow-950/30 text-yellow-500/80 border-yellow-900/40",dim:"bg-bg-elev text-fg-faint border-line"}[r];return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s}`,children:e})}function F({values:e}){const r=e.map(s=>String(s)).join(" | ");return n.jsxs("span",{className:"text-[10px] text-fg-faint truncate max-w-xs",title:`allowed: ${r}`,children:["∈ ",r.length>30?r.slice(0,30)+"…":r]})}function G(e,r,s){const a=k(o=>o.files),m=k(o=>o.loadJson),f=k(o=>o.watchFile),x=k(o=>o.unwatchFile),u=b.useMemo(()=>{const o={};for(const g of e.scopes){const d=e.resolve(g,r??"",s);d&&(o[g]=d)}return o},[e,r,s]);return b.useEffect(()=>{const o=Object.values(u).filter(Boolean);return o.forEach(g=>{a[g]||m(g),f(g)}),()=>{o.forEach(g=>x(g))}},[JSON.stringify(u)]),u}function z(e){const r=e??{},s={...r.$defs??{},...r.definitions??{}},a=t=>{if(!t)return{};if(!t.$ref)return x(t);const i=/^#\/(\$defs|definitions)\/(.+)$/.exec(t.$ref);if(!i)return x(t);const c=s[i[2]];return c?a({...c,...u(t)}):x(t)},m=new Set(["__proto__","constructor","prototype"]),f=t=>{if(!t)return{};const i={};for(const[c,l]of Object.entries(t))m.has(c)||(i[c]=l);return i},x=t=>{if(!t.allOf||t.allOf.length===0)return t;const i={...t};delete i.allOf;for(const c of t.allOf){const l=a(c);Object.assign(i,{properties:{...f(i.properties),...f(l.properties)},required:B([...i.required??[],...l.required??[]])}),!i.description&&l.description&&(i.description=l.description),i.default===void 0&&l.default!==void 0&&(i.default=l.default),!i.type&&l.type&&(i.type=l.type)}return i},u=t=>{const{$ref:i,...c}=t;return c},o=(t,i)=>{let c=a(t);for(const l of i){if(!c)return null;if(/^\d+$/.test(l)){const v=Array.isArray(c.items)?c.items[Number(l)]:c.items;c=v?a(v):null;continue}const j=c.properties;if(j&&!m.has(l)&&l in j){c=a(j[l]);continue}const p=c.additionalProperties;if(p&&typeof p=="object"){c=a(p);continue}return null}return c},g=(t,i,c)=>{if(!t)return null;const l={};if(t.description&&(l.description=t.description),t.type&&(l.type=t.type),t.default!==void 0&&(l.default=t.default),t.enum&&(l.enum=t.enum),t.const!==void 0&&(l.const=t.const),t.deprecated&&(l.deprecated=!0),t.examples&&(l.examples=t.examples),t.properties&&(l.knownChildren=Object.keys(t.properties)),c&&(i!=null&&i.includes(c))&&(l.required=!0),t.items&&!Array.isArray(t.items)){const j=a(t.items),p=g(j,void 0,null);p&&(l.itemsInfo=p)}return(t.additionalProperties===!0||typeof t.additionalProperties=="object")&&(l.open=!0),l},d=t=>i=>{if(i.length===0)return g(a(t),void 0,null);const c=o(t,i.slice(0,-1));if(!c)return null;const l=i[i.length-1],j=o(c,[l]);return g(j,c.required,l)},h=t=>{const i=a(t);return Object.keys(i.properties??{})},y=t=>({at:d(t),rootKeys:()=>h(t),rootedAt:i=>{const c=o(t,i);return y(c??{})}});return y(r)}function B(e){return Array.from(new Set(e))}let $=null;function V(){return $||($=z(K)),$}export{T as E,V as s,G as u};
|
|
1
|
+
import{az as b,ak as n,S as N,aR as k,ax as K}from"./index-tn5JIVPj.js";function T({node:e,targetScope:r,onOverride:s,schema:a,rootLabel:m}){const[f,x]=b.useState(""),[u,o]=b.useState(!0),g=[],d=e.kind==="object"&&Object.keys(e.children).length===0,y=((a==null?void 0:a.rootKeys())??[]).length>0;return n.jsxs("div",{className:"h-full flex flex-col",children:[n.jsxs("div",{className:"shrink-0 border-b border-line bg-bg-elev px-3 py-1.5 flex items-center gap-2 text-xs",children:[n.jsxs("span",{className:"text-fg-faint",children:["merged · overrides → ",n.jsx("span",{className:"text-fg-dim",children:N[r].label})]}),y&&n.jsxs("label",{className:"flex items-center gap-1 text-fg-faint cursor-pointer",children:[n.jsx("input",{type:"checkbox",checked:u,onChange:t=>o(t.target.checked),className:"accent-accent"}),"show unset keys"]}),n.jsx("div",{className:"flex-1"}),n.jsx("input",{value:f,onChange:t=>x(t.target.value),placeholder:"filter keys or descriptions…",className:"bg-bg border border-line rounded px-2 py-0.5 text-xs text-fg w-56"})]}),n.jsx("div",{className:"flex-1 overflow-auto px-3 py-2 font-mono text-xs",children:d&&!u?n.jsx("div",{className:"text-fg-faint italic px-2 py-4",children:"no settings resolved — all scope files empty or missing"}):n.jsx(E,{node:e,path:g,label:m??"(root)",targetScope:r,onOverride:s,filter:f,depth:0,schema:a,showGhosts:u,defaultOpen:!0})})]})}function E(e){return e.node.kind==="object"?n.jsx(P,{...e,node:e.node}):e.node.kind==="array"?n.jsx(R,{...e,node:e.node}):n.jsx(J,{...e,node:e.node})}function A(e,r){return r.trim()?e.toLowerCase().includes(r.toLowerCase()):!0}function w(e,r,s){return!!(!s.trim()||e.some(a=>A(a,s))||r!=null&&r.description&&A(r.description,s))}function P({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u,showGhosts:o,defaultOpen:g}){const[d,h]=b.useState(g??x<2),y=(u==null?void 0:u.at(r))??null,t=new Set(Object.keys(e.children)),i=o?(y==null?void 0:y.knownChildren)??[]:[],c=Array.from(new Set([...t,...i])).sort(),l=b.useMemo(()=>f.trim()?c.filter(p=>{const v=(u==null?void 0:u.at([...r,p]))??null;return w([...r,p],v,f)?!0:t.has(p)?L(e.children[p],[...r,p],f,u):!1}):c,[c.join(","),f,e,r.join("."),u]);if(f.trim()&&l.length===0&&!w(r,y,f))return null;if(r.length===0)return n.jsx("div",{children:l.map(p=>j(p))});return n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:x*14},onClick:()=>h(!d),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:d?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" {",t.size,i.length>t.size?`/${new Set([...t,...i]).size}`:"","}"]}),(y==null?void 0:y.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:y.description,children:["— ",y.description]})]}),d&&l.map(p=>j(p))]});function j(p){if(t.has(p))return n.jsx(E,{node:e.children[p],path:[...r,p],label:p,targetScope:a,onOverride:m,filter:f,depth:x+1,schema:u,showGhosts:o},p);const v=(u==null?void 0:u.at([...r,p]))??null;return v?n.jsx(I,{path:[...r,p],label:p,info:v,targetScope:a,onSet:m,depth:x+1,filter:f},p):null}}function L(e,r,s,a){const m=(a==null?void 0:a.at(r))??null;if(w(r,m,s))return!0;if(e.kind!=="object")return!1;for(const f of Object.keys(e.children))if(L(e.children[f],[...r,f],s,a))return!0;return!1}function R({node:e,path:r,label:s,filter:a,depth:m,schema:f}){const[x,u]=b.useState(!0),o=(f==null?void 0:f.at(r))??null;return w(r,o,a)?n.jsxs("div",{children:[n.jsxs("div",{className:"flex items-center gap-1 py-0.5 hover:bg-bg-elev/50 rounded cursor-pointer",style:{paddingLeft:m*14},onClick:()=>u(!x),children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0",children:x?"▾":"▸"}),n.jsx("span",{className:"text-fg",children:s}),n.jsxs("span",{className:"text-fg-faint",children:[" [",e.items.length,"]"]}),n.jsx(S,{tone:"warn",children:"accumulated"}),(o==null?void 0:o.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:o.description,children:["— ",o.description]})]}),x&&e.items.map((g,d)=>n.jsxs("div",{className:"flex items-start gap-2 py-0.5",style:{paddingLeft:(m+1)*14},children:[n.jsx("span",{className:"text-fg-faint w-3 shrink-0"}),n.jsxs("span",{className:"text-fg-faint",children:[d,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-xl",children:O(g.value)}),n.jsx(C,{scope:g.fromScope})]},d))]}):null}function J({node:e,path:r,label:s,targetScope:a,onOverride:m,filter:f,depth:x,schema:u}){const[o,g]=b.useState(!1),d=(u==null?void 0:u.at(r))??null;if(!w(r,d,f))return null;const h=e.winner!==a,y=e.shadowed.length>0;return n.jsxs("div",{children:[n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded",style:{paddingLeft:x*14},children:[n.jsx("span",{className:`w-3 shrink-0 ${y?"text-fg-faint cursor-pointer":""}`,onClick:()=>y&&g(!o),children:y?o?"▾":"▸":""}),n.jsxs("span",{className:"text-fg",children:[s,":"]}),n.jsx("span",{className:"text-fg-dim truncate max-w-md",children:O(e.value)}),n.jsx(C,{scope:e.winner}),(d==null?void 0:d.deprecated)&&n.jsx(S,{tone:"warn",children:"deprecated"}),(d==null?void 0:d.default)!==void 0&&_(e.value,d.default)&&n.jsx(S,{tone:"dim",children:"= default"}),e.typeMismatch&&n.jsx("span",{className:"text-yellow-500/80",title:"shadowed scopes have a different value type",children:"⚠"}),(d==null?void 0:d.description)&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:d.description,children:["— ",d.description]}),n.jsx("div",{className:"flex-1"}),(d==null?void 0:d.enum)&&n.jsx(F,{values:d.enum}),n.jsx("button",{disabled:!h,onClick:()=>m(r,e.value),className:`opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border rounded transition-opacity ${h?"border-accent/50 text-accent hover:bg-accent hover:text-bg":"border-line text-fg-faint cursor-not-allowed"}`,title:h?`copy into ${N[a].label} draft`:`already winning in ${N[a].label}`,children:"override"})]}),o&&e.shadowed.map((t,i)=>n.jsxs("div",{className:"flex items-center gap-2 py-0.5 text-fg-faint line-through",style:{paddingLeft:(x+1)*14+12},children:[n.jsx("span",{children:"shadowed:"}),n.jsx("span",{className:"truncate max-w-xl",children:O(t.value)}),n.jsx(C,{scope:t.scope,dim:!0})]},i))]})}function I({path:e,label:r,info:s,targetScope:a,onSet:m,depth:f,filter:x}){if(!w(e,s,x))return null;const u=s.default!==void 0?s.default:M(s.type),o=Array.isArray(s.type)?s.type.join("|"):s.type??"?";return n.jsxs("div",{className:"group flex items-center gap-2 py-0.5 hover:bg-bg-elev/50 rounded opacity-60",style:{paddingLeft:f*14},children:[n.jsx("span",{className:"w-3 shrink-0"}),n.jsx("span",{className:"text-fg-dim",children:r}),n.jsx(S,{tone:"dim",children:o}),s.required&&n.jsx(S,{tone:"warn",children:"required"}),s.default!==void 0&&n.jsxs("span",{className:"text-fg-faint",children:["default: ",O(s.default)]}),s.description&&n.jsxs("span",{className:"text-fg-faint truncate max-w-md",title:s.description,children:["— ",s.description]}),n.jsx("div",{className:"flex-1"}),s.enum&&n.jsx(F,{values:s.enum}),n.jsx("button",{onClick:()=>m(e,u),className:"opacity-0 group-hover:opacity-100 px-1.5 py-0 text-[10px] border border-accent/50 rounded text-accent hover:bg-accent hover:text-bg transition-opacity",title:`write to ${N[a].label} draft`,children:"set"})]})}function _(e,r){return e===r?!0:typeof e!=typeof r?!1:e&&r&&typeof e=="object"?JSON.stringify(e)===JSON.stringify(r):!1}function M(e){switch(Array.isArray(e)?e[0]:e){case"boolean":return!1;case"integer":case"number":return 0;case"string":return"";case"array":return[];case"object":return{};case"null":return null;default:return null}}function O(e){if(typeof e=="string")return JSON.stringify(e);if(e===null)return"null";if(typeof e=="object"){const r=JSON.stringify(e);return r.length>100?r.slice(0,100)+"…":r}return String(e)}function C({scope:e,dim:r}){const s=e==="user"?"bg-bg-hi text-fg-dim border-line":e==="project"?"bg-blue-950/30 text-blue-400/80 border-blue-900/40":"bg-purple-950/30 text-purple-400/80 border-purple-900/40";return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s} ${r?"opacity-60":""}`,children:N[e].label})}function S({children:e,tone:r="default"}){const s={default:"bg-bg-hi text-fg-dim border-line",warn:"bg-yellow-950/30 text-yellow-500/80 border-yellow-900/40",dim:"bg-bg-elev text-fg-faint border-line"}[r];return n.jsx("span",{className:`text-[10px] px-1 py-0 rounded border uppercase tracking-wide shrink-0 ${s}`,children:e})}function F({values:e}){const r=e.map(s=>String(s)).join(" | ");return n.jsxs("span",{className:"text-[10px] text-fg-faint truncate max-w-xs",title:`allowed: ${r}`,children:["∈ ",r.length>30?r.slice(0,30)+"…":r]})}function G(e,r,s){const a=k(o=>o.files),m=k(o=>o.loadJson),f=k(o=>o.watchFile),x=k(o=>o.unwatchFile),u=b.useMemo(()=>{const o={};for(const g of e.scopes){const d=e.resolve(g,r??"",s);d&&(o[g]=d)}return o},[e,r,s]);return b.useEffect(()=>{const o=Object.values(u).filter(Boolean);return o.forEach(g=>{a[g]||m(g),f(g)}),()=>{o.forEach(g=>x(g))}},[JSON.stringify(u)]),u}function z(e){const r=e??{},s={...r.$defs??{},...r.definitions??{}},a=t=>{if(!t)return{};if(!t.$ref)return x(t);const i=/^#\/(\$defs|definitions)\/(.+)$/.exec(t.$ref);if(!i)return x(t);const c=s[i[2]];return c?a({...c,...u(t)}):x(t)},m=new Set(["__proto__","constructor","prototype"]),f=t=>{if(!t)return{};const i={};for(const[c,l]of Object.entries(t))m.has(c)||(i[c]=l);return i},x=t=>{if(!t.allOf||t.allOf.length===0)return t;const i={...t};delete i.allOf;for(const c of t.allOf){const l=a(c);Object.assign(i,{properties:{...f(i.properties),...f(l.properties)},required:B([...i.required??[],...l.required??[]])}),!i.description&&l.description&&(i.description=l.description),i.default===void 0&&l.default!==void 0&&(i.default=l.default),!i.type&&l.type&&(i.type=l.type)}return i},u=t=>{const{$ref:i,...c}=t;return c},o=(t,i)=>{let c=a(t);for(const l of i){if(!c)return null;if(/^\d+$/.test(l)){const v=Array.isArray(c.items)?c.items[Number(l)]:c.items;c=v?a(v):null;continue}const j=c.properties;if(j&&!m.has(l)&&l in j){c=a(j[l]);continue}const p=c.additionalProperties;if(p&&typeof p=="object"){c=a(p);continue}return null}return c},g=(t,i,c)=>{if(!t)return null;const l={};if(t.description&&(l.description=t.description),t.type&&(l.type=t.type),t.default!==void 0&&(l.default=t.default),t.enum&&(l.enum=t.enum),t.const!==void 0&&(l.const=t.const),t.deprecated&&(l.deprecated=!0),t.examples&&(l.examples=t.examples),t.properties&&(l.knownChildren=Object.keys(t.properties)),c&&(i!=null&&i.includes(c))&&(l.required=!0),t.items&&!Array.isArray(t.items)){const j=a(t.items),p=g(j,void 0,null);p&&(l.itemsInfo=p)}return(t.additionalProperties===!0||typeof t.additionalProperties=="object")&&(l.open=!0),l},d=t=>i=>{if(i.length===0)return g(a(t),void 0,null);const c=o(t,i.slice(0,-1));if(!c)return null;const l=i[i.length-1],j=o(c,[l]);return g(j,c.required,l)},h=t=>{const i=a(t);return Object.keys(i.properties??{})},y=t=>({at:d(t),rootKeys:()=>h(t),rootedAt:i=>{const c=o(t,i);return y(c??{})}});return y(r)}function B(e){return Array.from(new Set(e))}let $=null;function V(){return $||($=z(K)),$}export{T as E,V as s,G as u};
|
package/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
+
<script type="module" crossorigin src="./assets/index-tn5JIVPj.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="./assets/index-CKH5Uxik.css">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
|
@@ -204,7 +204,10 @@ can't load skills.
|
|
|
204
204
|
|
|
205
205
|
**Every plan ends with one `validate` PRD.** After the work-item PRDs are written, author
|
|
206
206
|
exactly one more through `scheduler_create_prd`: `agentType: "validator"`, `tag: "build"`,
|
|
207
|
-
`estimateMinutes: 10`, `dependsOn` = every other slug in this plan
|
|
207
|
+
`estimateMinutes: 10`, `dependsOn` = every other slug in this plan (`dependsOn` is capped at
|
|
208
|
+
100 entries; for a plan bigger than that, list only the plan's sink PRDs — those nothing else
|
|
209
|
+
in the plan depends on — since `hasDownstreamValidator` walks `dependsOn` transitively, so a
|
|
210
|
+
validator anchored on the sinks alone still covers every upstream PRD), slug
|
|
208
211
|
`validate-<short-plan-name>`. Its Goal lists the plan's PRD slugs and titles; its Acceptance
|
|
209
212
|
criteria give, per PRD, where its file lives
|
|
210
213
|
(`<cwd>/session-manager-operations/scheduler/epics/<epic-id>/prds/<NN>-<slug>.md` while queued,
|
|
@@ -215,8 +218,10 @@ can't load skills.
|
|
|
215
218
|
While a plan has a pending validator, the scheduler suppresses both the per-PRD validation
|
|
216
219
|
prompt into this session and the in-run `/code-review` steps for its work-items — one review,
|
|
217
220
|
once, at the end. If `scheduler_create_prd` rejects `agentType: "validator"` (persona not
|
|
218
|
-
installed on this machine yet
|
|
219
|
-
|
|
221
|
+
installed on this machine yet — the usual cause is a running app older than 0.96.0, since the
|
|
222
|
+
validator persona is only seeded to `~/.claude/agents` on app boot), say so and fall back to
|
|
223
|
+
today's per-PRD validation; do not hand-write the file. Note the stale-app cause in your report
|
|
224
|
+
so the human knows to restart onto >=0.96.0.
|
|
220
225
|
|
|
221
226
|
- **Sub-tasked Acceptance Criteria** (either shape, when a single PRD legitimately spans more
|
|
222
227
|
than one concern dimension from step 3 — e.g. it has both core-functionality and
|
|
@@ -452,8 +457,10 @@ can't load skills.
|
|
|
452
457
|
## Phase 2 — Validation runs as its own job; this session only decides
|
|
453
458
|
|
|
454
459
|
Every plan queued in Phase 1 ends with a `validate` PRD (agentType `validator`) whose
|
|
455
|
-
`dependsOn` lists every other slug in the plan,
|
|
456
|
-
|
|
460
|
+
`dependsOn` lists every other slug in the plan — or, for a plan over the 100-entry cap, just the
|
|
461
|
+
plan's sink PRDs, since `hasDownstreamValidator` walks `dependsOn` transitively and still credits
|
|
462
|
+
every upstream PRD — so the scheduler runs it exactly once, after the last work-item lands.
|
|
463
|
+
That job — not this session — re-runs each PRD's gate, checks every
|
|
457
464
|
acceptance criterion against the tree, reviews the plan's combined diff (`/code-review`,
|
|
458
465
|
`/security-review`), commits a review record, and ends with `VALIDATION: <slug>
|
|
459
466
|
VERIFIED|REFUTED` per PRD. The scheduler appends one verdict event per PRD to this Epic (the
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ipcSchemas-dependsOn.test.cjs — dependsOn array cap for schedulerCreatePrd
|
|
3
|
+
* (PRD: raise-dependson-cap-to-100). A /develop plan's validate PRD depends
|
|
4
|
+
* on every other slug in the plan, so the cap must clear a 36-PRD plan while
|
|
5
|
+
* still bounding an unbounded payload.
|
|
6
|
+
*
|
|
7
|
+
* Run: timeout 300 npx vitest run src/main/__tests__/ipcSchemas-dependsOn.test.cjs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
import { test, expect } from 'vitest';
|
|
13
|
+
const { schemas } = require('../ipcSchemas.cjs');
|
|
14
|
+
|
|
15
|
+
const basePrd = {
|
|
16
|
+
title: 'validate the plan',
|
|
17
|
+
estimateMinutes: 5,
|
|
18
|
+
goal: 'Validate the plan',
|
|
19
|
+
acceptanceCriteria: ['it validates'],
|
|
20
|
+
implementationNotes: 'n/a',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
test('schedulerCreatePrd accepts 36 dependsOn entries', () => {
|
|
24
|
+
const dependsOn = Array.from({ length: 36 }, (_, i) => `prd-${i}`);
|
|
25
|
+
const result = schemas.schedulerCreatePrd.safeParse({ ...basePrd, dependsOn });
|
|
26
|
+
expect(result.success).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('schedulerCreatePrd rejects 101 dependsOn entries', () => {
|
|
30
|
+
const dependsOn = Array.from({ length: 101 }, (_, i) => `prd-${i}`);
|
|
31
|
+
const result = schemas.schedulerCreatePrd.safeParse({ ...basePrd, dependsOn });
|
|
32
|
+
expect(result.success).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('schedulerCreatePrd accepts exactly 100 dependsOn entries', () => {
|
|
36
|
+
const dependsOn = Array.from({ length: 100 }, (_, i) => `prd-${i}`);
|
|
37
|
+
const result = schemas.schedulerCreatePrd.safeParse({ ...basePrd, dependsOn });
|
|
38
|
+
expect(result.success).toBe(true);
|
|
39
|
+
});
|
|
@@ -69,3 +69,57 @@ test('false when the depending row is not agentType validator', () => {
|
|
|
69
69
|
];
|
|
70
70
|
expect(hasDownstreamValidator(job, jobs)).toBe(false);
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
test('true when a pending validator reaches the job transitively via one sink (2-hop)', () => {
|
|
74
|
+
const job = { slug: '111-alpha' };
|
|
75
|
+
const jobs = [
|
|
76
|
+
job,
|
|
77
|
+
{ slug: '150-sink', agentType: 'dev-lead', status: 'pending', dependsOn: ['111-alpha'] },
|
|
78
|
+
{ slug: '999-validate', agentType: 'validator', status: 'pending', dependsOn: ['150-sink'] },
|
|
79
|
+
];
|
|
80
|
+
expect(hasDownstreamValidator(job, jobs)).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('true when a pending validator reaches the job transitively via two intermediate sinks (3-hop)', () => {
|
|
84
|
+
const job = { slug: '111-alpha' };
|
|
85
|
+
const jobs = [
|
|
86
|
+
job,
|
|
87
|
+
{ slug: '140-mid', agentType: 'dev-lead', status: 'pending', dependsOn: ['111-alpha'] },
|
|
88
|
+
{ slug: '150-sink', agentType: 'dev-lead', status: 'pending', dependsOn: ['140-mid'] },
|
|
89
|
+
{ slug: '999-validate', agentType: 'validator', status: 'pending', dependsOn: ['150-sink'] },
|
|
90
|
+
];
|
|
91
|
+
expect(hasDownstreamValidator(job, jobs)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('false when the validator only reaches an unrelated branch', () => {
|
|
95
|
+
const job = { slug: '111-alpha' };
|
|
96
|
+
const jobs = [
|
|
97
|
+
job,
|
|
98
|
+
{ slug: '222-beta', agentType: 'dev-lead', status: 'pending', dependsOn: [] },
|
|
99
|
+
{ slug: '150-sink', agentType: 'dev-lead', status: 'pending', dependsOn: ['222-beta'] },
|
|
100
|
+
{ slug: '999-validate', agentType: 'validator', status: 'pending', dependsOn: ['150-sink'] },
|
|
101
|
+
];
|
|
102
|
+
expect(hasDownstreamValidator(job, jobs)).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('cycle in dependsOn terminates and returns false when the job is not reachable', () => {
|
|
106
|
+
const job = { slug: '111-alpha' };
|
|
107
|
+
const jobs = [
|
|
108
|
+
job,
|
|
109
|
+
{ slug: '200-a', agentType: 'dev-lead', status: 'pending', dependsOn: ['201-b'] },
|
|
110
|
+
{ slug: '201-b', agentType: 'dev-lead', status: 'pending', dependsOn: ['200-a'] },
|
|
111
|
+
{ slug: '999-validate', agentType: 'validator', status: 'pending', dependsOn: ['200-a'] },
|
|
112
|
+
];
|
|
113
|
+
expect(hasDownstreamValidator(job, jobs)).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('false when a same-named validator/sink pair belongs to a different project (cwd)', () => {
|
|
117
|
+
const job = { slug: '011-alpha', cwd: '/projectA' };
|
|
118
|
+
const jobs = [
|
|
119
|
+
job,
|
|
120
|
+
{ slug: '012-sink', cwd: '/projectA', agentType: 'dev-lead', status: 'pending', dependsOn: [] },
|
|
121
|
+
{ slug: '512-sink', cwd: '/projectB', agentType: 'dev-lead', status: 'pending', dependsOn: ['011-alpha'] },
|
|
122
|
+
{ slug: '999-validate', cwd: '/projectB', agentType: 'validator', status: 'pending', dependsOn: ['sink'] },
|
|
123
|
+
];
|
|
124
|
+
expect(hasDownstreamValidator(job, jobs)).toBe(false);
|
|
125
|
+
});
|
package/src/main/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
3
|
-
"gitSha": "
|
|
4
|
-
"gitShortSha": "
|
|
2
|
+
"version": "0.97.0",
|
|
3
|
+
"gitSha": "c68cf49f55d9fda43114a70f1fb21142979cce11",
|
|
4
|
+
"gitShortSha": "c68cf49f",
|
|
5
5
|
"gitBranch": "HEAD",
|
|
6
|
-
"builtAt": "2026-09-
|
|
6
|
+
"builtAt": "2026-09-25T03:03:56.111Z",
|
|
7
7
|
"dirty": false
|
|
8
8
|
}
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -322,10 +322,14 @@ const PrdAgentTypeSchema = z.string().regex(PERSONA_NAME_RE, 'agent name must be
|
|
|
322
322
|
// Shared shape for a `dependsOn` slug entry — schedulerCreatePrd and
|
|
323
323
|
// adminUpdatePrd (scheduler_update_prd, PRD 1124) must accept the exact same
|
|
324
324
|
// slug shape or an update could write a dependsOn value create would have
|
|
325
|
-
// rejected. Array-level constraints (`.max(
|
|
326
|
-
// call site since update also allows an explicit empty array to
|
|
327
|
-
// dependency, which create has no reason to accept.
|
|
325
|
+
// rejected. Array-level constraints (`.max(MAX_DEPENDS_ON)`, `.optional()`)
|
|
326
|
+
// stay at each call site since update also allows an explicit empty array to
|
|
327
|
+
// CLEAR the dependency, which create has no reason to accept.
|
|
328
328
|
const DepSlugSchema = z.string().min(1).max(160).regex(/^[A-Za-z0-9][\w.-]*$/);
|
|
329
|
+
// Array-level cap shared by every dependsOn field below — a validate PRD in a
|
|
330
|
+
// large /develop plan depends on every other slug in the plan, so this must
|
|
331
|
+
// stay well above the largest plan size the PRD authoring flow allows.
|
|
332
|
+
const MAX_DEPENDS_ON = 100;
|
|
329
333
|
// Wave-authoring decision (scheduler wave-disposition PRD), shared between
|
|
330
334
|
// schedulerCreatePrd (authoring time) and adminPrdFrontmatterPatch (the
|
|
331
335
|
// after-the-fact change path) so both can never disagree on what a valid
|
|
@@ -350,7 +354,7 @@ const schedulerCreatePrd = z.object({
|
|
|
350
354
|
parallelGroup: z.number().int().min(1).max(999999).optional(),
|
|
351
355
|
// Explicit ordering (PRD 832): slugs that must complete before this PRD
|
|
352
356
|
// becomes eligible. Written to frontmatter as `dependsOn: [a, b]`.
|
|
353
|
-
dependsOn: z.array(DepSlugSchema).max(
|
|
357
|
+
dependsOn: z.array(DepSlugSchema).max(MAX_DEPENDS_ON).optional(),
|
|
354
358
|
// Explicit wave-authoring decision (scheduler wave-disposition PRD) — only
|
|
355
359
|
// meaningful (and only stamped) when this PRD joins an Epic that already
|
|
356
360
|
// has incomplete PRDs and lands as a wave root (no dependsOn of its own);
|
|
@@ -417,7 +421,7 @@ const scheduleSetPrdDisposition = z.object({
|
|
|
417
421
|
slug: z.string().regex(SCHEDULE_SLUG_RE),
|
|
418
422
|
cwd: z.string().min(1).max(4096).optional(),
|
|
419
423
|
disposition: PrdDispositionSchema,
|
|
420
|
-
dependsOn: z.array(DepSlugSchema).max(
|
|
424
|
+
dependsOn: z.array(DepSlugSchema).max(MAX_DEPENDS_ON).optional(),
|
|
421
425
|
});
|
|
422
426
|
|
|
423
427
|
// Bulk archive: slug list, capped to limit unbounded retag/archive payloads.
|
|
@@ -501,7 +505,7 @@ const adminPrdFrontmatterPatch = z.object({
|
|
|
501
505
|
// non-empty array is validated write-time against the SAME resolver
|
|
502
506
|
// (depSlugResolve.cjs) scheduler_create_prd uses, so update and create
|
|
503
507
|
// can never disagree about what a dependsOn entry resolves to.
|
|
504
|
-
dependsOn: z.array(DepSlugSchema).max(
|
|
508
|
+
dependsOn: z.array(DepSlugSchema).max(MAX_DEPENDS_ON).optional(),
|
|
505
509
|
// Patchable after creation too — the Scheduler UI's "change disposition"
|
|
506
510
|
// action (promote a wave to its own head, or attach it behind another
|
|
507
511
|
// chain) rewrites this alongside dependsOn via the same
|
|
@@ -8,21 +8,73 @@
|
|
|
8
8
|
* filesystem or scheduler knowledge — the caller supplies the queue rows.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const { bareSlug } = require('./depSlugResolve.cjs');
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* hasDownstreamValidator(job, jobs) → boolean
|
|
13
15
|
*
|
|
14
16
|
* True iff `jobs` contains a row with `agentType === 'validator'`, status
|
|
15
|
-
* `pending` or `running`,
|
|
16
|
-
*
|
|
17
|
+
* `pending` or `running`, that reaches `job.slug` transitively by walking
|
|
18
|
+
* `dependsOn` edges through the supplied rows (e.g. validator -> sink ->
|
|
19
|
+
* job). A validator job itself never has a downstream validator (it IS the
|
|
20
|
+
* plan's validation pass). Plans over the `dependsOn` cap point the
|
|
21
|
+
* validator at only the plan's sink PRDs, so a direct-only check would
|
|
22
|
+
* wrongly say every non-sink work item has no downstream validator.
|
|
23
|
+
*
|
|
24
|
+
* `jobs` may be the machine-wide queue (every project's rows merged), so
|
|
25
|
+
* when `job.cwd` is known the walk excludes rows carrying a DIFFERENT
|
|
26
|
+
* explicit `cwd` — a same-bare-named PRD in an unrelated project can never
|
|
27
|
+
* satisfy this job's validator check. A row with no `cwd` recorded is kept
|
|
28
|
+
* (queue rows may omit it; `scheduleJobSchema.cjs` makes it optional) rather
|
|
29
|
+
* than guessed at, since a wrong exclusion (a real validator missed) is far
|
|
30
|
+
* worse than the narrow residual risk of an unstamped row colliding by bare
|
|
31
|
+
* name. Slug matching within that scope reuses the canonical
|
|
32
|
+
* `depSlugResolve.cjs` rule (exact slug, else bare-name with the leading
|
|
33
|
+
* `NN-` stripped) so this walk agrees with the scheduler's own dependency
|
|
34
|
+
* gate on what a `dependsOn` entry resolves to.
|
|
17
35
|
*/
|
|
18
36
|
function hasDownstreamValidator(job, jobs) {
|
|
19
37
|
if (!job || !Array.isArray(jobs)) return false;
|
|
20
38
|
if (job.agentType === 'validator') return false;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
&& (row.
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
|
|
40
|
+
const projectJobs = job.cwd
|
|
41
|
+
? jobs.filter((row) => row && (row.cwd == null || row.cwd === job.cwd))
|
|
42
|
+
: jobs.filter(Boolean);
|
|
43
|
+
|
|
44
|
+
const rowBySlug = new Map();
|
|
45
|
+
const rowsByBareSlug = new Map();
|
|
46
|
+
for (const row of projectJobs) {
|
|
47
|
+
rowBySlug.set(row.slug, row);
|
|
48
|
+
const bare = bareSlug(row.slug);
|
|
49
|
+
if (!rowsByBareSlug.has(bare)) rowsByBareSlug.set(bare, []);
|
|
50
|
+
rowsByBareSlug.get(bare).push(row);
|
|
51
|
+
}
|
|
52
|
+
const rowsForSlug = (slug) => {
|
|
53
|
+
const exact = rowBySlug.get(slug);
|
|
54
|
+
if (exact) return [exact];
|
|
55
|
+
return rowsByBareSlug.get(bareSlug(slug)) ?? [];
|
|
56
|
+
};
|
|
57
|
+
const matchesJob = (slug) => slug === job.slug || bareSlug(slug) === bareSlug(job.slug);
|
|
58
|
+
|
|
59
|
+
const validators = projectJobs.filter((row) => row.agentType === 'validator'
|
|
60
|
+
&& (row.status === 'pending' || row.status === 'running'));
|
|
61
|
+
|
|
62
|
+
for (const validator of validators) {
|
|
63
|
+
const visited = new Set();
|
|
64
|
+
const queue = Array.isArray(validator.dependsOn) ? [...validator.dependsOn] : [];
|
|
65
|
+
while (queue.length) {
|
|
66
|
+
const slug = queue.shift();
|
|
67
|
+
if (visited.has(slug)) continue;
|
|
68
|
+
visited.add(slug);
|
|
69
|
+
if (matchesJob(slug)) return true;
|
|
70
|
+
for (const row of rowsForSlug(slug)) {
|
|
71
|
+
for (const depSlug of row.dependsOn ?? []) {
|
|
72
|
+
if (!visited.has(depSlug)) queue.push(depSlug);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
26
78
|
}
|
|
27
79
|
|
|
28
80
|
module.exports = { hasDownstreamValidator };
|