@smartmemory/compose 0.1.8-beta → 0.1.10-beta

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 (69) hide show
  1. package/bin/compose.js +171 -39
  2. package/bin/git-hooks/post-commit.template +2 -1
  3. package/bin/git-hooks/pre-push.template +2 -1
  4. package/dist/assets/{_baseUniq-3jW4HAOf.js → _baseUniq-1Y6jTF8t.js} +1 -1
  5. package/dist/assets/{arc-DzzDimyd.js → arc-D6ZzqBFD.js} +1 -1
  6. package/dist/assets/{architectureDiagram-Q4EWVU46-CtAgwORz.js → architectureDiagram-Q4EWVU46-Dz07OB2P.js} +1 -1
  7. package/dist/assets/{blockDiagram-DXYQGD6D-Bryby0c_.js → blockDiagram-DXYQGD6D-CIx_vZA9.js} +1 -1
  8. package/dist/assets/{c4Diagram-AHTNJAMY-C7N9RTJ8.js → c4Diagram-AHTNJAMY-oPCtldKL.js} +1 -1
  9. package/dist/assets/channel-BOEco5yc.js +1 -0
  10. package/dist/assets/{chunk-4BX2VUAB-wijkFgZY.js → chunk-4BX2VUAB-DE1mVs3B.js} +1 -1
  11. package/dist/assets/{chunk-4TB4RGXK-zdSZGRS2.js → chunk-4TB4RGXK-BBOEH_yf.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-6zqzTZQQ.js → chunk-55IACEB6-BNnoj3te.js} +1 -1
  13. package/dist/assets/{chunk-EDXVE4YY-frd1Vwf-.js → chunk-EDXVE4YY-BONF1eoC.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-CdkRK5Hx.js → chunk-FMBD7UC4-ij7VBSva.js} +1 -1
  15. package/dist/assets/{chunk-OYMX7WX6-C6bMB0cf.js → chunk-OYMX7WX6-CzccIkdC.js} +1 -1
  16. package/dist/assets/{chunk-QZHKN3VN-4vsxN3jq.js → chunk-QZHKN3VN-6U3wTZV_.js} +1 -1
  17. package/dist/assets/{chunk-YZCP3GAM-DbNARKip.js → chunk-YZCP3GAM-B0KFbf0L.js} +1 -1
  18. package/dist/assets/classDiagram-6PBFFD2Q-Z--fTQT6.js +1 -0
  19. package/dist/assets/classDiagram-v2-HSJHXN6E-Z--fTQT6.js +1 -0
  20. package/dist/assets/clone-WlHlToLE.js +1 -0
  21. package/dist/assets/{cose-bilkent-S5V4N54A-BpXeV7Vj.js → cose-bilkent-S5V4N54A-CicoNwP8.js} +1 -1
  22. package/dist/assets/{dagre-KV5264BT-DQLu_W8r.js → dagre-KV5264BT-CwIGE26v.js} +1 -1
  23. package/dist/assets/{diagram-5BDNPKRD-skaOoe5A.js → diagram-5BDNPKRD-2VTTGl95.js} +1 -1
  24. package/dist/assets/{diagram-G4DWMVQ6-DezlfFH4.js → diagram-G4DWMVQ6-3Pf96ieo.js} +1 -1
  25. package/dist/assets/{diagram-MMDJMWI5-BUu-v-wT.js → diagram-MMDJMWI5-BMJV4Wvv.js} +1 -1
  26. package/dist/assets/{diagram-TYMM5635-CziQ6LPs.js → diagram-TYMM5635-DekMw47L.js} +1 -1
  27. package/dist/assets/{erDiagram-SMLLAGMA-BsAyOVTI.js → erDiagram-SMLLAGMA-CoDv7HYm.js} +1 -1
  28. package/dist/assets/{flowDiagram-DWJPFMVM-CbYWJOLq.js → flowDiagram-DWJPFMVM-Cnh8oAOL.js} +1 -1
  29. package/dist/assets/{ganttDiagram-T4ZO3ILL-CAwgDkLl.js → ganttDiagram-T4ZO3ILL-4E_ddNnO.js} +1 -1
  30. package/dist/assets/{gitGraphDiagram-UUTBAWPF-DK4RlkjO.js → gitGraphDiagram-UUTBAWPF-BezAbuh1.js} +1 -1
  31. package/dist/assets/{graph-orv1XHGx.js → graph-Dwh_GDZn.js} +1 -1
  32. package/dist/assets/{index-Ceywghsu.js → index-DHdiTAmV.js} +224 -224
  33. package/dist/assets/{infoDiagram-42DDH7IO-DQyA75sK.js → infoDiagram-42DDH7IO-CQG6i5uG.js} +1 -1
  34. package/dist/assets/{ishikawaDiagram-UXIWVN3A-C-F_5q4k.js → ishikawaDiagram-UXIWVN3A-DU-pg-DQ.js} +1 -1
  35. package/dist/assets/{journeyDiagram-VCZTEJTY-Bj8UIvK-.js → journeyDiagram-VCZTEJTY-D16FySlp.js} +1 -1
  36. package/dist/assets/{kanban-definition-6JOO6SKY-DZYr8Dp1.js → kanban-definition-6JOO6SKY-CeBu_tC8.js} +1 -1
  37. package/dist/assets/{layout-CBaTKjpX.js → layout-20cH9PdF.js} +1 -1
  38. package/dist/assets/{linear-j1sI_SiN.js → linear-BJLAYBLR.js} +1 -1
  39. package/dist/assets/{min-DtJISjld.js → min-DpwGyJGd.js} +1 -1
  40. package/dist/assets/{mindmap-definition-QFDTVHPH-Bulb64RS.js → mindmap-definition-QFDTVHPH-Cs8A-hRb.js} +1 -1
  41. package/dist/assets/{pieDiagram-DEJITSTG-D11keQxr.js → pieDiagram-DEJITSTG-B_3gJ-t1.js} +1 -1
  42. package/dist/assets/{quadrantDiagram-34T5L4WZ-BEcWQiEG.js → quadrantDiagram-34T5L4WZ-CWwkmlo7.js} +1 -1
  43. package/dist/assets/{requirementDiagram-MS252O5E-Cbp23uDf.js → requirementDiagram-MS252O5E-B5_FnptK.js} +1 -1
  44. package/dist/assets/{sankeyDiagram-XADWPNL6-Dae1hMc5.js → sankeyDiagram-XADWPNL6-C1V7g3pp.js} +1 -1
  45. package/dist/assets/{sequenceDiagram-FGHM5R23-C16abORi.js → sequenceDiagram-FGHM5R23-Cx40AcDN.js} +1 -1
  46. package/dist/assets/{stateDiagram-FHFEXIEX-CbEtfhbx.js → stateDiagram-FHFEXIEX-MB9EofRm.js} +1 -1
  47. package/dist/assets/stateDiagram-v2-QKLJ7IA2-B2uq4PdS.js +1 -0
  48. package/dist/assets/{timeline-definition-GMOUNBTQ-BV7JTNMI.js → timeline-definition-GMOUNBTQ-BVeH32Lw.js} +1 -1
  49. package/dist/assets/{vennDiagram-DHZGUBPP-DBZiT48j.js → vennDiagram-DHZGUBPP-COiwH6EY.js} +1 -1
  50. package/dist/assets/{wardley-RL74JXVD-Cc8uoiL3.js → wardley-RL74JXVD-BOOKZXAy.js} +1 -1
  51. package/dist/assets/{wardleyDiagram-NUSXRM2D-DEYcWGo5.js → wardleyDiagram-NUSXRM2D-Cjobr6lF.js} +1 -1
  52. package/dist/assets/{xychartDiagram-5P7HB3ND-bFhLXv2b.js → xychartDiagram-5P7HB3ND-Bj1YVrJO.js} +1 -1
  53. package/dist/index.html +1 -1
  54. package/lib/discover-workspaces.js +109 -0
  55. package/lib/resolve-workspace.js +166 -0
  56. package/lib/vision-writer.js +7 -1
  57. package/package.json +1 -2
  58. package/server/compose-mcp-tools.js +114 -93
  59. package/server/compose-mcp.js +42 -0
  60. package/server/design-routes.js +5 -2
  61. package/server/index.js +5 -0
  62. package/server/project-root.js +4 -0
  63. package/server/workspace-middleware.js +120 -0
  64. package/server/workspace-routes.js +25 -0
  65. package/dist/assets/channel-DDkv7DUd.js +0 -1
  66. package/dist/assets/classDiagram-6PBFFD2Q-J6ZTeCbW.js +0 -1
  67. package/dist/assets/classDiagram-v2-HSJHXN6E-J6ZTeCbW.js +0 -1
  68. package/dist/assets/clone-5MVZ89iV.js +0 -1
  69. package/dist/assets/stateDiagram-v2-QKLJ7IA2-CyY84hEA.js +0 -1
