@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.
- package/bin/compose.js +171 -39
- package/bin/git-hooks/post-commit.template +2 -1
- package/bin/git-hooks/pre-push.template +2 -1
- package/dist/assets/{_baseUniq-3jW4HAOf.js → _baseUniq-1Y6jTF8t.js} +1 -1
- package/dist/assets/{arc-DzzDimyd.js → arc-D6ZzqBFD.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-CtAgwORz.js → architectureDiagram-Q4EWVU46-Dz07OB2P.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-Bryby0c_.js → blockDiagram-DXYQGD6D-CIx_vZA9.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-C7N9RTJ8.js → c4Diagram-AHTNJAMY-oPCtldKL.js} +1 -1
- package/dist/assets/channel-BOEco5yc.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-wijkFgZY.js → chunk-4BX2VUAB-DE1mVs3B.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-zdSZGRS2.js → chunk-4TB4RGXK-BBOEH_yf.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-6zqzTZQQ.js → chunk-55IACEB6-BNnoj3te.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-frd1Vwf-.js → chunk-EDXVE4YY-BONF1eoC.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-CdkRK5Hx.js → chunk-FMBD7UC4-ij7VBSva.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-C6bMB0cf.js → chunk-OYMX7WX6-CzccIkdC.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-4vsxN3jq.js → chunk-QZHKN3VN-6U3wTZV_.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-DbNARKip.js → chunk-YZCP3GAM-B0KFbf0L.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Z--fTQT6.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-Z--fTQT6.js +1 -0
- package/dist/assets/clone-WlHlToLE.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BpXeV7Vj.js → cose-bilkent-S5V4N54A-CicoNwP8.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DQLu_W8r.js → dagre-KV5264BT-CwIGE26v.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-skaOoe5A.js → diagram-5BDNPKRD-2VTTGl95.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-DezlfFH4.js → diagram-G4DWMVQ6-3Pf96ieo.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-BUu-v-wT.js → diagram-MMDJMWI5-BMJV4Wvv.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-CziQ6LPs.js → diagram-TYMM5635-DekMw47L.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-BsAyOVTI.js → erDiagram-SMLLAGMA-CoDv7HYm.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-CbYWJOLq.js → flowDiagram-DWJPFMVM-Cnh8oAOL.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-CAwgDkLl.js → ganttDiagram-T4ZO3ILL-4E_ddNnO.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DK4RlkjO.js → gitGraphDiagram-UUTBAWPF-BezAbuh1.js} +1 -1
- package/dist/assets/{graph-orv1XHGx.js → graph-Dwh_GDZn.js} +1 -1
- package/dist/assets/{index-Ceywghsu.js → index-DHdiTAmV.js} +224 -224
- package/dist/assets/{infoDiagram-42DDH7IO-DQyA75sK.js → infoDiagram-42DDH7IO-CQG6i5uG.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-C-F_5q4k.js → ishikawaDiagram-UXIWVN3A-DU-pg-DQ.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-Bj8UIvK-.js → journeyDiagram-VCZTEJTY-D16FySlp.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-DZYr8Dp1.js → kanban-definition-6JOO6SKY-CeBu_tC8.js} +1 -1
- package/dist/assets/{layout-CBaTKjpX.js → layout-20cH9PdF.js} +1 -1
- package/dist/assets/{linear-j1sI_SiN.js → linear-BJLAYBLR.js} +1 -1
- package/dist/assets/{min-DtJISjld.js → min-DpwGyJGd.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-Bulb64RS.js → mindmap-definition-QFDTVHPH-Cs8A-hRb.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-D11keQxr.js → pieDiagram-DEJITSTG-B_3gJ-t1.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-BEcWQiEG.js → quadrantDiagram-34T5L4WZ-CWwkmlo7.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-Cbp23uDf.js → requirementDiagram-MS252O5E-B5_FnptK.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-Dae1hMc5.js → sankeyDiagram-XADWPNL6-C1V7g3pp.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-C16abORi.js → sequenceDiagram-FGHM5R23-Cx40AcDN.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-CbEtfhbx.js → stateDiagram-FHFEXIEX-MB9EofRm.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-B2uq4PdS.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-BV7JTNMI.js → timeline-definition-GMOUNBTQ-BVeH32Lw.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-DBZiT48j.js → vennDiagram-DHZGUBPP-COiwH6EY.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-Cc8uoiL3.js → wardley-RL74JXVD-BOOKZXAy.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-DEYcWGo5.js → wardleyDiagram-NUSXRM2D-Cjobr6lF.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-bFhLXv2b.js → xychartDiagram-5P7HB3ND-Bj1YVrJO.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/discover-workspaces.js +109 -0
- package/lib/resolve-workspace.js +166 -0
- package/lib/vision-writer.js +7 -1
- package/package.json +1 -2
- package/server/compose-mcp-tools.js +114 -93
- package/server/compose-mcp.js +42 -0
- package/server/design-routes.js +5 -2
- package/server/index.js +5 -0
- package/server/project-root.js +4 -0
- package/server/workspace-middleware.js +120 -0
- package/server/workspace-routes.js +25 -0
- package/dist/assets/channel-DDkv7DUd.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-J6ZTeCbW.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-J6ZTeCbW.js +0 -1
- package/dist/assets/clone-5MVZ89iV.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CyY84hEA.js +0 -1
package/dist/assets/{xychartDiagram-5P7HB3ND-bFhLXv2b.js → xychartDiagram-5P7HB3ND-Bj1YVrJO.js}
RENAMED
|
@@ -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-
|
|
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&>.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-
|
|
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
|
+
}
|
package/lib/vision-writer.js
CHANGED
|
@@ -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: {
|
|
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.
|
|
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
|
|
15
|
-
export
|
|
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(
|
|
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(
|
|
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
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
+
|