I absence of welldefined rules, it's a matter of opinion, how one does a "clockwise spiral walk" in a quadratic matrix.
First, I would define the TOP-LEFT corner as point (1,1) with the addition, that (1,1) is always the top-left corner.
For a 1x1, 2x2, 3x3, 4x4 and 5x5 matrix I would go this way (I use the 25 letters to show my clockwise spiral way, starting at top-left with the letter 'a'):
 

1x1    2x2    3x3    4x4    5x5
a-a    a-d    a-i    a-     a-y

a      ab     abc    abcd   abcde
       dc     hid    lmne   pqrsf
              gfe    kpof   oxytg
                     jihg   nwvuh
                            mlkji
                            
Matrix: 4x4, starting points:
(1,1) --> abcdefghijklmnop
(1,2) --> bcdefghijklmnop
(2,3) --> nop
(2,4) --> efghijklmnop
(3,1) --> klmnop
(3,2) --> p
(3,3) --> op

You always go from the starting point to the endpoint (in the center)

All odd matrices (1x1, 3x3, 5x4,...) have a middle-point at (N+1\2, N+1\2)

According to original constraints #2 "The starting position is always valid within the matrix". I interpret that as 
one can start at any point a clockwise spiral reading, for example, reading the 4x4 matrix, starting at (3,4) gives you: 'fghijklmnop' The sequence 'abcde' is skipped.

A reading like: 'fghijklabcdenm' gives a clockwise spiral but never touches 'op' on the other hand, reading like: 'fghijklabcdenopm' is not clockwise-spiral because at the sequence 'eno' suddenly takes an counterclockwise turn!

You talk about a multidimensional matrix but obviously mean a two dimensional matrix - right?
You talk about a matrix of size: N x N but neither the given code signaure nor the task description specify where the value N is given. In your examples you create the matrix by continuous incrementing the root node of matrix - the root node is equal to N, is this always valid or just in your examples or in other words, would this be a valid call:

kill box
set box(1)="A,B"
set box(2)="C,D"

do ##class(codeGolf.ClockwiseWord).Solution(.box,1,1)

I know, I one can obtain the value for N with a simple $order()

set N = $order(matrix(""),-1)

You expect a correct solution, we expect correct a description
justmy2cents
 

And do not forget, if the application has/uses parts of "older code" then the so called "naked syntax" may also be a issue (of course not, if you just want to know the name of the global).

Classmethod Test()
{
  kill ^myGlobal
  kill ^yourGlobal
  set ^myGlobal(2)="some data"
  do ..moreData("data1")
  set ^yourGlobal(3)="other data"
  do ..moreData("data2")
  
  // Now, the globals look like
  //
  // ^myGlobal(2)="some data"
  // ^myGlobal(9)="data1"
  //
  // ^yourGlobal(3)="other data"
  // ^yourGlobal(9)="data2"
}

ClassMethod moreData(data)
{
  set ^(9)=data	
}

Beside all the "nice" combinations of direct sets, indirections, naked synates etc. do not forget, your application may call routinies/methods which are in deployed mode (third party APIs and utilities - hopefully with  documentation)

You are right, using the hash value of a line solves the problem of long lines (hence the hint in my answer: 'similar (short) solution') but does NOT solve the second requirement, the sorting.
Hash values do not keep the sort order of the original values!

set a="Sara",b="John" write $system.Encryption.SHA1Hash(a)]$system.Encryption.SHA1Hash(b),!,a]b
1
1
set a="SSSS",b="JJJJ" write $system.Encryption.SHA1Hash(a)]$system.Encryption.SHA1Hash(b),!,a]b
0
1

If I remember correctly, I developed a method for sorting long lines a few years ago. I'll look into it this weekend.

In case, your file contains lines NO LONGER than 500 characters than you can drop duplicated lines and sort them in one go:

ClassMethod RemoveAndSort(inpFN = "json.txt")
{
    kill ^||tmp
    set inpStr=##class(%Stream.FileCharacter).%New()
    set outStr=##class(%Stream.TmpCharacter).%New()
    do inpStr.LinkToFile(inpFN)
    
    do inpStr.Rewind()
    while 'inpStr.AtEnd {             // put the JSON lines into a tmp global
        set line=inpStr.ReadLine()    // duplicates are overwritten, all lines are sorted
        if $match(line,"[\{\[].+[\}\]]") { set ^||tmp(line)="" } // ignore non-JSON lines
    }
    
    set line=""
    for {set line=$o(^||tmp(line)) quit:line=""  do outStr.WriteLine(line) }
    
    quit outStr // save the new stream or just use it
}

A similar (short) solution exists for removing duplicated lines until MAXSTRING line lengths.