@@ -1,4 +1,4 @@
1
- import{s as gi,g as xi,q as Xt,p as di,a as fi,b as pi,_ as a,l as Yt,I as mi,e as yi,z as bi,D as _t,i as Ai,F as Nt,G as wi,K as Ci,aH as Si,R as Wt}from"./index-Ceywghsu.js";import{i as _i}from"./init-Gi6I4Gst.js";import{o as ki}from"./ordinal-Cboi1Yqb.js";import{l as zt}from"./linear-j1sI_SiN.js";import"./defaultLocale-DX6XiGOO.js";function Ri(e,t,i){e=+e,t=+t,i=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+i;for(var s=-1,n=Math.max(0,Math.ceil((t-e)/i))|0,o=new Array(n);++s<n;)o[s]=e+s*i;return o}function bt(){var e=ki().unknown(void 0),t=e.domain,i=e.range,s=0,n=1,o,u,p=!1,f=0,T=0,P=.5;delete e.unknown;function _(){var y=t().length,E=n<s,v=E?n:s,L=E?s:n;o=(L-v)/Math.max(1,y-f+T*2),p&&(o=Math.floor(o)),v+=(L-v-o*(y-f))*P,u=o*(1-f),p&&(v=Math.round(v),u=Math.round(u));var I=Ri(y).map(function(m){return v+o*m});return i(E?I.reverse():I)}return e.domain=function(y){return arguments.length?(t(y),_()):t()},e.range=function(y){return arguments.length?([s,n]=y,s=+s,n=+n,_()):[s,n]},e.rangeRound=function(y){return[s,n]=y,s=+s,n=+n,p=!0,_()},e.bandwidth=function(){return u},e.step=function(){return o},e.round=function(y){return arguments.length?(p=!!y,_()):p},e.padding=function(y){return arguments.length?(f=Math.min(1,T=+y),_()):f},e.paddingInner=function(y){return arguments.length?(f=Math.min(1,y),_()):f},e.paddingOuter=function(y){return arguments.length?(T=+y,_()):T},e.align=function(y){return arguments.length?(P=Math.max(0,Math.min(1,y)),_()):P},e.copy=function(){return bt(t(),[s,n]).round(p).paddingInner(f).paddingOuter(T).align(P)},_i.apply(_(),arguments)}var At=(function(){var e=a(function(F,h,c,g){for(c=c||{},g=F.length;g--;c[F[g]]=h);return c},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],s=[1,3],n=[1,5],o=[1,6],u=[1,7],p=[1,5,10,12,14,16,18,19,21,23,34,35,36],f=[1,25],T=[1,26],P=[1,28],_=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],I=[1,34],m=[1,35],R=[1,36],l=[1,37],W=[1,43],O=[1,42],X=[1,47],Y=[1,50],S=[1,10,12,14,16,18,19,21,23,34,35,36],U=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],b=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],w=[1,64],V={trace:a(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:a(function(h,c,g,x,C,r,rt){var d=r.length-1;switch(C){case 5:x.setOrientation(r[d]);break;case 9:x.setDiagramTitle(r[d].text.trim());break;case 12:x.setLineData({text:"",type:"text"},r[d]);break;case 13:x.setLineData(r[d-1],r[d]);break;case 14:x.setBarData({text:"",type:"text"},r[d]);break;case 15:x.setBarData(r[d-1],r[d]);break;case 16:this.$=r[d].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=r[d].trim(),x.setAccDescription(this.$);break;case 19:this.$=r[d-1];break;case 20:this.$=[Number(r[d-2]),...r[d]];break;case 21:this.$=[Number(r[d])];break;case 22:x.setXAxisTitle(r[d]);break;case 23:x.setXAxisTitle(r[d-1]);break;case 24:x.setXAxisTitle({type:"text",text:""});break;case 25:x.setXAxisBand(r[d]);break;case 26:x.setXAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 27:this.$=r[d-1];break;case 28:this.$=[r[d-2],...r[d]];break;case 29:this.$=[r[d]];break;case 30:x.setYAxisTitle(r[d]);break;case 31:x.setYAxisTitle(r[d-1]);break;case 32:x.setYAxisTitle({type:"text",text:""});break;case 33:x.setYAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 37:this.$={text:r[d],type:"text"};break;case 38:this.$={text:r[d],type:"text"};break;case 39:this.$={text:r[d],type:"markdown"};break;case 40:this.$=r[d];break;case 41:this.$=r[d-1]+""+r[d];break}},"anonymous"),table:[e(t,i,{3:1,4:2,7:4,5:s,34:n,35:o,36:u}),{1:[3]},e(t,i,{4:2,7:4,3:8,5:s,34:n,35:o,36:u}),e(t,i,{4:2,7:4,6:9,3:10,5:s,8:[1,11],34:n,35:o,36:u}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},e(p,[2,34]),e(p,[2,35]),e(p,[2,36]),{1:[2,1]},e(t,i,{4:2,7:4,3:21,5:s,34:n,35:o,36:u}),{1:[2,3]},e(p,[2,5]),e(t,[2,7],{4:22,34:n,35:o,36:u}),{11:23,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:39,13:38,24:W,27:O,29:40,30:41,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:45,15:44,27:X,33:46,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:49,17:48,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:52,17:51,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{20:[1,53]},{22:[1,54]},e(S,[2,18]),{1:[2,2]},e(S,[2,8]),e(S,[2,9]),e(U,[2,37],{40:55,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l}),e(U,[2,38]),e(U,[2,39]),e(b,[2,40]),e(b,[2,42]),e(b,[2,43]),e(b,[2,44]),e(b,[2,45]),e(b,[2,46]),e(b,[2,47]),e(b,[2,48]),e(b,[2,49]),e(b,[2,50]),e(b,[2,51]),e(S,[2,10]),e(S,[2,22],{30:41,29:56,24:W,27:O}),e(S,[2,24]),e(S,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,11]),e(S,[2,30],{33:60,27:X}),e(S,[2,32]),{31:[1,61]},e(S,[2,12]),{17:62,24:Y},{25:63,27:w},e(S,[2,14]),{17:65,24:Y},e(S,[2,16]),e(S,[2,17]),e(b,[2,41]),e(S,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},e(S,[2,31]),{27:[1,69]},e(S,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},e(S,[2,15]),e(S,[2,26]),e(S,[2,27]),{11:59,32:72,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,33]),e(S,[2,19]),{25:73,27:w},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:a(function(h,c){if(c.recoverable)this.trace(h);else{var g=new Error(h);throw g.hash=c,g}},"parseError"),parse:a(function(h){var c=this,g=[0],x=[],C=[null],r=[],rt=this.table,d="",ct=0,It=0,hi=2,Mt=1,li=r.slice.call(arguments,1),D=Object.create(this.lexer),$={yy:{}};for(var ft in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ft)&&($.yy[ft]=this.yy[ft]);D.setInput(h,$.yy),$.yy.lexer=D,$.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var pt=D.yylloc;r.push(pt);var ci=D.options&&D.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ui(B){g.length=g.length-2*B,C.length=C.length-B,r.length=r.length-B}a(ui,"popStack");function Vt(){var B;return B=x.pop()||D.lex()||Mt,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=c.symbols_[B]||B),B}a(Vt,"lex");for(var M,q,z,mt,G={},ut,N,Bt,gt;;){if(q=g[g.length-1],this.defaultActions[q]?z=this.defaultActions[q]:((M===null||typeof M>"u")&&(M=Vt()),z=rt[q]&&rt[q][M]),typeof z>"u"||!z.length||!z[0]){var yt="";gt=[];for(ut in rt[q])this.terminals_[ut]&&ut>hi&&gt.push("'"+this.terminals_[ut]+"'");D.showPosition?yt="Parse error on line "+(ct+1)+`:
1
+ import{s as gi,g as xi,q as Xt,p as di,a as fi,b as pi,_ as a,l as Yt,I as mi,e as yi,z as bi,D as _t,i as Ai,F as Nt,G as wi,K as Ci,aH as Si,R as Wt}from"./index-DHdiTAmV.js";import{i as _i}from"./init-Gi6I4Gst.js";import{o as ki}from"./ordinal-Cboi1Yqb.js";import{l as zt}from"./linear-BJLAYBLR.js";import"./defaultLocale-DX6XiGOO.js";function Ri(e,t,i){e=+e,t=+t,i=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+i;for(var s=-1,n=Math.max(0,Math.ceil((t-e)/i))|0,o=new Array(n);++s<n;)o[s]=e+s*i;return o}function bt(){var e=ki().unknown(void 0),t=e.domain,i=e.range,s=0,n=1,o,u,p=!1,f=0,T=0,P=.5;delete e.unknown;function _(){var y=t().length,E=n<s,v=E?n:s,L=E?s:n;o=(L-v)/Math.max(1,y-f+T*2),p&&(o=Math.floor(o)),v+=(L-v-o*(y-f))*P,u=o*(1-f),p&&(v=Math.round(v),u=Math.round(u));var I=Ri(y).map(function(m){return v+o*m});return i(E?I.reverse():I)}return e.domain=function(y){return arguments.length?(t(y),_()):t()},e.range=function(y){return arguments.length?([s,n]=y,s=+s,n=+n,_()):[s,n]},e.rangeRound=function(y){return[s,n]=y,s=+s,n=+n,p=!0,_()},e.bandwidth=function(){return u},e.step=function(){return o},e.round=function(y){return arguments.length?(p=!!y,_()):p},e.padding=function(y){return arguments.length?(f=Math.min(1,T=+y),_()):f},e.paddingInner=function(y){return arguments.length?(f=Math.min(1,y),_()):f},e.paddingOuter=function(y){return arguments.length?(T=+y,_()):T},e.align=function(y){return arguments.length?(P=Math.max(0,Math.min(1,y)),_()):P},e.copy=function(){return bt(t(),[s,n]).round(p).paddingInner(f).paddingOuter(T).align(P)},_i.apply(_(),arguments)}var At=(function(){var e=a(function(F,h,c,g){for(c=c||{},g=F.length;g--;c[F[g]]=h);return c},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],s=[1,3],n=[1,5],o=[1,6],u=[1,7],p=[1,5,10,12,14,16,18,19,21,23,34,35,36],f=[1,25],T=[1,26],P=[1,28],_=[1,29],y=[1,30],E=[1,31],v=[1,32],L=[1,33],I=[1,34],m=[1,35],R=[1,36],l=[1,37],W=[1,43],O=[1,42],X=[1,47],Y=[1,50],S=[1,10,12,14,16,18,19,21,23,34,35,36],U=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],b=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],w=[1,64],V={trace:a(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:a(function(h,c,g,x,C,r,rt){var d=r.length-1;switch(C){case 5:x.setOrientation(r[d]);break;case 9:x.setDiagramTitle(r[d].text.trim());break;case 12:x.setLineData({text:"",type:"text"},r[d]);break;case 13:x.setLineData(r[d-1],r[d]);break;case 14:x.setBarData({text:"",type:"text"},r[d]);break;case 15:x.setBarData(r[d-1],r[d]);break;case 16:this.$=r[d].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=r[d].trim(),x.setAccDescription(this.$);break;case 19:this.$=r[d-1];break;case 20:this.$=[Number(r[d-2]),...r[d]];break;case 21:this.$=[Number(r[d])];break;case 22:x.setXAxisTitle(r[d]);break;case 23:x.setXAxisTitle(r[d-1]);break;case 24:x.setXAxisTitle({type:"text",text:""});break;case 25:x.setXAxisBand(r[d]);break;case 26:x.setXAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 27:this.$=r[d-1];break;case 28:this.$=[r[d-2],...r[d]];break;case 29:this.$=[r[d]];break;case 30:x.setYAxisTitle(r[d]);break;case 31:x.setYAxisTitle(r[d-1]);break;case 32:x.setYAxisTitle({type:"text",text:""});break;case 33:x.setYAxisRangeData(Number(r[d-2]),Number(r[d]));break;case 37:this.$={text:r[d],type:"text"};break;case 38:this.$={text:r[d],type:"text"};break;case 39:this.$={text:r[d],type:"markdown"};break;case 40:this.$=r[d];break;case 41:this.$=r[d-1]+""+r[d];break}},"anonymous"),table:[e(t,i,{3:1,4:2,7:4,5:s,34:n,35:o,36:u}),{1:[3]},e(t,i,{4:2,7:4,3:8,5:s,34:n,35:o,36:u}),e(t,i,{4:2,7:4,6:9,3:10,5:s,8:[1,11],34:n,35:o,36:u}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},e(p,[2,34]),e(p,[2,35]),e(p,[2,36]),{1:[2,1]},e(t,i,{4:2,7:4,3:21,5:s,34:n,35:o,36:u}),{1:[2,3]},e(p,[2,5]),e(t,[2,7],{4:22,34:n,35:o,36:u}),{11:23,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:39,13:38,24:W,27:O,29:40,30:41,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:45,15:44,27:X,33:46,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:49,17:48,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{11:52,17:51,24:Y,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},{20:[1,53]},{22:[1,54]},e(S,[2,18]),{1:[2,2]},e(S,[2,8]),e(S,[2,9]),e(U,[2,37],{40:55,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l}),e(U,[2,38]),e(U,[2,39]),e(b,[2,40]),e(b,[2,42]),e(b,[2,43]),e(b,[2,44]),e(b,[2,45]),e(b,[2,46]),e(b,[2,47]),e(b,[2,48]),e(b,[2,49]),e(b,[2,50]),e(b,[2,51]),e(S,[2,10]),e(S,[2,22],{30:41,29:56,24:W,27:O}),e(S,[2,24]),e(S,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,11]),e(S,[2,30],{33:60,27:X}),e(S,[2,32]),{31:[1,61]},e(S,[2,12]),{17:62,24:Y},{25:63,27:w},e(S,[2,14]),{17:65,24:Y},e(S,[2,16]),e(S,[2,17]),e(b,[2,41]),e(S,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},e(S,[2,31]),{27:[1,69]},e(S,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},e(S,[2,15]),e(S,[2,26]),e(S,[2,27]),{11:59,32:72,37:24,38:f,39:T,40:27,41:P,42:_,43:y,44:E,45:v,46:L,47:I,48:m,49:R,50:l},e(S,[2,33]),e(S,[2,19]),{25:73,27:w},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:a(function(h,c){if(c.recoverable)this.trace(h);else{var g=new Error(h);throw g.hash=c,g}},"parseError"),parse:a(function(h){var c=this,g=[0],x=[],C=[null],r=[],rt=this.table,d="",ct=0,It=0,hi=2,Mt=1,li=r.slice.call(arguments,1),D=Object.create(this.lexer),$={yy:{}};for(var ft in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ft)&&($.yy[ft]=this.yy[ft]);D.setInput(h,$.yy),$.yy.lexer=D,$.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var pt=D.yylloc;r.push(pt);var ci=D.options&&D.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ui(B){g.length=g.length-2*B,C.length=C.length-B,r.length=r.length-B}a(ui,"popStack");function Vt(){var B;return B=x.pop()||D.lex()||Mt,typeof B!="number"&&(B instanceof Array&&(x=B,B=x.pop()),B=c.symbols_[B]||B),B}a(Vt,"lex");for(var M,q,z,mt,G={},ut,N,Bt,gt;;){if(q=g[g.length-1],this.defaultActions[q]?z=this.defaultActions[q]:((M===null||typeof M>"u")&&(M=Vt()),z=rt[q]&&rt[q][M]),typeof z>"u"||!z.length||!z[0]){var yt="";gt=[];for(ut in rt[q])this.terminals_[ut]&&ut>hi&&gt.push("'"+this.terminals_[ut]+"'");D.showPosition?yt="Parse error on line "+(ct+1)+`:
2
2
  `+D.showPosition()+`
3
3
  Expecting `+gt.join(", ")+", got '"+(this.terminals_[M]||M)+"'":yt="Parse error on line "+(ct+1)+": Unexpected "+(M==Mt?"end of input":"'"+(this.terminals_[M]||M)+"'"),this.parseError(yt,{text:D.match,token:this.terminals_[M]||M,line:D.yylineno,loc:pt,expected:gt})}if(z[0]instanceof Array&&z.length>1)throw new Error("Parse Error: multiple actions possible at state: "+q+", token: "+M);switch(z[0]){case 1:g.push(M),C.push(D.yytext),r.push(D.yylloc),g.push(z[1]),M=null,It=D.yyleng,d=D.yytext,ct=D.yylineno,pt=D.yylloc;break;case 2:if(N=this.productions_[z[1]][1],G.$=C[C.length-N],G._$={first_line:r[r.length-(N||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(N||1)].first_column,last_column:r[r.length-1].last_column},ci&&(G._$.range=[r[r.length-(N||1)].range[0],r[r.length-1].range[1]]),mt=this.performAction.apply(G,[d,It,ct,$.yy,z[1],C,r].concat(li)),typeof mt<"u")return mt;N&&(g=g.slice(0,-1*N*2),C=C.slice(0,-1*N),r=r.slice(0,-1*N)),g.push(this.productions_[z[1]][0]),C.push(G.$),r.push(G._$),Bt=rt[g[g.length-2]][g[g.length-1]],g.push(Bt);break;case 3:return!0}}return!0},"parse")},k=(function(){var F={EOF:1,parseError:a(function(c,g){if(this.yy.parser)this.yy.parser.parseError(c,g);else throw new Error(c)},"parseError"),setInput:a(function(h,c){return this.yy=c||this.yy||{},this._input=h,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:a(function(){var h=this._input[0];this.yytext+=h,this.yyleng++,this.offset++,this.match+=h,this.matched+=h;var c=h.match(/(?:\r\n?|\n).*/g);return c?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),h},"input"),unput:a(function(h){var c=h.length,g=h.split(/(?:\r\n?|\n)/g);this._input=h+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-c),this.offset-=c;var x=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var C=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===x.length?this.yylloc.first_column:0)+x[x.length-g.length].length-g[0].length:this.yylloc.first_column-c},this.options.ranges&&(this.yylloc.range=[C[0],C[0]+this.yyleng-c]),this.yyleng=this.yytext.length,this},"unput"),more:a(function(){return this._more=!0,this},"more"),reject:a(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).
4
4
  `+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:a(function(h){this.unput(this.match.slice(h))},"less"),pastInput:a(function(){var h=this.matched.substr(0,this.matched.length-this.match.length);return(h.length>20?"...":"")+h.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:a(function(){var h=this.match;return h.length<20&&(h+=this._input.substr(0,20-h.length)),(h.substr(0,20)+(h.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:a(function(){var h=this.pastInput(),c=new Array(h.length+1).join("-");return h+this.upcomingInput()+`
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Compose</title>
7
- <script type="module" crossorigin src="/assets/index-Ceywghsu.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DHdiTAmV.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DKBsEUJ-.css">
9
9
  </head>
10
10
  <body>
@@ -0,0 +1,109 @@
1
+ /**
2
+ * discover-workspaces.js — bounded bidirectional discovery of compose workspaces.
3
+ *
4
+ * Walks upward to find an "anchor" (any of ANCHOR_MARKERS), then scans the anchor
5
+ * subtree to MAX_DEPTH for `.compose/` markers. Hard-capped at MAX_VISITED dirs;
6
+ * over-cap throws an Error with code='WorkspaceDiscoveryTooBroad'. Permission
7
+ * errors during readdir are skipped silently — discovery is best-effort, not
8
+ * authoritative for individual subtrees.
9
+ *
10
+ * Exports:
11
+ * - findAnchor(startDir) → string|null
12
+ * - discoverWorkspaces(startDir) → { anchor, candidates: [{id, root, configPath}] }
13
+ * - deriveId({root}) → {id, root, configPath}
14
+ */
15
+ import path from 'node:path';
16
+ import fs from 'node:fs';
17
+
18
+ export const ANCHOR_MARKERS = ['.compose', '.stratum.yaml', '.git'];
19
+ export const WORKSPACE_MARKER = '.compose';
20
+ export const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.turbo']);
21
+ export const MAX_DEPTH = 3;
22
+ export const MAX_VISITED = 500;
23
+
24
+ /**
25
+ * Walk upward from startDir; return the first directory containing any
26
+ * ANCHOR_MARKER, or null if none found before filesystem root.
27
+ */
28
+ export function findAnchor(startDir) {
29
+ let dir = path.resolve(startDir);
30
+ const { root } = path.parse(dir);
31
+ while (true) {
32
+ for (const marker of ANCHOR_MARKERS) {
33
+ if (fs.existsSync(path.join(dir, marker))) return dir;
34
+ }
35
+ if (dir === root) return null;
36
+ const parent = path.dirname(dir);
37
+ if (parent === dir) return null;
38
+ dir = parent;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Discover candidate workspaces under the anchor for startDir.
44
+ * If no anchor exists upward, anchors at startDir itself.
45
+ */
46
+ export function discoverWorkspaces(startDir) {
47
+ const anchor = findAnchor(startDir) ?? path.resolve(startDir);
48
+ const visited = { count: 0 };
49
+ const candidates = [];
50
+ walkDescendants(anchor, 0, candidates, visited);
51
+ if (fs.existsSync(path.join(anchor, WORKSPACE_MARKER))) {
52
+ if (!candidates.find((c) => c.root === anchor)) {
53
+ candidates.unshift({ root: anchor });
54
+ }
55
+ }
56
+ return { anchor, candidates: candidates.map(deriveId) };
57
+ }
58
+
59
+ function walkDescendants(dir, depth, out, visited) {
60
+ if (depth > MAX_DEPTH) return;
61
+ if (++visited.count > MAX_VISITED) {
62
+ const e = new Error(
63
+ `Workspace discovery exceeded ${MAX_VISITED} directories from anchor. ` +
64
+ 'Set COMPOSE_TARGET=/absolute/path to bypass discovery.',
65
+ );
66
+ e.code = 'WorkspaceDiscoveryTooBroad';
67
+ throw e;
68
+ }
69
+ let entries;
70
+ try {
71
+ entries = fs.readdirSync(dir, { withFileTypes: true });
72
+ } catch (err) {
73
+ // EACCES, EPERM, ENOENT (race with rm), ENOTDIR (symlink target gone) —
74
+ // skip silently. Discovery is best-effort; missing perms aren't fatal.
75
+ return;
76
+ }
77
+ for (const entry of entries) {
78
+ if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
79
+ const child = path.join(dir, entry.name);
80
+ if (fs.existsSync(path.join(child, WORKSPACE_MARKER))) {
81
+ out.push({ root: child });
82
+ }
83
+ walkDescendants(child, depth + 1, out, visited);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Resolve {id, root, configPath} for a candidate workspace root.
89
+ * Honors `.compose/compose.json#workspaceId` if it matches the canonical regex;
90
+ * otherwise falls back to path.basename(root).
91
+ *
92
+ * Exported so resolve-workspace.js can derive ids without re-running discovery.
93
+ */
94
+ export function deriveId({ root }) {
95
+ const configPath = path.join(root, '.compose', 'compose.json');
96
+ let id = path.basename(root);
97
+ try {
98
+ const cfg = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
99
+ if (
100
+ typeof cfg.workspaceId === 'string' &&
101
+ /^[a-z][a-z0-9-]{1,63}$/.test(cfg.workspaceId)
102
+ ) {
103
+ id = cfg.workspaceId;
104
+ }
105
+ } catch {
106
+ // missing/unreadable/malformed → basename is fine
107
+ }
108
+ return { id, root, configPath };
109
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * resolve-workspace.js — single resolver chain for compose workspaces.
3
+ *
4
+ * Precedence:
5
+ * 1. explicit hint.workspaceId (cheap upward walk first; falls back to discovery)
6
+ * 2. COMPOSE_TARGET env (absolute path bypasses discovery; id routes through it)
7
+ * 3. hint.getBinding() (MCP binding)
8
+ * 4. discovery (auto-pick when exactly one candidate; throws otherwise)
9
+ *
10
+ * Throws structured errors with `.code`: WorkspaceUnknown, WorkspaceAmbiguous,
11
+ * WorkspaceIdCollision, WorkspaceUnset. The CLI's dieOnWorkspaceError consumes them.
12
+ *
13
+ * Design intent: explicit-flag path uses findWorkspaceById (cheap upward walk)
14
+ * BEFORE invoking discoverWorkspaces — this lets users escape WorkspaceDiscoveryTooBroad
15
+ * by passing --workspace=<ancestor-id>. A descendant id still routes through discovery.
16
+ */
17
+ import path from 'node:path';
18
+ import fs from 'node:fs';
19
+ import { discoverWorkspaces, deriveId } from './discover-workspaces.js';
20
+
21
+ export class WorkspaceUnknown extends Error {
22
+ constructor(id) {
23
+ super(`Unknown workspaceId: ${id}`);
24
+ this.code = 'WorkspaceUnknown';
25
+ this.id = id;
26
+ }
27
+ }
28
+
29
+ export class WorkspaceAmbiguous extends Error {
30
+ constructor(candidates) {
31
+ super('Multiple workspaces match cwd');
32
+ this.code = 'WorkspaceAmbiguous';
33
+ this.candidates = candidates;
34
+ }
35
+ }
36
+
37
+ export class WorkspaceIdCollision extends Error {
38
+ constructor(id, roots) {
39
+ super(`workspaceId "${id}" used by multiple roots`);
40
+ this.code = 'WorkspaceIdCollision';
41
+ this.id = id;
42
+ this.roots = roots;
43
+ }
44
+ }
45
+
46
+ export class WorkspaceUnset extends Error {
47
+ constructor() {
48
+ super('No workspace resolved');
49
+ this.code = 'WorkspaceUnset';
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Resolve a workspace from hints + env + cwd.
55
+ *
56
+ * @param {object} hint
57
+ * @param {string} [hint.cwd] — defaults to process.cwd()
58
+ * @param {string} [hint.workspaceId] — explicit --workspace=<id>
59
+ * @param {() => string|null} [hint.getBinding] — MCP binding accessor
60
+ * @returns {{id: string, root: string, configPath: string, source: string}}
61
+ */
62
+ export function resolveWorkspace(hint = {}) {
63
+ const cwd = hint.cwd ?? process.cwd();
64
+
65
+ // 1. Explicit flag — authoritative. Cheap upward walk first; fall back to
66
+ // discovery (which may throw TooBroad for pathological trees).
67
+ if (hint.workspaceId) {
68
+ const found = findWorkspaceById(cwd, hint.workspaceId);
69
+ if (found) return { ...found, source: 'explicit-flag' };
70
+ const { candidates } = discoverWorkspaces(cwd);
71
+ return resolveByIdScopedCollisionCheck(hint.workspaceId, candidates, 'explicit-flag');
72
+ }
73
+
74
+ // 2. COMPOSE_TARGET — absolute path is authoritative without discovery.
75
+ if (process.env.COMPOSE_TARGET) {
76
+ const t = process.env.COMPOSE_TARGET;
77
+ if (path.isAbsolute(t)) {
78
+ if (!fs.existsSync(t)) {
79
+ const e = new Error(`COMPOSE_TARGET=${t} does not exist`);
80
+ e.code = 'WorkspaceUnknown';
81
+ e.id = t;
82
+ throw e;
83
+ }
84
+ return { ...deriveId({ root: t }), source: 'env' };
85
+ }
86
+ const { candidates } = discoverWorkspaces(cwd);
87
+ return resolveByIdScopedCollisionCheck(t, candidates, 'env');
88
+ }
89
+
90
+ // 3. MCP binding — scoped collision check on the bound id.
91
+ if (hint.getBinding) {
92
+ const id = hint.getBinding();
93
+ if (id) {
94
+ const { candidates } = discoverWorkspaces(cwd);
95
+ return resolveByIdScopedCollisionCheck(id, candidates, 'mcp-binding');
96
+ }
97
+ }
98
+
99
+ // 4. Discovery — collisions matter because we're auto-picking.
100
+ const { candidates } = discoverWorkspaces(cwd);
101
+ detectCollisions(candidates);
102
+ if (candidates.length === 0) throw new WorkspaceUnset();
103
+ if (candidates.length === 1) return { ...candidates[0], source: 'discovery' };
104
+ throw new WorkspaceAmbiguous(candidates.map(({ id, root }) => ({ id, root })));
105
+ }
106
+
107
+ /**
108
+ * Cheap upward-only lookup: walk ancestors from startDir, return the first
109
+ * `.compose/` directory whose derived id matches targetId. Lets users bypass
110
+ * descendant-cap entirely via `--workspace=<ancestor-id>`.
111
+ */
112
+ function findWorkspaceById(startDir, targetId) {
113
+ let dir = path.resolve(startDir);
114
+ const { root } = path.parse(dir);
115
+ while (true) {
116
+ if (fs.existsSync(path.join(dir, '.compose'))) {
117
+ const candidate = deriveId({ root: dir });
118
+ if (candidate.id === targetId) return candidate;
119
+ }
120
+ if (dir === root) return null;
121
+ const parent = path.dirname(dir);
122
+ if (parent === dir) return null;
123
+ dir = parent;
124
+ }
125
+ }
126
+
127
+ function resolveByIdScopedCollisionCheck(id, candidates, source) {
128
+ const matching = candidates.filter((c) => c.id === id);
129
+ if (matching.length === 0) throw new WorkspaceUnknown(id);
130
+ if (matching.length > 1) {
131
+ throw new WorkspaceIdCollision(id, matching.map((m) => m.root));
132
+ }
133
+ return { ...matching[0], source };
134
+ }
135
+
136
+ function detectCollisions(candidates) {
137
+ const byId = new Map();
138
+ for (const c of candidates) {
139
+ if (!byId.has(c.id)) byId.set(c.id, []);
140
+ byId.get(c.id).push(c.root);
141
+ }
142
+ for (const [id, roots] of byId) {
143
+ if (roots.length > 1) throw new WorkspaceIdCollision(id, roots);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Pull --workspace=<id> or --workspace <id> out of args, mutating in place.
149
+ * Returns the id, or null if absent.
150
+ */
151
+ export function getWorkspaceFlag(args) {
152
+ for (let i = 0; i < args.length; i++) {
153
+ const a = args[i];
154
+ if (a === '--workspace' && i + 1 < args.length) {
155
+ const id = args[i + 1];
156
+ args.splice(i, 2);
157
+ return id;
158
+ }
159
+ if (typeof a === 'string' && a.startsWith('--workspace=')) {
160
+ const id = a.slice('--workspace='.length);
161
+ args.splice(i, 1);
162
+ return id;
163
+ }
164
+ }
165
+ return null;
166
+ }
@@ -34,11 +34,13 @@ export class VisionWriter {
34
34
  * @param {string} dataDir Path to the data directory (e.g. `.compose/data/`)
35
35
  * @param {object} [opts]
36
36
  * @param {number} [opts.port] Server port (default: resolvePort())
37
+ * @param {string} [opts.workspaceId] Workspace id; when set, sent as `X-Compose-Workspace-Id` on REST calls
37
38
  */
38
39
  constructor(dataDir, opts = {}) {
39
40
  this.filePath = path.join(dataDir, 'vision-state.json');
40
41
  this._dataDir = dataDir;
41
42
  this._port = opts.port ?? resolvePort();
43
+ this.workspaceId = opts.workspaceId;
42
44
  }
43
45
 
44
46
  // ---------------------------------------------------------------------------
@@ -126,7 +128,11 @@ export class VisionWriter {
126
128
  const res = await fetch(`${this._baseUrl}${urlPath}`, {
127
129
  ...opts,
128
130
  signal: controller.signal,
129
- headers: { 'Content-Type': 'application/json', ...opts.headers },
131
+ headers: {
132
+ 'Content-Type': 'application/json',
133
+ ...(this.workspaceId ? { 'X-Compose-Workspace-Id': this.workspaceId } : {}),
134
+ ...opts.headers,
135
+ },
130
136
  });
131
137
  if (!res.ok) {
132
138
  const body = await res.text().catch(() => '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.1.8-beta",
3
+ "version": "0.1.10-beta",
4
4
  "description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
5
5
  "author": "SmartMemory",
6
6
  "license": "MIT",
@@ -95,7 +95,6 @@
95
95
  "date-fns": "^3.6.0",
96
96
  "diff": "^8.0.4",
97
97
  "express": "^4.21.0",
98
- "ink": "^5.2.1",
99
98
  "lucide-react": "^0.563.0",
100
99
  "mermaid": "^11.13.0",
101
100
  "react": "^19.2.5",
@@ -9,11 +9,12 @@ import fs from 'node:fs';
9
9
  import http from 'node:http';
10
10
  import path from 'node:path';
11
11
  import { ArtifactManager, ARTIFACT_SCHEMAS } from './artifact-manager.js';
12
- import { getTargetRoot, getDataDir, resolveProjectPath } from './project-root.js';
12
+ import { getTargetRoot, getDataDir, resolveProjectPath, switchProject, setCurrentWorkspaceId } from './project-root.js';
13
+ import { resolveWorkspace } from '../lib/resolve-workspace.js';
14
+ import { discoverWorkspaces } from '../lib/discover-workspaces.js';
13
15
 
14
- export const PROJECT_ROOT = getTargetRoot();
15
- export const VISION_FILE = path.join(getDataDir(), 'vision-state.json');
16
- export const SESSIONS_FILE = path.join(getDataDir(), 'sessions.json');
16
+ export function getVisionFile() { return path.join(getDataDir(), 'vision-state.json'); }
17
+ export function getSessionsFile() { return path.join(getDataDir(), 'sessions.json'); }
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // Data access
@@ -21,7 +22,7 @@ export const SESSIONS_FILE = path.join(getDataDir(), 'sessions.json');
21
22
 
22
23
  export function loadVisionState() {
23
24
  try {
24
- const raw = fs.readFileSync(VISION_FILE, 'utf-8');
25
+ const raw = fs.readFileSync(getVisionFile(), 'utf-8');
25
26
  const state = JSON.parse(raw);
26
27
  if (Array.isArray(state.gates)) {
27
28
  const seen = new Map();
@@ -36,7 +37,7 @@ export function loadVisionState() {
36
37
 
37
38
  export function loadSessions() {
38
39
  try {
39
- const raw = fs.readFileSync(SESSIONS_FILE, 'utf-8');
40
+ const raw = fs.readFileSync(getSessionsFile(), 'utf-8');
40
41
  const sessions = JSON.parse(raw);
41
42
  return Array.isArray(sessions) ? sessions : [];
42
43
  } catch {
@@ -153,21 +154,13 @@ export function toolGetBlockedItems() {
153
154
  export async function toolGetCurrentSession({ featureCode } = {}) {
154
155
  if (featureCode) {
155
156
  // Delegate to REST API for live session + lifecycle context
156
- return new Promise((resolve, reject) => {
157
- const url = new URL(`${_getComposeApi()}/api/session/current?featureCode=${encodeURIComponent(featureCode)}`);
158
- const req = http.request(
159
- { hostname: url.hostname, port: url.port, path: `${url.pathname}${url.search}`, method: 'GET' },
160
- (res) => {
161
- let buf = '';
162
- res.on('data', chunk => buf += chunk);
163
- res.on('end', () => {
164
- try { resolve(JSON.parse(buf)); } catch { resolve({ session: null }); }
165
- });
166
- },
167
- );
168
- req.on('error', () => resolve({ session: null }));
169
- req.end();
170
- });
157
+ try {
158
+ const { body } = await _httpRequest('GET',
159
+ `/api/session/current?featureCode=${encodeURIComponent(featureCode)}`);
160
+ return typeof body === 'object' && body !== null ? body : { session: null };
161
+ } catch {
162
+ return { session: null };
163
+ }
171
164
  }
172
165
  // Existing disk-read path (keep as-is)
173
166
  const sessions = loadSessions();
@@ -306,30 +299,20 @@ export async function toolValidateProject(args = {}) {
306
299
  }
307
300
 
308
301
  export async function toolBindSession({ featureCode }) {
309
- const postData = JSON.stringify({ featureCode });
310
- return new Promise((resolve, reject) => {
311
- const url = new URL(`${_getComposeApi()}/api/session/bind`);
312
- const req = http.request(
313
- { hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
314
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } },
315
- (res) => {
316
- let buf = '';
317
- res.on('data', chunk => buf += chunk);
318
- res.on('end', () => {
319
- let parsed;
320
- try { parsed = JSON.parse(buf); } catch { parsed = { error: buf }; }
321
- if (res.statusCode >= 400) {
322
- reject(new Error(parsed.error || `HTTP ${res.statusCode}: ${buf}`));
323
- } else {
324
- resolve(parsed);
325
- }
326
- });
327
- },
328
- );
329
- req.on('error', (err) => reject(new Error(`Compose server unreachable: ${err.message}`)));
330
- req.write(postData);
331
- req.end();
332
- });
302
+ let result;
303
+ try {
304
+ result = await _httpRequest('POST', '/api/session/bind', { featureCode });
305
+ } catch (err) {
306
+ throw new Error(`Compose server unreachable: ${err.message}`);
307
+ }
308
+ const { status, body } = result;
309
+ if (status >= 400) {
310
+ const errMsg = (body && typeof body === 'object' && body.error)
311
+ ? body.error
312
+ : `HTTP ${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`;
313
+ throw new Error(errMsg);
314
+ }
315
+ return body;
333
316
  }
334
317
 
335
318
  // ---------------------------------------------------------------------------
@@ -340,57 +323,67 @@ function _getComposeApi() {
340
323
  return `http://127.0.0.1:${process.env.COMPOSE_PORT || process.env.PORT || 3001}`;
341
324
  }
342
325
 
343
- function _postLifecycle(itemId, action, body) {
344
- return new Promise((resolve, reject) => {
345
- const data = JSON.stringify(body);
346
- const url = new URL(`${_getComposeApi()}/api/vision/items/${itemId}/lifecycle/${action}`);
347
- const req = http.request(
348
- { hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
349
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
350
- (res) => {
351
- let buf = '';
352
- res.on('data', (chunk) => buf += chunk);
353
- res.on('end', () => {
354
- let parsed;
355
- try { parsed = JSON.parse(buf); }
356
- catch { parsed = { error: buf }; }
357
- if (res.statusCode >= 400) {
358
- reject(new Error(parsed.error || `HTTP ${res.statusCode}: ${buf}`));
359
- } else {
360
- resolve(parsed);
361
- }
362
- });
363
- },
364
- );
365
- req.on('error', (err) => reject(new Error(`Compose server unreachable: ${err.message}`)));
366
- req.end(data);
367
- });
326
+ async function _postLifecycle(itemId, action, body) {
327
+ let result;
328
+ try {
329
+ result = await _httpRequest('POST', `/api/vision/items/${itemId}/lifecycle/${action}`, body);
330
+ } catch (err) {
331
+ throw new Error(`Compose server unreachable: ${err.message}`);
332
+ }
333
+ const { status, body: respBody } = result;
334
+ if (status >= 400) {
335
+ const errMsg = (respBody && typeof respBody === 'object' && respBody.error)
336
+ ? respBody.error
337
+ : `HTTP ${status}: ${typeof respBody === 'string' ? respBody : JSON.stringify(respBody)}`;
338
+ throw new Error(errMsg);
339
+ }
340
+ return respBody;
341
+ }
342
+
343
+ async function _postGate(gateId, action, body) {
344
+ let result;
345
+ try {
346
+ result = await _httpRequest('POST', `/api/vision/gates/${gateId}/${action}`, body);
347
+ } catch (err) {
348
+ throw new Error(`Compose server unreachable: ${err.message}`);
349
+ }
350
+ const { status, body: respBody } = result;
351
+ if (status >= 400) {
352
+ const errMsg = (respBody && typeof respBody === 'object' && respBody.error)
353
+ ? respBody.error
354
+ : `HTTP ${status}: ${typeof respBody === 'string' ? respBody : JSON.stringify(respBody)}`;
355
+ throw new Error(errMsg);
356
+ }
357
+ return respBody;
368
358
  }
369
359
 
370
- function _postGate(gateId, action, body) {
360
+ /**
361
+ * Centralized http.request wrapper for Compose REST calls from the MCP layer.
362
+ * Injects X-Compose-Workspace-Id from the current session binding when set.
363
+ * COMP-WORKSPACE-HTTP T5.
364
+ */
365
+ async function _httpRequest(method, urlPath, body = null) {
366
+ const port = process.env.COMPOSE_PORT || process.env.PORT || 3001;
367
+ const headers = { 'Content-Type': 'application/json' };
368
+ if (_binding?.id) headers['X-Compose-Workspace-Id'] = _binding.id;
369
+ let payload = null;
370
+ if (body !== null && body !== undefined) {
371
+ payload = JSON.stringify(body);
372
+ headers['Content-Length'] = Buffer.byteLength(payload);
373
+ }
374
+ const opts = { hostname: '127.0.0.1', port, path: urlPath, method, headers };
371
375
  return new Promise((resolve, reject) => {
372
- const data = JSON.stringify(body);
373
- const url = new URL(`${_getComposeApi()}/api/vision/gates/${gateId}/${action}`);
374
- const req = http.request(
375
- { hostname: url.hostname, port: url.port, path: url.pathname, method: 'POST',
376
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
377
- (res) => {
378
- let buf = '';
379
- res.on('data', (chunk) => buf += chunk);
380
- res.on('end', () => {
381
- let parsed;
382
- try { parsed = JSON.parse(buf); }
383
- catch { parsed = { error: buf }; }
384
- if (res.statusCode >= 400) {
385
- reject(new Error(parsed.error || `HTTP ${res.statusCode}: ${buf}`));
386
- } else {
387
- resolve(parsed);
388
- }
389
- });
390
- },
391
- );
392
- req.on('error', (err) => reject(new Error(`Compose server unreachable: ${err.message}`)));
393
- req.end(data);
376
+ const req = http.request(opts, (res) => {
377
+ let data = '';
378
+ res.on('data', (chunk) => { data += chunk; });
379
+ res.on('end', () => {
380
+ try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
381
+ catch { resolve({ status: res.statusCode, body: data }); }
382
+ });
383
+ });
384
+ req.on('error', reject);
385
+ if (payload !== null) req.write(payload);
386
+ req.end();
394
387
  });
395
388
  }
396
389
 
@@ -466,3 +459,31 @@ export function toolGetPendingGates({ itemId }) {
466
459
  return { count: pending.length, gates: pending };
467
460
  }
468
461
 
462
+ // ---------------------------------------------------------------------------
463
+ // Workspace binding (MCP session-scoped)
464
+ // ---------------------------------------------------------------------------
465
+ //
466
+ // `_binding` is process-global by intent. Claude Code spawns ONE stdio MCP
467
+ // child per Claude session; the child's lifetime IS the session's lifetime.
468
+ // "Session-scoped" therefore equals "process-scoped" in this architecture.
469
+ // COMP-WORKSPACE-ID Decision 5 documents this. The HTTP server (port 4001)
470
+ // is the shared-across-sessions process, NOT this module — it gets the
471
+ // workspace per request via the X-Compose-Workspace-Id header instead.
472
+
473
+ let _binding = null;
474
+
475
+ export function toolSetWorkspace({ workspaceId }) {
476
+ const resolved = resolveWorkspace({ workspaceId });
477
+ switchProject(resolved.root);
478
+ setCurrentWorkspaceId(resolved.id);
479
+ _binding = resolved;
480
+ return { id: resolved.id, root: resolved.root, source: 'mcp-binding' };
481
+ }
482
+
483
+ export function toolGetWorkspace() {
484
+ const { candidates } = discoverWorkspaces(process.cwd());
485
+ return { current: _binding, candidates };
486
+ }
487
+
488
+ export function _getBinding() { return _binding?.id ?? null; }
489
+