@@ -21,6 +21,10 @@ const {
2121const {
2222 commitTaskRecoveryState
2323} = require ( '../hooks/_runtime/task-recovery-store-v5.cjs' )
24+ const {
25+ collectActiveWorkspaceProjectNamespaces,
26+ resolveWorkspaceProjectTarget
27+ } = require ( '../hooks/_runtime/workspace-layout.cjs' )
2428
2529const ROOT = path . resolve ( __dirname , '..' )
2630const CLI = path . join ( ROOT , 'index.js' )
@@ -596,6 +600,10 @@ function testInitBootstrapsWorkspaceProfileAndOneNamedProject() {
596600 const ambiguous = JSON . parse ( runCliFailure ( [ 'init' , '--profile' , 'api' , '--json' ] , ambiguousRoot ) )
597601 assert . strictEqual ( ambiguous . errorCode , 'PROFILE_TARGET_AMBIGUOUS' )
598602 assert . ok ( ! fs . existsSync ( path . join ( ambiguousRoot , '.devcodex' ) ) , 'ambiguous project target must not write runtime or Profile files' )
603+ fs . mkdirSync ( path . join ( ambiguousRoot , 'api' ) , { recursive : true } )
604+ const exact = JSON . parse ( runCli ( [ 'init' , '--profile' , 'api' , '--json' ] , ambiguousRoot ) )
605+ assert . strictEqual ( exact . payload . projectProfile . namespace , 'api' , 'an exact existing directory must win over leaf aliases' )
606+ assert . deepStrictEqual ( fs . readdirSync ( path . join ( ambiguousRoot , 'api' ) ) , [ ] , 'init must not write inside the selected empty directory' )
599607 fs . rmSync ( ambiguousRoot , { recursive : true , force : true } )
600608}
601609
@@ -604,6 +612,8 @@ function testExplicitProfileTargetsAndDryRunStayPhysicalAndZeroWrite() {
604612 writeFile ( dryRunRoot , 'docs/package.json' , '{ "name": "docs" }\n' )
605613 writeFile ( dryRunRoot , 'clients/acme/api/package.json' , '{ "name": "deep-api" }\n' )
606614 writeFile ( dryRunRoot , 'dist/fake/package.json' , '{ "name": "derived-fake" }\n' )
615+ fs . mkdirSync ( path . join ( dryRunRoot , 'empty' ) , { recursive : true } )
616+ writeFile ( dryRunRoot , 'notes/context.txt' , 'manual target without a project marker\n' )
607617 const before = walk ( dryRunRoot ) . map ( file => path . relative ( dryRunRoot , file ) ) . sort ( )
608618 const dry = JSON . parse ( runCli ( [ 'init' , '--profile' , 'docs' , '--dry-run' , '--json' ] , dryRunRoot ) )
609619 const after = walk ( dryRunRoot ) . map ( file => path . relative ( dryRunRoot , file ) ) . sort ( )
@@ -614,6 +624,18 @@ function testExplicitProfileTargetsAndDryRunStayPhysicalAndZeroWrite() {
614624 path . resolve ( item . dest ) . startsWith ( path . join ( dryRunRoot , '.devcodex' , 'docs' , 'profile' ) + path . sep )
615625 ) , 'targeted dry-run actions must use the future workspace namespace path' )
616626
627+ const empty = JSON . parse ( runCli ( [ 'init' , '--profile' , 'empty' , '--json' ] , dryRunRoot ) )
628+ assert . strictEqual ( empty . payload . projectProfile . namespace , 'empty' )
629+ assert . deepStrictEqual ( fs . readdirSync ( path . join ( dryRunRoot , 'empty' ) ) , [ ] , 'empty project directory must remain untouched' )
630+ assert . ok ( fs . existsSync ( path . join ( dryRunRoot , '.devcodex' , 'empty' , 'profile' , 'README.md' ) ) )
631+ assert . ok ( collectActiveWorkspaceProjectNamespaces ( dryRunRoot ) . includes ( 'empty' ) , 'canonical Profile must keep an empty physical project discoverable' )
632+ assert . strictEqual ( resolveWorkspaceProjectTarget ( dryRunRoot , 'empty' ) . namespace , 'empty' )
633+
634+ const unmarked = JSON . parse ( runCli ( [ 'init' , '--profile' , 'notes' , '--json' ] , dryRunRoot ) )
635+ assert . strictEqual ( unmarked . payload . projectProfile . namespace , 'notes' )
636+ assert . strictEqual ( fs . readFileSync ( path . join ( dryRunRoot , 'notes' , 'context.txt' ) , 'utf8' ) , 'manual target without a project marker\n' )
637+ assert . ok ( ! fs . existsSync ( path . join ( dryRunRoot , 'notes' , '.devcodex' ) ) , 'unmarked project directory must not receive legacy runtime files' )
638+
617639 const deep = JSON . parse ( runCli ( [ 'init' , '--profile' , 'clients/acme/api' , '--json' ] , dryRunRoot ) )
618640 assert . strictEqual ( deep . payload . projectProfile . namespace , 'clients/acme/api' )
619641 assert . ok ( fs . existsSync ( path . join ( dryRunRoot , '.devcodex' , 'clients' , 'acme' , 'api' , 'profile' , 'README.md' ) ) )
@@ -633,8 +655,19 @@ function testExplicitProfileTargetsAndDryRunStayPhysicalAndZeroWrite() {
633655 assert . strictEqual ( derived . errorCode , 'PROFILE_TARGET_NOT_FOUND' )
634656 assert . deepStrictEqual ( derived . details . candidates , [ ] )
635657 assert . ok ( ! fs . existsSync ( path . join ( derivedRoot , '.devcodex' ) ) )
658+ const exactDerived = JSON . parse ( runCli ( [ 'init' , '--profile' , 'dist/fake' , '--json' ] , derivedRoot ) )
659+ assert . strictEqual ( exactDerived . payload . projectProfile . namespace , 'dist/fake' )
660+ assert . ok ( fs . existsSync ( path . join ( derivedRoot , '.devcodex' , 'dist' , 'fake' , 'profile' , 'README.md' ) ) )
661+ assert . ok ( collectActiveWorkspaceProjectNamespaces ( derivedRoot ) . includes ( 'dist/fake' ) , 'explicit derived namespace must remain discoverable after provisioning' )
636662 fs . rmSync ( derivedRoot , { recursive : true , force : true } )
637663
664+ const nonDirectoryRoot = createTempRoot ( 'devcodex-cli-non-directory-target-' )
665+ writeFile ( nonDirectoryRoot , 'not-a-directory' , 'file target\n' )
666+ const nonDirectory = JSON . parse ( runCliFailure ( [ 'init' , '--profile' , 'not-a-directory' , '--json' ] , nonDirectoryRoot ) )
667+ assert . strictEqual ( nonDirectory . errorCode , 'PROFILE_TARGET_DIRECTORY_MISSING' )
668+ assert . ok ( ! fs . existsSync ( path . join ( nonDirectoryRoot , '.devcodex' ) ) , 'a file target must fail without runtime writes' )
669+ fs . rmSync ( nonDirectoryRoot , { recursive : true , force : true } )
670+
638671 const runtimeOnlyRoot = createTempRoot ( 'devcodex-cli-runtime-only-target-' )
639672 writeFile ( runtimeOnlyRoot , '.devcodex/history/profile/README.md' , '# historical runtime only\n' )
640673 const runtimeOnly = JSON . parse ( runCliFailure ( [ 'init' , '--profile' , 'history' , '--json' ] , runtimeOnlyRoot ) )
0 commit comments