With the help of a standard editor only - that will be a difficult and cumbersome task, for example, in the below snippet try to find the "^Test" global (not the routine ^Test):

 set value = ^Test  // take a value from the global ^Test
 do ^Test           // call the routine ^Test
 
 // a more complex case
 //
 set ref = "^Test"      // in this case you are lucky
 set ref = "^" _ "Test" // this could be a stumbling stone
 // few lines later
 set value = @ref  // get the value of ^Test global
 do @ref           // call the routine ^Test

You will need some kind of an "intelligent" editor which can make a difference between a call (do) like operation and a "get-value" like operation. The one I know of (but I have never used it, so I don't know how good it works) is the RE/parser of GJS. Asking Google is a good starting point.

Some comments (and please excuse the rude comments) before my answer
1) in general, it's never a good idea, to enforce a "new coding standard" for an old code. This is especially valid for old MUMPS and old ObjectScript code - except, you redesign or at least rework, the entire codebase.
2) use the Codesnippet button to write a more readable code (it's not necessary, but it helps us)
3) include only relevant parts/lines of code, in the above code, all that ^OLIVERs are completely irrelevant and makes the reading and understanding of an foreigen code more difficult, with other words, help us to help you!

And now to your problem. The old code is written in a mode, which we call "non procedural" code, i.e. all variables are public except the NEWed (which are invisible). The original code changes or kills at least the OLDIO543 variable, somewhere in the called part (do @CALL), and this fact is considered with the

 N TMPFILE,OLDIO
 S OLDIO=$IO
 I $G(^TMP("RMPV","SILENT"),1) S IOP="NULL",%ZIS=0 D ^%ZIS I '$G(POP,1) U IO
 D INIT^@DRVNAME
 D  ; scope variables
 .N (DUZ,CALL) ; Protect %response  // <-- save all except DUZ and call
 .S IOF="""""",IOM=80,U="^"
 .D @CALL
 U OLDIO

To move all commands from argumentless DO into a classmethod is OK, but you can't change a "non procedural" flow to a "procedural"!
So the solution is, you leave the old NEW as is and define your class method as:

ClassMethod doCall() [ ProcedureBlock=0 ]
{
 ...
}

If you want to keep your procedural code then you have to rework the code, which is called by "do @CALL" too and probably the code which is called from inside of "DO @CALL". Good luck!

This is the rare case where the statement “RTFM” leads you down the wrong path or, better said, reading the documentation confuses you more than informs.

So you have to change your viewpoint. And that depends on, what do you do.
If you work with ObjectScript (and of course, with objects) then your first citation "Case-sensitive: variable names..." holds.

But if you are more that SQL guy, who day in, day out busy with Updates, Inserts and Selects, then you will say, "No, no, my table and field names aren't case sensitive".

And now we sit in the middle of those two worlds, where a case sensitive Objectscript meets a case insensitive SQL world.

So what is the status quo?

All the (persistent) classes can be seen (and used) as SQL tables, but this freedom has its price (or, better, its consequence): you must be aware, that the two worlds (ObjectScript and SQL) do not use the same case-rule.

An ObjecScript class Test.Person can be named in SQL as Test.Person, as TEST.Person, as TEST.PERSON or even as TeSt.PeRsOn.

To de-escalate the resulting confusion, the following rule was added:

It's forbidden to have two ObjectScript classes, which have the same spelling but (somewhere) a different case (the Studio wizard checks this). I hope, this helps a bit...

Class DC.Bundle Extends (%RegisteredObject, %XML.Adaptor)
{
Property Entries As list Of Entry(XMLNAME = "Entry", XMLPROJECTION = "ELEMENT");

ClassMethod Test()
{
	s bnd=..%New()
	s ent=##class(Entry).%New(),ent.Name="John",ent.Age=40 d bnd.Entries.Insert(ent) k ent
	s ent=##class(Entry).%New(),ent.Name="Paul",ent.Age=45 d bnd.Entries.Insert(ent) k ent
	
	d bnd.XMLExportToString(.xx) q xx
}
}

Class DC.Entry Extends (%RegisteredObject, %XML.Adaptor)
{
Property Name As %String;
Property Age As %Integer;
}

The test output is:


USER>set xml=##class(DC.Bundle).Test()

USER>write xml
<Bundle><Entry><Name>John</Name><Age>40</Age></Entry><Entry><Name>Paul</Name><Age>45</Age></Entry></Bundle>
USER>

USER>zzxs xml
<Bundle>
    <Entry>
        <Name>John</Name>
        <Age>40</Age>
    </Entry>
    <Entry>
        <Name>Paul</Name>
        <Age>45</Age>
    </Entry>
</Bundle>

USER>

First, my experience with tail recursion is very limited,
second, after adding the TailFact() method and a little change to Times() method in the above class, I must say, I don't see any benefit of the TailFact() method. Quite the contrary, now the function have to push two values on the stack instead of one...

/// Factorial using tail_recursion
/// 
ClassMethod TailFact(n)
{
	quit ..tfact(n,1)
}

ClassMethod tfact(n, f)
{
	quit $select(n>1:..tfact(n-1,f*n),1:f)
}

/// There is a noticeable time difference between MulFact() and RecFact().
/// 
/// Recursion helps to keep an algorithm clear an simple but needs more
/// resources - for a few loops it's OK, but for many loops it's a no-go
/// (beside a stack space, a stack frame must be prepared for each loop cycle)
ClassMethod Times(n)
{
	while $zh#1 {} s t1=$zh f i=1:1:1E6 { d ..MulFact(n) } s t1=$zh-t1
	while $zh#1 {} s t2=$zh f i=1:1:1E6 { d ..RecFact(n) } s t2=$zh-t2
	while $zh#1 {} s t3=$zh f i=1:1:1E6 { d ..TailFact(n) } s t3=$zh-t3
	write " Fact:",$j(t1,10,4),!,"Rfact:",$j(t2,10,4),!,"Tfact:",$j(t3,10,4),!
	write "RDiff:",$j(t2-t1/t1*100,8,2),"%",!,"TDiff:",$j(t3-t1/t1*100,8,2),!
}

and here some time values

USER>d ##class(DC.Math).Times(5)
 Fact:    0.3306
Rfact:    1.2199
Tfact:    1.4219
RDiff:  268.97%
TDiff:  330.07

USER>d ##class(DC.Math).Times(10)
 Fact:    0.4433
Rfact:    2.3913
Tfact:    2.5549
RDiff:  439.40%
TDiff:  476.28

USER>d ##class(DC.Math).Times(20)
 Fact:    0.7034
Rfact:    4.8017
Tfact:    5.2183
RDiff:  582.63%
TDiff:  641.86

If I understand you correctly, you want for a given number of objects all the possible combinations? That is, if we have 3 objects (3 numbers, 3 words or whatever) then you want:
1 of 3 (3 possibilities): (1) (2) (3)
2 of 3 (3 possibilities): (1,2) (1,3) (2,3)
3 of 3 (1 possibility)   : (1,2,3)
For 3 objects there are in sum 7 possible combinations (see above). Your ^x(1,1) string contains 137 items. I'm pretty sure, you will never see that mutch combination...

Here some numbers for all possible combinations for:
  1 item :                                                       1
 10 items:                                                   1,023
 20 items:                                               1,048,575
 50 items:                                   1,125,899,906,842,623
 75 items:                          37,778,931,862,957,161,730,000
100 items:               1,267,650,600,228,229,401,000,000,000,000
137 items: 174,224,571,863,520,493,300,000,000,000,000,000,000,000

A year has 365.25*86400, that are 31,557,600 seconds.  With another words, you would need a computer system, which is able to genarate 5.5 * 10^33 combinations each second! I don't have such a beast, and I'm sure, you too.

Anyway, maybe the below class gives you some clue, how to compute and how to generate combinations. One more note, recursion is the tool to make (some) solutions clear and simple but recursion in loops, especially in loops with thousands or millions of passes, isn't a good idea. Beside of resources (stack, memory) for each new call a stackframe must be prepared and after the call removed again. This costs time, much time. Try that Times() method in the below class.

/// Some math functions
Class DC.Math Extends %RegisteredObject
{

/// Factorial by multiplication (the more practical way)
/// Return n!
/// 
ClassMethod MulFact(n)
{
	for i=2:1:n-1 set n=n*i
	quit n+'n
}

/// Factorial by recursion (the way, people learn recursion in the school)
/// Return n!
/// 
ClassMethod RecFact(n)
{
	quit $select(n>1:n*..RecFact(n-1),1:1)
}

/// There is a noticeable time difference between MulFact() and RecFact().
/// 
/// Recursion helps to keep an algorithm clear an simple but needs more
/// resources - for a few loops it's OK, but for many loops it's a no-go
/// (beside a stack space, a stack frame must be prepared for each loop cycle)
ClassMethod Times(n)
{
	while $zh#1 {} s t1=$zh f i=1:1:1E6 { d ..MulFact(n) } s t1=$zh-t1
	while $zh#1 {} s t2=$zh f i=1:1:1E6 { d ..RecFact(n) } s t2=$zh-t2
	write " Fact:",$j(t1,10,4),!,"Rfact:",$j(t2,10,4),!," Diff:",$j(t2-t1/t1*100,8,2),"%",!
}

/// The Holy Trinity of the statistic functions: permutations, combinations and variations
/// 
/// Here we go with the combinations.
/// 
/// Return the number of combinations (without repetition) of K elements from a set of N objects
/// 
/// The number of possible combinations is: N over K
/// which is the same as:  N! / (K! * (N-K)!)
/// 
/// Note:
/// one can't make a direct use of the (above) Factorial function
/// because the MAXNUM limit will be hit very soon
ClassMethod Comb(k, n)
{
	set c=1 for k=1:1:k { set c=c/k*n, n=n-1 } quit c
}

/// Return the sum of all possible combinations of N objects
/// i.e. (1-of-n) + (2-of-n) + (3-of-n) + ... + (n-of-n)
/// 
ClassMethod AllComb(n)
{
	set s=0 for i=1:1:n { set s=s+..Comb(i,n) } quit s
}

/// Generate a list of combinations for K elements of N objects
/// 
ClassMethod GenComb(k, n)
{
	for i=1:1:k set e(i)=n-k+i
	set s=0, c(0)=0
	
10	set s=s+1, c(s)=c(s-1)
14	for c(s)=c(s)+1:1:e(s) goto 10:s<k do ..work1(.c,s)
16	set s=s-1 if s goto 14:c(s)<e(s),16
}

/// This method is the workhorse
/// either, print the combinations (one k-tuple at time)
/// or do your own task.
ClassMethod work1(c, s) [ Internal ]
{
	write "(" for i=1:1:s write:i>1 "," write c(i)
	write ")",!
}

/// For example, you could use that ^x(1,1) string
/// 
ClassMethod work2(c, s)
{
	set t=^x(1,1)
	write "(" f i=1:1:s write:i>1 "," write $p(t,",",c(i))
	write ")",!
}

/// save each combination (OK, c(0) should be killed)
/// 
ClassMethod work3(c, s)
{
	m ^mtemp("comb",$i(^mtemp("comb")))=c
}

}

write ##class(...).Comb(3,4) to compute the combinations 3-of-4 (3 items of 4 objects)
write ##class(...).AllComb(3) to compute all combinations of 3 objects (1-of-3 plus  2-of-3 plus 3-of-3)
do ##class(...).GenComb(3,4) to show all 3-of-4 combinations
For all possible combinations of N objects you must do a loop:
for i=1:1:N do ##class(...).GenComb(i,N)

I don't see any difference between SolutinA and SolutionB.

First, $$$OK is converted at COMPILE time into 1 and second, your test code  neither has an execute nor an indirection - or do I miss something? But you can try the below code for timing difference

ClassMethod XTime()
{
	f i=1:1:3 d ..exc()
	w !
	f i=1:1:3 d ..ind()
}

ClassMethod exc()
{
	s arg="s x=1234_(1/2)"
	while $zh["." {} s t=$zh f i=1:1:1E6 { x arg } w $zh-t,!
}

ClassMethod ind()
{
	s arg="x=1234_(1/2)"
	while $zh["." {} s t=$zh f i=1:1:1E6 { s @arg } w $zh-t,!
}

Indirection has its rule:  Indirection works with PUBLIC variables,
i.e. the variable, you address, mustbe a public variable, in your case the <arg> variable.
This is due to compatibility with old applications,
developed before the introduction of the block structure.
  
You have two options

instead of using argument indirection (what you currently do),
use name indirection for label and routinname, see method Test1()

@lab and @(rou)  because label- and routine-names are not variables.

If you want to keep argument indirection, just tell your class, that certain variables were PUBLIC
see method Test2()

In your example, you got a wrong result because, by chance the variable <arg> was defined in the terminal session with the same value as in methode code, see method Test3()


ClassMethod Test1()
{
    s arg="argument-1"
    s lab="say", rou="hello"
    d @lab^@(rou)(arg)
}

ClassMethod Test2() [ PublicList = arg ]
{
	s arg = "argument-2"
	s routine = "say^hello(arg)"
	d @routine
}

ClassMethod Test3()
{
	s routine = "say^hello(arg)"
	d @routine
}

Now some tests in a terminal

kill   // we kill all local variables
do ##class(your.class).Test1() ---> argument-1

kill
do ##class(your.class).Test2() ---> argument-2

kill
do ##class(your.class).Test3() ---> <UNDEF> *arg ==> missing arg

kill
set arg="surprise"
do ##class(your.class).Test3() ---> surprise

// you can prove things the other way too

set arg="my-value"  // variables, defined in a terminal session are public
do ##class(your.class).Test1() ---> argument-1
write arg --> my-value  // arg wasn't overwritten!

do ##class(your.class).Test2() ---> argument-2
write arg --> argument-2  // arg IS OVERWRITTEN

Method Test3() shows, indirection works with public variables